FFmpeg RTP拉流源码分析

原因:由于需要进行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的解析。

你可能感兴趣的:(FFmpeg)