嵌入式音视频-存储录像实现方案mp4v2

嵌入式音视频-存储录像实现方案mp4v2 txp 2023-11-03 14:40:37 227

前言:

最近需要将H264视频编码成MP4格式。研究了一下,一种方法是采用ffmpeg库,可以先将H264文件解码,再编码生成MP4文件,但这种方式效率较低,10M的视频可能需要几秒钟才能完成。另一种方式根据MP4文件协议直接将H264包封装成MP4格式,由于是直接基于MP4的封装,因而效率很高。H264可以很方便的封装成FLV文件,但MP4格式格式相对比较复杂,封装起来会比较麻烦。由于没时间研究MP4协议,在Google Code上找到一个开源的MP4编解码库Mp4v2:

https://github.com/TechSmith/mp4v2

易百纳社区

通过Mp4v2可以很方便的将H264编码成MP4格式文件。为了方便使用,基于该库封装了一个MP4Encoder类,MP4Encoder封装的接口如下。目前仅支持将H264文件或数据帧编码成MP4文件。

代码实现:

class MP4Encoder
{
public:
 MP4Encoder(void);
 ~MP4Encoder(void);
public:
 // open or creat a mp4 file.
 MP4FileHandle CreateMP4File(const char *fileName,int width,int height,int timeScale = 90000,int frameRate = 25);
 // wirte 264 metadata in mp4 file.
 bool Write264Metadata(MP4FileHandle hMp4File,LPMP4ENC_Metadata lpMetadata);
 // wirte 264 data, data can contain  multiple frame.
 int WriteH264Data(MP4FileHandle hMp4File,const unsigned char* pData,int size); 
    // close mp4 file.
 void CloseMP4File(MP4FileHandle hMp4File);
    // convert H264 file to mp4 file.
 // no need to call CreateMP4File and CloseMP4File,it will create/close mp4 file automaticly.
 bool WriteH264File(const char* pFile264,const char* pFileMp4);
 // Prase H264 metamata from H264 data frame
 static bool PraseMetadata(const unsigned char* pData,int size,MP4ENC_Metadata &metadata);
}; 

客户端调用示例代码:

#include <stdio.h>
#include "MP4Encoder\MP4Encoder.h"
 
int main(int argc, char** argv)
{
 MP4Encoder mp4Encoder;
 // convert H264 file to mp4 file
 mp4Encoder.WriteH264File("test.264","test.mp4");
}

MP4Encoder完整的代码如下:

/******************************************************************** 
filename:   MP4Encoder.h
created:    2013-04-16
author:     firehood 
purpose:    MP4编码器,基于开源库mp4v2实现(https://code.google.com/p/mp4v2/)。
*********************************************************************/
#pragma once
#include "mp4v2\mp4v2.h"
 
// NALU单元
typedef struct _MP4ENC_NaluUnit
{
 int type;
 int size;
 unsigned char *data;
}MP4ENC_NaluUnit;
 
typedef struct _MP4ENC_Metadata
{
 // video, must be h264 type
 unsigned int nSpsLen;
 unsigned char Sps[1024];
 unsigned int nPpsLen;
 unsigned char Pps[1024];
 
} MP4ENC_Metadata,*LPMP4ENC_Metadata;
 
class MP4Encoder
{
public:
 MP4Encoder(void);
 ~MP4Encoder(void);
public:
 // open or creat a mp4 file.
 MP4FileHandle CreateMP4File(const char *fileName,int width,int height,int timeScale = 90000,int frameRate = 25);
 // wirte 264 metadata in mp4 file.
 bool Write264Metadata(MP4FileHandle hMp4File,LPMP4ENC_Metadata lpMetadata);
 // wirte 264 data, data can contain  multiple frame.
 int WriteH264Data(MP4FileHandle hMp4File,const unsigned char* pData,int size); 
    // close mp4 file.
 void CloseMP4File(MP4FileHandle hMp4File);
    // convert H264 file to mp4 file.
 // no need to call CreateMP4File and CloseMP4File,it will create/close mp4 file automaticly.
 bool WriteH264File(const char* pFile264,const char* pFileMp4);
 // Prase H264 metamata from H264 data frame
 static bool PraseMetadata(const unsigned char* pData,int size,MP4ENC_Metadata &metadata);
private:
 // read one nalu from H264 data buffer
 static int ReadOneNaluFromBuf(const unsigned char *buffer,unsigned int nBufferSize,unsigned int offSet,MP4ENC_NaluUnit &nalu);
private:
 int m_nWidth;
 int m_nHeight;
 int m_nFrameRate;
 int m_nTimeScale;
 MP4TrackId m_videoId;
}; 

MP4Encoder.cpp:

/******************************************************************** 
filename:   MP4Encoder.cpp
created:    2013-04-16
author:     firehood 
purpose:    MP4编码器,基于开源库mp4v2实现(https://code.google.com/p/mp4v2/)。
*********************************************************************/
#include "MP4Encoder.h"
#include <string.h>
 
#define BUFFER_SIZE  (1024*1024)
 
MP4Encoder::MP4Encoder(void):
m_videoId(NULL),
m_nWidth(0),
m_nHeight(0),
m_nTimeScale(0),
m_nFrameRate(0)
{
}
 
MP4Encoder::~MP4Encoder(void)
{
}
 
MP4FileHandle MP4Encoder::CreateMP4File(const char *pFileName,int width,int height,int timeScale/* = 90000*/,int frameRate/* = 25*/)
{
 if(pFileName == NULL)
 {
  return false;
 }
 // create mp4 file
 MP4FileHandle hMp4file = MP4Create(pFileName);
 if (hMp4file == MP4_INVALID_FILE_HANDLE)
 {
  printf("ERROR:Open file fialed.\n");
  return false;
 }
 m_nWidth = width;
 m_nHeight = height;
 m_nTimeScale = 90000;
 m_nFrameRate = 25;
 MP4SetTimeScale(hMp4file, m_nTimeScale);
 return hMp4file;
}
 
bool MP4Encoder::Write264Metadata(MP4FileHandle hMp4File,LPMP4ENC_Metadata lpMetadata)
{
 m_videoId = MP4AddH264VideoTrack
  (hMp4File, 
  m_nTimeScale, 
  m_nTimeScale / m_nFrameRate, 
  m_nWidth, // width
  m_nHeight,// height
  lpMetadata->Sps[1], // sps[1] AVCProfileIndication
  lpMetadata->Sps[2], // sps[2] profile_compat
  lpMetadata->Sps[3], // sps[3] AVCLevelIndication
  3);           // 4 bytes length before each NAL unit
 if (m_videoId == MP4_INVALID_TRACK_ID)
 {
  printf("add video track failed.\n");
  return false;
 }
 MP4SetVideoProfileLevel(hMp4File, 0x01); //  Simple Profile @ Level 3
 
 // write sps
    MP4AddH264SequenceParameterSet(hMp4File,m_videoId,lpMetadata->Sps,lpMetadata->nSpsLen);
 
 // write pps
 MP4AddH264PictureParameterSet(hMp4File,m_videoId,lpMetadata->Pps,lpMetadata->nPpsLen);
 
 return true;
}
 
int MP4Encoder::WriteH264Data(MP4FileHandle hMp4File,const unsigned char* pData,int size)
{
 if(hMp4File == NULL)
 {
  return -1;
 }
 if(pData == NULL)
 {
  return -1;
 }
 MP4ENC_NaluUnit nalu;
 int pos = 0, len = 0;
 while (len = ReadOneNaluFromBuf(pData,size,pos,nalu))
 {
  if(nalu.type == 0x07) // sps
  {
   // 添加h264 track    
   m_videoId = MP4AddH264VideoTrack
    (hMp4File, 
    m_nTimeScale, 
    m_nTimeScale / m_nFrameRate, 
    m_nWidth,     // width
    m_nHeight,    // height
    nalu.data[1], // sps[1] AVCProfileIndication
    nalu.data[2], // sps[2] profile_compat
    nalu.data[3], // sps[3] AVCLevelIndication
    3);           // 4 bytes length before each NAL unit
   if (m_videoId == MP4_INVALID_TRACK_ID)
   {
    printf("add video track failed.\n");
    return 0;
   }
   MP4SetVideoProfileLevel(hMp4File, 1); //  Simple Profile @ Level 3
 
   MP4AddH264SequenceParameterSet(hMp4File,m_videoId,nalu.data,nalu.size);
  }
  else if(nalu.type == 0x08) // pps
  {
   MP4AddH264PictureParameterSet(hMp4File,m_videoId,nalu.data,nalu.size);
  }
  else
  {
   int datalen = nalu.size+4;
   unsigned char *data = new unsigned char[datalen];
   // MP4 Nalu前四个字节表示Nalu长度
   data[0] = nalu.size>>24;
   data[1] = nalu.size>>16;
   data[2] = nalu.size>>8;
   data[3] = nalu.size&0xff;
   memcpy(data+4,nalu.data,nalu.size);
   if(!MP4WriteSample(hMp4File, m_videoId, data, datalen,MP4_INVALID_DURATION, 0, 1))
   {
    return 0;
   }
   delete[] data;
  }
  
  pos += len;
 }
    return pos;
}
 
int MP4Encoder::ReadOneNaluFromBuf(const unsigned char *buffer,unsigned int nBufferSize,unsigned int offSet,MP4ENC_NaluUnit &nalu)
{
 int i = offSet;
 while(i<nBufferSize)
 {
  if(buffer[i++] == 0x00 &&
   buffer[i++] == 0x00 &&
   buffer[i++] == 0x00 &&
   buffer[i++] == 0x01
   )
  {
   int pos = i;
   while (pos<nBufferSize)
   {
    if(buffer[pos++] == 0x00 &&
     buffer[pos++] == 0x00 &&
     buffer[pos++] == 0x00 &&
     buffer[pos++] == 0x01
     )
    {
     break;
    }
   }
   if(pos == nBufferSize)
   {
    nalu.size = pos-i; 
   }
   else
   {
    nalu.size = (pos-4)-i;
   }
 
   nalu.type = buffer[i]&0x1f;
   nalu.data =(unsigned char*)&buffer[i];
   return (nalu.size+i-offSet);
  }
 }
 return 0;
}
 
void MP4Encoder::CloseMP4File(MP4FileHandle hMp4File)
{
 if(hMp4File)
 {
  MP4Close(hMp4File);
  hMp4File = NULL;
 }
}
 
bool MP4Encoder::WriteH264File(const char* pFile264,const char* pFileMp4)
{
    if(pFile264 == NULL || pFileMp4 == NULL)
 {
  return false;
 }
 
 MP4FileHandle hMp4File = CreateMP4File(pFileMp4,352,288);
 
    if(hMp4File == NULL)
 {
  printf("ERROR:Create file failed!");
  return false;
 }
 
 FILE *fp = fopen(pFile264, "rb");  
 if(!fp)  
 {  
  printf("ERROR:open file failed!");
  return false;
 }  
 fseek(fp, 0, SEEK_SET);
 
 unsigned char *buffer  = new unsigned char[BUFFER_SIZE];
 int pos = 0;
 while(1)
 {
  int readlen = fread(buffer+pos, sizeof(unsigned char), BUFFER_SIZE-pos, fp);
 
 
  if(readlen<=0)
  {
   break;
  }
 
  readlen += pos;
 
  int writelen = 0;
  for(int i = readlen-1; i>=0; i--)
  {
    if(buffer[i--] == 0x01 &&
     buffer[i--] == 0x00 &&
     buffer[i--] == 0x00 &&
     buffer[i--] == 0x00
     )
    {
     writelen = i+5;
     break;
    }
  }
  
  writelen = WriteH264Data(hMp4File,buffer,writelen);
  if(writelen<=0)
  {
   break;
  }
  memcpy(buffer,buffer+writelen,readlen-writelen+1);
  pos = readlen-writelen+1;
 }
 fclose(fp);
 
 delete[] buffer;
 CloseMP4File(hMp4File);
 
 return true;
}
 
bool MP4Encoder:: PraseMetadata(const unsigned char* pData,int size,MP4ENC_Metadata &metadata)
{
 if(pData == NULL || size<4)
 {
  return false;
 }
 MP4ENC_NaluUnit nalu;
 int pos = 0;
 bool bRet1 = false,bRet2 = false;
 while (int len = ReadOneNaluFromBuf(pData,size,pos,nalu))
 {
  if(nalu.type == 0x07)
  {
   memcpy(metadata.Sps,nalu.data,nalu.size);
   metadata.nSpsLen = nalu.size;
   bRet1 = true;
  }
  else if((nalu.type == 0x08))
  {
   memcpy(metadata.Pps,nalu.data,nalu.size);
   metadata.nPpsLen = nalu.size;
   bRet2 = true;
  }
  pos += len;
 }
 if(bRet1 && bRet2)
 {
  return true;
 }
 return false;
}


声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
txp
红包 1 1 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
txp
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区