ffmpeg源码简析(八)解码 av_read_frame(),avcodec_decode_video2(),avformat_close_input()

1.av_read_frame()

av_read_frame()的作用是读取码流中的音频若干帧或者视频一帧。例如,解码视频的时候,每解码一个视频帧,需要先调用 av_read_frame()获得一帧视频的压缩数据,然后才能对该数据进行解码(例如H.264中一帧压缩数据通常对应一个NAL)。

通过av_read_packet(***),读取一个包,需要说明的是此函数必须是包含整数帧的,不存在半帧的情况,以ts流为例,是读取一个完整的PES包(一个完整pes包包含若干视频或音频es包),读取完毕后,通过av_parser_parse2(***)分析出视频一帧(或音频若干帧),返回,下次进入循环的时候,如果上次的数据没有完全取完,则st = s->cur_st;不会是NULL,即再此进入av_parser_parse2(***)流程,而不是下面的av_read_packet(**)流程,这样就保证了,如果读取一次包含了N帧视频数据(以视频为例),则调用av_read_frame(***)N次都不会去读数据,而是返回第一次读取的数据,直到全部解析完毕。
  • 1

av_read_frame()的声明位于libavformat\avformat.h,如下所示。

int av_read_frame(AVFormatContext *s, AVPacket *pkt); 
  • 1

av_read_frame()使用方法在注释中写得很详细,用中文简单描述一下它的两个参数:

s:输入的AVFormatContext

pkt:输出的AVPacket

如果返回0则说明读取正常。

av_read_frame()的定义位于libavformat\utils.c,如下所示:

    //获取一个AVPacket  
    /* 
     * av_read_frame - 新版本的ffmpeg用的是av_read_frame,而老版本的是av_read_packet 
     * 。区别是av_read_packet读出的是包,它可能是半帧或多帧,不保证帧的完整性。av_read_frame对 
     * av_read_packet进行了封装,使读出的数据总是完整的帧 
     */  
    int av_read_frame(AVFormatContext *s, AVPacket *pkt)  
    {  
        const int genpts = s->flags & AVFMT_FLAG_GENPTS;  
        int          eof = 0;  

        if (!genpts)  
            /** 
             * This buffer is only needed when packets were already buffered but 
             * not decoded, for example to get the codec parameters in MPEG 
             * streams. 
             * 一般情况下会调用read_frame_internal(s, pkt) 
             * 直接返回 
             */  
            return s->packet_buffer ? read_from_packet_buffer(s, pkt) :  
                                      read_frame_internal(s, pkt);  

        for (;;) {  
            int ret;  
            AVPacketList *pktl = s->packet_buffer;  

            if (pktl) {  
                AVPacket *next_pkt = &pktl->pkt;  

                if (next_pkt->dts != AV_NOPTS_VALUE) {  
                    int wrap_bits = s->streams[next_pkt->stream_index]->pts_wrap_bits;  
                    while (pktl && next_pkt->pts == AV_NOPTS_VALUE) {  
                        if (pktl->pkt.stream_index == next_pkt->stream_index &&  
                            (av_compare_mod(next_pkt->dts, pktl->pkt.dts, 2LL << (wrap_bits - 1)) < 0) &&  
                             av_compare_mod(pktl->pkt.pts, pktl->pkt.dts, 2LL << (wrap_bits - 1))) { //not b frame  
                            next_pkt->pts = pktl->pkt.dts;  
                        }  
                        pktl = pktl->next;  
                    }  
                    pktl = s->packet_buffer;  
                }  

                /* read packet from packet buffer, if there is data */  
                if (!(next_pkt->pts == AV_NOPTS_VALUE &&  
                      next_pkt->dts != AV_NOPTS_VALUE && !eof))  
                    return read_from_packet_buffer(s, pkt);  
            }  

            ret = read_frame_internal(s, pkt);  
            if (ret < 0) {  
                if (pktl && ret != AVERROR(EAGAIN)) {  
                    eof = 1;  
                    continue;  
                } else  
                    return ret;  
            }  

            if (av_dup_packet(add_to_pktbuf(&s->packet_buffer, pkt,  
                              &s->packet_buffer_end)) < 0)  
                return AVERROR(ENOMEM);  
        }  
    }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

可以从源代码中看出,av_read_frame()调用了read_frame_internal()。

read_frame_internal() 
read_frame_internal()代码比较长,这里只简单看一下它前面的部分。它前面部分有2步是十分关键的:

(1)调用了ff_read_packet()从相应的AVInputFormat读取数据。

(2)如果媒体频流需要使用AVCodecParser,则调用parse_packet()解析相应的AVPacket。

下面我们分成分别看一下ff_read_packet()和parse_packet()

ff_read_packet() 
ff_read_packet()中最关键的地方就是调用了AVInputFormat的read_packet()方法。AVInputFormat的read_packet()是一个函数指针,指向当前的AVInputFormat的读取数据的函数。

flv_read_packet()

flv_read_packet()的定义位于libavformat\flvdec.c 
它的主要功能就是根据(FLV)文件格式的规范,逐层解析(Tag)以及(TagData),获取Tag以及TagData中的信息。

parse_packet()

parse_packet()给需要AVCodecParser的媒体流提供解析AVPacket的功能 
最终调用了相应AVCodecParser的av_parser_parse2()函数,解析出来AVPacket。此后根据解析的信息还进行了一系列的赋值工作

2.avcodec_decode_video2()

avcodec_decode_video2()的作用是解码一帧视频数据。输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame。该函数的声明位于libavcodec\avcodec.h

int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture,  
                         int *got_picture_ptr,  
                         const AVPacket *avpkt); 
  • 1
  • 2
  • 3

源代码位于libavcodec\utils.c,如下所示:

    int attribute_align_arg avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture,  
                                                  int *got_picture_ptr,  
                                                  const AVPacket *avpkt)  
    {  
        AVCodecInternal *avci = avctx->internal;  
        int ret;  
        // copy to ensure we do not change avpkt  
        AVPacket tmp = *avpkt;  

        if (!avctx->codec)  
            return AVERROR(EINVAL);  
        //检查是不是视频(非音频)  
        if (avctx->codec->type != AVMEDIA_TYPE_VIDEO) {  
            av_log(avctx, AV_LOG_ERROR, "Invalid media type for video\n");  
            return AVERROR(EINVAL);  
        }  

        *got_picture_ptr = 0;  
        //检查宽、高设置是否正确  
        if ((avctx->coded_width || avctx->coded_height) && av_image_check_size(avctx->coded_width, avctx->coded_height, 0, avctx))  
            return AVERROR(EINVAL);  

        av_frame_unref(picture);  

        if ((avctx->codec->capabilities & CODEC_CAP_DELAY) || avpkt->size || (avctx->active_thread_type & FF_THREAD_FRAME)) {  
            int did_split = av_packet_split_side_data(&tmp);  
            ret = apply_param_change(avctx, &tmp);  
            if (ret < 0) {  
                av_log(avctx, AV_LOG_ERROR, "Error applying parameter changes.\n");  
                if (avctx->err_recognition & AV_EF_EXPLODE)  
                    goto fail;  
            }  

            avctx->internal->pkt = &tmp;  
            if (HAVE_THREADS && avctx->active_thread_type & FF_THREAD_FRAME)  
                ret = ff_thread_decode_frame(avctx, picture, got_picture_ptr,  
                                             &tmp);  
            else {  
                //最关键的解码函数  
                ret = avctx->codec->decode(avctx, picture, got_picture_ptr,  
                                           &tmp);  
                //设置pkt_dts字段的值  
                picture->pkt_dts = avpkt->dts;  

                if(!avctx->has_b_frames){  
                    av_frame_set_pkt_pos(picture, avpkt->pos);  
                }  
                //FIXME these should be under if(!avctx->has_b_frames)  
                /* get_buffer is supposed to set frame parameters */  
                if (!(avctx->codec->capabilities & CODEC_CAP_DR1)) {  
                    //对一些字段进行赋值  
                    if (!picture->sample_aspect_ratio.num)    picture->sample_aspect_ratio = avctx->sample_aspect_ratio;  
                    if (!picture->width)                      picture->width               = avctx->width;  
                    if (!picture->height)                     picture->height              = avctx->height;  
                    if (picture->format == AV_PIX_FMT_NONE)   picture->format              = avctx->pix_fmt;  
                }  
            }  
            add_metadata_from_side_data(avctx, picture);  

    fail:  
            emms_c(); //needed to avoid an emms_c() call before every return;  

            avctx->internal->pkt = NULL;  
            if (did_split) {  
                av_packet_free_side_data(&tmp);  
                if(ret == tmp.size)  
                    ret = avpkt->size;  
            }  

            if (*got_picture_ptr) {  
                if (!avctx->refcounted_frames) {  
                    int err = unrefcount_frame(avci, picture);  
                    if (err < 0)  
                        return err;  
                }  

                avctx->frame_number++;  
                av_frame_set_best_effort_timestamp(picture,  
                                                   guess_correct_pts(avctx,  
                                                                     picture->pkt_pts,  
                                                                     picture->pkt_dts));  
            } else  
                av_frame_unref(picture);  
        } else  
            ret = 0;  

        /* many decoders assign whole AVFrames, thus overwriting extended_data; 
         * make sure it's set correctly */  
        av_assert0(!picture->extended_data || picture->extended_data == picture->data);  

    #if FF_API_AVCTX_TIMEBASE  
        if (avctx->framerate.num > 0 && avctx->framerate.den > 0)  
            avctx->time_base = av_inv_q(av_mul_q(avctx->framerate, (AVRational){avctx->ticks_per_frame, 1}));  
    #endif  

        return ret;  
    }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97

从代码中可以看出,avcodec_decode_video2()主要做了以下几个方面的工作:

(1)对输入的字段进行了一系列的检查工作:例如宽高是否正确,输入是否为视频等等。

(2)通过ret = avctx->codec->decode(avctx, picture, got_picture_ptr,&tmp)这句代码,调用了相应AVCodec的decode()函数,完成了解码操作。

(3)对得到的AVFrame的一些字段进行了赋值,例如宽高、像素格式等等。

其中第二部是关键的一步,它调用了AVCodec的decode()方法完成了解码。AVCodec的decode()方法是一个函数指针,指向了具体解码器的解码函数。在这里我们以H.264解码器为例,看一下解码的实现过程。H.264解码器对应的AVCodec的定义位于libavcodec\h264.c

3.avformat_close_input()

avformat_close_input()函数。该函数用于关闭一个AVFormatContext,一般情况下是和avformat_open_input()成对使用的。 
声明位于libavformat\avformat.h,如下所示

void avformat_close_input(AVFormatContext **s); 
  • 1

下面看一下avformat_close_input()的源代码,位于libavformat\utils.c文件中。

oid avformat_close_input(AVFormatContext **ps)  
{  
    AVFormatContext *s;  
    AVIOContext *pb;  

    if (!ps || !*ps)  
        return;  

    s  = *ps;  
    pb = s->pb;  

    if ((s->iformat && strcmp(s->iformat->name, "image2") && s->iformat->flags & AVFMT_NOFILE) ||  
        (s->flags & AVFMT_FLAG_CUSTOM_IO))  
        pb = NULL;  

    flush_packet_queue(s);  

    if (s->iformat)  
        if (s->iformat->read_close)  
            s->iformat->read_close(s);  

    avformat_free_context(s);  

    *ps = NULL;  

    avio_close(pb);  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

从源代码中可以看出,avformat_close_input()主要做了以下几步工作:

(1)调用AVInputFormat的read_close()方法关闭输入流
(2)调用avformat_free_context()释放AVFormatContext
(3)调用avio_close()关闭并且释放AVIOContext
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值