ffmpeg源码简析(七)解码-avformat_open_input,avformat_find_stream_info()

1.avformat_open_input

打开媒体的的过程开始于avformat_open_input,因此该函数的重要性不可忽视。

在该函数中,FFMPEG完成了:

输入输出结构体AVIOContext的初始化;

输入数据的协议(例如RTMP,或者file)的识别(通过一套评分机制):1判断文件名的后缀 2读取文件头的数据进行比对;

使用获得最高分的文件协议对应的URLProtocol,通过函数指针的方式,与FFMPEG连接(非专业用词);

剩下的就是调用该URLProtocol的函数进行open,read等操作了

URLProtocol结构如下,是一大堆函数指针的集合(avio.h文件)

typedef struct URLProtocol {  
    const char *name;  
    int (*url_open)(URLContext *h, const char *url, int flags);  
    int (*url_read)(URLContext *h, unsigned char *buf, int size);  
    int (*url_write)(URLContext *h, const unsigned char *buf, int size);  
    int64_t (*url_seek)(URLContext *h, int64_t pos, int whence);  
    int (*url_close)(URLContext *h);  
    struct URLProtocol *next;  
    int (*url_read_pause)(URLContext *h, int pause);  
    int64_t (*url_read_seek)(URLContext *h, int stream_index,  
                             int64_t timestamp, int flags);  
    int (*url_get_file_handle)(URLContext *h);  
    int priv_data_size;  
    const AVClass *priv_data_class;  
    int flags;  
    int (*url_check)(URLContext *h, int mask);  
} URLProtocol; 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

URLProtocol功能就是完成各种输入协议的读写等操作

每个具体的输入协议都有自己对应的URLProtocol。

比如file协议(FFMPEG把文件也当做一种特殊的协议)(*file.c文件)

URLProtocol ff_pipe_protocol = {  
    .name                = "pipe",  
    .url_open            = pipe_open,  
    .url_read            = file_read,  
    .url_write           = file_write,  
    .url_get_file_handle = file_get_handle,  
    .url_check           = file_check,  
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

或者rtmp协议(此处使用了librtmp)(librtmp.c文件)

    URLProtocol ff_rtmp_protocol = {  
        .name                = "rtmp",  
        .url_open            = rtmp_open,  
        .url_read            = rtmp_read,  
        .url_write           = rtmp_write,  
        .url_close           = rtmp_close,  
        .url_read_pause      = rtmp_read_pause,  
        .url_read_seek       = rtmp_read_seek,  
        .url_get_file_handle = rtmp_get_file_handle,  
        .priv_data_size      = sizeof(RTMP),  
        .flags               = URL_PROTOCOL_FLAG_NETWORK,  
    };  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

可见它们把各自的函数指针都赋值给了URLProtocol结构体的函数指针

因此avformat_open_input只需调用url_open,url_read这些函数就可以完成各种具体输入协议的open,read等操作了

avformat_open_input()。该函数用于打开多媒体数据并且获得一些相关的信息。它的声明位于libavformat\avformat.h,如下所示。

int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options); 
  • 1
ps:函数调用成功之后处理过的AVFormatContext结构体。
file:打开的视音频流的URL。
fmt:强制指定AVFormatContext中AVInputFormat的。这个参数一般情况下可以设置为NULL,这样FFmpeg可以自动检测AVInputFormat。

dictionay:附加的一些选项,一般情况下可以设置为NULL。

函数执行成功的话,其返回值大于等于0。

位于libavformat\utils.c中,如下所示。

    int avformat_open_input(AVFormatContext **ps, const char *filename,  
                            AVInputFormat *fmt, AVDictionary **options)  
    {  
        AVFormatContext *s = *ps;  
        int ret = 0;  
        AVDictionary *tmp = NULL;  
        ID3v2ExtraMeta *id3v2_extra_meta = NULL;  

        if (!s && !(s = avformat_alloc_context()))  
            return AVERROR(ENOMEM);  
        if (!s->av_class) {  
            av_log(NULL, AV_LOG_ERROR, "Input context has not been properly allocated by avformat_alloc_context() and is not NULL either\n");  
            return AVERROR(EINVAL);  
        }  
        if (fmt)  
            s->iformat = fmt;  

        if (options)  
            av_dict_copy(&tmp, *options, 0);  

        if ((ret = av_opt_set_dict(s, &tmp)) < 0)  
            goto fail;  

        if ((ret = init_input(s, filename, &tmp)) < 0)  
            goto fail;  
        s->probe_score = ret;  

        if (s->format_whitelist && av_match_list(s->iformat->name, s->format_whitelist, ',') <= 0) {  
            av_log(s, AV_LOG_ERROR, "Format not on whitelist\n");  
            ret = AVERROR(EINVAL);  
            goto fail;  
        }  

        avio_skip(s->pb, s->skip_initial_bytes);  

        /* Check filename in case an image number is expected. */  
        if (s->iformat->flags & AVFMT_NEEDNUMBER) {  
            if (!av_filename_number_test(filename)) {  
                ret = AVERROR(EINVAL);  
                goto fail;  
            }  
        }  

        s->duration = s->start_time = AV_NOPTS_VALUE;  
        av_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename));  

        /* Allocate private data. */  
        if (s->iformat->priv_data_size > 0) {  
            if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) {  
                ret = AVERROR(ENOMEM);  
                goto fail;  
            }  
            if (s->iformat->priv_class) {  
                *(const AVClass **) s->priv_data = s->iformat->priv_class;  
                av_opt_set_defaults(s->priv_data);  
                if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0)  
                    goto fail;  
            }  
        }  

        /* e.g. AVFMT_NOFILE formats will not have a AVIOContext */  
        if (s->pb)  
            ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, 0);  

        if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)  
            if ((ret = s->iformat->read_header(s)) < 0)  
                goto fail;  

        if (id3v2_extra_meta) {  
            if (!strcmp(s->iformat->name, "mp3") || !strcmp(s->iformat->name, "aac") ||  
                !strcmp(s->iformat->name, "tta")) {  
                if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0)  
                    goto fail;  
            } else  
                av_log(s, AV_LOG_DEBUG, "demuxer does not support additional id3 data, skipping\n");  
        }  
        ff_id3v2_free_extra_meta(&id3v2_extra_meta);  

        if ((ret = avformat_queue_attached_pictures(s)) < 0)  
            goto fail;  

        if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->pb && !s->data_offset)  
            s->data_offset = avio_tell(s->pb);  

        s->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;  

        if (options) {  
            av_dict_free(options);  
            *options = tmp;  
        }  
        *ps = s;  
        return 0;  

    fail:  
        ff_id3v2_free_extra_meta(&id3v2_extra_meta);  
        av_dict_free(&tmp);  
        if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO))  
            avio_close(s->pb);  
        avformat_free_context(s);  
        *ps = NULL;  
        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
  • 98
  • 99
  • 100
  • 101
  • 102

avformat_open_input()源代码比较长,一部分是一些容错代码,比如说如果发现传入的AVFormatContext指针没有初始化过,就调用avformat_alloc_context()初始化该结构体;还有一部分是针对一些格式做的特殊处理,比如id3v2信息的处理等等。有关上述两种信息不再详细分析,在这里只选择它关键的两个函数进行分析:

init_input():绝大部分初始化工作都是在这里做的。

s->iformat->read_header():读取多媒体数据文件头,根据视音频流创建相应的AVStream。

下面我们逐一看看上述函数。

init_input() 
它的主要工作就是打开输入的视频数据并且探测视频的格式。该函数的定义位于libavformat\utils.c,如下所示。

static int init_input(AVFormatContext *s, const char *filename,  
                      AVDictionary **options)  
{  
    int ret;  
    AVProbeData pd = { filename, NULL, 0 };  
    int score = AVPROBE_SCORE_RETRY;  

    if (s->pb) {  
        s->flags |= AVFMT_FLAG_CUSTOM_IO;  
        if (!s->iformat)  
            return av_probe_input_buffer2(s->pb, &s->iformat, filename,  
                                         s, 0, s->format_probesize);  
        else if (s->iformat->flags & AVFMT_NOFILE)  
            av_log(s, AV_LOG_WARNING, "Custom AVIOContext makes no sense and "  
                                      "will be ignored with AVFMT_NOFILE format.\n");  
        return 0;  
    }  

    if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||  
        (!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))  
        return score;  

    if ((ret = avio_open2(&s->pb, filename, AVIO_FLAG_READ | s->avio_flags,  
                          &s->interrupt_callback, options)) < 0)  
        return ret;  
    if (s->iformat)  
        return 0;  
    return av_probe_input_buffer2(s->pb, &s->iformat, filename,  
                                 s, 0, s->format_probesize);  
}  
  • 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

这个函数在短短的几行代码中包含了好几个return,因此逻辑还是有点复杂的,我们可以梳理一下: 
在函数的开头的score变量是一个判决AVInputFormat的分数的门限值,如果最后得到的AVInputFormat的分数低于该门限值,就认为没有找到合适的AVInputFormat。FFmpeg内部判断封装格式的原理实际上是对每种AVInputFormat给出一个分数,满分是100分,越有可能正确的AVInputFormat给出的分数就越高。最后选择分数最高的AVInputFormat作为推测结果。

整个函数的逻辑大体如下:

(1)当使用了自定义的AVIOContext的时候(AVFormatContext中的AVIOContext不为空,即s->pb!=NULL),如果指定了AVInputFormat就直接返回,如果没有指定就调用av_probe_input_buffer2()推测AVInputFormat。这一情况出现的不算很多,但是当我们从内存中读取数据的时候(需要初始化自定义的AVIOContext),就会执行这一步骤。
(2)在更一般的情况下,如果已经指定了AVInputFormat,就直接返回;如果没有指定AVInputFormat,就调用av_probe_input_format(NULL,…)根据文件路径判断文件格式。这里特意把av_probe_input_format()的第1个参数写成“NULL”,是为了强调这个时候实际上并没有给函数提供输入数据,此时仅仅通过文件路径推测AVInputFormat。

(3)如果发现通过文件路径判断不出来文件格式,那么就需要打开文件探测文件格式了,这个时候会首先调用avio_open2()打开文件,然后调用av_probe_input_buffer2()推测AVInputFormat。

av_probe_input_format2() 
av_probe_input_format2()是一个API函数,该函数用于根据输入数据查找合适的AVInputFormat,声明位于libavformat\avformat.h

AVInputFormat *av_probe_input_format2(AVProbeData *pd, int is_opened, int *score_max);
  • 1

pd:存储输入数据信息的AVProbeData结构体。 
is_opened:文件是否打开。

score_max:判决AVInputFormat的门限值。只有某格式判决分数大于该门限值的时候,函数才会返回该封装格式,否则返回NULL。

av_probe_input_format2()函数的定义位于libavformat\format.c,如下所示。

AVInputFormat *av_probe_input_format2(AVProbeData *pd, int is_opened, int *score_max)  
{  
    int score_ret;  
    AVInputFormat *fmt = av_probe_input_format3(pd, is_opened, &score_ret);  
    if (score_ret > *score_max) {  
        *score_max = score_ret;  
        return fmt;  
    } else  
        return NULL;  
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

从函数中可以看出,av_probe_input_format2()调用了av_probe_input_format3(),并且增加了一个判断,当av_probe_input_format3()返回的分数大于score_max的时候,才会返回AVInputFormat,否则返回NULL。

下面我们看一下av_probe_input_format3()。
AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened, int *score_ret); 
  • 1

从函数声明中可以看出,av_probe_input_format3()和av_probe_input_format2()的区别是函数的第3个参数不同:av_probe_input_format2()是一个分数的门限值,而av_probe_input_format3()是一个探测后的最匹配的格式的分数值。

av_probe_input_format3()根据输入数据查找合适的AVInputFormat。输入的数据位于AVProbeData中。前文已经提到过。

av_probe_input_buffer2() 
av_probe_input_buffer2()是一个API函数,它根据输入的媒体数据推测该媒体数据的AVInputFormat,声明位于libavformat\avformat.h

avformat_find_stream_info()

avformat_find_stream_info()。该函数可以读取一部分视音频数据并且获得一些相关的信息。avformat_find_stream_info()的声明位于libavformat\avformat.h,如下所示。

int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
  • 1

简单解释一下它的参数的含义:

ic:输入的AVFormatContext。
options:额外的选项,目前没有深入研究过。

函数正常执行后返回值大于等于0。

avformat_find_stream_info()的定义位于libavformat\utils.c。它的代码比较长 
由于avformat_find_stream_info()代码比较长,难以全部分析,在这里只能简单记录一下它的要点。该函数主要用于给每个媒体流(音频/视频)的AVStream结构体赋值。我们大致浏览一下这个函数的代码,会发现它其实已经实现了解码器的查找,解码器的打开,视音频帧的读取,视音频帧的解码等工作。换句话说,该函数实际上已经“走通”的解码的整个流程。下面看一下除了成员变量赋值之外,该函数的几个关键流程。

1.查找解码器:find_decoder()
2.打开解码器:avcodec_open2()
3.读取完整的一帧压缩编码的数据:read_frame_internal()
注:av_read_frame()内部实际上就是调用的read_frame_internal()。
4.解码一些压缩编码数据:try_decode_frame()

下面选择上述流程中几个关键函数的代码简单看一下。

find_decoder() 
find_decoder()用于找到合适的解码器

read_frame_internal()

read_frame_internal()的功能是读取一帧压缩码流数据。FFmpeg的API函数av_read_frame()内部调用的就是read_frame_internal()。

try_decode_frame() 
try_decode_frame()的功能可以从字面上的意思进行理解:“尝试解码一些帧”

从try_decode_frame()的定义可以看出,该函数首先判断视音频流的解码器是否已经打开,如果没有打开的话,先打开相应的解码器。接下来根据视音频流类型的不同,调用不同的解码函数进行解码:视频流调用avcodec_decode_video2(),音频流调用avcodec_decode_audio4(),字幕流调用avcodec_decode_subtitle2()。解码的循环会一直持续下去直到满足了while()的所有条件。

while()语句的条件中有一个has_codec_parameters()函数,用于判断AVStream中的成员变量是否都已经设置完毕。该函数在avformat_find_stream_info()中的多个地方被使用过。

has_codec_parameters() 
has_codec_parameters()用于检查AVStream中的成员变量是否都已经设置完毕

estimate_timings() 
estimate_timings()位于avformat_find_stream_info()最后面,用于估算AVFormatContext以及AVStream的时长duration。

有3种估算方法: 
(1)通过pts(显示时间戳)。该方法调用estimate_timings_from_pts()。它的基本思想就是读取视音频流中的结束位置AVPacket的PTS和起始位置AVPacket的PTS,两者相减得到时长信息。 
(2)通过已知流的时长。该方法调用fill_all_stream_timings()。它的代码没有细看,但从函数的注释的意思来说,应该是当有些视音频流有时长信息的时候,直接赋值给其他视音频流。 
(3)通过bitrate(码率)。该方法调用estimate_timings_from_bit_rate()。它的基本思想就是获得整个文件大小,以及整个文件的bitrate,两者相除之后得到时长信息。

estimate_timings_from_bit_rate() 
在这里附上上述几种方法中最简单的函数estimate_timings_from_bit_rate()的代码。

static void estimate_timings_from_bit_rate(AVFormatContext *ic)  
{  
    int64_t filesize, duration;  
    int i, show_warning = 0;  
    AVStream *st;  


    /* if bit_rate is already set, we believe it */  
    if (ic->bit_rate <= 0) {  
        int bit_rate = 0;  
        for (i = 0; i < ic->nb_streams; i++) {  
            st = ic->streams[i];  
            if (st->codec->bit_rate > 0) {  
                if (INT_MAX - st->codec->bit_rate < bit_rate) {  
                    bit_rate = 0;  
                    break;  
                }  
                bit_rate += st->codec->bit_rate;  
            }  
        }  
        ic->bit_rate = bit_rate;  
    }  


    /* if duration is already set, we believe it */  
    if (ic->duration == AV_NOPTS_VALUE &&  
        ic->bit_rate != 0) {  
        filesize = ic->pb ? avio_size(ic->pb) : 0;  
        if (filesize > ic->data_offset) {  
            filesize -= ic->data_offset;  
            for (i = 0; i < ic->nb_streams; i++) {  
                st      = ic->streams[i];  
                if (   st->time_base.num <= INT64_MAX / ic->bit_rate  
                    && st->duration == AV_NOPTS_VALUE) {  
                    duration = av_rescale(8 * filesize, st->time_base.den,  
                                          ic->bit_rate *  
                                          (int64_t) st->time_base.num);  
                    st->duration = duration;  
                    show_warning = 1;  
                }  
            }  
        }  
    }  
    if (show_warning)  
        av_log(ic, AV_LOG_WARNING,  
               "Estimating duration from bitrate, this may be inaccurate\n");  
} 
  • 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

从代码中可以看出,该函数做了两步工作: 
(1)如果AVFormatContext中没有bit_rate信息,就把所有AVStream的bit_rate加起来作为AVFormatContext的bit_rate信息。 
(2)使用文件大小filesize除以bitrate得到时长信息。具体的方法是: 
AVStream->duration=(filesize*8/bit_rate)/time_base

1)filesize乘以8是因为需要把Byte转换为Bit 
2)具体的实现函数是那个av_rescale()函数。x=av_rescale(a,b,c)的含义是x=a*b/c。 
3)之所以要除以time_base,是因为AVStream中的duration的单位是time_base,注意这和AVFormatContext中的duration的单位(单位是AV_TIME_BASE,固定取值为1000000)是不一样的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值