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文件,可从页面(可能需要FQ)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 于创意城

转载于:https://www.cnblogs.com/tangxiaosir/p/5391877.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值