海思HI3531D使用ffmpeg实时封装多路H264视频+AAC音频为MP4
零、前提:多路实时封装H264裸码流和AAC音频到mp4容器,不涉及编解码方面。第一次做视音频方面项目,以此文做个笔记。参考了雷神写的许多ffmpeg的文章,雷神的是对封装容器的转换。同时参考了https://blog.csdn.net/zhenglie110/article/details/88030925的实时封装,他的代码已经实现了大部分主要功能,我也是在项目中拿他的代码进行加工的,...
零、前提:
2020-3-7
实时封装多路H264裸码流和AAC音频到mp4,不涉及编码。编码我都用海思做了,没研究过ffmpeg的编码。
大部分代码都在注释里面写的很清楚了。
第一次做视音频方面项目,以此文做个笔记,也希望能帮到你,毕竟我也是从这里获得了很多帮助。
参考了雷神写的许多ffmpeg封装的文章。
同时参考了https://blog.csdn.net/zhenglie110/article/details/88030925的实时封装,他的代码已经实现了大部分主要功能,我也是在项目中拿他的代码进行加工的,连函数名都没变,够懒的,但不代表没有认真解读过,也做了些改动。
1,增加了音频写入函数。
2,由于我使用的是ffmpeg-4.x版本,新版本已经弃用了AVStream结构体中的AVCodec codec,改用AVCodecParameters codecpar代替,所以这里顺便给改了,不改的话在封装的时候会出警告,不影响功能。我看不惯警告,消灭掉。
3,增加了SPS和PPS信息的写入,不然mp4文件没有封面缩略图。
H264裸码流是通过海思提供的HI_MPI_VENC_GetStream获取到VENC_PACK_S结构体中。
AAC音频是HI_MPI_AENC_GetStream获取到AUDIO_STREAM_S结构体中。
一、实现代码:
代码在HI3531D上运行通过,适当调整即可
0、自定义的一个结构体:
typedef struct
{
AVFormatContext* g_OutFmt_Ctx[VENC_MAX_CHN_NUM]; //每个通道的AVFormatContext
int vi[VENC_MAX_CHN_NUM]; //视频流索引号
int ai[VENC_MAX_CHN_NUM]; //音频流索引号
HI_BOOL b_First_IDR_Find[VENC_MAX_CHN_NUM]; //第一帧是I帧标志
long int VptsInc[VENC_MAX_CHN_NUM]; //用于视频帧递增计数
long int AptsInc[VENC_MAX_CHN_NUM]; //音频帧递增
HI_U64 Audio_PTS[VENC_MAX_CHN_NUM]; //音频PTS
HI_U64 Video_PTS[VENC_MAX_CHN_NUM]; //视频PTS
HI_U64 Afirst[VENC_MAX_CHN_NUM]; //是文件第一帧音频标志
HI_U64 Vfirst[VENC_MAX_CHN_NUM]; //视频第一帧标志
HI_BOOL state;
long int moov_pos[VENC_MAX_CHN_NUM]; //moov的pos,未使用
int moov_flags[VENC_MAX_CHN_NUM]; //moov前置标志,未使用
int file_flags[VENC_MAX_CHN_NUM];
char filename[VENC_MAX_CHN_NUM][1024]; //文件名
}FfmpegConf;
1、创建mp4文件:
每个通道的文件名不一样,几通道就调用几次。
这个函数我并没有往AVFormatContext里面添加视音频流以及视音频参数设置,我是等第一个I帧来了之后,
根据SPS和PPS信息添加视频流。海思的I帧包含4个数据包(NormalP模式):IDR,SEI,SPS,PPS。
也可以在这个函数里面直接添加流,需要事先获取SPS和PPS。
根据我的测试,SPS和PPS是固定的,只要海思编码设置确定后。
但是只要变了,就要重新写SPS、PPS,所以,我是在读取到I帧的时候,读取SPS、PPS信息来添加流。
//MP4创建函数:初始化,写文件头。
//参数:通道号,文件名,fc,
int HI_PDT_CreateMp4(VENC_CHN VeChn, char *pfile, FfmpegConf *fc)
{
int ret = 0;
char pszFileName[256] = {0}; //保存文件名
AVOutputFormat *pOutFmt = NULL; //输出Format指针
#ifdef FFMPEG_MUXING
AVCodec *audio_codec;
#endif
sprintf(pszFileName,"%s.mp4",pfile);
// av_register_all(); //已弃用
avformat_alloc_output_context2(&(fc->g_OutFmt_Ctx[VeChn]), NULL, NULL, pszFileName);//初始化输出视频码流的AVFormatContext。
if (NULL == fc->g_OutFmt_Ctx[VeChn]) //失败处理
{
ADD_LOG("Muxing:Could not deduce output format from file extension: using mp4. \n");//添加日志
avformat_alloc_output_context2(&(fc->g_OutFmt_Ctx[VeChn]), NULL, "mp4", pszFileName);
if (NULL == fc->g_OutFmt_Ctx[VeChn])
{
ADD_LOG("Muxing:avformat_alloc_output_context2 failed\n");
return -1;
}
}
// set_mov_moov_ahead( fc->g_OutFmt_Ctx[VeChn] ); //前置moov
pOutFmt = fc->g_OutFmt_Ctx[VeChn]->oformat; //获取输出Format指针
if (pOutFmt->video_codec == AV_CODEC_ID_NONE) //检查视频编码器
{
ADD_LOG("Muxing:add_video_stream ID failed\n");
goto exit_outFmt_failed;
}
if (pOutFmt->audio_codec == AV_CODEC_ID_NONE) //检查音频编码器
{
ADD_LOG("Muxing:add_audio_stream ID failed\n");
goto exit_outFmt_failed;
}
if (!(pOutFmt->flags & AVFMT_NOFILE)) //应该是判断文件IO是否打开
{
ret = avio_open(&(fc->g_OutFmt_Ctx[VeChn]->pb), pszFileName, AVIO_FLAG_WRITE); //创建并打开mp4文件
if (ret < 0)
{
#ifdef DEBUG
printf("pszFileName=%s\n",pszFileName);
#endif
ADD_LOG("Muxing:could not create video file\n");
goto exit_avio_open_failed;
}
}
//初始化一些参数
fc->Video_PTS[VeChn]=0;
fc->Audio_PTS[VeChn]=0;
fc->Vfirst[VeChn]=0;
fc->Afirst[VeChn]=0;
fc->vi[VeChn]=-1;
fc->ai[VeChn]=-1;
fc->b_First_IDR_Find[VeChn] = 0;
return HI_SUCCESS;
//错误处理
exit_avio_open_failed:
if (fc->g_OutFmt_Ctx[VeChn] && !(fc->g_OutFmt_Ctx[VeChn]->flags & AVFMT_NOFILE))
avio_close(fc->g_OutFmt_Ctx[VeChn]->pb);
exit_outFmt_failed:
if(NULL != fc->g_OutFmt_Ctx[VeChn])
avformat_free_context(fc->g_OutFmt_Ctx[VeChn]);
return -1;
}
2、写视频帧:
直接从 VENC_STREAM_S 中提取视频帧数据写入MP4。
HI_S32 HI_PDT_WriteVideo(VENC_CHN VeChn, VENC_STREAM_S *pstStream, FfmpegConf *fc)
{
unsigned int i=0; //
unsigned char* pPackVirtAddr = NULL; //码流首地址
unsigned int u32PackLen = 0; //码流长度
int ret = 0;
AVStream *Vpst = NULL; //视频流指针
AVPacket pkt; //音视频包结构体,这个包不是海思的包,填充之后,用于最终写入数据
uint8_t sps_buf[32]; //
uint8_t pps_buf[32];
uint8_t sps_pps_buf[64];
unsigned int pps_len=0;
unsigned int sps_len=0;
if(NULL == pstStream) //裸码流有效判断
{
return HI_SUCCESS;
}
//u32PackCount是海思中记录此码流结构体中码流包数量,一般含I帧的是4个包,P帧1个
for (i = 0 ; i < pstStream->u32PackCount; i++)
{
//从海思码流包中获取数据地址,长度
pPackVirtAddr = pstStream->pstPack[i].pu8Addr + pstStream->pstPack[i].u32Offset;
u32PackLen = pstStream->pstPack[i].u32Len -pstStream->pstPack[i].u32Offset;
av_init_packet(&pkt); //初始化AVpack包,
pkt.flags=AV_PKT_FLAG_KEY; //默认是关键帧,关不关键好像都没问题
switch(pstStream->pstPack[i].DataType.enH264EType)
{
case H264E_NALU_SPS: //如果这个包是SPS
pkt.flags = 0; //不是关键帧
if(fc->b_First_IDR_Find[VeChn] == 2) //如果不是第一个SPS帧
{
continue; //不处理,丢弃
//我只要新建文件之后的第一个SPS PPS信息,后面都是一样的,只要第一个即可
}
else //如果是第一个SPS帧
{
sps_len = u32PackLen;
memcpy(sps_buf, pPackVirtAddr, sps_len);
if(fc->b_First_IDR_Find[VeChn] ==1) //如果PPS帧已经收到
{
memcpy(sps_pps_buf, sps_buf, sps_len); //复制sps
memcpy(sps_pps_buf+sps_len, pps_buf, pps_len); //加上pps
//去添加视频流,和SPS PPS信息,这步之后才开始写入视频帧
ret = HI_ADD_SPS_PPS(VeChn, sps_pps_buf, sps_len+pps_len, fc);
if(ret<0)return HI_FAILURE;
}
fc->b_First_IDR_Find[VeChn]++; //
}
continue; //继续
//break;
case H264E_NALU_PPS:
pkt.flags = 0; //不是关键帧
if(fc->b_First_IDR_Find[VeChn] == 2) //如果不是第一个PPS帧
{
continue;
}
else //是第一个PPS帧
{
pps_len = u32PackLen;
memcpy(pps_buf, pPackVirtAddr, pps_len); //复制
if(fc->b_First_IDR_Find[VeChn] ==1) //如果SPS帧已经收到
{
memcpy(sps_pps_buf, sps_buf, sps_len);
memcpy(sps_pps_buf+sps_len, pps_buf, pps_len);
//这里和SPS那里互斥,只有一个会执行,主要是看SPS和PPS包谁排在后面
ret = HI_ADD_SPS_PPS(VeChn, sps_pps_buf, sps_len+pps_len, fc);
if(ret<0)return HI_FAILURE;
}
fc->b_First_IDR_Find[VeChn]++;
}
continue;
case H264E_NALU_SEI: //增强帧
continue; //不稀罕这个帧
case H264E_NALU_PSLICE: //P帧
case H264E_NALU_IDRSLICE: //I帧
if(fc->b_First_IDR_Find[VeChn] !=2) //如果这个文件还没有收到过sps和pps帧
{
continue; //跳过,不处理这帧
}
break;
default:
break;
}
if(fc->vi[VeChn]<0) //流索引号,如果g_OutFmt_Ctx里面还没有新建视频流,也就是说还没收到I帧
{
#ifdef DEBUG
printf("vi less than 0 \n");
#endif
return HI_SUCCESS;
}
if(fc->Vfirst[VeChn]==0) //如果是文件的第一帧视频
{
fc->Vfirst[VeChn]=1;
#ifdef USING_SEQ //使用帧序号计算PTS
fc->Video_PTS[VeChn] = pstStream->u32Seq; //记录初始序号
#endif
#ifdef USING_PTS //直接使用海思的PTS
fc->Video_PTS[VeChn] = pstStream->pstPack[i].u64PTS; //记录开始时间戳
#endif
}
Vpst = fc->g_OutFmt_Ctx[VeChn]->streams[fc->vi[VeChn]]; //根据索引号获取视频流地址
pkt.stream_index = Vpst->index; //视频流的索引号 赋给 包里面的流索引号,表示这个包属于视频流
//以下,时间基转换,PTS很重要,涉及音视频同步问题
#if 0 //原博主的,可以用
pkt.pts = av_rescale_q_rnd((fc->VptsInc[VeChn]++), Vpst->codec->time_base,Vpst->time_base,(enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
pkt.dts = av_rescale_q_rnd(pkt.pts, Vpst->time_base,Vpst->time_base,(enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
#endif
#if 1 //我用的
#ifdef USING_SEQ
//跟原博主差不多,我怕中间丢帧,导致不同步,所以用序号来计算
pkt.pts = av_rescale_q_rnd(pstStream->u32Seq - fc->Video_PTS[VeChn], (AVRational){1, STREAM_FRAME_RATE},Vpst->time_base,(enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
pkt.dts = pkt.pts; //只有I、P帧,相等就行了
#endif
#ifdef USING_PTS
//海思的PTS是us单位,所以将真实世界的1000000us转成90000Hz频率的时间
pkt.pts = pkt.dts =(int64_t)((pstStream->pstPack[i].u64PTS - fc->Video_PTS[VeChn]) *0.09+0.5);
#endif
#endif
//一秒25帧,一帧40ms,好像写0也行,ffmpeg内部处理了?
//按理说,新建流的时候,给了codepar帧率参数,ffmpeg是可以计算的
pkt.duration=40;
pkt.duration = av_rescale_q(pkt.duration, Vpst->time_base, Vpst->time_base);
pkt.pos = -1; //默认
//最重要的数据要给AVpack包
pkt.data = pPackVirtAddr ; //接受视频数据NAUL
pkt.size = u32PackLen; //视频数据长度
//把AVpack包写入mp4
ret = av_interleaved_write_frame(fc->g_OutFmt_Ctx[VeChn], &pkt);
if (ret < 0)
{
ADD_LOG("Muxing:cannot write video frame\n");
return HI_FAILURE;
}
}
return HI_SUCCESS;
}
2.1、写视频函数的子函数:
在收到第一帧SPS、PPS后被调用。
调用HI_PDT_Add_Stream()向 AVFormatContext* g_OutFmt_Ctx 中创建音视频流并添加流信息,
然后写入文件头
HI_S32 HI_ADD_SPS_PPS(VENC_CHN VeChn, uint8_t *buf, uint32_t size, FfmpegConf *fc)
{
HI_S32 ret;
ret = HI_PDT_Add_Stream(VeChn,fc); //创建一个新流并添加到当前AVFormatContext中
if(ret<0)
{
ADD_LOG("Muxing:HI_PDT_Add_Stream faild\n");
goto Add_Stream_faild;
}
fc->g_OutFmt_Ctx[VeChn]->streams[fc->vi[VeChn]]->codecpar->extradata_size = size;
fc->g_OutFmt_Ctx[VeChn]->streams[fc->vi[VeChn]]->codecpar->extradata = (uint8_t*)av_malloc(size + AV_INPUT_BUFFER_PADDING_SIZE);
memcpy(fc->g_OutFmt_Ctx[VeChn]->streams[fc->vi[VeChn]]->codecpar->extradata, buf, size); //写入SPS和PPS
ret = avformat_write_header(fc->g_OutFmt_Ctx[VeChn], NULL); //写文件头
if(ret<0)
{
ADD_LOG("Muxing:avformat_write_header faild\n");
goto write_header_faild;
}
return HI_SUCCESS;
write_header_faild:
if (fc->g_OutFmt_Ctx[VeChn] && !(fc->g_OutFmt_Ctx[VeChn]->flags & AVFMT_NOFILE))
avio_close(fc->g_OutFmt_Ctx[VeChn]->pb);
Add_Stream_faild:
if(NULL != fc->g_OutFmt_Ctx[VeChn])
avformat_free_context(fc->g_OutFmt_Ctx[VeChn]);
fc->vi[VeChn] = -1;
fc->ai[VeChn] = -1; //AeChn
fc->VptsInc[VeChn]=0;
fc->AptsInc[VeChn]=0;
fc->b_First_IDR_Find[VeChn] = 0; //sps,pps帧标志清除
return HI_FAILURE;
}
2.2、添加流,被HI_ADD_SPS_PPS()调用:
static int HI_PDT_Add_Stream(VENC_CHN VeChn, FfmpegConf *fc)
{
AVOutputFormat *pOutFmt = NULL; //用于获取AVFormatContext->Format
// AVCodecContext *vAVCodecCtx = NULL ; //用于获取弃用的AVStream->Codec
AVCodecParameters *vAVCodecPar=NULL; //新替代参数AVStream->CodecPar
// AVCodecContext *aAVCodecCtx = NULL;
AVCodecParameters *aAVCodecPar=NULL;
AVStream *vAVStream = NULL; //用于指向新建的视频流
AVStream *aAVStream = NULL; //用于指向新建的音频流
AVCodec *vcodec = NULL; //用于指向视频编码器
AVCodec *acodec = NULL; //用于指向音频编码器
pOutFmt = fc->g_OutFmt_Ctx[VeChn]->oformat; //输出Format
vcodec = avcodec_find_encoder(pOutFmt->video_codec); //查找视频编码器,默认就是H264了
// vcodec = avcodec_find_encoder(AV_CODEC_ID_H264); //查找一个H264编码器
if (NULL == vcodec)
{
ADD_LOG("Muxing:could not find video encoder H264\n");
return -1;
}
acodec = avcodec_find_encoder(pOutFmt->audio_codec); //查找音频编码器,默认AAC
// acodec = avcodec_find_encoder(AV_CODEC_ID_AAC); //查找一个AAC编码器
if (NULL == acodec)
{
ADD_LOG("Muxing:could not find audio encoder AAC\n");
return -1;
}
//根据视频编码器信息(H264),在AVFormatContext里新建视频流通道
vAVStream = avformat_new_stream(fc->g_OutFmt_Ctx[VeChn], vcodec);
if (NULL == vAVStream)
{
ADD_LOG("Muxing:could not allocate vcodec stream \n");
return -1;
}
//给新建的视频流一个ID,0
vAVStream->id = fc->g_OutFmt_Ctx[VeChn]->nb_streams-1; //nb_streams是当前AVFormatContext里面流的数量
//根据音频编码器信息(AAC),在AVFormatContext里新建音频流通道
aAVStream = avformat_new_stream(fc->g_OutFmt_Ctx[VeChn], acodec);
if (NULL == aAVStream)
{
ADD_LOG("Muxing:could not allocate acodec stream \n");
return -1;
}
//给新建的音频流一个ID,1
aAVStream->id = fc->g_OutFmt_Ctx[VeChn]->nb_streams-1;
fc->vi[VeChn] = vAVStream->index; //获取视频流的索引号
fc->ai[VeChn] = aAVStream->index; //获取音频流的索引号
// vAVCodecCtx = vAVStream->codec; //弃用
vAVCodecPar = vAVStream->codecpar; //
// aAVCodecCtx = aAVStream->codec; //弃用
aAVCodecPar = aAVStream->codecpar;
// avcodec_parameters_to_context(aAVCodecCtx, aAVCodecPar); //可以不用这个函数,太麻烦,还要复制回去
if(vcodec->type == AVMEDIA_TYPE_VIDEO) //编码器是视频编码器
{
//对视频流的参数设置
vAVCodecPar->codec_type = AVMEDIA_TYPE_VIDEO;
vAVCodecPar->codec_id = AV_CODEC_ID_H264;
vAVCodecPar->bit_rate = 2000; //kbps,好像不需要
vAVCodecPar->width = 1280; //像素
vAVCodecPar->height = 720;
vAVStream->time_base = (AVRational){1, STREAM_FRAME_RATE}; //时间基
vAVCodecPar->format = AV_PIX_FMT_YUV420P;
//下面就是SPS+PPS信息,可以在这里直接添加,那么就可以在创建mp4时调用这个添加流的函数
// vAVCodecPar->extradata = (uint8_t*)av_malloc(27 + AV_INPUT_BUFFER_PADDING_SIZE);
// memcpy(vAVCodecPar->extradata, sps_pps, 27); //sps_pps事先记录下来
// vAVCodecPar->extradata_size =27;
}
if(acodec->type ==AVMEDIA_TYPE_AUDIO) //音频
{
aAVCodecPar->codec_type = AVMEDIA_TYPE_AUDIO;
aAVCodecPar->codec_id = AV_CODEC_ID_AAC; //AAC
aAVCodecPar->format = acodec->sample_fmts ? (acodec)->sample_fmts[0] : AV_SAMPLE_FMT_FLTP; //AAC只能用浮点,默认也是
aAVCodecPar->bit_rate = 48000; //bps
aAVCodecPar->sample_rate = AUDIO_RATE; //宏 16000采样率
aAVCodecPar->channel_layout = AV_CH_LAYOUT_MONO; //单声道
aAVCodecPar->channels =1; //通道数1
aAVCodecPar->frame_size = 1024; //实测好像不影响,一般AAC是1024字节
aAVCodecPar->profile = FF_PROFILE_AAC_LOW; //AAC等级设置
aAVStream->time_base = (AVRational){1, aAVCodecPar->sample_rate}; //时间基
}
if (fc->g_OutFmt_Ctx[VeChn]->oformat->flags & AVFMT_GLOBALHEADER)
{
vAVCodecPar->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
vAVCodecPar->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
return HI_SUCCESS;
}
3、写音频帧:
HI_S32 HI_PDT_WriteAudio(AENC_CHN AeChn, AUDIO_STREAM_S *pstStream, FfmpegConf *fc)
{
unsigned char* pPackVirtAddr = NULL; //码流数据首地址
unsigned int u32PackLen = 0; //码流数据长度
int ret = 0;
AVStream *Apst = NULL; //用于指向音频流
AVPacket pkt; //定义一个包
if(NULL == pstStream)
{
return HI_SUCCESS;
}
if(fc->ai[AeChn]<0) //如果文件里面没有音频流
{
#ifdef DEBUG
printf("ai[%d] less than 0 \n",AeChn);
#endif
return HI_SUCCESS;
}
//获取音频码流信息
pPackVirtAddr = pstStream->pStream; //带7字节ADTS头
u32PackLen = pstStream->u32Len;
av_init_packet(&pkt); //初始化AVpack包
Apst = fc->g_OutFmt_Ctx[AeChn]->streams[fc->ai[AeChn]]; //ai[AeChn]代表音频流索引号
if(fc->Afirst[AeChn]==0) //如果是第一个音频帧
{
fc->Afirst[AeChn]=1;
#ifdef USING_SEQ
fc->Audio_PTS[AeChn] = pstStream->u32Seq; //记录下初始序列号
#endif
#ifdef USING_PTS
fc->Audio_PTS[AeChn] = pstStream->u64TimeStamp; //记录下开始时间戳
#endif
}
#ifdef USING_SEQ
// pkt.pts = pkt.dts =(pstStream->u32Seq - fc->Audio_PTS[AeChn]) * 1024; //也可以
pkt.pts = pkt.dts =av_rescale_q_rnd(pstStream->u32Seq - fc->Audio_PTS[AeChn],
(AVRational){1000, 15625}, //16000/1024*1000=15625
Apst->time_base,
(enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
#endif
#ifdef USING_PTS
pkt.pts = pkt.dts = (pstStream->u64TimeStamp - fc->Audio_PTS[AeChn])/(1000000/AUDIO_RATE); //时间基转换
#endif
pkt.duration=64; //64ms
pkt.duration = av_rescale_q(pkt.duration, Apst->time_base, Apst->time_base); //转换
pkt.pos = -1;
pkt.stream_index = Apst->index; //音频流的索引号 赋给 包里面流索引号,表示这个包属于音频流
pkt.data = pPackVirtAddr ; //接受音频数据
pkt.size = u32PackLen; //音频数据长度
//写入一帧
ret = av_interleaved_write_frame(fc->g_OutFmt_Ctx[AeChn], &pkt);
if (ret < 0)
{
ADD_LOG("Muxing:cannot write audio frame\n");
return HI_FAILURE;
}
return HI_SUCCESS;
}
4、写入文件尾,关闭文件:
void HI_PDT_CloseMp4(VENC_CHN VeChn, FfmpegConf *fc)
{
int ret;
if (fc->g_OutFmt_Ctx[VeChn])
{
ret = av_write_trailer(fc->g_OutFmt_Ctx[VeChn]); //写文件尾
if(ret<0)
{
#ifdef DEBUG
printf("av_write_trailer faild\n");
#endif
}
}
if (fc->g_OutFmt_Ctx[VeChn] && !(fc->g_OutFmt_Ctx[VeChn]->oformat->flags & AVFMT_NOFILE)) //文件状态检测
{
ret = avio_close(fc->g_OutFmt_Ctx[VeChn]->pb); //关闭文件
if(ret<0)
{
#ifdef DEBUG
printf("avio_close faild\n");
#endif
}
}
if (fc->g_OutFmt_Ctx[VeChn])
{
avformat_free_context(fc->g_OutFmt_Ctx[VeChn]); //释放结构体
fc->g_OutFmt_Ctx[VeChn] = NULL;
}
//清除相关标志
fc->vi[VeChn] = -1;
fc->ai[VeChn] = -1;
fc->VptsInc[VeChn]=0;
fc->AptsInc[VeChn]=0;
fc->Afirst[VeChn]=0;
fc->Vfirst[VeChn]=0;
fc->b_First_IDR_Find[VeChn] = 0;
}
二、封装流程
1、定义FfmpegConf结构体实体,并memset。
2、HI_PDT_CreateMp4() 创建mp4文件,一次创建一个通道的。
3、在海思的while中调用写入函数,音频来了就调音频HI_PDT_WriteAudio(),视频来了就调视频HI_PDT_WriteVideo()写入。
4、出循环之后,调用HI_PDT_CloseMp4(),一次关闭一个通道的。
备注:所有函数都需要传入VeChn或者AeChn通道号,表示当前要操作的是哪个通道的。
三、过程中遇到的一些问题总结:
1、mp4文件必须有头有尾才能播放,写入音视频中途是无法播放的。如果设备正在写入,突然断电,视频将无法播放。
【所以后来放弃了ffmpeg封装mp4,用了mpeg-ps(节目流)封装,代码https://download.csdn.net/download/qq_30659437/12338349】
2、ffmpeg过于强大,我使用了ffmpeg的API之后,编译出来的执行程序约19M,去掉ffmpeg后1M。
3、音视频同步问题PTS,主要就是一个转换概念,真实世界1s=1000000us,播放器采用的是90000hz,也就是1000000对应90000,
视频一秒25帧,每帧的时间递增就是40000us,那么播放器就是40000/1000000=PTS/90000。
4、还有一些遗留问题,虽然解决了,但是没有理解其原理。。。
能力有限,希望指正!
更多推荐
所有评论(0)