赞
踩
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上运行通过,适当调整即可
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;
每个通道的文件名不一样,几通道就调用几次。
这个函数我并没有往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; }
直接从 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; }
在收到第一帧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; }
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; }
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; }
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、还有一些遗留问题,虽然解决了,但是没有理解其原理。。。
能力有限,希望指正!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。