ffmpeg-4-decoder API-avformat_open_input函数详解

decoder应用demo详见: https://blog.csdn.net/cheriyou_/article/details/100126042

这篇文章主要分析下demo里面decoder接口的具体实现过程。

1. avformat_open_input 解析头信息

ffmpeg/libavformat/utils.c

// 此函数在demo中只有filename是从命令行输入的,其他都是null
int avformat_open_input(AVFormatContext **ps, const char *filename,
                        ff_const59 AVInputFormat *fmt, AVDictionary **options)
{
    // 声明一些参数
    AVFormatContext *s = *ps;
    int i, ret = 0;
    AVDictionary *tmp = NULL;
    ID3v2ExtraMeta *id3v2_extra_meta = NULL;

    if (fmt)
        s->iformat = fmt;

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

    if (s->pb) // must be before any goto fail
        s->flags |= AVFMT_FLAG_CUSTOM_IO;

    if ((ret = av_opt_set_dict(s, &tmp)) < 0) // 如果tmp=null直接返回0,否则把tmp的参数取出并赋值给s
        goto fail;

    if (!(s->url = av_strdup(filename ? filename : ""))) { // 把filename给s->url
        ret = AVERROR(ENOMEM);
        goto fail;
    }

#if FF_API_FORMAT_FILENAME
FF_DISABLE_DEPRECATION_WARNINGS
    av_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename));
FF_ENABLE_DEPRECATION_WARNINGS
#endif
    if ((ret = init_input(s, filename, &tmp)) < 0) // 打开输入文件并解析input的格式
        goto fail;
    s->probe_score = ret;

    if (!s->protocol_whitelist && s->pb && s->pb->protocol_whitelist) {
        s->protocol_whitelist = av_strdup(s->pb->protocol_whitelist);
        if (!s->protocol_whitelist) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
    }

    if (!s->protocol_blacklist && s->pb && s->pb->protocol_blacklist) {
        s->protocol_blacklist = av_strdup(s->pb->protocol_blacklist);
        if (!s->protocol_blacklist) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
    }

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

    avio_skip(s->pb, s->skip_initial_bytes); // skip skip_initial_bytes 在开始解析

    /* Check filename in case an image number is expected. */
    if (s->iformat->flags & AVFMT_NEEDNUMBER) {
        if (!av_filename_number_test(filename)) { // 判断filename是否是正确的输入格式
            ret = AVERROR(EINVAL);
            goto fail;
        }
    }

    s->duration = s->start_time = AV_NOPTS_VALUE; // 初始化开始时间和总时长

    /* 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); // 针对inputformat设置默认参数
            if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0)
                goto fail;
        }
    }

   // 读取文件中的id3v2字段的值
    if (s->pb)
        ff_id3v2_read_dict(s->pb, &s->internal->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);

    // 从buffer里面读取视频头,确定文件头正确
    if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
        if ((ret = s->iformat->read_header(s)) < 0)
            goto fail;

    if (!s->metadata) {
        s->metadata = s->internal->id3v2_meta;
        s->internal->id3v2_meta = NULL;
    } else if (s->internal->id3v2_meta) {
        int level = AV_LOG_WARNING;
        if (s->error_recognition & AV_EF_COMPLIANT)
            level = AV_LOG_ERROR;
        av_log(s, level, "Discarding ID3 tags because more suitable tags were found.\n");
        av_dict_free(&s->internal->id3v2_meta);
        if (s->error_recognition & AV_EF_EXPLODE)
            return AVERROR_INVALIDDATA;
    }

    // 解析文件id3v2中的apic/chapter/priv
    if (id3v2_extra_meta) {
        if (!strcmp(s->iformat->name, "mp3") || !strcmp(s->iformat->name, "aac") ||
            !strcmp(s->iformat->name, "tta") || !strcmp(s->iformat->name, "wav")) {
            if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0)
                goto fail;
            if ((ret = ff_id3v2_parse_chapters(s, &id3v2_extra_meta)) < 0)
                goto fail;
            if ((ret = ff_id3v2_parse_priv(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); // 释放id3v2_extra_meta

    if ((ret = avformat_queue_attached_pictures(s)) < 0) // 对s中每个流的数据包列表循环进行解析,并分配buffer。
        goto fail;

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

    s->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;

    update_stream_avctx(s); // 调用avcodec_parameters_to_context去解析文件的cts信息

    for (i = 0; i < s->nb_streams; i++)
        s->streams[i]->internal->orig_codec_id = s->streams[i]->codecpar->codec_id; // 每个流的codec id

    if (options) {
        av_dict_free(options);
        *options = tmp;
    }
    *ps = s; // 将s赋值给ps并返回
    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_closep(&s->pb);
    avformat_free_context(s);
    *ps = NULL;
    return ret;
}

下面看看init_input具体怎么实现的呢?

// 打开输入文件并解析input的格式
static int init_input(AVFormatContext *s, const char *filename,
                      AVDictionary **options)
{
    int ret;
    AVProbeData pd = { filename, NULL, 0 };
    int score = AVPROBE_SCORE_RETRY;

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

    if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||
        (!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))
        return score;
//如果发现通过文件路径判断不出来文件格式,那么就需要打开文件探测文件格式了,这个
//时候会首先调用io_open打开文件,io_open在avformat_alloc_context()----->avformat_get_context_defaults(ic)----->s->io_open  = io_open_default赋值。
//然后调用av_probe_input_buffer2()推测AVInputFormat。
    if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)
        return ret;

    if (s->iformat)
        return 0;
    return av_probe_input_buffer2(s->pb, &s->iformat, filename,
                                 s, 0, s->format_probesize);
}

看到这里云里雾里,av_probe_input_buffer2和av_probe_input_format2有什么区别?感觉都是获取inputformat啊?接着向下看。

// 函数作用: 它根据输入的媒体数据推测该媒体数据的AVInputFormat
int av_probe_input_buffer2(AVIOContext *pb, ff_const59 AVInputFormat **fmt,
                          const char *filename, void *logctx,
                          unsigned int offset, unsigned int max_probe_size)
{
    // 变量声明
    AVProbeData pd = { filename ? filename : "" };
    uint8_t *buf = NULL;
    int ret = 0, probe_size, buf_offset = 0;
    int score = 0;
    int ret2;
    // 异常处理
    if (!max_probe_size)
        max_probe_size = PROBE_BUF_MAX;
    else if (max_probe_size < PROBE_BUF_MIN) {
        av_log(logctx, AV_LOG_ERROR,
               "Specified probe size value %u cannot be < %u\n", max_probe_size, PROBE_BUF_MIN);
        return AVERROR(EINVAL);
    }

    if (offset >= max_probe_size)
        return AVERROR(EINVAL);

    if (pb->av_class) {
        uint8_t *mime_type_opt = NULL;
        char *semi;
        av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type_opt);
        pd.mime_type = (const char *)mime_type_opt;
        semi = pd.mime_type ? strchr(pd.mime_type, ';') : NULL;
        if (semi) {
            *semi = '\0';
        }
    }
    
    // 开始循环处理文件
    for (probe_size = PROBE_BUF_MIN; probe_size <= max_probe_size && !*fmt;
         probe_size = FFMIN(probe_size << 1,
                            FFMAX(max_probe_size, probe_size + 1))) {
        score = probe_size < max_probe_size ? AVPROBE_SCORE_RETRY : 0;

        /* Read probe data. */
        if ((ret = av_reallocp(&buf, probe_size + AVPROBE_PADDING_SIZE)) < 0) // 给buf分配空间
            goto fail;
        if ((ret = avio_read(pb, buf + buf_offset,
                             probe_size - buf_offset)) < 0) { // 从pb中读取数据
            /* Fail if error was not end of file, otherwise, lower score. */
            if (ret != AVERROR_EOF)
                goto fail;

            score = 0;
            ret   = 0;          /* error was end of file, nothing read */
        }
        buf_offset += ret;
        if (buf_offset < offset)
            continue;
        pd.buf_size = buf_offset - offset;
        pd.buf = &buf[offset]; // 把读取出来的buffer给pd

        memset(pd.buf + pd.buf_size, 0, AVPROBE_PADDING_SIZE);

        /* Guess file format. */
        *fmt = av_probe_input_format2(&pd, 1, &score); // 从pd中解析文件的inputformat
        if (*fmt) {
            /* This can only be true in the last iteration. */
            if (score <= AVPROBE_SCORE_RETRY) {
                av_log(logctx, AV_LOG_WARNING,
                       "Format %s detected only with low score of %d, "
                       "misdetection possible!\n", (*fmt)->name, score);
            } else
                av_log(logctx, AV_LOG_DEBUG,
                       "Format %s probed with size=%d and score=%d\n",
                       (*fmt)->name, probe_size, score);
#if 0
            FILE *f = fopen("probestat.tmp", "ab");
            fprintf(f, "probe_size:%d format:%s score:%d filename:%s\n", probe_size, (*fmt)->name, score, filename);
            fclose(f);
#endif
        }
    }

    if (!*fmt)
        ret = AVERROR_INVALIDDATA;

fail:
    /* Rewind. Reuse probe buffer to avoid seeking. */
    ret2 = ffio_rewind_with_probe_data(pb, &buf, buf_offset);
    if (ret >= 0)
        ret = ret2;

    av_freep(&pd.mime_type);
    return ret < 0 ? ret : score;
}

看完顿时明白: 如果有自定义的AVIOContext就用av_probe_input_buffer2,av_probe_input_buffer2的实现是用一个for循环,从文件里面读取数据,刚开始的时候只读取2048个字节,如果可以判断出来文件格式则直接退出,否则逐渐增加读取数据的量,直到可以判断文件格式。最大读取的数据量是1M,也可自己设置。在此循环中调用av_probe_input_format2,这个函数通过使用avio_read读取的数据来判断文件的格式。

ps: 有两个变量名比较像pd和pb,pd是AVProbeData,pb是AVIOContext注意区分!

接下来看看拿到了pd之后av_probe_input_format2函数的具体处理

// score_max是判断AVInputFormat的门限值,当获取的AVInputFormat的分值超过score_max时就会返回此AVInputFormat,否则返回null。
ff_const59 AVInputFormat *av_probe_input_format2(ff_const59 AVProbeData *pd, int is_opened, int *score_max)
{
    int score_ret;
    ff_const59 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;
}

// score_ret是返回的AVInputFormat的score
ff_const59 AVInputFormat *av_probe_input_format3(ff_const59 AVProbeData *pd, int is_opened, int *score_ret)
{
    AVProbeData lpd = *pd;
    const AVInputFormat *fmt1 = NULL;
    ff_const59 AVInputFormat *fmt = NULL;
    int score, score_max = 0;
    void *i = 0;
    const static uint8_t zerobuffer[AVPROBE_PADDING_SIZE];
    enum nodat {
        NO_ID3,
        ID3_ALMOST_GREATER_PROBE,
        ID3_GREATER_PROBE,
        ID3_GREATER_MAX_PROBE,
    } nodat = NO_ID3;

    if (!lpd.buf)
        lpd.buf = (unsigned char *) zerobuffer;

    // id3是和mp3相关的东西
    if (lpd.buf_size > 10 && ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC)) {
        int id3len = ff_id3v2_tag_len(lpd.buf);
        if (lpd.buf_size > id3len + 16) {
            if (lpd.buf_size < 2LL*id3len + 16)
                nodat = ID3_ALMOST_GREATER_PROBE;
            lpd.buf      += id3len;
            lpd.buf_size -= id3len;
        } else if (id3len >= PROBE_BUF_MAX) {
            nodat = ID3_GREATER_MAX_PROBE;
        } else
            nodat = ID3_GREATER_PROBE;
    }

    while ((fmt1 = av_demuxer_iterate(&i))) { // 从0开始解析,此函数中有一个demuxer_list,存放了各种文件格式的inoutformat,此处就是获取list下标为0的格式的inputformat.
        if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))
            continue;
        score = 0; // 初始化score为0
        if (fmt1->read_probe) {
        // 如果AVInputFormat中包含read_probe(),就调用read_probe()函数获取匹配分数。如果结果匹配的话,一般会获得AVPROBE_SCORE_MAX的分值,即100分。
            score = fmt1->read_probe(&lpd);
            if (score)
                av_log(NULL, AV_LOG_TRACE, "Probing %s score:%d size:%d\n", fmt1->name, score, lpd.buf_size);
            if (fmt1->extensions && av_match_ext(lpd.filename, fmt1->extensions)) {
                switch (nodat) {
                case NO_ID3:
                    score = FFMAX(score, 1);
                    break;
                case ID3_GREATER_PROBE:
                case ID3_ALMOST_GREATER_PROBE:
                    score = FFMAX(score, AVPROBE_SCORE_EXTENSION / 2 - 1);
                    break;
                case ID3_GREATER_MAX_PROBE:
                    score = FFMAX(score, AVPROBE_SCORE_EXTENSION);
                    break;
                }
            }
        } else if (fmt1->extensions) {
        // 如果不包含read_probe(),就用av_match_ext比较扩展名是否匹配。
            if (av_match_ext(lpd.filename, fmt1->extensions))
                score = AVPROBE_SCORE_EXTENSION; // 50分
        }
        // 比较mime_type是否一致,如果一致可以得到75分
        if (av_match_name(lpd.mime_type, fmt1->mime_type)) {
            if (AVPROBE_SCORE_MIME > score) {
                av_log(NULL, AV_LOG_DEBUG, "Probing %s score:%d increased to %d due to MIME type\n", fmt1->name, score, AVPROBE_SCORE_MIME);
                score = AVPROBE_SCORE_MIME;
            }
        }
        if (score > score_max) { // 如果score > score_max更新score_max并给fmt赋值
            score_max = score;
            fmt       = (AVInputFormat*)fmt1;
        } else if (score == score_max)
            fmt = NULL;
    }
    if (nodat == ID3_GREATER_PROBE)
        score_max = FFMIN(AVPROBE_SCORE_EXTENSION / 2 - 1, score_max);
    *score_ret = score_max;

    return fmt;
}

至此我们就可以通过input file获取文件的inputformat。

接下来我们看看如何判断filename是否是正确的输入格式

// 输入只有filename,输出是否是正确的输入文件
int av_filename_number_test(const char *filename)
{
    char buf[1024];
    return filename &&
           (av_get_frame_filename(buf, sizeof(buf), filename, 1) >= 0);
}

int av_get_frame_filename(char *buf, int buf_size, const char *path, int number)
{
    return av_get_frame_filename2(buf, buf_size, path, number, 0);
}

int av_get_frame_filename2(char *buf, int buf_size, const char *path, int number, int flags)
{
    const char *p;
    char *q, buf1[20], c;
    int nd, len, percentd_found;

    q = buf;
    p = path;
    percentd_found = 0;

    // for 循环解析filename的字符,查看是否是正确的输入格式
    for (;;) {
        c = *p++;
        if (c == '\0')
            break;
        if (c == '%') {
            do {
                nd = 0;
                while (av_isdigit(*p))
                    nd = nd * 10 + *p++ - '0';
                c = *p++;
            } while (av_isdigit(c));

            switch (c) {
            case '%':
                goto addchar;
            case 'd':
                if (!(flags & AV_FRAME_FILENAME_FLAGS_MULTIPLE) && percentd_found)
                    goto fail;
                percentd_found = 1;
                if (number < 0)
                    nd += 1;
                snprintf(buf1, sizeof(buf1), "%0*d", nd, number);
                len = strlen(buf1);
                if ((q - buf + len) > buf_size - 1)
                    goto fail;
                memcpy(q, buf1, len);
                q += len;
                break;
            default:
                goto fail;
            }
        } else {
addchar:
            if ((q - buf) < buf_size - 1)
                *q++ = c;
        }
    }
    if (!percentd_found)
        goto fail;
    *q = '\0';
    return 0;
fail:
    *q = '\0';
    return -1;
}

上面这个函数,可以忽略,不重要,只是判断输入是否正确。

接下来,我们看看,怎么获取id3的数据:

id3是一种metadata容器,多应用于MP3格式的音频文件中。 它可以将相关的曲名、演唱者、专辑、音轨数等信息存储在MP3文件中。id3v1是把这些信息放在MP3文件的末尾,id3v2是把这些信息记录在mp3的头部。而且ID3V2结构比ID3V1的结构要复杂得多,但比前者全面且可以伸缩和扩展。

音频数据头信息获取,不太了解,先跳过。

// 代码位置: ffmpeg/libavformat/id3v2.c
// 和前面一样的套路,实际调用id3v2_read_internal
void ff_id3v2_read_dict(AVIOContext *pb, AVDictionary **metadata,
                        const char *magic, ID3v2ExtraMeta **extra_meta)
{
    id3v2_read_internal(pb, metadata, NULL, magic, extra_meta, 0);
}

static void id3v2_read_internal(AVIOContext *pb, AVDictionary **metadata,
                                AVFormatContext *s, const char *magic,
                                ID3v2ExtraMeta **extra_meta, int64_t max_search_size)
{
    int len, ret;
    uint8_t buf[ID3v2_HEADER_SIZE];
    int found_header;
    int64_t start, off;

    if (max_search_size && max_search_size < ID3v2_HEADER_SIZE)
        return;

    start = avio_tell(pb);
    do {
        /* save the current offset in case there's nothing to read/skip */
        off = avio_tell(pb);
        if (max_search_size && off - start >= max_search_size - ID3v2_HEADER_SIZE) {
            avio_seek(pb, off, SEEK_SET);
            break;
        }

        ret = ffio_ensure_seekback(pb, ID3v2_HEADER_SIZE);
        if (ret >= 0)
            ret = avio_read(pb, buf, ID3v2_HEADER_SIZE);
        if (ret != ID3v2_HEADER_SIZE) {
            avio_seek(pb, off, SEEK_SET);
            break;
        }
        found_header = ff_id3v2_match(buf, magic);
        if (found_header) {
            /* parse ID3v2 header */
            len = ((buf[6] & 0x7f) << 21) |
                  ((buf[7] & 0x7f) << 14) |
                  ((buf[8] & 0x7f) << 7) |
                   (buf[9] & 0x7f);
            id3v2_parse(pb, metadata, s, len, buf[3], buf[5], extra_meta);
        } else {
            avio_seek(pb, off, SEEK_SET);
        }
    } while (found_header);
    ff_metadata_conv(metadata, NULL, ff_id3v2_34_metadata_conv);
    ff_metadata_conv(metadata, NULL, id3v2_2_metadata_conv);
    ff_metadata_conv(metadata, NULL, ff_id3v2_4_metadata_conv);
    merge_date(metadata);
}

接下来是从buffer里面读取视频头,确定解码器,s->iformat->read_header(s).

read_header有很多种实现方式,根据你文件的inputformat会选择不同的实现方式。

此处以ffmpeg/libavformat/Ffmetadec.c中的read_header函数为例:

static int read_header(AVFormatContext *s)
{
    AVDictionary **m = &s->metadata;
    AVBPrint bp;

    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED); // 初始化print buffer

    while(!avio_feof(s->pb)) { // 判断是否文件尾?
        get_bprint_line(s->pb, &bp); // 从s->pb中获取bp
        // 接下来会判断bp的内容,是stream还是chapter.
        if (!memcmp(bp.str, ID_STREAM, strlen(ID_STREAM))) {
            AVStream *st = avformat_new_stream(s, NULL); // 给s分配stream

            if (!st)
                return AVERROR(ENOMEM);

            st->codecpar->codec_type = AVMEDIA_TYPE_DATA; // 确认st的解码器类型
            st->codecpar->codec_id   = AV_CODEC_ID_FFMETADATA;

            m = &st->metadata; // 把st->metadata给m
        } else if (!memcmp(bp.str, ID_CHAPTER, strlen(ID_CHAPTER))) {
            AVChapter *ch = read_chapter(s);

            if (!ch)
                return AVERROR(ENOMEM);

            m = &ch->metadata;
        } else
            read_tag(bp.str, m);
    }

    av_bprint_finalize(&bp, NULL); // 清除buf->str

    s->start_time = 0;
    if (s->nb_chapters)
        s->duration = av_rescale_q(s->chapters[s->nb_chapters - 1]->end,
                                   s->chapters[s->nb_chapters - 1]->time_base,
                                   AV_TIME_BASE_Q);

    return 0;
}
// 其实这个函数最重要的就是通过AVFormatContext的AVIOContext判断了解码器的类型,读到了metadate,
//并给AVFormatContext增加了streams。

接下来依次看看av_bprint_init、get_bprint_line、avformat_new_stream、av_bprint_finalize、av_rescale_q的具体实现。

void av_bprint_init(AVBPrint *buf, unsigned size_init, unsigned size_max)
{
    unsigned size_auto = (char *)buf + sizeof(*buf) -
                         buf->reserved_internal_buffer;

    if (size_max == 1)
        size_max = size_auto;
    buf->str      = buf->reserved_internal_buffer;
    buf->len      = 0;
    buf->size     = FFMIN(size_auto, size_max);
    buf->size_max = size_max;
    *buf->str = 0;
    if (size_init > buf->size)
        av_bprint_alloc(buf, size_init - 1); // 给buffer分配size_init-1这么大的空间
}

综上,av_bprint_init的作用就是初始化AVBPrint,并给他分配buffer。

static void get_bprint_line(AVIOContext *s, AVBPrint *bp)
{

    do {
        av_bprint_clear(bp); // clear bp
        read_line_to_bprint_escaped(s, bp); // 
    } while (!avio_feof(s) && (bp->str[0] == ';' || bp->str[0] == '#' || bp->str[0] == 0));
}

static int64_t read_line_to_bprint_escaped(AVIOContext *s, AVBPrint *bp)
{
    int len, end;
    int64_t read = 0;
    char tmp[1024];
    char c;
    char prev = ' ';

    do {
        len = 0;
        do {
            c = avio_r8(s); // 从s中读取数据当前buffer读取到的位置的内容
            end = prev != '\\' && (c == '\r' || c == '\n' || c == '\0');
            if (!end)
                tmp[len++] = c; // 如果不是end就把c放入tmp
            prev = c;
        } while (!end && len < sizeof(tmp));// 如果end或者数据量超过了1024就跳出循环
        av_bprint_append_data(bp, tmp, len); // 把tmp的数据拷贝给bp
        read += len; // 累计读取的有效数据的长度
    } while (!end); // end时跳出循环

    if (c == '\r' && avio_r8(s) != '\n' && !avio_feof(s))
        avio_skip(s, -1);

    if (!c && s->error)
        return s->error;

    if (!c && !read && avio_feof(s))
        return AVERROR_EOF;

    return read;
}

void av_bprint_append_data(AVBPrint *buf, const char *data, unsigned size)
{
    unsigned room, real_n;

    while (1) {
        room = av_bprint_room(buf); // get buf的可用大小
        if (size < room)
            break;
        if (av_bprint_alloc(buf, size)) // 如果空间不够就需要再分配空间
            break;
    }
    if (room) {
        real_n = FFMIN(size, room - 1);
        memcpy(buf->str + buf->len, data, real_n); // 把data的数据拷贝给buf
    }
    av_bprint_grow(buf, size); // 扩大buf的len
}

接下来看看avformat_queue_attached_pictures,这个函数是对s中每个流的数据包列表循环进行解析,并分配buffer。具体实现如下:

int avformat_queue_attached_pictures(AVFormatContext *s)
{
    int i, ret;
    for (i = 0; i < s->nb_streams; i++) // 循环处理每个流
        if (s->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC &&
            s->streams[i]->discard < AVDISCARD_ALL) {
            if (s->streams[i]->attached_pic.size <= 0) {
                av_log(s, AV_LOG_WARNING,
                    "Attached picture on stream %d has invalid size, "
                    "ignoring\n", i);
                continue;
            }

            ret = ff_packet_list_put(&s->internal->raw_packet_buffer, // 列表头
                                     &s->internal->raw_packet_buffer_end, // 列表尾
                                     &s->streams[i]->attached_pic, // 需要介入列表的数据包
                                     FF_PACKETLIST_FLAG_REF_PACKET);
        // 将AVPacket附加到列表中。
            if (ret < 0) // 插入失败
                return ret;
        }
    return 0;
}

int ff_packet_list_put(AVPacketList **packet_buffer,
                       AVPacketList **plast_pktl,
                       AVPacket      *pkt, int flags)
{
    AVPacketList *pktl = av_mallocz(sizeof(AVPacketList));
    int ret;

    if (!pktl)
        return AVERROR(ENOMEM);

    if (flags & FF_PACKETLIST_FLAG_REF_PACKET) {
        if ((ret = av_packet_ref(&pktl->pkt, pkt)) < 0) { // 把pkt的内容复制给pktl->pkt
            av_free(pktl);
            return ret;
        }
    } else {
        // TODO: Adapt callers in this file so the line below can use
        //       av_packet_move_ref() to effectively move the reference
        //       to the list.
        pktl->pkt = *pkt;
    }

    if (*packet_buffer)
        (*plast_pktl)->next = pktl; // 让链表的头的next为pktl
    else
        *packet_buffer = pktl; // 如果链表头为null就让链表头为pkt1

    /* Add the packet in the buffered packet list. */
    *plast_pktl = pktl; // 让链表尾为pktl
    return 0;
}

至此每个流的AVPacket都放在s->internal->raw_packet_buffer中。

接下来就是update_stream_avctx,调用avcodec_parameters_to_context去解析文件的cts信息

static int update_stream_avctx(AVFormatContext *s)
{
    int i, ret;
    for (i = 0; i < s->nb_streams; i++) { // 循环处理每个流
        AVStream *st = s->streams[i];

        if (!st->internal->need_context_update)
            continue;

        /* close parser, because it depends on the codec */
        if (st->parser && st->internal->avctx->codec_id != st->codecpar->codec_id) {
            av_parser_close(st->parser);
            st->parser = NULL;
        }

        /* update internal codec context, for the parser */
        ret = avcodec_parameters_to_context(st->internal->avctx, st->codecpar);
        if (ret < 0)
            return ret;

#if FF_API_LAVF_AVCTX
FF_DISABLE_DEPRECATION_WARNINGS
        /* update deprecated public codec context */
        ret = avcodec_parameters_to_context(st->codec, st->codecpar);
        if (ret < 0)
            return ret;
FF_ENABLE_DEPRECATION_WARNINGS
#endif

        st->internal->need_context_update = 0;
    }
    return 0;
}

// 这个函数的作用就是把AVCodecParameters的参数赋值给AVCodecContext
int avcodec_parameters_to_context(AVCodecContext *codec,
                                  const AVCodecParameters *par)
{
    codec->codec_type = par->codec_type;
    codec->codec_id   = par->codec_id;
    codec->codec_tag  = par->codec_tag;

    codec->bit_rate              = par->bit_rate;
    codec->bits_per_coded_sample = par->bits_per_coded_sample;
    codec->bits_per_raw_sample   = par->bits_per_raw_sample;
    codec->profile               = par->profile;
    codec->level                 = par->level;

    switch (par->codec_type) {
    case AVMEDIA_TYPE_VIDEO:
        codec->pix_fmt                = par->format;
        codec->width                  = par->width;
        codec->height                 = par->height;
        codec->field_order            = par->field_order;
        codec->color_range            = par->color_range;
        codec->color_primaries        = par->color_primaries;
        codec->color_trc              = par->color_trc;
        codec->colorspace             = par->color_space;
        codec->chroma_sample_location = par->chroma_location;
        codec->sample_aspect_ratio    = par->sample_aspect_ratio;
        codec->has_b_frames           = par->video_delay;
        break;
    case AVMEDIA_TYPE_AUDIO:
        codec->sample_fmt       = par->format;
        codec->channel_layout   = par->channel_layout;
        codec->channels         = par->channels;
        codec->sample_rate      = par->sample_rate;
        codec->block_align      = par->block_align;
        codec->frame_size       = par->frame_size;
        codec->delay            =
        codec->initial_padding  = par->initial_padding;
        codec->trailing_padding = par->trailing_padding;
        codec->seek_preroll     = par->seek_preroll;
        break;
    case AVMEDIA_TYPE_SUBTITLE:
        codec->width  = par->width;
        codec->height = par->height;
        break;
    }

    if (par->extradata) {
        av_freep(&codec->extradata);
        codec->extradata = av_mallocz(par->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
        if (!codec->extradata)
            return AVERROR(ENOMEM);
        memcpy(codec->extradata, par->extradata, par->extradata_size);
        codec->extradata_size = par->extradata_size;
    }

    return 0;
}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值