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

此部分网络读取视频数据代码另开一篇博文分析。

概念定义:
PTS:Presentation Time Stamp。PTS主要用于度量解码后的视频帧什么时候被显示出来
DTS:Decode Time Stamp。DTS主要是标识读入内存中的bit流在什么时候开始送入解码器中进行解码
也就是pts反映帧什么时候开始显示,dts反映数据流什么时候开始解码。

此部分主要是走读 ijkplayer 数据输入流,我们需要先看看相关数据结构,如下:

typedef struct AVIOContext {
    
    
    /**
     * A class for private options.
     *
     * If this AVIOContext is created by avio_open2(), av_class is set and
     * passes the options down to protocols.
     *
     * If this AVIOContext is manually allocated, then av_class may be set by
     * the caller.
     *
     * warning -- this field can be NULL, be sure to not pass this AVIOContext
     * to any av_opt_* functions in that case.
     */
    const AVClass *av_class;                    ///> 此 AVCLass 可以指向抽象各个协议体,

                                                /* 如
                                                static const AVClass av_format_context_class = {
                                                    .class_name     = "AVFormatContext",
                                                    .item_name      = format_to_name,
                                                    .option         = avformat_options,
                                                    .version        = LIBAVUTIL_VERSION_INT,
                                                    .child_next     = format_child_next,
                                                    .child_class_next = format_child_class_next,
                                                    .category       = AV_CLASS_CATEGORY_MUXER,
                                                    .get_category   = get_category,
                                                };
                                                */

    /*
     * The following shows the relationship between buffer, buf_ptr, buf_end, buf_size,
     * and pos, when reading and when writing (since AVIOContext is used for both):
     *
     **********************************************************************************
     *                                   READING
     **********************************************************************************
     *
     *                            |              buffer_size              |
     *                            |---------------------------------------|
     *                            |                                       |
     *
     *                         buffer          buf_ptr       buf_end
     *                            +---------------+-----------------------+
     *                            |/ / / / / / / /|/ / / / / / /|         |
     *  read buffer:              |/ / consumed / | to be read /|         |
     *                            |/ / / / / / / /|/ / / / / / /|         |
     *                            +---------------+-----------------------+
     *
     *                                                         pos
     *              +-------------------------------------------+-----------------+
     *  input file: |                                           |                 |
     *              +-------------------------------------------+-----------------+
     *
     *
     **********************************************************************************
     *                                   WRITING
     **********************************************************************************
     *
     *                                          |          buffer_size          |
     *                                          |-------------------------------|
     *                                          |                               |
     *
     *                                       buffer              buf_ptr     buf_end
     *                                          +-------------------+-----------+
     *                                          |/ / / / / / / / / /|           |
     *  write buffer:                           | / to be flushed / |           |
     *                                          |/ / / / / / / / / /|           |
     *                                          +-------------------+-----------+
     *
     *                                         pos
     *               +--------------------------+-----------------------------------+
     *  output file: |                          |                                   |
     *               +--------------------------+-----------------------------------+
     *
     */
    unsigned char *buffer;  /**< Start of the buffer. */
    int buffer_size;        /**< Maximum buffer size */
    unsigned char *buf_ptr; /**< Current position in the buffer */
    unsigned char *buf_end; /**< End of the data, may be less than
                                 buffer+buffer_size if the read function returned
                                 less data than requested, e.g. for streams where
                                 no more data has been received yet. */
    void *opaque;           /**< A private pointer, passed to the read/write/seek/...
                                 functions. */
    int (*read_packet)(void *opaque, uint8_t *buf, int buf_size);
    int (*write_packet)(void *opaque, uint8_t *buf, int buf_size);
    int64_t (*seek)(void *opaque, int64_t offset, int whence);
    int64_t pos;            /**< position in the file of the current buffer */
    int must_flush;         /**< true if the next seek should flush */
    int eof_reached;        /**< true if eof reached */
    int write_flag;         /**< true if open for writing */
    int max_packet_size;
    unsigned long checksum;
    unsigned char *checksum_ptr;
    unsigned long (*update_checksum)(unsigned long checksum, const uint8_t *buf, unsigned int size);
    int error;              /**< contains the error code or 0 if no error happened */
    /**
     * Pause or resume playback for network streaming protocols - e.g. MMS.
     */
    int (*read_pause)(void *opaque, int pause);
    /**
     * Seek to a given timestamp in stream with the specified stream_index.
     * Needed for some network streaming protocols which don't support seeking
     * to byte position.
     */
    int64_t (*read_seek)(void *opaque, int stream_index,
                         int64_t timestamp, int flags);
    /**
     * A combination of AVIO_SEEKABLE_ flags or 0 when the stream is not seekable.
     */
    int seekable;

    /**
     * max filesize, used to limit allocations
     * This field is internal to libavformat and access from outside is not allowed.
     */
    int64_t maxsize;

    /**
     * avio_read and avio_write should if possible be satisfied directly
     * instead of going through a buffer, and avio_seek will always
     * call the underlying seek function directly.
     */
    int direct;

    /**
     * Bytes read statistic
     * This field is internal to libavformat and access from outside is not allowed.
     */
    int64_t bytes_read;

    /**
     * seek statistic
     * This field is internal to libavformat and access from outside is not allowed.
     */
    int seek_count;

    /**
     * writeout statistic
     * This field is internal to libavformat and access from outside is not allowed.
     */
    int writeout_count;

    /**
     * Original buffer size
     * used internally after probing and ensure seekback to reset the buffer size
     * This field is internal to libavformat and access from outside is not allowed.
     */
    int orig_buffer_size;

    /**
     * Threshold to favor readahead over seek.
     * This is current internal only, do not use from outside.
     */
    int short_seek_threshold;

    /**
     * ',' separated list of allowed protocols.
     */
    const char *protocol_whitelist;

    /**
     * ',' separated list of disallowed protocols.
     */
    const char *protocol_blacklist;

    /**
     * A callback that is used instead of write_packet.
     */
    int (*write_data_type)(void *opaque, uint8_t *buf, int buf_size,
                           enum AVIODataMarkerType type, int64_t time);
    /**
     * If set, don't call write_data_type separately for AVIO_DATA_MARKER_BOUNDARY_POINT,
     * but ignore them and treat them as AVIO_DATA_MARKER_UNKNOWN (to avoid needlessly
     * small chunks of data returned from the callback).
     */
    int ignore_boundary_point;

    /**
     * Internal, not meant to be used from outside of AVIOContext.
     */
    enum AVIODataMarkerType current_type;
    int64_t last_time;

    /**
     * A callback that is used instead of short_seek_threshold.
     * This is current internal only, do not use from outside.
     */
    int (*short_seek_get)(void *opaque);
} AVIOContext;

此 AVIOContext 结构在 AVFormatContext 中定义的变量名称 pb ,下面走读代码中会用。

先回顾一下,在读线程中 read_thread() ,在进入LOOP体前调用 avformat_open_input() 函数,
此函数根据 url filename 查找文件是本地文件还是网络文件,如果是网络文件就需要在支持的协议
中,找到对应协议方法。
用此通讯协议方法获取到音视频输入的第一帧数据,根据数据格式查找对应的音视频解码器,并初始化
解码器的相关参数,使通讯传输协议和解码器具备工作状态。

///> 读取视频数据源格式信息,源码路径extra\ffmpeg\libavformat\utils.c
///> 在读线程中调用此 avformat_open_input(&ic, is->filename, is->iformat, &ffp->format_opts);函数
int avformat_open_input(AVFormatContext **ps, const char *filename,
                        AVInputFormat *fmt, AVDictionary **options)
{
    
    
    AVFormatContext *s = *ps;
    int i, ret = 0;
    AVDictionary *tmp = NULL;
    AVDictionary *tmp2 = 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);                                    ///> 把 option 的格式配置 tmp 中

    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 值设置到 s 中
        goto fail;

    av_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename));///> is->filename 是url地址信息
    if ((ret = init_input(s, filename, &tmp)) < 0)                         ///> 1. 通过 filename 初始化 AVFormatContext 的 input 相关参数
        goto fail;
    s->probe_score = ret;

    if (!s->protocol_whitelist && s->pb && s->pb->protocol_whitelist) {
    
       ///> s->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 黑名单
        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);

    /* 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);
            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_dict(s->pb, &s->internal->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);


    if (!(s->flags&AVFMT_FLAG_PRIV_OPT)) {
    
    
        if (s->iformat->read_header2) {
    
                             
            if (options)
                av_dict_copy(&tmp2, *options, 0);

            if ((ret = s->iformat->read_header2(s, &tmp2)) < 0)   ///> 2. 调用输入格式的 read_header2() 函数,匹配编码方式
                goto fail;
        } else if (s->iformat->read_header 
                      && (ret = s->iformat->read_header(s)) < 0)  ///> 调用输入格式的 read_header() 函数,匹配编码方式
            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;
    }

    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;
            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)                ///> 3. 向 avformat queue 加入图片,数据从哪里来呢,此函数前数据就进入队列。
        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);                                            ///> 更新流 av context 内容

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

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

fail:
    ff_id3v2_free_extra_meta(&id3v2_extra_meta);
    av_dict_free(&tmp);
    av_dict_free(&tmp2);
    if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO))
        avio_closep(&s->pb);
    avformat_free_context(s);
    *ps = NULL;
    return ret;
}
///> 配置数据源的视频格式,此部分获取视频数据、并把视频数据进行分类。!!!

///> 1. 初始化输入流
/* Open input file and probe the format if necessary. */
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->pb 是 AVIOContext 结构体指针
        s->flags |= AVFMT_FLAG_CUSTOM_IO;
        if (!s->iformat)
            return av_probe_input_buffer2(s->pb, &s->iformat, filename,
                                         s, 0, s->format_probesize);                            ///> 2. 给 iformat 构造初始状态参数
        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 = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)   ///> 1.2. 此函数是 io_open() 打开文件或网络socket
        return ret;

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

///> 1.2 此函数是 io_open() 打开文件或网络socket
///> 函数 io_open 是指针, 我们需要找到具体指向的函数;该函数指针如下定义:
struct AVFormatContext {
    
    
    struct AVInputFormat *iformat;
    struct AVOutputFormat *oformat;

    AVIOContext *pb;
    AVStream **streams;

    char filename[1024];

    AVDictionary *metadata;                         //> Metadata that applies to the whole file.

    AVIOInterruptCB interrupt_callback;             //> Custom interrupt callbacks for the I/O layer.

    AVCodec *video_codec;
    AVCodec *audio_codec;
    AVCodec *subtitle_codec;

    char *protocol_whitelist;

    int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url,
                   int flags, AVDictionary **options);              //> A callback for opening new IO streams.

    void (*io_close)(struct AVFormatContext *s, AVIOContext *pb);   //> A callback for closing the streams opened with AVFormatContext.io_open().

};
///> 截取结构体重点内容,我们关注的是 io_open 赋值指向具体实现函数。

//   在 ff_play.c 中的 read_thread() 函数中,初始化调用了函数
static int read_thread(void *arg)
{
    
    
    FFPlayer *ffp = arg;                        ///> arg = ffp 播放器实例
    
    ......

    ic = avformat_alloc_context();                                                    ///> 2.6.1 创建 avformat context 内容,把读文件或socket的io_open指针
    if (!ic) {
    
                                                                            ///  指向 io_open_default()函数,源码路径 libavformat\option.c
        av_log(NULL, AV_LOG_FATAL, "Could not allocate context.\n");
        ret = AVERROR(ENOMEM);
        goto fail;
    }

    .......
}

///> 此函数在各硬件架构相关的 libavformt\option.c文件中,
AVFormatContext *avformat_alloc_context(void)
{
    
    
    AVFormatContext *ic;
    ic = av_malloc(sizeof(AVFormatContext));
    if (!ic) return ic;
    avformat_get_context_defaults(ic);                                              //> 在 avformat_get_context_defaults(ic) 函数中,具体进行赋值操作

    ic->internal = av_mallocz(sizeof(*ic->internal));
    if (!ic->internal) {
    
    
        avformat_free_context(ic);
        return NULL;
    }
    ic->internal->offset = AV_NOPTS_VALUE;
    ic->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;
    ic->internal->shortest_end = AV_NOPTS_VALUE;

    return ic;
}

///>  初始化指针指向的函数
static void avformat_get_context_defaults(AVFormatContext *s)
{
    
    
    memset(s, 0, sizeof(AVFormatContext));

    s->av_class = &av_format_context_class;

    s->io_open  = io_open_default;                                                     //> io_open 指针指向 io_open_default() 函数
    s->io_close = io_close_default;
    av_opt_set_defaults(s);
}

///> 接下来我们看看io_open_default()函数具体实现.
///> 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)
{
    
    
    int loglevel;

    if (!strcmp(url, s->filename) ||
        s->iformat && !strcmp(s->iformat->name, "image2") ||
        s->oformat && !strcmp(s->oformat->name, "image2")
    ) {
    
    
        loglevel = AV_LOG_DEBUG;
    } else
        loglevel = AV_LOG_INFO;

    av_log(s, loglevel, "Opening \'%s\' for %s\n", url, flags & AVIO_FLAG_WRITE ? "writing" : "reading");
                ///> 调用此函数
    return ffio_open_whitelist(pb, url, flags, &s->interrupt_callback, options, s->protocol_whitelist, s->protocol_blacklist);
}


///> ffio_open_whitelist函数,在源文件路径 libavformt\aviobuf.c , 函数中使用 URLContext 类型,结构如下。
typedef struct URLContext {
    
    
    const AVClass *av_class;    /**< information for av_log(). Set by url_open(). */
    const struct URLProtocol *prot;
    void *priv_data;
    char *filename;             /**< specified URL */
    int flags;
    int max_packet_size;        /**< if non zero, the stream is packetized with this max packet size */
    int is_streamed;            /**< true if streamed (no seek possible), default = false */
    int is_connected;
    AVIOInterruptCB interrupt_callback;
    int64_t rw_timeout;         /**< maximum time to wait for (network) read/write operation completion, in mcs */
    const char *protocol_whitelist;
    const char *protocol_blacklist;
    int min_packet_size;        /**< if non zero, the stream is packetized with this min packet size */
} URLContext;

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;

    err = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL);  ///> 调用此函数匹配 filename 中协议类别
    if (err < 0)
        return err;
    err = ffio_fdopen(s, h);                                                                       ///> 调用 ffio_fdopen 函数
    if (err < 0) {
    
    
        ffurl_close(h);
        return err;
    }
    return 0;
}

///> ffurl_open_whitelist函数,源文件路径 libavformt\avio.c
int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags,
                         const AVIOInterruptCB *int_cb, AVDictionary **options,
                         const char *whitelist, const char* blacklist,
                         URLContext *parent)
{
    
    
    AVDictionary *tmp_opts = NULL;
    AVDictionaryEntry *e;
    int ret = ffurl_alloc(puc, filename, flags, int_cb);            ///> 为 puc 申请内存空间并创建对象,并匹配协议类别
    if (ret < 0)
        return ret;
    if (parent)
        av_opt_copy(*puc, parent);
    if (options &&
        (ret = av_opt_set_dict(*puc, options)) < 0)
        goto fail;
    if (options && (*puc)->prot->priv_data_class &&
        (ret = av_opt_set_dict((*puc)->priv_data, options)) < 0)
        goto fail;

    if (!options)
        options = &tmp_opts;

    av_assert0(!whitelist ||
               !(e=av_dict_get(*options, "protocol_whitelist", NULL, 0)) ||
               !strcmp(whitelist, e->value));
    av_assert0(!blacklist ||
               !(e=av_dict_get(*options, "protocol_blacklist", NULL, 0)) ||
               !strcmp(blacklist, e->value));

    if ((ret = av_dict_set(options, "protocol_whitelist", whitelist, 0)) < 0)
        goto fail;

    if ((ret = av_dict_set(options, "protocol_blacklist", blacklist, 0)) < 0)
        goto fail;

    if ((ret = av_opt_set_dict(*puc, options)) < 0)
        goto fail;

    ret = ffurl_connect(*puc, options);                         ///> 调用当前文件中的 ffurl_connect 函数

    if (!ret)
        return 0;
fail:
    ffurl_close(*puc);
    *puc = NULL;
    return ret;
}

///> ffurl_connect 函数的实现

int ffurl_connect(URLContext *uc, AVDictionary **options)
{
    
    
    int err;
    AVDictionary *tmp_opts = NULL;
    AVDictionaryEntry *e;

    if (!options)
        options = &tmp_opts;

    // Check that URLContext was initialized correctly and lists are matching if set
    av_assert0(!(e=av_dict_get(*options, "protocol_whitelist", NULL, 0)) ||
               (uc->protocol_whitelist && !strcmp(uc->protocol_whitelist, e->value)));
    av_assert0(!(e=av_dict_get(*options, "protocol_blacklist", NULL, 0)) ||
               (uc->protocol_blacklist && !strcmp(uc->protocol_blacklist, e->value)));

    if (uc->protocol_whitelist && av_match_list(uc->prot->name, uc->protocol_whitelist, ',') <= 0) {
    
    
        av_log(uc, AV_LOG_ERROR, "Protocol '%s' not on whitelist '%s'!\n", uc->prot->name, uc->protocol_whitelist);
        return AVERROR(EINVAL);
    }

    if (uc->protocol_blacklist && av_match_list(uc->prot->name, uc->protocol_blacklist, ',') > 0) {
    
    
        av_log(uc, AV_LOG_ERROR, "Protocol '%s' on blacklist '%s'!\n", uc->prot->name, uc->protocol_blacklist);
        return AVERROR(EINVAL);
    }

    if (!uc->protocol_whitelist && uc->prot->default_whitelist) {
    
    
        av_log(uc, AV_LOG_DEBUG, "Setting default whitelist '%s'\n", uc->prot->default_whitelist);
        uc->protocol_whitelist = av_strdup(uc->prot->default_whitelist);
        if (!uc->protocol_whitelist) {
    
    
            return AVERROR(ENOMEM);
        }
    } else if (!uc->protocol_whitelist)
        av_log(uc, AV_LOG_DEBUG, "No default whitelist set\n"); // This should be an error once all declare a default whitelist

    if ((err = av_dict_set(options, "protocol_whitelist", uc->protocol_whitelist, 0)) < 0)
        return err;
    if ((err = av_dict_set(options, "protocol_blacklist", uc->protocol_blacklist, 0)) < 0)
        return err;

    err =
        uc->prot->url_open2 ? uc->prot->url_open2(uc,                               ///> 在建立网络链接时,调用 url_open2 
                                                  uc->filename,
                                                  uc->flags,
                                                  options) :
        uc->prot->url_open(uc, uc->filename, uc->flags);                            ///> 如果open2失败则调用 url_open 函数

    av_dict_set(options, "protocol_whitelist", NULL, 0);
    av_dict_set(options, "protocol_blacklist", NULL, 0);

    if (err)
        return err;
    uc->is_connected = 1;
    /* We must be careful here as ffurl_seek() could be slow,
     * for example for http */
    if ((uc->flags & AVIO_FLAG_WRITE) || !strcmp(uc->prot->name, "file"))
        if (!uc->is_streamed && ffurl_seek(uc, 0, SEEK_SET) < 0)
            uc->is_streamed = 1;
    return 0;
}
///> 支持我们马上就能够看到 url_open 函数是如果操作的了。
///> 还有点小周折、url_open是函数指针,具体指向的函数需要查找初始化的过程。

///> 查找过程如下. 
///> 在打开 white_list 函数中, 为 puc 申请内存空间并创建对象,并匹配协议类别
int ffurl_alloc(URLContext **puc, const char *filename, int flags,
                const AVIOInterruptCB *int_cb)
{
    
    
    const URLProtocol *p = NULL;

    p = url_find_protocol(filename);                                          ///> 调用此函数 根据filename 查找对应 URLProtocol 支持实例  
    if (p)
       return url_alloc_for_protocol(puc, p, filename, flags, int_cb);        ///> 调用此函数 创建 URLContext 类型实例

    *puc = NULL;
    if (av_strstart(filename, "https:", NULL))
        av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with "
                                     "openssl, gnutls "
                                     "or securetransport enabled.\n");
    return AVERROR_PROTOCOL_NOT_FOUND;
}

///> 根据filename 查找到对应 URLProtocol 实例 
static const struct URLProtocol *url_find_protocol(const char *filename)
{
    
    
    const URLProtocol **protocols;
    char proto_str[128], proto_nested[128], *ptr;
    size_t proto_len = strspn(filename, URL_SCHEME_CHARS);
    int i;

    if (filename[proto_len] != ':' &&
        (strncmp(filename, "subfile,", 8) || !strchr(filename + proto_len + 1, ':')) ||
        is_dos_path(filename))
        strcpy(proto_str, "file");
    else
        av_strlcpy(proto_str, filename,
                   FFMIN(proto_len + 1, sizeof(proto_str)));

    av_strlcpy(proto_nested, proto_str, sizeof(proto_nested));
    if ((ptr = strchr(proto_nested, '+')))
        *ptr = '\0';

    protocols = ffurl_get_protocols(NULL, NULL);        ///> 获取 protocols[] 数组指针
    if (!protocols)
        return NULL;
    for (i = 0; protocols[i]; i++) {
    
                        ///> protocols[i] = 0 时,遍历所有内容.
            const URLProtocol *up = protocols[i];
        if (!strcmp(proto_str, up->name)) {
    
                 ///> 此部分匹配协议是否支持
            av_freep(&protocols);
            return up;
        }
        if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME &&
            !strcmp(proto_nested, up->name)) {
    
    
            av_freep(&protocols);
            return up;
        }
    }
    av_freep(&protocols);

    return NULL;
}
///> 在函数中就给 url_open 函数赋值了,可以我们怎么没有看到赋值过程呢? 我们很快就能接口 ijkplayer 的通讯协议抽象规则了。
///> 我们先看一下 URLProtocol 结构体的定义。
typedef struct URLProtocol {
    
    
    const char *name;
    int     (*url_open)( URLContext *h, const char *url, int flags);        ///> 我们上面的函数中调用的函数 url_open
    /**
     * This callback is to be used by protocols which open further nested
     * protocols. options are then to be passed to ffurl_open()/ffurl_connect()
     * for those nested protocols.
     */                                                                     ///> 我们上面的函数中调用的函数 url_open2
    int     (*url_open2)(URLContext *h, const char *url, int flags, AVDictionary **options);
    int     (*url_accept)(URLContext *s, URLContext **c);
    int     (*url_handshake)(URLContext *c);
    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);
    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 (*url_get_multi_file_handle)(URLContext *h, int **handles,
                                     int *numhandles);
    int (*url_get_short_seek)(URLContext *h);
    int (*url_shutdown)(URLContext *h, int flags);
    int priv_data_size;
    const AVClass *priv_data_class;
    int flags;
    int (*url_check)(URLContext *h, int mask);
    int (*url_open_dir)(URLContext *h);
    int (*url_read_dir)(URLContext *h, AVIODirEntry **next);
    int (*url_close_dir)(URLContext *h);
    int (*url_delete)(URLContext *h);
    int (*url_move)(URLContext *h_src, URLContext *h_dst);
    const char *default_whitelist;
} URLProtocol;

我们在回顾一下流程:

  1. 在读线程中 read_thread() ,在进入LOOP体前调用 avformat_open_input() 函数
  2. 在avformat_open_input函数中,调用 init_input(s, filename, &tmp)) 初始化 “input” 函数
  3. 调用 io_open() 函数打开文件或网络socket 的输入方式,该指针指向的是 io_open_default() 函数
  4. io_open_default 函数调用了 ffio_open_whitelist() 函数来查找支持的协议
  5. 打开白名单函数调用 ffurl_connect() -> url_open 函数,该函数又是一个指针
  6. url_open指针赋值是在 url_find_protocol() 函数中赋值的,可是我们并没有看到具体赋值语句。

现在有些尴尬了,跑了这么大圈还是没有看到打开文件或打开网络链接的具体过程,
如果我们需要重构 ijkplayer 添加私有协议该如果入手呢?我们目前就处于这么个状态呢,
要向 ijkplayer 中扩充协议内容、增加控制项。

本篇文章我要把 ijkplayer 的通讯 profile 设计逻辑给梳理出来,首先回顾上一篇内容
在函数 url_find_protocol 中,

static const struct URLProtocol *url_find_protocol(const char *filename)
{
    
    
    const URLProtocol **protocols;
    
    protocols = ffurl_get_protocols(NULL, NULL);        ///> 获取 protocols[] 数组指针
    

    return NULL;
}

函数 ffurl_get_protocols() 返回值就是一个 ijkplayer 支持的通讯协议项,
此函数的源文件路径ffmpeg-x86\libavformat\protocols.c 文件中。

///> 文件开头就声明很多的外部 const URLProtocol 对象,URLProtocol结构体定义前面已经列出。
extern const URLProtocol ff_rtp_protocol;
extern const URLProtocol ff_sctp_protocol;
extern const URLProtocol ff_srtp_protocol;
extern const URLProtocol ff_subfile_protocol;
extern const URLProtocol ff_tee_protocol;
extern const URLProtocol ff_tcp_protocol;
extern const URLProtocol ff_tls_gnutls_protocol;
extern const URLProtocol ff_tls_schannel_protocol;
extern const URLProtocol ff_tls_securetransport_protocol;
extern const URLProtocol ff_tls_openssl_protocol;
extern const URLProtocol ff_udp_protocol;
extern const URLProtocol ff_udplite_protocol;
extern const URLProtocol ff_unix_protocol;
extern const URLProtocol ff_librtmp_protocol;
///> 也就是说此定义都占有内存空间、都是实例内容。

///> 我们在看ffurl_get_protocols函数,
const URLProtocol **ffurl_get_protocols(const char *whitelist,
                                        const char *blacklist)
{
    
    
    const URLProtocol **ret;
    int i, ret_idx = 0;

    ret = av_mallocz_array(FF_ARRAY_ELEMS(url_protocols), sizeof(*ret));    ///> url_protocols[i] 数组是全局变量,还未查找定义。此问题未找到思路。
    if (!ret)
        return NULL;

    for (i = 0; url_protocols[i]; i++) {
    
                                        ///> 遍历 url_protocols[] 支持协议数组,在数组匹配支持的协议
        const URLProtocol *up = url_protocols[i];

        if (whitelist && *whitelist && !av_match_name(up->name, whitelist))
            continue;
        if (blacklist && *blacklist && av_match_name(up->name, blacklist))
            continue;

        ret[ret_idx++] = up;
    }

    return ret;
}

///> 此函数是把名单之外的协议项指针,都放入 ret 数组中,所以上一篇文章中看到的函数会 protocols 遍历这个数组。
///> 这个逻辑是对应起来了,但是现在的问题是 url_protocols[] 数组内容是哪里来的呢?? 

全局搜索整个项目,在硬件架构相关文件夹下,路径 ijkplayer/android/contrib/ffmpeg-x86/configure 脚本文件,此文件中
有如下内容:

# generate the lists of enabled components
print_enabled_components(){
    
    
    file=$1
    struct_name=$2
    name=$3
    shift 3
    echo "static const $struct_name * const $name[] = {" > $TMPH
    for c in $*; do
        enabled $c && printf "    &ff_%s,\n" $c >> $TMPH
    done
    echo "    NULL };" >> $TMPH
    cp_if_changed $TMPH $file
}

print_enabled_components libavcodec/bsf_list.c AVBitStreamFilter bitstream_filters $BSF_LIST
print_enabled_components libavformat/protocol_list.c URLProtocol url_protocols $PROTOCOL_LIST

也就是说在编译源码的时候,会执行此脚本生成 libavformat/protocol_list.c 文件,此文件内容如下:

robot@ubuntu:~/ljbPlayer/ijkplayer/android/contrib/ffmpeg-x86$ cat libavformat/protocol_list.c 
static const URLProtocol * const url_protocols[] = {
    
    
    &ff_async_protocol,
    &ff_cache_protocol,
    &ff_data_protocol,
    &ff_ffrtmphttp_protocol,
    &ff_file_protocol,
    &ff_ftp_protocol,
    &ff_hls_protocol,
    &ff_http_protocol,
    &ff_httpproxy_protocol,
    &ff_ijkhttphook_protocol,
    &ff_ijklongurl_protocol,
    &ff_ijkmediadatasource_protocol,
    &ff_ijksegment_protocol,
    &ff_ijktcphook_protocol,
    &ff_ijkio_protocol,
    &ff_pipe_protocol,
    &ff_prompeg_protocol,
    &ff_rtmp_protocol,
    &ff_rtmpt_protocol,
    &ff_tee_protocol,
    &ff_tcp_protocol,
    &ff_udp_protocol,
    &ff_udplite_protocol,
    NULL };
robot@ubuntu:~/ljbPlayer/ijkplayer/android/contrib/ffmpeg-x86$ 

至此,我们通过三篇代码走读记录,把 ijkplayer 的数据通讯 profile 设计逻辑给梳理清楚了,
此处的清楚是之通过 so 库方式, 项目中还有其他入口方式如 cmd 方式等。

我们在总结一下:

  1. IJKFF_PLAYER 库会生成硬件架构相关的so库,通过项目文件结果也可以看出此规律。
  2. 扩充通讯协议时、只需要在相应的硬件架构中增加自己的协议文件,匹配协议就能够成功。
  3. 视频格式 profile 支持方法与通讯的 profile 设计思路大致推断逻辑应该相似。

猜你喜欢

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