利用MP4v2进行MP4格式封装
最开始只想用最简单的方法实现rtsp流保存为MP4,查找了很多资料,发现mp4v2比较简单,就开始用它进行MP4的封装,后来又实现了用ffmpeg进行格式的封装,发现MP4v2封装的文件效果比较好,同时,对于固定帧率的文件,不用手动写时间戳,只有设置默认的参数,就可以很容易实现音视频的同步。相比较ffmpeg操作流程确实要简单很多,这个毕竟是专门用来进行MP4格式的封装。
其实MP4v2用法很简单,主要是几个重要的参数设置。
MP4FileHandle MP4Create (const char* fileName,uint32_t flags)
功能:创建MP4文件句柄。
返回:MP4文件句柄。
参数:fileName 要录制的MP4文件名;flags 创建文件类型,如果要创建普通文件用默认值0就可以,如要录制大于4G的MP4文件此处要设置MP4_CREATE_64BIT_DATA。
bool MP4SetTimeScale( MP4FileHandle hFile, uint32_t value )
功能:设置时间标度。
返回:成功返回true,失败返回false。
参数:hFile MP4文件句柄,value 要设置的值(每秒的时钟ticks数)。
MP4TrackId MP4AddH264VideoTrack(MP4FileHandle hFile,
uint32_t timeScale,
MP4Duration sampleDuration,
uint16_t width,
uint16_t height,
uint8_t AVCProfileIndication,
uint8_t profile_compat,
uint8_t AVCLevelIndication,
uint8_t sampleLenFieldSizeMinusOne)
功能:添加h264视频track。
返回:返回track id号。
参数:hFile MP4文件句柄,timeScale 视频每秒的ticks数(如90000),sampleDuration 有两种设置情况,width height 视频的宽高,AVCProfileIndication profile (baseline profile, main profile, etc. see),profile_compat compatible profile,AVCLevelIndication levels,sampleLenFieldSizeMinusOne 设置为3.
注意: 固定帧率时,sampleDuration 设置为Video_Time_Scale / nFrameRate,Video_Time_Scale一般为90000,nFrameRate为帧率,一般为25或30;变帧率时,sampleDuration 设置为MP4_INVALID_DURATION;注意根据实际情况进行设置。AVCProfileIndication,profile_compat, AVCLevelIndication,这三个参数值是在h264流中得到的。
MP4TrackId MP4AddAudioTrack(MP4FileHandle hFile,
uint32_t timeScale,
MP4Duration sampleDuration,
uint8_t audioType)
功能:添加音频(aac)track。
返回:返回track id号。
参数:hFile MP4句柄,timeScale音频每秒的ticks数(如16000),下面两参数设置为MP4_INVALID_DURATION和MP4_MPEG4_AUDIO_TYPE。
注意:固定帧率时,sampleDuration 设置为1024;变帧率时,sampleDuration 设置为MP4_INVALID_DURATION;
bool MP4SetTrackESConfiguration(MP4FileHandle hFile,
MP4TrackId trackId,
const uint8_t* pConfig,
uint32_t configSize );
功能:设置音频解码信息(如果设置错误会导致没有声音)。
返回:成功返回true,失败返回false。
参数:hFile 文件句柄,trackId 音频的track id,pConfig 记录解码信息的二进制流,configSize 解码串的长度。
注意:mpeg4ip 使用faac进行aac音频编码的,在编码时可以调用相应的函数得到二进制串pConfig和长度configSize,但是如果aac不是用faac编码的,这是需要自己填充pConfig,可以参考faac的实现.
bool MP4WriteSample(MP4FileHandle hFile,
MP4TrackId trackId,
const uint8_t* pBytes,
uint32_t numBytes,
MP4Duration duration DEFAULT(MP4_INVALID_DURATION),
MP4Duration renderingOffset DEFAULT(0),
bool isSyncSample DEFAULT(true) );
功能:写一帧视频数据或写一段音频数据。
返回:成功返回true,失败返回false。
参数:hFile 文件句柄,trackId 音频或视频的track id,pBytes为要写的数据流指针,numBytes为数据字节长度,duration为前一视频帧与当前视频帧之间的ticks数,或这是前一段音频数据和当前音频数据之间的ticks。isSyncSample 对视频来说是否为关键帧。
注意:变帧率时,duration设置为当前帧的时间减去前一帧的时间,并除以各自的timeScale。音频和视频在此处操作一样,存储的均为连续两帧之间的时间差。固定帧率时,duration 设置为MP4_INVALID_DURATION
void MP4AddH264SequenceParameterSet(MP4FileHandle hFile,
MP4TrackId trackId,
const uint8_t* pSequence,
uint16_t sequenceLen );
和
void MP4AddH264PictureParameterSet(MP4FileHandle hFile,
MP4TrackId trackId,
const uint8_t* pPict,
uint16_t pictLen );
功能:添加序列参数集,添加图像参数集。
参数:hFile 文件句柄,trackId 视频track id,pSequence和pPict为要写入的序列图像参数集的数据指针,sequenceLen和pictLen为串长度。
注意:当检测到序列参数集或图像参数集更新时要调用MP4AddH264SequenceParameterSet或MP4AddH264PictureParameterSet进行更新。
void MP4Close(MP4FileHandle hFile,
uint32_t flags DEFAULT(0) );
功能:关闭以打开的MP4文件。
参数:hFile 文件句柄,flags 是否允许在关闭MP4文件前做一些额外的优化处理。
注意:在录制较小的MP4文件时可以把flags设置为默认值,如果录制较大的文件最好把flags设置为MP4_CLOSE_DO_NOT_COMPUTE_BITRATE否则调用MP4Close函数会用掉很长的时间。
以下是代码中对mp4v2接口的封装类代码
#include "mp4v2/mp4v2.h"
class MP4Encoder
{
public:
MP4Encoder(void);
~MP4Encoder(void);
int MP4CreateFile(const char *sFileName,int Movie_Time_Scale=90000);
int MP4AddH264Track(const uint8_t *sData, int nSize,int nWidth, int nHeight, int nFrameRate = 25,int Video_Time_Scale=90000);
int MP4AddAACTrack(const uint8_t *sData, int nSize,int Audio_Time_Scale=44100);
int MP4WriteH264Data(uint8_t *sData, int nSize);
int MP4WriteAACData(const uint8_t *sData, int nSize);
void MP4ReleaseFile();
private:
MP4FileHandle m_hFile;
MP4TrackId m_videoTrack, m_audioTrack;
};
#define DEFAULT_VIDEO_TRACK_NUM 3
#define DEFAULT_VIDEO_PROFILE_LEVEL 1
#define DEFAULT_AUDIO_PROFILE_LEVEL 2
MP4Encoder::MP4Encoder(void)
: m_hFile(MP4_INVALID_FILE_HANDLE)
, m_videoTrack(MP4_INVALID_TRACK_ID)
, m_audioTrack(MP4_INVALID_TRACK_ID)
{
}
MP4Encoder::~MP4Encoder(void)
{
}
int MP4Encoder::MP4CreateFile(const char *sFileName,int Movie_Time_Scale)
{
m_hFile = MP4Create(sFileName);
if (m_hFile == MP4_INVALID_FILE_HANDLE)
{
printf("create mp4 file error!\n");
return -1;
}
if (!MP4SetTimeScale(m_hFile, Movie_Time_Scale))
{
printf("set time Scale error!\n");
return -1;
}
return 0;
}
int MP4Encoder::MP4AddH264Track(const uint8_t *sData, int nSize,
int nWidth, int nHeight, int nFrameRate ,int Video_Time_Scale)
{
int sps, pps;
for (sps = 0; sps < nSize;)
if (sData[sps++] == 0x00 && sData[sps++] == 0x00 && sData[sps++] == 0x00
&& sData[sps++] == 0x01)
break;
for (pps = sps; pps < nSize;)
if (sData[pps++] == 0x00 && sData[pps++] == 0x00 && sData[pps++] == 0x00
&& sData[pps++] == 0x01)
break;
if (sps >= nSize || pps >= nSize)
return -1;
m_videoTrack = MP4AddH264VideoTrack(m_hFile, Video_Time_Scale,
Video_Time_Scale / nFrameRate, nWidth, nHeight,
sData[sps + 1], sData[sps + 2], sData[sps + 3], DEFAULT_VIDEO_TRACK_NUM);//sps 后面的三个字节
if (MP4_INVALID_TRACK_ID == m_videoTrack)
{
printf("add video track error!\n");
return -1;
}
MP4SetVideoProfileLevel(m_hFile, DEFAULT_VIDEO_PROFILE_LEVEL);
MP4AddH264SequenceParameterSet(m_hFile, m_videoTrack, sData + sps,pps - sps - 4);//sps 内容 起始位置+长度
MP4AddH264PictureParameterSet(m_hFile, m_videoTrack, sData + pps,nSize - pps);// pps 内容 起始位置+长度
return 0;
}
int MP4Encoder::MP4AddAACTrack(const uint8_t *sData, int nSize,int Audio_Time_Scale)
{
m_audioTrack = MP4AddAudioTrack(m_hFile, Audio_Time_Scale,1024, MP4_MPEG4_AUDIO_TYPE);
if (MP4_INVALID_TRACK_ID == m_audioTrack)
{
printf("add audio track error!\n");
return -1;
}
MP4SetAudioProfileLevel(m_hFile, DEFAULT_AUDIO_PROFILE_LEVEL);
if (!MP4SetTrackESConfiguration(m_hFile, m_audioTrack, sData, nSize))
{
printf("set track ESConfiguration error!\n");
return -1;
}
return 0;
}
int MP4Encoder::MP4WriteH264Data(uint8_t *sData, int nSize)
{
bool result = false;
sData[0] = (nSize - 4) >> 24;
sData[1] = (nSize - 4) >> 16;
sData[2] = (nSize - 4) >> 8;
sData[3] = nSize - 4;
if(nSize<0||sData==NULL)
return -1;
if ((sData[4] & 0x1F) == 5)//判断I帧
result = MP4WriteSample(m_hFile, m_videoTrack, sData, nSize,MP4_INVALID_DURATION,0,true);
else
result = MP4WriteSample(m_hFile, m_videoTrack, sData, nSize,MP4_INVALID_DURATION,0,false);
if (!result)
{
printf("write h264 frame error!\n");
return -1;
}
return 0;
}
int MP4Encoder::MP4WriteAACData(const uint8_t *sData, int nSize)
{
bool result = false;
if(nSize<0||sData==NULL)
return -1;
result = MP4WriteSample(m_hFile, m_audioTrack, sData, nSize,MP4_INVALID_DURATION,0,true);
if (!result)
{
printf("write aac frame error!\n");
return -1;
}
return 0;
}
void MP4Encoder::MP4ReleaseFile()
{
if (m_hFile != MP4_INVALID_FILE_HANDLE)
{
//MP4Close(m_hFile,MP4_CLOSE_DO_NOT_COMPUTE_BITRATE);
MP4Close(m_hFile);
m_hFile = MP4_INVALID_FILE_HANDLE;
}
}
根据上面给出的类,在录制时只设置相关的参数即可。
MP4v2录制rtsp工程地址下载:https://download.csdn.net/download/unfound/10620870
缺失的dll下载地址:链接:https://pan.baidu.com/s/1TvpgLOs1YDPvY9HurpJ_iA 密码:fkoj