原因:由于需要进行RTP拉流分析,故在此分析FFmpeg拉流流程.
概况:FFmpeg通过url进行udp连通,然后通过sps和pps进行解码器参数初始化,然后进行解码。
伪代码详细分析如下:由于输入文件可以是sdp文件,也可是url,故通过avformat_open_input简单分析可知通过输入文件类型,通过源码可以看出无论是文件操作还是协议操作都是定义为URLProtocol类型,故通过url_open打开文件或者udp创建和连接.然后通过read_header进行sdp文件解析.实现在rtsp.c中的sdp_read_header和ff_sdp_parse方法.
int avformat_open_input(AVFormatContext **ps, const char *filename,
AVInputFormat *fmt, AVDictionary **options)
{
if ((ret = init_input(s, filename, &tmp)) < 0)
goto fail;
if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
if ((ret = s->iformat->read_header(s)) < 0)
goto fail;
}
//avio.c
int ffurl_connect(URLContext *uc, AVDictionary **options)
{
err =
uc->prot->url_open2 ? uc->prot->url_open2(uc,
uc->filename,
uc->flags,
options) :
uc->prot->url_open(uc, uc->filename, uc->flags);
}
随后通过avcodec_open2进行解码器创建,此时内部会通过ff_h264_decode_extradata方法进行extradata数据解析,用于获取sps和pps数据,由于此时是实时流故该extradata数据不包含数据。此时通过ff_thread_init方法针对解码线程进行数据初始化和线程启动frame_worker_thread.
int attribute_align_arg avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options)
{
if (HAVE_THREADS
&& !(avctx->internal->frame_thread_encoder && (avctx->active_thread_type&FF_THREAD_FRAME))) {
ret = ff_thread_init(avctx);
if (ret < 0) {
goto free_and_end;
}
}
}
通过工作线程可以看出是数据解码,此时就要涉及到每个线程中都具有一个PerThreadContext对象,该对象内部包含一个AVFrame和一个AVPacket,每次进行AVPacket的数据解码后将解码后的AVFrame数据保存在当前AVFrame对象中。而每次解码中的AVPacket的来源下面进行分析.
static attribute_align_arg void *frame_worker_thread(void *arg)
{
while (1) {
p->result = codec->decode(avctx, p->frame, &p->got_frame, &p->avpkt);
}
}
typedef struct PerThreadContext {
{
AVPacket avpkt; ///< Input packet (for decoding) or output (for encoding).
AVFrame *frame; ///< Output frame (for decoding) or input (for encoding).
} PerThreadContext;
每次在获取到AVPacket后需要进行解码,则会通过调用avcodec_send_packet方法,而该方法内部调用ff_thread_decode_frame方法,而该方法内部通过submit_packet方法将当前传递进来的AVPkacet对象保存到下一个解码线程。然后通过av_frame_move_ref方法获取最早线程中的AVFrame。
int attribute_align_arg avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt)
{
if (!avci->buffer_frame->buf[0]) {
ret = decode_receive_frame_internal(avctx, avci->buffer_frame);
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
return ret;
}
}
static int decode_simple_internal(AVCodecContext *avctx, AVFrame *frame)
{
if (HAVE_THREADS && avctx->active_thread_type & FF_THREAD_FRAME) {
ret = ff_thread_decode_frame(avctx, frame, &got_frame, &tmp);
} else {
ret = avctx->codec->decode(avctx, frame, &got_frame, &tmp);
}
}
int ff_thread_decode_frame(AVCodecContext *avctx,
AVFrame *picture, int *got_picture_ptr,
AVPacket *avpkt)
{
err = submit_packet(p, avctx, avpkt);
av_frame_move_ref(picture, p->frame);
}
总结:首先通过avforamt_open_input方法将输入的url进行udp创建和连接,然后通过avcodec_open2启动解码线程,通过avcodec_send_packet方法向解码线程投递AVPacket和获取AVFrame,通过avcodec_receive_frame进行AVFrame的获取,解码器的参数配置是通过decode_nal_units方法中sps和pps的解析。