编译环境:Ubuntu16.04 64位
交叉编译工具:arm-hisiv500-linux-gcc
1. 交叉编译mp4v2
下载合适版本的mp2v2源码,我下载的是2.0.0的bz包mp4v2_2.0.0~dfsg0.orig.tar.bz2
tar jxf mp4v2_2.0.0~dfsg0.orig.tar.bz2
cd mp4v2-2.0.0;./configure --host=arm-hisiv500-linux CC=arm-hisiv500-linux-gcc CXX=arm-hisiv500-linux-g++ --disable-debug
make
生成的libmp4v2.a在目录.libs
头文件位于include/mp4v2目录,头文件需要拷贝完全,代码中仅需要包含mp4v2.h
#include "mp4v2/mp4v2.h"
注意:移植后,链接libmp4v2.a需要加上-lstdc++选项。
2. sample代码
注意:代码基于hi3519平台。
2.1 创建文件
MP4FileHandle hMP4File = MP4CreateEx(fileName, 0, 1, 1, 0, 0, 0, 0);
MP4SetTimeScale(hMP4File, 90000);
2.2 关闭文件
MP4Close(hMP4File, 0);
2.3 写数据
typedef struct _MP4ENC_NaluUnit
{
int type;
int size;
unsigned char *data;
}MP4ENC_NaluUnit;
typedef struct _MP4ENC_INFO
{
unsigned int u32FrameRate;
unsigned int u32Width;
unsigned int u32Height;
unsigned int u32Profile;
}MP4ENC_INFO;
static HI_S32 Sample_MP4_ReadNalu(HI_U8 *pPack, HI_U32 nPackLen, unsigned int offSet, MP4ENC_NaluUnit *pNaluUnit)
{
int i = offSet;
while (i < nPackLen)
{
if (pPack[i++] == 0x00 && pPack[i++] == 0x00 && pPack[i++] == 0x00 && pPack[i++] == 0x01)// 开始码
{
int pos = i;
while (pos < nPackLen)
{
if (pPack[pos++] == 0x00 && pPack[pos++] == 0x00 && pPack[pos++] == 0x00 && pPack[pos++] == 0x01)
break;
}
if (pos == nPackLen)
pNaluUnit->size = pos - i;
else
pNaluUnit->size = (pos - 4) - i;
pNaluUnit->type = pPack[i] & 0x1f;
pNaluUnit->data = (unsigned char *)&pPack[i];
return (pNaluUnit->size + i - offSet);
}
}
return 0;
}
static HI_S32 Sample_MP4_WRITE(MP4FileHandle hFile, MP4TrackId *pTrackId,VENC_STREAM_S *pstStream, MP4ENC_INFO *stMp4Info)
{
int i = 0;
for (i = 0; i < pstStream->u32PackCount; i++)
{
HI_U8 *pPack = pstStream->pstPack[i].pu8Addr + pstStream->pstPack[i].u32Offset;
HI_U32 nPackLen = pstStream->pstPack[i].u32Len - pstStream->pstPack[i].u32Offset;
MP4ENC_NaluUnit stNaluUnit;
memset(&stNaluUnit, 0, sizeof(stNaluUnit));
int nPos = 0, nLen = 0;
while ((nLen = Sample_MP4_ReadNalu(pPack, nPackLen, nPos, &stNaluUnit)) != 0)
{
switch (stNaluUnit.type)
{
case H264E_NALU_SPS:
if (*pTrackId == MP4_INVALID_TRACK_ID)
{
*pTrackId = MP4AddH264VideoTrack(hFile, 90000, 90000 / stMp4Info->u32FrameRate, stMp4Info->u32Width, stMp4Info->u32Height, stNaluUnit.data[1], stNaluUnit.data[2], stNaluUnit.data[3], 3);
if (*pTrackId == MP4_INVALID_TRACK_ID)
{
return HI_FAILURE;
}
MP4SetVideoProfileLevel(hFile, stMp4Info->u32Profile);
MP4AddH264SequenceParameterSet(hFile,*pTrackId,stNaluUnit.data,stNaluUnit.size);
}
break;
case H264E_NALU_PPS:
if (*pTrackId == MP4_INVALID_TRACK_ID)
{
break;
}
MP4AddH264PictureParameterSet(hFile,*pTrackId,stNaluUnit.data,stNaluUnit.size);
break;
case H264E_NALU_IDRSLICE:
case H264E_NALU_PSLICE:
{
if (*pTrackId == MP4_INVALID_TRACK_ID)
{
break;
}
int nDataLen = stNaluUnit.size + 4;
unsigned char *data = (unsigned char *)malloc(nDataLen);
data[0] = stNaluUnit.size >> 24;
data[1] = stNaluUnit.size >> 16;
data[2] = stNaluUnit.size >> 8;
data[3] = stNaluUnit.size & 0xff;
memcpy(data + 4, stNaluUnit.data, stNaluUnit.size);
if (!MP4WriteSample(hFile, *pTrackId, data, nDataLen, MP4_INVALID_DURATION, 0, 1))
{
free(data);
return HI_FAILURE;
}
free(data);
}
break;
default :
break;
}
nPos += nLen;
}
}
return HI_SUCCESS;
}
以上代码没有封装音频,可以稍作修改,用作其他平台。
mp4v2貌似不支持h265,后续将使用其他的开源库做封装,详见我的另一篇博客《使用gpac封装mp4》。
2.4 SEI写入
很久没有看mp4v2的代码了,最近有个项目需要MP4支持SEI信息,整理一下。
先了解MP4下的Mdat Box,可以从其他博客找到相关资料,这个Box是存储音视频数据的Box。
H264/H265是由一系列的NALU组成,NALU结构如下:
Start code + NALU header + NALU payload
但是在MP4文件中,H264/H265 slice并不是以00 00 00 01 Start Code做分割,而是存储在Mdat Box的Data中。
Mdat Box的格式:
Box header + Box Data
展开如下:
Box length + Box Type + NALU length + NALU header + Nalu Data…
NALU length + NALU header + Nalu Data
其中,Box header为Box length + Box Type,Box Data为多个NALU length + NALU header + Nalu Data的拼接。
以H264为例,如果要存储带SEI的I帧或者P帧,可以将SEI和图像内容拼接成一个包,调用MP4WriteSample,示例代码中调用MP4WriteSample写入的即是(NALU length + NALU header + Nalu Data)数据。
转载请注明出处,如有错漏之处,敬请指正。