H264视频编码成MP4文件

时间:2024-02-18 18:59:04
 

H264视频编码成MP4文件

        最近需要将H264视频编码成MP4格式。研究了一下,一种方法是采用ffmpeg库,可以先将H264文件解码,再编码生成MP4文件,但这种方式效率较低,10M的视频可能需要几秒钟才能完成。另一种方式根据MP4文件协议直接将H264包封装成MP4格式,由于是直接基于MP4的封装,因而效率很高。H264可以很方便的封装成FLV文件,但MP4格式格式相对比较复杂,封装起来会比较麻烦。由于没时间研究MP4协议,在Google Code上找到一个开源的MP4编解码库Mp4v2(https://code.google.com/p/mp4v2/),通过Mp4v2可以很方便的将H264编码成MP4格式文件。为了方便使用,基于该库封装了一个MP4Encoder类,MP4Encoder封装的接口如下。目前仅支持将H264文件或数据帧编码成MP4文件。

[cpp] view plain copy
 
  1. class MP4Encoder  
  2. {  
  3. public:  
  4.     MP4Encoder(void);  
  5.     ~MP4Encoder(void);  
  6. public:  
  7.     // open or creat a mp4 file.  
  8.     MP4FileHandle CreateMP4File(const char *fileName,int width,int height,int timeScale = 90000,int frameRate = 25);  
  9.     // wirte 264 metadata in mp4 file.  
  10.     bool Write264Metadata(MP4FileHandle hMp4File,LPMP4ENC_Metadata lpMetadata);  
  11.     // wirte 264 data, data can contain  multiple frame.  
  12.     int WriteH264Data(MP4FileHandle hMp4File,const unsigned char* pData,int size);   
  13.     // close mp4 file.  
  14.     void CloseMP4File(MP4FileHandle hMp4File);  
  15.     // convert H264 file to mp4 file.  
  16.     // no need to call CreateMP4File and CloseMP4File,it will create/close mp4 file automaticly.  
  17.     bool WriteH264File(const char* pFile264,const char* pFileMp4);  
  18.     // Prase H264 metamata from H264 data frame  
  19.     static bool PraseMetadata(const unsigned char* pData,int size,MP4ENC_Metadata &metadata);  
  20. };   

 

客户端调用示例代码:

 

[cpp] view plain copy
 
  1. #include <stdio.h>  
  2. #include "MP4Encoder\MP4Encoder.h"  
  3.   
  4. int main(int argc, char** argv)  
  5. {  
  6.     MP4Encoder mp4Encoder;  
  7.     // convert H264 file to mp4 file  
  8.     mp4Encoder.WriteH264File("test.264","test.mp4");  
  9. }  

 

 

 

MP4Encoder完整的代码如下:

 

[cpp] view plain copy
 
  1. /********************************************************************  
  2. filename:   MP4Encoder.h 
  3. created:    2013-04-16 
  4. author:     firehood  
  5. purpose:    MP4编码器,基于开源库mp4v2实现(https://code.google.com/p/mp4v2/)。 
  6. *********************************************************************/  
  7. #pragma once  
  8. #include "mp4v2\mp4v2.h"  
  9.   
  10. // NALU单元  
  11. typedef struct _MP4ENC_NaluUnit  
  12. {  
  13.     int type;  
  14.     int size;  
  15.     unsigned char *data;  
  16. }MP4ENC_NaluUnit;  
  17.   
  18. typedef struct _MP4ENC_Metadata  
  19. {  
  20.     // video, must be h264 type  
  21.     unsigned int    nSpsLen;  
  22.     unsigned char   Sps[1024];  
  23.     unsigned int    nPpsLen;  
  24.     unsigned char   Pps[1024];  
  25.   
  26. } MP4ENC_Metadata,*LPMP4ENC_Metadata;  
  27.   
  28. class MP4Encoder  
  29. {  
  30. public:  
  31.     MP4Encoder(void);  
  32.     ~MP4Encoder(void);  
  33. public:  
  34.     // open or creat a mp4 file.  
  35.     MP4FileHandle CreateMP4File(const char *fileName,int width,int height,int timeScale = 90000,int frameRate = 25);  
  36.     // wirte 264 metadata in mp4 file.  
  37.     bool Write264Metadata(MP4FileHandle hMp4File,LPMP4ENC_Metadata lpMetadata);  
  38.     // wirte 264 data, data can contain  multiple frame.  
  39.     int WriteH264Data(MP4FileHandle hMp4File,const unsigned char* pData,int size);   
  40.     // close mp4 file.  
  41.     void CloseMP4File(MP4FileHandle hMp4File);  
  42.     // convert H264 file to mp4 file.  
  43.     // no need to call CreateMP4File and CloseMP4File,it will create/close mp4 file automaticly.  
  44.     bool WriteH264File(const char* pFile264,const char* pFileMp4);  
  45.     // Prase H264 metamata from H264 data frame  
  46.     static bool PraseMetadata(const unsigned char* pData,int size,MP4ENC_Metadata &metadata);  
  47. private:  
  48.     // read one nalu from H264 data buffer  
  49.     static int ReadOneNaluFromBuf(const unsigned char *buffer,unsigned int nBufferSize,unsigned int offSet,MP4ENC_NaluUnit &nalu);  
  50. private:  
  51.     int m_nWidth;  
  52.     int m_nHeight;  
  53.     int m_nFrameRate;  
  54.     int m_nTimeScale;  
  55.     MP4TrackId m_videoId;  
  56. };   

MP4Encoder.cpp

[cpp] view plain copy
 
  1. /********************************************************************  
  2. filename:   MP4Encoder.cpp 
  3. created:    2013-04-16 
  4. author:     firehood  
  5. purpose:    MP4编码器,基于开源库mp4v2实现(https://code.google.com/p/mp4v2/)。 
  6. *********************************************************************/  
  7. #include "MP4Encoder.h"  
  8. #include <string.h>  
  9.   
  10. #define BUFFER_SIZE  (1024*1024)  
  11.   
  12. MP4Encoder::MP4Encoder(void):  
  13. m_videoId(NULL),  
  14. m_nWidth(0),  
  15. m_nHeight(0),  
  16. m_nTimeScale(0),  
  17. m_nFrameRate(0)  
  18. {  
  19. }  
  20.   
  21. MP4Encoder::~MP4Encoder(void)  
  22. {  
  23. }  
  24.   
  25. MP4FileHandle MP4Encoder::CreateMP4File(const char *pFileName,int width,int height,int timeScale/* = 90000*/,int frameRate/* = 25*/)  
  26. {  
  27.     if(pFileName == NULL)  
  28.     {  
  29.         return false;  
  30.     }  
  31.     // create mp4 file  
  32.     MP4FileHandle hMp4file = MP4Create(pFileName);  
  33.     if (hMp4file == MP4_INVALID_FILE_HANDLE)  
  34.     {  
  35.         printf("ERROR:Open file fialed.\n");  
  36.         return false;  
  37.     }  
  38.     m_nWidth = width;  
  39.     m_nHeight = height;  
  40.     m_nTimeScale = 90000;  
  41.     m_nFrameRate = 25;  
  42.     MP4SetTimeScale(hMp4file, m_nTimeScale);  
  43.     return hMp4file;  
  44. }  
  45.   
  46. bool MP4Encoder::Write264Metadata(MP4FileHandle hMp4File,LPMP4ENC_Metadata lpMetadata)  
  47. {  
  48.     m_videoId = MP4AddH264VideoTrack  
  49.         (hMp4File,   
  50.         m_nTimeScale,   
  51.         m_nTimeScale / m_nFrameRate,   
  52.         m_nWidth, // width  
  53.         m_nHeight,// height  
  54.         lpMetadata->Sps[1], // sps[1] AVCProfileIndication  
  55.         lpMetadata->Sps[2], // sps[2] profile_compat  
  56.         lpMetadata->Sps[3], // sps[3] AVCLevelIndication  
  57.         3);           // 4 bytes length before each NAL unit  
  58.     if (m_videoId == MP4_INVALID_TRACK_ID)  
  59.     {  
  60.         printf("add video track failed.\n");  
  61.         return false;  
  62.     }  
  63.     MP4SetVideoProfileLevel(hMp4File, 0x01); //  Simple Profile @ Level 3  
  64.   
  65.     // write sps  
  66.     MP4AddH264SequenceParameterSet(hMp4File,m_videoId,lpMetadata->Sps,lpMetadata->nSpsLen);  
  67.   
  68.     // write pps  
  69.     MP4AddH264PictureParameterSet(hMp4File,m_videoId,lpMetadata->Pps,lpMetadata->nPpsLen);  
  70.   
  71.     return true;  
  72. }  
  73.   
  74. int MP4Encoder::WriteH264Data(MP4FileHandle hMp4File,const unsigned char* pData,int size)  
  75. {  
  76.     if(hMp4File == NULL)  
  77.     {  
  78.         return -1;  
  79.     }  
  80.     if(pData == NULL)  
  81.     {  
  82.         return -1;  
  83.     }  
  84.     MP4ENC_NaluUnit nalu;  
  85.     int pos = 0, len = 0;  
  86.     while (len = ReadOneNaluFromBuf(pData,size,pos,nalu))  
  87.     {  
  88.         if(nalu.type == 0x07) // sps  
  89.         {  
  90.             // 添加h264 track      
  91.             m_videoId = MP4AddH264VideoTrack  
  92.                 (hMp4File,   
  93.                 m_nTimeScale,   
  94.                 m_nTimeScale / m_nFrameRate,   
  95.                 m_nWidth,     // width  
  96.                 m_nHeight,    // height  
  97.                 nalu.data[1], // sps[1] AVCProfileIndication  
  98.                 nalu.data[2], // sps[2] profile_compat  
  99.                 nalu.data[3], // sps[3] AVCLevelIndication  
  100.                 3);           // 4 bytes length before each NAL unit  
  101.             if (m_videoId == MP4_INVALID_TRACK_ID)  
  102.             {  
  103.                 printf("add video track failed.\n");  
  104.                 return 0;  
  105.             }  
  106.             MP4SetVideoProfileLevel(hMp4File, 1); //  Simple Profile @ Level 3  
  107.   
  108.             MP4AddH264SequenceParameterSet(hMp4File,m_videoId,nalu.data,nalu.size);  
  109.         }  
  110.         else if(nalu.type == 0x08) // pps  
  111.         {  
  112.             MP4AddH264PictureParameterSet(hMp4File,m_videoId,nalu.data,nalu.size);  
  113.         }  
  114.         else  
  115.         {  
  116.             int datalen = nalu.size+4;  
  117.             unsigned char *data = new unsigned char[datalen];  
  118.             // MP4 Nalu前四个字节表示Nalu长度  
  119.             data[0] = nalu.size>>24;  
  120.             data[1] = nalu.size>>16;  
  121.             data[2] = nalu.size>>8;  
  122.             data[3] = nalu.size&0xff;  
  123.             memcpy(data+4,nalu.data,nalu.size);  
  124.             if(!MP4WriteSample(hMp4File, m_videoId, data, datalen,MP4_INVALID_DURATION, 0, 1))  
  125.             {  
  126.                 return 0;  
  127.             }  
  128.             delete[] data;  
  129.         }  
  130.           
  131.         pos += len;  
  132.     }  
  133.     return pos;  
  134. }  
  135.   
  136. int MP4Encoder::ReadOneNaluFromBuf(const unsigned char *buffer,unsigned int nBufferSize,unsigned int offSet,MP4ENC_NaluUnit &nalu)  
  137. {  
  138.     int i = offSet;  
  139.     while(i<nBufferSize)  
  140.     {  
  141.         if(buffer[i++] == 0x00 &&  
  142.             buffer[i++] == 0x00 &&  
  143.             buffer[i++] == 0x00 &&  
  144.             buffer[i++] == 0x01  
  145.             )  
  146.         {  
  147.             int pos = i;  
  148.             while (pos<nBufferSize)  
  149.             {  
  150.                 if(buffer[pos++] == 0x00 &&  
  151.                     buffer[pos++] == 0x00 &&  
  152.                     buffer[pos++] == 0x00 &&  
  153.                     buffer[pos++] == 0x01  
  154.                     )  
  155.                 {  
  156.                     break;  
  157.                 }  
  158.             }  
  159.             if(pos == nBufferSize)  
  160.             {  
  161.                 nalu.size = pos-i;    
  162.             }  
  163.             else  
  164.             {  
  165.                 nalu.size = (pos-4)-i;  
  166.             }  
  167.   
  168.             nalu.type = buffer[i]&0x1f;  
  169.             nalu.data =(unsigned char*)&buffer[i];  
  170.             return (nalu.size+i-offSet);  
  171.         }  
  172.     }  
  173.     return 0;  
  174. }  
  175.   
  176. void MP4Encoder::CloseMP4File(MP4FileHandle hMp4File)  
  177. {  
  178.     if(hMp4File)  
  179.     {  
  180.         MP4Close(hMp4File);  
  181.         hMp4File = NULL;  
  182.     }  
  183. }  
  184.   
  185. bool MP4Encoder::WriteH264File(const char* pFile264,const char* pFileMp4)  
  186. {  
  187.     if(pFile264 == NULL || pFileMp4 == NULL)  
  188.     {  
  189.         return false;  
  190.     }  
  191.   
  192.     MP4FileHandle hMp4File = CreateMP4File(pFileMp4,352,288);  
  193.   
  194.     if(hMp4File == NULL)  
  195.     {  
  196.         printf("ERROR:Create file failed!");  
  197.         return false;  
  198.     }  
  199.   
  200.     FILE *fp = fopen(pFile264, "rb");    
  201.     if(!fp)    
  202.     {    
  203.         printf("ERROR:open file failed!");  
  204.         return false;  
  205.     }    
  206.     fseek(fp, 0, SEEK_SET);  
  207.   
  208.     unsigned char *buffer  = new unsigned char[BUFFER_SIZE];  
  209.     int pos = 0;  
  210.     while(1)  
  211.     {  
  212.         int readlen = fread(buffer+pos, sizeof(unsigned char), BUFFER_SIZE-pos, fp);  
  213.   
  214.   
  215.         if(readlen<=0)  
  216.         {  
  217.             break;  
  218.         }  
  219.   
  220.         readlen += pos;  
  221.   
  222.         int writelen = 0;  
  223.         for(int i = readlen-1; i>=0; i--)  
  224.         {  
  225.                 if(buffer[i--] == 0x01 &&  
  226.                     buffer[i--] == 0x00 &&  
  227.                     buffer[i--] == 0x00 &&  
  228.                     buffer[i--] == 0x00  
  229.                     )  
  230.                 {  
  231.                     writelen = i+5;  
  232.                     break;  
  233.                 }  
  234.         }  
  235.           
  236.         writelen = WriteH264Data(hMp4File,buffer,writelen);  
  237.         if(writelen<=0)  
  238.         {  
  239.             break;  
  240.         }  
  241.         memcpy(buffer,buffer+writelen,readlen-writelen+1);  
  242.         pos = readlen-writelen+1;  
  243.     }  
  244.     fclose(fp);  
  245.   
  246.     delete[] buffer;  
  247.     CloseMP4File(hMp4File);  
  248.   
  249.     return true;  
  250. }  
  251.   
  252. bool MP4Encoder:: PraseMetadata(const unsigned char* pData,int size,MP4ENC_Metadata &metadata)  
  253. {  
  254.     if(pData == NULL || size<4)  
  255.     {  
  256.         return false;  
  257.     }  
  258.     MP4ENC_NaluUnit nalu;  
  259.     int pos = 0;  
  260.     bool bRet1 = false,bRet2 = false;  
  261.     while (int len = ReadOneNaluFromBuf(pData,size,pos,nalu))  
  262.     {  
  263.         if(nalu.type == 0x07)  
  264.         {  
  265.             memcpy(metadata.Sps,nalu.data,nalu.size);  
  266.             metadata.nSpsLen = nalu.size;  
  267.             bRet1 = true;  
  268.         }  
  269.         else if((nalu.type == 0x08))  
  270.         {  
  271.             memcpy(metadata.Pps,nalu.data,nalu.size);  
  272.             metadata.nPpsLen = nalu.size;  
  273.             bRet2 = true;  
  274.         }  
  275.         pos += len;  
  276.     }  
  277.     if(bRet1 && bRet2)  
  278.     {  
  279.         return true;  
  280.     }  
  281.     return false;  
  282. }