您的位置:首页 > 其它

.264视频文件封装成.MP4方法

2016-10-19 16:58 495 查看
http://www.cnblogs.com/tangxiaosir/p/5391877.html

海康威视输出的视频格式为.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.   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文件夹下:




 
 
编译工程。
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++工程结构图如下:


 
3.   C#工程对dll文件的调用
新建C#工程,以控制台为例,并将刚才生成的C#类文件拷贝到工程根目录下:


 
另外,需要将生成的dll文件和libmp4v2.dll(可从之前Mp4v2网站上下载)和拷贝到C#工程运行目录下(若直接添加引用会报错):


 
最后,可编写测试函数,实现C#中对.264文件到.mp4文件的转换过程:
 


其中WriteH264File函数的第一个参数为.264文件地址,第二个参数为目标文件地址(如有中文,需要UTF8编码,建议使用英文路径)。
 
 
 
 
 
--2016/4/14 于创意城

标签: 264 视频 MP4 C#

好文要顶 关注我 收藏该文 





汤Sir
关注 - 0
粉丝 - 0

+加关注

0

0

posted on 2016-04-14 17:20 汤Sir 阅读(440) 评论(1) 编辑 收藏


评论


#1楼 2016-06-18
13:30 TinyGuan  

请问下按你给的例子,出现错误,请问是为什么呢?

“System.TypeInitializationException”类型的未经处理的异常在 videoWrapCSharp.exe 中发生 

其他信息: “videoWrapCPlusPINVOKE”的类型初始值设定项引发异常。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: