ijkplayer 代码走读之 播放器网络数据读取过程详解2

本篇是 《播放器网络视频数据读取过程详解》 的延续部分,我们回顾一下上一篇。

回顾前面内容

我们详细分析了自定义的协议是如何以静态方式、注册到 IJKPLAYER 协议profile中。
本篇分析打开自定义协议、读取数据流数据并匹配解封装函数,也即是说本篇要把私有协议、私有数据封装
的数据流模式,实现在ijkplayer中播放流程说清楚。

static int init_input(AVFormatContext *s, const char *filename,
                      AVDictionary **options)
{
    
    
    if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags,      ///> 1.2. 此函数是 io_open() 打开文件或网络socket
                                                                options)) < 0)   
        return ret;

    if (s->iformat)
        return 0;
    return av_probe_input_buffer2(s->pb, &s->iformat, filename,                     ///> 1.3 读数据流的第一帧数据
                                 s, 0, s->format_probesize);
}

在 init_input() 函数中调用了 io_open() 函数,
ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options);
这个函数其根本是执行此函数
static int io_open_default(AVFormatContext *s, AVIOContext **pb,
const char *url, int flags, AVDictionary **options)
在该函数中调用函数
ffio_open_whitelist(pb, url, flags, &s->interrupt_callback, options, s->protocol_whitelist, s->protocol_blacklist);
匹配通讯协议,并根据通讯建立通讯连接。前面文章已经描述此过程,再次我们简单回顾。

第一 解封装器匹配过程


int ffio_open_whitelist(AVIOContext **s, const char *filename, int flags,
                         const AVIOInterruptCB *int_cb, AVDictionary **options,
                         const char *whitelist, const char *blacklist
                        )
{
    
    
    URLContext *h;
    int err;
                                                ///> 此函数匹配 filename 中协议类别,创建通讯对象和关联结构体初始化。
    err = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL);
    if (err < 0)
        return err;
    err = ffio_fdopen(s, h);                    ///> 调用 ffio_fdopen 函数,分配AVIOContext的io buffer,
    if (err < 0) {
    
    
        ffurl_close(h);
        return err;
    }
    return 0;
}

int ffio_fdopen(AVIOContext **s, URLContext *h)
{
    
    
    AVIOInternal *internal = NULL;
    uint8_t *buffer = NULL;
    int buffer_size, max_packet_size;
    av_log(NULL, AV_LOG_INFO, "%s/%s(), LINE:%d \n",__FILE__, __func__, __LINE__);
    max_packet_size = h->max_packet_size;
    if (max_packet_size) {
    
    
        buffer_size = max_packet_size; /* no need to bufferize more than one packet */
    } else {
    
    
        buffer_size = IO_BUFFER_SIZE;
    }
    buffer = av_malloc(buffer_size);                ///> 用于预取视频数据的缓存, io buffer大小为32k
    if (!buffer)
        return AVERROR(ENOMEM);

    internal = av_mallocz(sizeof(*internal));       ///> AVIOInternal指针, 指向URLContext
    if (!internal)
        goto fail;

    internal->h = h;
                           ///> 此函数动态设置 read_packet 指针指向的函数为 io_read_packet() 函数。                        
    *s = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE,    ///> 分配AVIOContext, 设置read_packet、write_packet、seek函数,
                            internal, io_read_packet, io_write_packet, io_seek);///> 对应为io_read_packet、io_write_packet、io_see
    if (!*s)
        goto fail;
    av_log(NULL, AV_LOG_INFO, "%s/%s(), LINE:%d \n",__FILE__, __func__, __LINE__);
    (*s)->protocol_whitelist = av_strdup(h->protocol_whitelist);
    if (!(*s)->protocol_whitelist && h->protocol_whitelist) {
    
    
        avio_closep(s);
        goto fail;
    }
    (*s)->protocol_blacklist = av_strdup(h->protocol_blacklist);
    if (!(*s)->protocol_blacklist && h->protocol_blacklist) {
    
    
        avio_closep(s);
        goto fail;
    }
    (*s)->direct = h->flags & AVIO_FLAG_DIRECT;
    av_log(NULL, AV_LOG_INFO, "%s/%s(), LINE:%d \n",__FILE__, __func__, __LINE__);
    (*s)->seekable = h->is_streamed ? 0 : AVIO_SEEKABLE_NORMAL;
    (*s)->max_packet_size = max_packet_size;
    (*s)->min_packet_size = h->min_packet_size;
    if(h->prot) {
    
    
        (*s)->read_pause = io_read_pause;
        (*s)->read_seek  = io_read_seek;

        if (h->prot->url_read_seek)
            (*s)->seekable |= AVIO_SEEKABLE_TIME;
    }
    (*s)->short_seek_get = io_short_seek;
    (*s)->av_class = &ff_avio_class;
    return 0;
fail:
    av_log(NULL, AV_LOG_INFO, "%s/%s(), LINE:%d \n",__FILE__, __func__, __LINE__);
    av_freep(&internal);
    av_freep(&buffer);
    return AVERROR(ENOMEM);
}

此 open_fdopen() 函数主要工作内容建立 AVIOContext 对象,并把对象关联到播放器对象上,就完成通讯协议
各部分的初始化工作,通讯链接已经建立起来。
接下来我们分析读取数据流第一帧数据 probe data 和数据流解封装的过程,在 init_input() 函数中,执行
io_open() 函数后,就调用下面这个函数。

///> av_probe_input_buffer2(s->pb, &s->iformat, filename,s, 0, s->format_probesize);     ///> 1.3 读数据流的第一帧数据
int av_probe_input_buffer2(AVIOContext *pb, 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) {
    
                                                                 ///> av_class 指针执行当前 编解码类对象
        uint8_t *mime_type_opt = NULL;
        char *semi;
        av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type_opt);        ///> 获取当前av_class对象的mime_type内容
        pd.mime_type = (const char *)mime_type_opt;
        semi = pd.mime_type ? strchr(pd.mime_type, ';') : NULL;
        if (semi) {
    
    
            *semi = '\0';
        }
    }
#if 0
    if (!*fmt && pb->av_class && av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type) >= 0 && mime_type) {
    
    
        if (!av_strcasecmp(mime_type, "audio/aacp")) {
    
    
            *fmt = av_find_input_format("aac");
        }
        av_freep(&mime_type);
    }
#endif
        ///> 此循环体是在  max_probe_size 范围内容,查找可以使用的解封装对象。
    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.buf缓存默认大小为2k+32 */
        if ((ret = av_reallocp(&buf, probe_size + AVPROBE_PADDING_SIZE)) < 0)       ///> extra allocated bytes at the end of the probe buffer
            goto fail;
        if ((ret = avio_read(pb, buf + buf_offset,                  ///> 1.3.1 此函数最终调用 s->read_packet(s->opaque, buf, size);
                             probe_size - buf_offset)) < 0)         ///> 读取 probe_size 长度数据到 buf 中供 av_probe_input_format2() 用
        {
    
    
            /* 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];                                      ///> 1.3.2 将buf缓存保存到AVProbeData中

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

        /* Guess file format. 根据AVPorbeData中的数据,探测出输入视频的容器格式,
           即 AVInputFormat,ffmpeg支持的AVInputFormat定义在 demuxer_list.c 中 
        */
        *fmt = av_probe_input_format2(&pd, 1, &score);              ///> 1.3.3 根据 pd.buf 中数据匹配 demuxer 格式
        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);       ///>  1.3.4 匹配到解封装器后,把probe中数据转移
    if (ret >= 0)
        ret = ret2;

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

根据上面的代码我们知道数据流格式匹配由函数 av_probe_input_format2() 处理。
至此我们先总结一下这个逻辑:

  1. 函数 av_probe_input_buffer2() 在 max_probe_size 数据长度内,匹配最优的解封装器;
  2. 成功就返回 AVInputFormat *fmt 指向的对象;(假设返回的是 ff_mov_demuxer 解封装器).
  3. 函数 init_input() 执行完返回到 avformat_open_input() 中,此函数后面就开始匹配解码器,在成功就进入连续播放的状态。

接下来 看 ff_mov_demuxer 解封装器是如何实现,此源码路径: libavformat / mov.c

#define OFFSET(x) offsetof(MOVContext, x)
#define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_DECODING_PARAM
static const AVOption mov_options[] = {
    
    
    {
    
    "use_absolute_path",
        "allow using absolute path when opening alias, this is a possible security issue",
        OFFSET(use_absolute_path), AV_OPT_TYPE_BOOL, {
    
    .i64 = 0},
        0, 1, FLAGS},
    {
    
    "seek_streams_individually",
        "Seek each stream individually to the to the closest point",
        OFFSET(seek_individually), AV_OPT_TYPE_BOOL, {
    
     .i64 = 1 },
        0, 1, FLAGS},
    {
    
    "ignore_editlist", "Ignore the edit list atom.", OFFSET(ignore_editlist), AV_OPT_TYPE_BOOL, {
    
    .i64 = 0},
        0, 1, FLAGS},
    {
    
    "advanced_editlist",
        "Modify the AVIndex according to the editlists. Use this option to decode in the order specified by the edits.",
        OFFSET(advanced_editlist), AV_OPT_TYPE_BOOL, {
    
    .i64 = 1},
        0, 1, FLAGS},
    {
    
    "ignore_chapters", "", OFFSET(ignore_chapters), AV_OPT_TYPE_BOOL, {
    
    .i64 = 0},
        0, 1, FLAGS},
    {
    
    "use_mfra_for",
        "use mfra for fragment timestamps",
        OFFSET(use_mfra_for), AV_OPT_TYPE_INT, {
    
    .i64 = FF_MOV_FLAG_MFRA_AUTO},
        -1, FF_MOV_FLAG_MFRA_PTS, FLAGS,
        "use_mfra_for"},
    {
    
    "auto", "auto", 0, AV_OPT_TYPE_CONST, {
    
    .i64 = FF_MOV_FLAG_MFRA_AUTO}, 0, 0,
        FLAGS, "use_mfra_for" },
    {
    
    "dts", "dts", 0, AV_OPT_TYPE_CONST, {
    
    .i64 = FF_MOV_FLAG_MFRA_DTS}, 0, 0,
        FLAGS, "use_mfra_for" },
    {
    
    "pts", "pts", 0, AV_OPT_TYPE_CONST, {
    
    .i64 = FF_MOV_FLAG_MFRA_PTS}, 0, 0,
        FLAGS, "use_mfra_for" },
    {
    
     "export_all", "Export unrecognized metadata entries", OFFSET(export_all),
        AV_OPT_TYPE_BOOL, {
    
     .i64 = 0 }, 0, 1, .flags = FLAGS },
    {
    
     "export_xmp", "Export full XMP metadata", OFFSET(export_xmp),
        AV_OPT_TYPE_BOOL, {
    
     .i64 = 0 }, 0, 1, .flags = FLAGS },
    {
    
     "activation_bytes", "Secret bytes for Audible AAX files", OFFSET(activation_bytes),
        AV_OPT_TYPE_BINARY, .flags = AV_OPT_FLAG_DECODING_PARAM },
    {
    
     "audible_fixed_key", // extracted from libAAX_SDK.so and AAXSDKWin.dll files!
        "Fixed key used for handling Audible AAX files", OFFSET(audible_fixed_key),
        AV_OPT_TYPE_BINARY, {
    
    .str="77214d4b196a87cd520045fd20a51d67"},
        .flags = AV_OPT_FLAG_DECODING_PARAM },
    {
    
     "decryption_key", "The media decryption key (hex)", OFFSET(decryption_key), AV_OPT_TYPE_BINARY, .flags = AV_OPT_FLAG_DECODING_PARAM },
    {
    
     "enable_drefs", "Enable external track support.", OFFSET(enable_drefs), AV_OPT_TYPE_BOOL,
        {
    
    .i64 = 0}, 0, 1, FLAGS },

    {
    
     NULL },
};

static const AVClass mov_class = {
    
    
    .class_name = "mov,mp4,m4a,3gp,3g2,mj2",
    .item_name  = av_default_item_name,
    .option     = mov_options,
    .version    = LIBAVUTIL_VERSION_INT,
};

AVInputFormat ff_mov_demuxer = {
    
    
    .name           = "mov,mp4,m4a,3gp,3g2,mj2",
    .long_name      = NULL_IF_CONFIG_SMALL("QuickTime / MOV"),
    .priv_class     = &mov_class,
    .priv_data_size = sizeof(MOVContext),
    .extensions     = "mov,mp4,m4a,3gp,3g2,mj2",
    .read_probe     = mov_probe,
    .read_header    = mov_read_header,
    .read_packet    = mov_read_packet,
    .read_close     = mov_read_close,
    .read_seek      = mov_read_seek,
    .flags          = AVFMT_NO_BYTE_SEEK,
};

ff_mov_demuxer 解封装器支持的格式"mov,mp4,m4a,3gp,3g2,mj2",我们先看 AVoption 定义如下:

/**
 * {"auto", "auto", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_MFRA_AUTO}, 0, 0, FLAGS, "use_mfra_for" },
 * 上面的数据对应到结构体中,如下。
 */
typedef struct AVOption {
    
    
    const char *name;      //> "auto",

    const char *help;      //> "auto",

    int offset;            //>  0,
    
    enum AVOptionType type; //> AV_OPT_TYPE_CONST, 

    union {
    
    
        int64_t i64;
        double dbl;
        const char *str;
        AVRational q;
    } default_val;          //> {.i64 = FF_MOV_FLAG_MFRA_AUTO},

    double min;             //> 0,

    double max;             //> 0,

    int flags;              //> FLAGS,

    const char *unit;       //> "use_mfra_for"
} AVOption;

程序在 AVInputFormat ff_mov_demuxer 定义时并没有见到给成员 mime_type 赋值;
而程序 av_probe_input_format2() 中在用,我们看看是如何使用的。

第二 . probe input 如何分解数据流格式

我们看下面的这个结构体,前面注解很清晰是 数据格式匹配。

/**
 * This structure contains the data a format has to probe a file.
 */
typedef struct AVProbeData {
    
    
    const char *filename;           ///> 字符串指针,与硬件平台结构有关。
    unsigned char *buf; /**< Buffer must have AVPROBE_PADDING_SIZE of extra allocated bytes filled with zero. */
    int buf_size;       /**< Size of buf except extra allocated bytes */
    const char *mime_type; /**< mime_type, when known. */
} AVProbeData;

接着看函数 av_probe_input_format2() 函数做了什么事情呢。

///> *fmt = av_probe_input_format2(&pd, 1, &score);
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;
}

AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened,
                                      int *score_ret)
{
    
    
    AVProbeData lpd = *pd;
    AVInputFormat *fmt1 = NULL, *fmt;
    int score, score_max = 0;
    const static uint8_t zerobuffer[AVPROBE_PADDING_SIZE];                     ///> AVPROBE_PADDING_SIZE = 32 bytes
    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;
                                                                             ///> 上面读取的第一帧数据是放到pb.buf中,直接解析读取到数据流内容。
    if (lpd.buf_size > 10 && ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC)) {
    
     ///> 2.1 匹配ID3V2特征头 10 bytes,
        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;
    }

    fmt = NULL;
    while ((fmt1 = av_iformat_next(fmt1))) {
    
                     ///> 2.2 遍历解复用器列表
        if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))
            continue;
        score = 0;
        if (fmt1->read_probe) {
    
    
            score = fmt1->read_probe(&lpd);                 ///> 2.3 调用解封装器的 read_probe,通过解析数据流内容,获取视频的得分值
            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)) {
    
     ///> ff_mov_demuxer 解封装器扩展 "mov,mp4,m4a,3gp,3g2,mj2",
                switch (nodat) {
    
                                                        ///> filename 字符中有 mp4,就可以匹配上这个解封装器。 
                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) {
    
    
            if (av_match_ext(lpd.filename, fmt1->extensions))   ///> 直接匹配扩展明
                score = AVPROBE_SCORE_EXTENSION;
        }

        if (av_match_name(lpd.mime_type, fmt1->mime_type)) {
    
       ///>  匹配 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_max = score;
            fmt       = 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;
}

///> 入口参数: ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC) //ID3v2_DEFAULT_MAGIC = "ID3"
///> 此函数在 id3v2.c 中,源码路径 libavformat/id3v2.c ,此文件中主要是解封装的源码内容。
int ff_id3v2_match(const uint8_t *buf, const char *magic)
{
    
    
    return  buf[0]         == magic[0] &&   // I
            buf[1]         == magic[1] &&   // D
            buf[2]         == magic[2] &&   // 3
            buf[3]         != 0xff     &&
            buf[4]         != 0xff     &&
           (buf[6] & 0x80) == 0        &&   // length << 21
           (buf[7] & 0x80) == 0        &&   // length << 14
           (buf[8] & 0x80) == 0        &&   // length << 7
           (buf[9] & 0x80) == 0;            // length 
}
///> 计算帧数据长度。
int ff_id3v2_tag_len(const uint8_t *buf)
{
    
    
    int len = ((buf[6] & 0x7f) << 21) +
              ((buf[7] & 0x7f) << 14) +
              ((buf[8] & 0x7f) << 7) +
              (buf[9] & 0x7f) +
              ID3v2_HEADER_SIZE;
    if (buf[5] & 0x10)
        len += ID3v2_HEADER_SIZE;
    return len;
}

///> 2.2 遍历解封装器
/** head of registered input format linked list */
static AVInputFormat *first_iformat = NULL;
/** head of registered output format linked list */
static AVOutputFormat *first_oformat = NULL;

static AVInputFormat **last_iformat = &first_iformat;       ///> 输入 解封装格式链表的 first_iformat 节点, 全局遍历是 AVInputFormat格式的数组集合
static AVOutputFormat **last_oformat = &first_oformat;      ///> 输出 封装格式链表 first_oformat 节点

AVInputFormat *av_iformat_next(const AVInputFormat *f)
{
    
    
    if (f)
        return f->next;
    else
        return first_iformat;
}

我们在看看 ID3V2.3 标签头结构格式定义,如下:

--------------------------------------------------------------------
名称  字节  说明
--------------------------------------------------------------------
Header  3    ID3V2.3 标识符"ID3"的Ascii码,否则认为没有ID3V2.3
Ver    1  版本号,=03
Revision 1  副版本号,=00
flag   1  标志字节,一般没意义,=00
Size   4  标签内容长度,高位在前,不包括标签头的10个字节
---------------------------------------------------------------------
说明:
- Size 字段的计算公式如下(从左至右):
size =字节1的值×&H200000+字节2的值×&H4000+字节3的值×&H80+字节4的值

- 如果所有标签帧的总长度<标签内容长度,则须用0填满。

至此我们基本清楚匹配 解封装器的过程,函数 ff_id3v2_match() 根据 ID3v2 的格式头进行匹配,
检测数据流是否为 ID3V2 流格式,扩展名匹配成功就确认此解封装器可以使用。

总结一下:

  1. IJKPLAYER 中支持的解封装器都添加到 以 first_iformat 为首的链表中;
  2. 封装器都添加到 以 first_oformat 为首的链表中;
  3. 用户可以参考 其他解封装器、或封装器程序、实现私有解封装或封装的方法。

第三. 接下来详细分析从数据流提取信息

我们还以 ID3v2 封装流格式为例,前面根据标签头已经匹配到 ff_mov_demuxer 解封装器,接下来程序从 init_input() 返回到
avformat_open_input 函数继续执行,如下:

int avformat_open_input(){
    
    

    if ((ret = init_input(s, filename, &tmp)) < 0)                         ///> 1. 通过 filename 初始化 AVFormatContext 的 input 相关参数
        goto fail;                                                         ///>    处理ID3V2的首帧数据 
    s->probe_score = ret;

    ...... 省略相关的代码

    /* e.g. AVFMT_NOFILE formats will not have a AVIOContext */
    if (s->pb)
        ff_id3v2_read_dict(s->pb, &s->internal->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);   ///> 此程序调用 id3v2_parse() 函数

    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)                                  ///> 处理帧标识为 APIC 的第二帧数据
                goto fail;
            if ((ret = ff_id3v2_parse_chapters(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;

    update_stream_avctx(s);

    for (i = 0; i < s->nb_streams; i++)
        s->streams[i]->internal->orig_codec_id = s->streams[i]->codecpar->codec_id;
}

///> 到此 avformat_open_input() 打开输入流函数就执行结束,程序返回至 read_thread() 线程中。

///> 建立新流
int ff_id3v2_parse_apic(AVFormatContext *s, ID3v2ExtraMeta **extra_meta)
{
    
    
    ID3v2ExtraMeta *cur;

    for (cur = *extra_meta; cur; cur = cur->next) {
    
    
        ID3v2ExtraMetaAPIC *apic;
        AVStream *st;

        if (strcmp(cur->tag, "APIC"))
            continue;
        apic = cur->data;

        if (!(st = avformat_new_stream(s, NULL)))
            return AVERROR(ENOMEM);

        st->disposition      |= AV_DISPOSITION_ATTACHED_PIC;
        st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
        st->codecpar->codec_id   = apic->id;

        if (AV_RB64(apic->buf->data) == 0x89504e470d0a1a0a)
            st->codecpar->codec_id = AV_CODEC_ID_PNG;

        if (apic->description[0])
            av_dict_set(&st->metadata, "title", apic->description, 0);

        av_dict_set(&st->metadata, "comment", apic->type, 0);

        av_init_packet(&st->attached_pic);
        st->attached_pic.buf          = apic->buf;
        st->attached_pic.data         = apic->buf->data;
        st->attached_pic.size         = apic->buf->size - AV_INPUT_BUFFER_PADDING_SIZE;
        st->attached_pic.stream_index = st->index;
        st->attached_pic.flags       |= AV_PKT_FLAG_KEY;

        apic->buf = NULL;
    }

    return 0;
}

///>
int ff_id3v2_parse_chapters(AVFormatContext *s, ID3v2ExtraMeta **extra_meta)
{
    
    
    int ret = 0;
    ID3v2ExtraMeta *cur;
    AVRational time_base = {
    
    1, 1000};
    ID3v2ExtraMetaCHAP **chapters = NULL;
    int num_chapters = 0;
    int i;

    // since extra_meta is a linked list where elements are prepended,
    // we need to reverse the order of chapters
    for (cur = *extra_meta; cur; cur = cur->next) {
    
    
        ID3v2ExtraMetaCHAP *chap;

        if (strcmp(cur->tag, "CHAP"))
            continue;
        chap = cur->data;

        if ((ret = av_dynarray_add_nofree(&chapters, &num_chapters, chap)) < 0)
            goto end;
    }

    for (i = 0; i < (num_chapters / 2); i++) {
    
    
        ID3v2ExtraMetaCHAP *right;
        int right_index;

        right_index = (num_chapters - 1) - i;
        right = chapters[right_index];

        chapters[right_index] = chapters[i];
        chapters[i] = right;
    }

    for (i = 0; i < num_chapters; i++) {
    
    
        ID3v2ExtraMetaCHAP *chap;
        AVChapter *chapter;

        chap = chapters[i];
        chapter = avpriv_new_chapter(s, i, time_base, chap->start, chap->end, chap->element_id);
        if (!chapter)
            continue;

        if ((ret = av_dict_copy(&chapter->metadata, chap->meta, 0)) < 0)
            goto end;
    }

end:
    av_freep(&chapters);
    return ret;
}

我们接下来在看 ID3v2 标签帧的结构,如下:

4:标签帧的结构
----------------------------------------------------------
名称  字节  说明
----------------------------------------------------------
FrameID 4    帧标识符的Ascii码,常用标识符的意义见表5
Size   4    帧内容及编码方式的合计长度,高位在前
Flags  2    标志,只使用了6位,详见表6,一般均=0
encode  4    帧内容所用的编码方式。许多帧没有此项
帧内容      至少 1 个字节
----------------------------------------------------------
说明:
  ①Size的计算同上。
  ②标签帧之间没有特殊的分隔符,要得到一个完整的标签帧内容必须先从帧头中得到帧内容长度。
    ③encode 有 4 个可能值:
       0:表示帧内容字符用 ISO-8859-1 编码;
       1:表示帧内容字符用 UTF-16LE 编码;
       2:表示帧内容字符用 UTF-16BE 编码;
       3:表示帧内容字符用 UTF-8 编码(仅ID3V2.4才支持)
  但经常看到的是"eng"这样的字符形式,它表示帧内容所使用的自然语言为英语。也许 D3V2 标签帧进化到现在,encode 已经用“自然语言”取代了“编码方式”。
  ⑤帧内容均为字符串,常以 00 开头。

表5:标签帧标识符的意义
---------------------------------------
名称  意义
---------------------------------------
AENC: 音频加密技术
APIC: 附加描述
COMM: 注释,相当于ID3v1的Comment
COMR: 广告
ENCR: 加密方法注册
ETC0: 事件时间编码
GEOB: 常规压缩对象
GRID: 组识别注册
IPLS: 复杂类别列表
MCDI: 音乐CD标识符
MLLT: MPEG位置查找表格
OWNE: 所有权
PRIV: 私有
PCNT: 播放计数
POPM: 普通仪表
POSS: 位置同步
RBUF: 推荐缓冲区大小
RVAD: 音量调节器
RVRB: 混响
SYLT: 同步歌词或文本
SYTC: 同步节拍编码
TALB: 专辑,相当于ID3v1的Album
TBPM: 每分钟节拍数 
TCOM: 作曲家 
TCON: 流派(风格),见表2
TCOP: 版权
TDAT: 日期
TDLY: 播放列表返录 
TENC: 编码 
TEXT: 歌词作者 
TFLT: 文件类型 
TIME: 时间
TIT1: 内容组描述 
TIT2: 标题,相当于ID3v1的Title 
TIT3: 副标题
TKEY: 最初关键字 
TLAN: 语言 
TLEN: 长度 
TMED: 媒体类型 
TOAL: 原唱片集 
TOFN: 原文件名 
TOLY: 原歌词作者
TOPE: 原艺术家
TORY: 最初发行年份 
TOWM: 文件所有者(许可证者) 
TPE1: 艺术家相当于ID3v1的Artist 
TPE2: 乐队
TPE3: 指挥者
TPE4: 翻译(记录员、修改员) 
TPOS: 作品集部分 
TPUB: 发行人 
TRCK: 音轨(曲号),相当于ID3v1的Track
TRDA: 录制日期 
TRSN: Intenet电台名称 
TRSO: Intenet电台所有者 
TSIZ: 大小  
TSRC: ISRC(国际的标准记录代码) 
TSSE: 编码使用的软件(硬件设置) 
TYER: 年代,相当于ID3v1的Year
TXXX: 年度
UFID: 唯一的文件标识符
USER: 使用条款
USLT: 歌词 
WCOM: 广告信息
WCOP: 版权信息
WOAF: 官方音频文件网页
WOAR: 官方艺术家网页
WOAS: 官方音频原始资料网页
WORS: 官方互联网无线配置首页
WPAY: 付款
WPUB: 出版商官方网页
WXXX: 用户定义的URL链接 
---------------------------------------
说明:
  ①帧内容是数字的,都用 Ascii 字符表示。
  ②有的 TCON(风格、流派)的帧内容是直接用字符串表示的,如“genre”,而有的则是用编号表示的,如“28 31 32 29”就是用字符串“(12)”表示 12 号风格,我们在解析的时候要注意。
  ③TRCK(音轨)的帧内容格式是:N/M。其中,分母表示专辑中共有 M 首歌曲,分子表示专辑中的第 N 首曲。

表6:标签帧中Flags标志的意义
----------------------------------------------------
位址 意义
----------------------------------------------------
0  标签保护标志,如设置表示此帧作废
1  文件保护标志,如设置表示此帧作废
2  只读标志,如设置表示此帧不能修改
3  压缩标志,如设置表示1个字节存放2个BCD码表示数字
4  加密标志
5  组标志,如设置表示此帧和其它的某帧是一组
----------------------------------------------------

至此总结以下:

  1. 当 url 中有 .MP4 关键字时,匹配到的是 ff_mov_demuxer 解封装器。
  2. ID3v2 的数据流是由 “标签头 + 标签帧1 + … + 标签帧n”,
    在 init_input() 函数处理标签头,ff_id3v2_parse_apic() 处理标签帧,建立数据流。
  3. 通过 avformat_queue_attached_pictures() 把标签帧解析的数据放入 raw_packet_buffer 的队尾。
  4. 函数 update_stream_avctx(s) 刷新流相关参数,接下来给数据流配置 codec_id 赋值指定解码器。
  5. 函数 avformat_open_input() 执行结束、返回到 read_thread() 线程函数中。

下一篇文章 《ijkplayer 代码走读之 h264 解封装器应用详解》,通过实例再次阐述 IJKPLAYER 的解协议、解封装的
实现逻辑。

猜你喜欢

转载自blog.csdn.net/weixin_38387929/article/details/121400025