ffmpeg保存MP4参见:https://blog.csdn.net/xinxinsky/article/details/88531524,代码:https://gitee.com/careye_open_source_platform_group/MP4MuxerTest/repository/archive/master.zip
从IPC采集H264帧和AAC帧,然后保存为MP4文件,直接上代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
const unsigned char RL_NALU_TYPE_NONE = -1;
const unsigned char RL_NALU_TYPE_SLICE = 0x01;
const unsigned char RL_NALU_TYPE_IDR = 0x05;
const unsigned char RL_NALU_TYPE_SEI = 0x06;
const unsigned char RL_NALU_TYPE_SPS = 0x07;
const unsigned char RL_NALU_TYPE_PPS = 0x08;
const unsigned char RL_NALU_TYPE_MASK = 0x1f;
//视频或者音画同步的主要参数是duration, 公式可以是 (当前帧录制时间-上一针录制时间)*90000/1000;
const int timeScale = 90000;
inline bool is_frame_delimeter(const uint8_t *data, int& len) {
len = (0x00 == data[0] && 0x00 == data[1] && 0x01 == data[2]) ? 3 :
((0x00 == data[0] && 0x00 == data[1] && 0x00 == data[2] && 0x01 == data[3]) ? 4 : 0);
return(0 != len);
}
int CreateMP4File(MP4FileHandle& pHandle, const char *strFileName) {
pHandle = MP4Create(strFileName, 0);
if(pHandle == MP4_INVALID_FILE_HANDLE)
{
printf("ERROR:Create mp4 handle fialed.\n");
return -1;
}
MP4SetTimeScale(pHandle, timeScale);
return 0;
}
static int WriteSPS(MP4FileHandle pHandle, uint8_t *pNalu, MP4TrackId& videoId, int nNaluSize,
int timeScale, int width, int height, int frameRate, int& addStream)
{
/*printf("------------------------------------\n");
printf("sps(%d)\n", nNaluSize);*/
if (addStream)
{
videoId = MP4AddH264VideoTrack
(pHandle,
timeScale, // 一秒钟多少timescale
timeScale/frameRate, // 每个帧有多少个timescale
width, // width
height, // height
pNalu[1], // sps[1] AVCProfileIndication
pNalu[2], // sps[2] profile_compat
pNalu[3], // sps[3] AVCLevelIndication
3); // 4 bytes length before each NAL unit
if (videoId == MP4_INVALID_TRACK_ID)
{
printf("Error:Can't add track.\n");
return -1;
}
MP4SetVideoProfileLevel(pHandle, 0x7F);
addStream = 0;
}
MP4AddH264SequenceParameterSet(pHandle, videoId, pNalu, nNaluSize);
return 0;
}
int addStream = 1;
int WriteVideoFrame(MP4FileHandle pHandle, MP4TrackId& videoId, int& addStream, uint8_t *pBuf, int nDataSize)
{
int width = 640; //这3个参数要从SPS中解析出来
int height = 480;
int frameRate = 25;
int nSCPLen = 0;
if(!is_frame_delimeter(pBuf, nSCPLen)) {
printf("NALU error.\n");
return -1;
}
assert(4 == nSCPLen);
int nNaluSize = nDataSize - nSCPLen;
unsigned char *pNalu = pBuf + nSCPLen; //pNalu = pBuf+4;
unsigned char naluType = pNalu[0] & RL_NALU_TYPE_MASK;
switch (naluType)
{
case 0x07: // SPS
if(0 != WriteSPS(pHandle, pNalu, videoId, nNaluSize,
timeScale, width, height, frameRate, addStream)) {
MP4Close(pHandle, 0);
return -1;
}
break;
case 0x08: // PPS
//printf("pps(%d)\n", nNaluSize);
MP4AddH264PictureParameterSet(pHandle, videoId, pNalu, nNaluSize);
break;
default:
//printf("slice(%d)\n", nNaluSize);
pBuf[0] = (nNaluSize>>24)&0xFF;
pBuf[1] = (nNaluSize>>16)&0xFF;
pBuf[2] = (nNaluSize>>8)&0xFF;
pBuf[3] = (nNaluSize>>0)&0xFF;
MP4WriteSample(pHandle, videoId, pBuf, nNaluSize + 4, MP4_INVALID_DURATION, 0, 1);
break;
}
return 0;
}
int WriteAudioFrame(MP4FileHandle pHandle, MP4TrackId& audioId, uint8_t *buf, int nFrameSize ) {
//去掉前面元数据头:7个字节或者9个字节
const unsigned int nMetaLen = ((buf[1] & 0x01) == 1 ? 7 : 9); //元数据长度
const int H2 = buf[3] & 0x03;
const int M8 = buf[4];
const int L3 = (int(buf[5] & 0xE0)) >> 5;
const int nTotalLen = (H2 << 9) | (M8 << 3) | L3; //音频帧长度,包括ADTS头
if(nTotalLen != int(nFrameSize)) {
printf("Len error.\n");
}
if(MP4_INVALID_TRACK_ID == audioId) {//添加aac音频
const int nProfileOffset = 2;
const uint8_t nProfileMask = 0xC0;
const uint8_t nSampleRateMask = 0x3C;
const uint32_t aSmapleRates[] = {96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000};
printf("ADTS: 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n",
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5],
buf[6], buf[7], buf[8], buf[9]);
//第2个字节的最高2位,profile, the MPEG-4 Audio Object Type minus 1
uint8_t nProfile = ((buf[nProfileOffset] & nProfileMask) >> 6) + 1;
//MPEG-4 Sampling Frequency Index (15 is forbidden)
uint8_t nSampleRate = (buf[nProfileOffset] & nSampleRateMask) >> 2;
//第3字节的最低位和第4字节的最高2位, MPEG-4 Channel Configuration (in the case of 0,
//the channel configuration is sent via an inband PCE)
uint8_t nChannel = ((buf[nProfileOffset] & 0x1) << 2) | ((buf[nProfileOffset + 1] & 0xc0) >> 6);
printf("profile: %u, sample-rate: %u - %u, channels: %u\n", nProfile, nSampleRate, aSmapleRates[nSampleRate], nChannel);
/*
timeScale 采样率 16000 32000 44100
sampleDuration 这个参数填写的是每一帧的字节数: sampleDuration * 1000 / timeScale = Duration Time
一帧的持续时间。
*/
audioId = MP4AddAudioTrack(pHandle, aSmapleRates[nSampleRate],
2048, //MP4_INVALID_DURATION
MP4_MPEG2_AAC_LC_AUDIO_TYPE); //MP4_MPEG4_AUDIO_TYPE
if (audioId == MP4_INVALID_TRACK_ID)
{
printf("add audio track failed.\n");
return -2;
}
MP4SetAudioProfileLevel(pHandle, nProfile); //0x2);
/*
aacObjectType(5bits) -- 就是nProfile.
sampleRateIdx(4bits),
numChannels(4bits)
*/
uint8_t aEsConfig[2] = {0};
aEsConfig[0] = ((nProfile << 3) | (nSampleRate >> 1));
aEsConfig[1] = ((nSampleRate & 0x01) << 7) | (nChannel << 3);
printf("ES config: 0x%02x 0x%02x\n", aEsConfig[0], aEsConfig[1]);
if(!MP4SetTrackESConfiguration(pHandle, audioId, aEsConfig, sizeof(aEsConfig))) {
printf("Fail to ES configure.\n");
return -4;
}
}
bool bRet = MP4WriteSample(pHandle, audioId, buf + nMetaLen,
nFrameSize - nMetaLen , MP4_INVALID_DURATION, 0, 1);
if(!bRet) {
printf("Fail to write audio frame.\n");
return -3;
}
return nFrameSize;
}
int main() {
MP4FileHandle pHandle = NULL;
int ret = CreateMP4File(pHandle, argv[3]);
if(0 != ret) {
printf("Fail to create MP4 file.\n");
return 1;
}
//...
while(1) {
nVideoRet = WriteVideoFrame(...);
nAudioRet = WriteAudioFrame(...);
}
MP4Close(pHandle, 0);
return 0;
}