首先我们要确定我们要处理的对象,在用ffmpeg推视音频流的整个流程中,我们都需要围绕着输入AVFormatContext和输出AVFormatContext这两个结构体去处理。再者,我们需要两个结构体来指明输入输出视音频流的格式,即还需要一个AVInputFormat和AVOutputFormat。即以下这几个参数将会贯穿全文:
AVFormatContext *ifmt_ctx, *ofmt_ctx;
AVInputFormat * ifmt;
AVOutputFormat * ofmt;
接下来分析一下主要流程:
大方向上分为四个部分:
1、对ifmt_ctx初始化:需要对AVFormatContext中的AVInputFormat、AVIOContext、AVStream进行初始化。
2、对ofmt_ctx初始化:需要对AVOutputFormat进行初始化;由于我们只需要推流,不需要改变输入音视频流的编码格式,所以我们直接从输入Stream拿AVCodecContext给输出就行了。最后还要打开输出AVIOContext。
3、循环从ifmt_ctx中读出AVPacket,经过处理后写入ofmt_ctx中。
4、循环结束,释放ifmt_ctx和ofmt_ctx这些结构体。
下面详细分析每一步应该怎么做:
1、对ifmt_ctx初始化。
①得到输入AVInputFormat——ifmt,注意这里的AVInputFormat还没有跟ifmt_ctx有关联,要用avformat_open_input()函数,后文会说到。
ifmt = av_find_input_format("h264"); //AVInputFormat的初始化
②向OS申请一块内存buffer,写一个从输入缓冲区(也就是我们的音视频来源)读数据到buffer的函数,把这个函数指针和这个buffer都传给avio_alloc_context();得到一个AVIOContext,并赋给ifmt_ctx->pb。
unsigned char * iobuffer=(unsigned char *)av_malloc(32768);
AVIOContext *avio =avio_alloc_context(iobuffer, 32768,0,pp,HISON_FILL_Buffer,NULL,NULL);
ifmt_ctx->pb=avio;
③利用步骤①中得到ifmt来初始化ifmt_ctx中的AVInputFormat,注意这步同时为ifmt创建了AVStream,但是这个AVStream尚未初始化,第④步初始化AVStream。
avformat_open_input(&ifmt_ctx, NULL, ifmt, NULL);
④初始化ifmt_ctx中的每一条流AVStream。
avformat_find_stream_info(ifmt_ctx, 0);
2、ofmt_ctx的初始化
①创建输出AVFormatContext——ofmt_ctx,并对ofmt_ctx中的AVOutputFormat进行初始化。
avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", g_push_param->DestAddress);
这里通过格式名称"flv"来初始化AVOutputFormat,因为这样速度会比较快。
②为ofmt_ctx创建AVStream
out_stream = avformat_new_stream(ofmt_ctx, NULL);
③高版本使用avcodec_parameters_copy()来初始化输出AVCodec的上下文,而低版本则使用avcodec_copy_context()
avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
// avcodec_copy_context(out_stream->codec, in_stream->codec);
④打开输出控制。
相对比,输入文件在avformat_open_input()打开了,而avformat_open_input()中就调用了avio_open();
avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
3、推流部分:
1)先往ofmt_ctx中写入封装头
avformat_write_header(ofmt_ctx, NULL);
2)循环体:
①从ifmt_ctx中读取一帧数据到AVPacket中。
av_read_frame(ifmt_ctx, &pkt);
②根据pkt中的stream_index确定现在是ofmt_ctx的哪一条流在工作。即确定in_stream和out_stream
③下面可以借鉴雷霄骅博客中的现有代码对out_stream中的pts、dts、duration、pos、延时进行设置。
if(pkt.pts==AV_NOPTS_VALUE){
//Write PTS
AVRational time_base1=ifmt_ctx->streams[videoindex]->time_base;
//Duration between 2 frames (us)
int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(ifmt_ctx->streams[videoindex]->r_frame_rate);
//Parameters
pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
pkt.dts=pkt.pts;
pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
}
//Important:Delay
if(pkt.stream_index==videoindex){
AVRational time_base=ifmt_ctx->streams[videoindex]->time_base;
AVRational time_base_q={1,AV_TIME_BASE};
int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
int64_t now_time = av_gettime() - start_time;
if (pts_time > now_time)
av_usleep(pts_time - now_time);
}
in_stream = ifmt_ctx->streams[pkt.stream_index];
out_stream = ofmt_ctx->streams[pkt.stream_index];
/* copy packet */
//Convert PTS/DTS
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
pkt.pos = -1;
//Print to Screen
if(pkt.stream_index==videoindex){
printf("Send %8d video frames to output URL\n",frame_index);
frame_index++;
}
④把pkt间隔写入ofmt_ctx中。
av_interleaved_write_frame(ofmt_ctx, &pkt);
⑤继续循环之前要对pkt进行清理,以免影响下一次循环的数据。
av_free_packet(&pkt);
4、退出循环
读完ifmt_ctx中的数据时,退出循环,需要写封装的尾,也要释放以上这些数据结构。
av_write_trailer(ofmt_ctx);
end:
avformat_close_input(&ifmt_ctx);
/* close output */
if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
avio_close(ofmt_ctx->pb);
avformat_free_context(ofmt_ctx);