C#實現.264視頻文件封裝成.MP4方法


.264視頻文件封裝成.MP4方法

需求:

海康威視輸出的視頻格式為.264格式,而html5端對其不支持,所以需要將其封裝成. mp4格式。

Tips:我們常常提到的. mp4格式視頻,其實指的是一種容器(或者說集合體),包括視頻、音頻,甚至是字幕等。而.264是指一種視頻的編碼方式,起壓縮作用。所以將.264文件轉換成.mp4文件,其實就是一個解碼的過程。

思路:

網上已經提供解決方案,采用ffmpeg庫,先將.264文件解碼,再編碼生成.mp4文件,但這種方式效率較低,10M的視頻可能需要幾秒鍾才能完成。另一種方式根據MP4文件協議直接將H264包封裝成MP4格式,由於是直接基於MP4的封裝,因而效率很高。在Google Code上找到一個開源的MP4編解碼庫Mp4v2(https://code.google.com/p/mp4v2/),通過Mp4v2可以很方便的將H264編碼成MP4格式文件。為了方便使用,基於該庫封裝了一個MP4Encoder類。

但上述解決方案采用的是C++語言編寫,要想在C#代碼中實現對其進行調用,有兩種解決方案:① webservice ,② 動態鏈接庫(dll)。由於C++編寫webservice較為困難(涉及到套接字等),故本文采用了生成dll方式,但方法的缺點是,即只要源代碼做改動,就得重新生成一遍,好在本文涉及的C++程序不需要做變動。

利用Swig工具可以很方便地實現上述生成dll需求。SWIG是個幫助使用C或者C++編寫的軟件能與其它各種高級編程語言進行嵌入聯接的開發工具。SWIG能應用於各種不同類型的語言包括常用腳本編譯語言例如Perl, PHP, Python, Tcl, Ruby and PHP。支持語言列表中也包括非腳本編譯語言,例如C#, Common Lisp (CLISP, Allegro CL, CFFI, UFFI), Java, Modula-3, OCAML以及R,甚至是編譯器或者匯編的計划應用(Guile, MzScheme, Chicken)。SWIG普遍應用於創建高級語言解析或匯編程序環境,用戶接口,作為一種用來測試C/C++或進行原型設計的工具。SWIG還能夠導出XML或Lisp s-expressions格式的解析樹。SWIG可以被自由使用,發布,修改用於商業或非商業中。

 

實現過程:

  1. 1.   C++實現格式轉換的具體過程:

新建C++ win32空項目DLL,如”videoWrapCPlus”,

 

選擇“DLL”應用類型:

 

新建“MP4Encoder”類:

 

修改MP4Encoder.h文件,代碼如下:

/******************************************************************** 

filename:   MP4Encoder.h

created:    2016-04-14

author:     TangSir 

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:    2016-04-14

author:     TangSir 

purpose:    MP4編碼器,基於開源庫mp4v2實現(https://code.google.com/p/mp4v2/)。

*********************************************************************/ 

#include "stdafx.h"   

#include "MP4Encoder.h" 

#include <string.h> 

#pragma comment(lib,"../lib/libmp4v2.lib")

 

#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; 

注意:代碼中引入了編解碼庫Mp4v2的頭文件和lib文件,可從頁面(可能需要翻牆)https://code.google.com/p/mp4v2/中下載:

 

 

分別將下載包中的相關lib鏈接庫和頭文件拷貝到C++工程(具體位置跟C++代碼中引用路徑想關聯)中,其中lib庫在下載包lib文件夾下,頭文件在inc文件夾下:

 

 

編譯工程。

  1. 2.   利用Swig工具進行dll生成

下載Swig文件,Swing的使用方式大概有兩種,一種是配置Vs對外部工具,一種是配置環境變量通過Cmd命令。前一種方法比較簡單,Vs2012配置如圖。

 

 

其中Title為外部工具名稱,Command配置為swig.exe的地址,下面兩個按圖中配置。這樣swig即配置完成,接下來是該工具的使用。

在C++工程根路徑上添加.i文件,如test.i,編寫內容如下:

%module videoWrapCPlus

%include <windows.i>

 

%{

#include "MP4Encoder.h"

%}

 

%include "MP4Encoder.h"

其中第一行module名需要與C++的dll名稱一致。

此時,在當前頁面的狀態下,點擊工具欄中剛剛配置的swig工具:

 

會在videoWrapCPlus工程目錄下生成幾個文件:

 

其中以cs為后綴名的文件為C#類型文件。接着在C++項目中添加test_wrap.cxx,並編譯整個工程,以生成dll動態鏈接庫。整個C++工程結構圖如下:

 

  1. 3.   C#工程對dll文件的調用

新建C#工程,以控制台為例,並將剛才生成的C#類文件拷貝到工程根目錄下:

 

另外,需要將生成的dll文件和libmp4v2.dll(可從之前Mp4v2網站上下載)和拷貝到C#工程運行目錄下(若直接添加引用會報錯):

 

最后,可編寫測試函數,實現C#中對.264文件到.mp4文件的轉換過程:

 

其中WriteH264File函數的第一個參數為.264文件地址,第二個參數為目標文件地址(如有中文,需要UTF8編碼,建議使用英文路徑)。

 

 

 

 

 

--2016/4/14 於創意城


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM