ffmpeg实战系列——003

Talk is cheap,Show me the code!

示例2、demuxing_decoding.c

以下例子并不完整,只列出核心数据结构和代码

static AVFormatContext *fmt_ctx = NULL;

static AVCodecContext *video_dec_ctx = NULL, *audio_dec_ctx;

static AVStream *video_stream = NULL, *audio_stream = NULL;

static int video_stream_idx = -1, audio_stream_idx = -1;

static AVFrame *frame = NULL;

static AVPacket pkt;

int main (int argc, char **argv)

{

    av_register_all();

    /* open input file, and allocate format context */

    if (avformat_open_input(&fmt_ctx, src_filename, NULL, NULL) < 0) {

    }

    /* retrieve stream information */

    if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {

    }

    if (open_codec_context(&video_stream_idx, &video_dec_ctx, fmt_ctx, AVMEDIA_TYPE_VIDEO) >= 0) {

        video_stream = fmt_ctx->streams[video_stream_idx];

        }

    }

    if (open_codec_context(&audio_stream_idx, &audio_dec_ctx, fmt_ctx, AVMEDIA_TYPE_AUDIO) >= 0) {

        audio_stream = fmt_ctx->streams[audio_stream_idx];

    }

    frame = av_frame_alloc();

    av_init_packet(&pkt);

    while (av_read_frame(fmt_ctx, &pkt) >= 0) {

        AVPacket orig_pkt = pkt;

        do {

            ret = decode_packet(&got_frame, 0);

        } while (pkt.size > 0);

        av_packet_unref(&orig_pkt);

    }

end:

    avcodec_free_context(&video_dec_ctx);

    avcodec_free_context(&audio_dec_ctx);

    avformat_close_input(&fmt_ctx);

    if (video_dst_file)

        fclose(video_dst_file);

    if (audio_dst_file)

        fclose(audio_dst_file);

    av_frame_free(&frame);

    av_free(video_dst_data[0]);

    return ret < 0;

}

static int open_codec_context(int *stream_idx,

                              AVCodecContext **dec_ctx, AVFormatContext *fmt_ctx, enum AVMediaType type)

{

    int ret, stream_index;

    AVStream *st;

    AVCodec *dec = NULL;

    AVDictionary *opts = NULL;

    ret = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0); //返回值是一个index,通过index可以拿到对应的avstream

    if (ret < 0) {

    } else {

        stream_index = ret;

        st = fmt_ctx->streams[stream_index];

        /* find decoder for the stream */

        dec = avcodec_find_decoder(st->codecpar->codec_id);

        /* Allocate a codec context for the decoder */

        *dec_ctx = avcodec_alloc_context3(dec);

        /* Copy codec parameters from input stream to output codec context */

        if ((ret = avcodec_parameters_to_context(*dec_ctx, st->codecpar)) < 0) {

            fprintf(stderr, "Failed to copy %s codec parameters to decoder context ",

                    av_get_media_type_string(type));

            return ret;

        }

        /* Init the decoders, with or without reference counting */

        av_dict_set(&opts, "refcounted_frames", refcount ? "1" : "0", 0);

        if ((ret = avcodec_open2(*dec_ctx, dec, &opts)) < 0) {

            fprintf(stderr, "Failed to open %s codec ",

                    av_get_media_type_string(type));

            return ret;

        }

        *stream_idx = stream_index;

    }

    return 0;

}

下面详细分析下这个过程:

1、avformat_open_input(&fmt_ctx, src_filename, NULL)

avformat_open_input(&fmt_ctx, src_filename, NULL, NULL)

int avformat_open_input(AVFormatContext **ps, const char *filename,

                        AVInputFormat *fmt, AVDictionary **options)

{

    AVFormatContext *s = *ps;

    int i, ret = 0;

    AVDictionary *tmp = NULL;

    ID3v2ExtraMeta *id3v2_extra_meta = NULL;

    if (!s && !(s = avformat_alloc_context()))

        return AVERROR(ENOMEM);

    if ((ret = init_input(s, filename, &tmp)) < 0)

        goto fail;

    s->probe_score = ret;

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

    /* 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;

        }

    }

    if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)

        if ((ret = s->iformat->read_header(s)) < 0)

            goto fail;

    s->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;

    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;

}

在分析之前先了解几个重要的数据结构:

——AVFormatContext

typedef struct AVFormatContext {

    const AVClass *av_class;

    struct AVInputFormat *iformat;

    struct AVOutputFormat *oformat;

    /**

     * - muxing: set by avformat_write_header()

     * - demuxing: set by avformat_open_input()

     */

    void *priv_data;             //最重要的成员

    /**

     * - demuxing: either set by the user before avformat_open_input() (then

     *             the user must close it manually) or set by avformat_open_input().

     * - muxing: set by the user before avformat_write_header(). T

     */

    AVIOContext *pb;            //最重要的成员

    unsigned int nb_streams;

    /**

     * A list of all streams in the file. New streams are created with

     * avformat_new_stream().

     *

     * - demuxing: streams are created by libavformat in avformat_open_input().

     *             If AVFMTCTX_NOHEADER is set in ctx_flags, then new streams may also

     *             appear in av_read_frame().

     * - muxing: streams are created by the user before avformat_write_header().

     *

     * Freed by libavformat in avformat_free_context().

     */

    AVStream **streams;

    /**

     * input or output filename

     *

     * - demuxing: set by avformat_open_input()

     * - muxing: may be set by the caller before avformat_write_header()

     */

    char filename[1024];

    int64_t duration;

    int64_t bit_rate;

    unsigned int packet_size;

    int max_delay;

    int64_t probesize;

    int64_t max_analyze_duration;

    const uint8_t *key;

    int keylen;

    unsigned int nb_programs;

    AVProgram **programs;

    enum AVCodecID video_codec_id;

    enum AVCodecID audio_codec_id;

    enum AVCodecID subtitle_codec_id;

    unsigned int max_index_size;

    unsigned int max_picture_buffer;

    unsigned int nb_chapters;

    AVChapter **chapters;

    AVDictionary *metadata;

    int64_t start_time_realtime;

    int fps_probe_size;

    int error_recognition;

    AVIOInterruptCB interrupt_callback;

    int64_t max_interleave_delta;

    int strict_std_compliance;

    int event_flags;

    int max_ts_probe;

    int avoid_negative_ts;

    int audio_preload;

    int max_chunk_duration;

    int max_chunk_size;

    int use_wallclock_as_timestamps;

    int avio_flags;

    enum AVDurationEstimationMethod duration_estimation_method;

    int64_t skip_initial_bytes;

    unsigned int correct_ts_overflow;

    int seek2any;

    int flush_packets;

    int probe_score;

    int format_probesize;

    char *codec_whitelist;

    char *format_whitelist;

    AVFormatInternal *internal;

    int io_repositioned;

    AVCodec *video_codec;

    AVCodec *audio_codec;

    AVCodec *subtitle_codec;

    AVCodec *data_codec;

    int metadata_header_padding;

    void *opaque;

    av_format_control_message control_message_cb;

    int64_t output_ts_offset;

    uint8_t *dump_separator;

    /**

     * Forced Data codec_id.

     * Demuxing: Set by user.

     */

    enum AVCodecID data_codec_id;

    char *protocol_whitelist;

    int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url,

                   int flags, AVDictionary **options);

    void (*io_close)(struct AVFormatContext *s, AVIOContext *pb);

    char *protocol_blacklist;

    int max_streams;

} AVFormatContext;

我们主要关注下面几个成员:

struct AVInputFormat *iformat;

void *priv_data;

AVIOContext *pb;

AVStream **streams;

——if ((ret = init_input(s, filename, &tmp)) < 0)

if ((ret = init_input(s, filename, &tmp)) < 0)

/* 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->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. ");

        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)

        return ret;

    if (s->iformat)

        return 0;

    return av_probe_input_buffer2(s->pb, &s->iformat, filename,

                                 s, 0, s->format_probesize);

}

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];

    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;

    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;

    }

    fmt = NULL;

    while ((fmt1 = av_iformat_next(fmt1))) {

        if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))

            continue;

        score = 0;

        if (fmt1->read_probe) {

            score = fmt1->read_probe(&lpd);

            if (score)

                av_log(NULL, AV_LOG_TRACE, "Probing %s score:%d size:%d ", 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) {

            if (av_match_ext(lpd.filename, fmt1->extensions))

                score = AVPROBE_SCORE_EXTENSION;

        }

        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 ", 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;

}

——AVInputFormat

typedef struct AVInputFormat {

    const char *name;

    const char *long_name;

    int flags;

    const char *extensions;

    const struct AVCodecTag * const *codec_tag;

    const AVClass *priv_class; ///< AVClass for the private context

    const char *mime_type;

    struct AVInputFormat *next;

    int raw_codec_id;

    int priv_data_size;

    int (*read_probe)(AVProbeData *);

    int (*read_header)(struct AVFormatContext *);

    int (*read_packet)(struct AVFormatContext *, AVPacket *pkt);

    int (*read_close)(struct AVFormatContext *);

    int (*read_seek)(struct AVFormatContext *,

                     int stream_index, int64_t timestamp, int flags);

    int64_t (*read_timestamp)(struct AVFormatContext *s, int stream_index,

                              int64_t *pos, int64_t pos_limit);

    int (*read_play)(struct AVFormatContext *);

    int (*read_pause)(struct AVFormatContext *);

    int (*read_seek2)(struct AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags);

    int (*get_device_list)(struct AVFormatContext *s, struct AVDeviceInfoList *device_list);

    int (*create_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);

    int (*free_device_capabilities)(struct AVFormatContext *s, struct AVDeviceCapabilitiesQuery *caps);

} AVInputFormat;

下面来看一种具体的AVInputFormat:

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,

};

重点关注:MOVContext这个结构体,在mov_read_header时被初始化。只要你要用ff_mov_demuxer干活,必须要有MOVContext实例,所有的mov上下文信息都来自于MOVContext

而实际上:AVFormatContext的priv_data成员就是一个MOVContext

    /* 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;

        }

    }

——如果没有识别到具体的AVInputFormat,那么就需要打开这个文件读取一些数据进行进一步分析:一般都是走这个flow:

ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options))

先来看看这个接口是在哪里赋值的:

AVFormatContext *avformat_alloc_context(void)

{

    AVFormatContext *ic;

    ic = av_malloc(sizeof(AVFormatContext));

    if (!ic) return 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;

    s->io_close = io_close_default;

    av_opt_set_defaults(s);

}

static int io_open_default(AVFormatContext *s, AVIOContext **pb,

                           const char *url, int flags, AVDictionary **options)

{

#if FF_API_OLD_OPEN_CALLBACKS

FF_DISABLE_DEPRECATION_WARNINGS

    if (s->open_cb)

        return s->open_cb(s, pb, url, flags, &s->interrupt_callback, options);

FF_ENABLE_DEPRECATION_WARNINGS

#endif

    return 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;

    err = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL);

    if (err < 0)

        return err;

    err = ffio_fdopen(s, h);

    if (err < 0) {

        ffurl_close(h);

        return err;

    }

    return 0;

}

这个两个也非常重要。开始吧:

第一个API:先分配并初始化一个URLContext,这个非常重要

    URLContext *h;

    int err;

err = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL);

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)

{

    int ret = ffurl_alloc(puc, filename, flags, int_cb);

    ret = ffurl_connect(*puc, options);

}

//这里就是先通过文件名找到URLProtocol,然后再用URLProtocol去分配和初始化URLContext

int ffurl_alloc(URLContext **puc, const char *filename, int flags,

                const AVIOInterruptCB *int_cb)

{

    const URLProtocol *p = NULL;

    p = url_find_protocol(filename);

    if (p)

       return url_alloc_for_protocol(puc, p, filename, flags, int_cb);

    return AVERROR_PROTOCOL_NOT_FOUND;

}

查找的过程没什么花样,全局链表搜索:

static const struct URLProtocol *url_find_protocol(const char *filename)

{

    const URLProtocol **protocols;

    char proto_str[128], proto_nested[128], *ptr;

    protocols = ffurl_get_protocols(NULL, NULL); //获取到所有的白名单里面的协议//URLProtocol

    if (!protocols)

        return NULL;

    for (i = 0; protocols[i]; i++) {

            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;

}

我们这里举一个实际的例子:

const URLProtocol ff_file_protocol = {

    .name                = "file",

    .url_open            = file_open,

    .url_read            = file_read,

    .url_write           = file_write,

    .url_seek            = file_seek,

    .url_close           = file_close,

    .url_get_file_handle = file_get_handle,

    .url_check           = file_check,

    .url_delete          = file_delete,

    .url_move            = file_move,

    .priv_data_size      = sizeof(FileContext),  //密切关注这个结构体

    .priv_data_class     = &file_class,

    .url_open_dir        = file_open_dir,

    .url_read_dir        = file_read_dir,

    .url_close_dir       = file_close_dir,

    .default_whitelist   = "file,crypto"

};

如果你需要调用 ff_file_protocol干活,那么FileContext是必须要提供的,所有的信息都来源于FileContext

下面通过找到的协议分配URLContext:主要是做些初始化,这里最重点强调:URLContext有一个成员是FileContext通过这个成员uc->priv_data,才得以调用ff_file_protocol

url_alloc_for_protocol(puc, p, filename, flags, int_cb);

static int url_alloc_for_protocol(URLContext **puc, const URLProtocol *up,

                                  const char *filename, int flags,

                                  const AVIOInterruptCB *int_cb)

{

    URLContext *uc;

    int err;

#if CONFIG_NETWORK

    if (up->flags & URL_PROTOCOL_FLAG_NETWORK && !ff_network_init())

        return AVERROR(EIO);

#endif

    if ((flags & AVIO_FLAG_READ) && !up->url_read) {

        av_log(NULL, AV_LOG_ERROR,

               "Impossible to open the '%s' protocol for reading ", up->name);

        return AVERROR(EIO);

    }

    if ((flags & AVIO_FLAG_WRITE) && !up->url_write) {

        av_log(NULL, AV_LOG_ERROR,

               "Impossible to open the '%s' protocol for writing ", up->name);

        return AVERROR(EIO);

    }

    uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1);

    if (!uc) {

        err = AVERROR(ENOMEM);

        goto fail;

    }

    uc->av_class = &ffurl_context_class;

    uc->filename = (char *)&uc[1];

    strcpy(uc->filename, filename);

    uc->prot            = up;        //这里将URLProtocol赋值给URLContext的prot           

    uc->flags           = flags;

    uc->is_streamed     = 0; /* default = not streamed */

    uc->max_packet_size = 0; /* default: stream file */

    if (up->priv_data_size) {

        uc->priv_data = av_mallocz(up->priv_data_size); //最重要的数据结构

        if (!uc->priv_data) {

            err = AVERROR(ENOMEM);

            goto fail;

        }

        if (up->priv_data_class) {

            int proto_len= strlen(up->name);

            char *start = strchr(uc->filename, ',');

            *(const AVClass **)uc->priv_data = up->priv_data_class;

            av_opt_set_defaults(uc->priv_data);

            if(!strncmp(up->name, uc->filename, proto_len) && uc->filename + proto_len == start){

                int ret= 0;

                char *p= start;

                char sep= *++p;

                char *key, *val;

                p++;

                if (strcmp(up->name, "subfile"))

                    ret = AVERROR(EINVAL);

                while(ret >= 0 && (key= strchr(p, sep)) && p<key && (val = strchr(key+1, sep))){

                    *val= *key= 0;

                    if (strcmp(p, "start") && strcmp(p, "end")) {

                        ret = AVERROR_OPTION_NOT_FOUND;

                    } else

                        ret= av_opt_set(uc->priv_data, p, key+1, 0);

                    if (ret == AVERROR_OPTION_NOT_FOUND)

                        av_log(uc, AV_LOG_ERROR, "Key '%s' not found. ", p);

                    *val= *key= sep;

                    p= val+1;

                }

                if(ret<0 || p!=key){

                    av_log(uc, AV_LOG_ERROR, "Error parsing options string %s ", start);

                    av_freep(&uc->priv_data);

                    av_freep(&uc);

                    err = AVERROR(EINVAL);

                    goto fail;

                }

                memmove(start, key+1, strlen(key));

            }

        }

    }

    if (int_cb)

        uc->interrupt_callback = *int_cb;

    *puc = uc;

    return 0;

fail:

    *puc = NULL;

    if (uc)

        av_freep(&uc->priv_data);

    av_freep(&uc);

#if CONFIG_NETWORK

    if (up->flags & URL_PROTOCOL_FLAG_NETWORK)

        ff_network_close();

#endif

    return err;

}

第二个API分析:err = ffio_fdopen(s, h);

其中:两个参数类型分别是:

AVIOContext **s

URLContext *h;

这里主要是将AVIOContext和URLContext进行绑定,同时初始化AVIOContext

int ffio_fdopen(AVIOContext **s, URLContext *h)

{

AVIOInternal *internal = NULL;   //其实就是一个URLContext,只是内部使用

//这里插一句:

typedef struct AVIOInternal {

    URLContext *h;

} AVIOInternal;

    uint8_t *buffer = NULL;

    int buffer_size, max_packet_size;

    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);

    if (!buffer)

        return AVERROR(ENOMEM);

    internal = av_mallocz(sizeof(*internal));

    if (!internal)

        goto fail;

    internal->h = h;     //将URLContext赋值给AVIOInternal

//分配AVIOContext,传入三个函数指针:

    *s = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE,

                            internal, io_read_packet, io_write_packet, io_seek);

    if (!*s)

        goto fail;

    (*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;

    (*s)->seekable = h->is_streamed ? 0 : AVIO_SEEKABLE_NORMAL;

    (*s)->max_packet_size = max_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_freep(&internal);

    av_freep(&buffer);

    return AVERROR(ENOMEM);

}

这里先别急,来看看这几个函数指针到底是什么鬼:

io_read_packet

io_write_packet,

io_seek,

io_read_pause,

io_read_seek,

io_short_seek

揭开庐山真面目:

static int io_read_packet(void *opaque, uint8_t *buf, int buf_size)

{

    AVIOInternal *internal = opaque;

    return ffurl_read(internal->h, buf, buf_size);

}

int ffurl_read(URLContext *h, unsigned char *buf, int size)

{

    if (!(h->flags & AVIO_FLAG_READ))

        return AVERROR(EIO);

    return retry_transfer_wrapper(h, buf, size, 1, h->prot->url_read); //好像看出什么来了,莫不是URLProtocol的url_read()函数?

}

static inline int retry_transfer_wrapper(URLContext *h, uint8_t *buf,

                                         int size, int size_min,

                                         int (*transfer_func)(URLContext *h,

                                                              uint8_t *buf,

                                                              int size))

{

    int ret, len;

    int fast_retries = 5;

    int64_t wait_since = 0;

    len = 0;

    while (len < size_min) {

        if (ff_check_interrupt(&h->interrupt_callback))

            return AVERROR_EXIT;

        ret = transfer_func(h, buf + len, size - len);  //卧槽,居然真的是URLProtocol的函数

        if (ret == AVERROR(EINTR))

            continue;

        if (h->flags & AVIO_FLAG_NONBLOCK)

            return ret;

    return len;

}

假如是文件类型:

const URLProtocol ff_file_protocol = {

    .name                = "file",

    .url_open            = file_open,

    .url_read            = file_read,

    .url_write           = file_write,

    .url_seek            = file_seek,

    .url_close           = file_close,

    .url_get_file_handle = file_get_handle,

    .url_check           = file_check,

    .url_delete          = file_delete,

    .url_move            = file_move,

    .priv_data_size      = sizeof(FileContext),

    .priv_data_class     = &file_class,

    .url_open_dir        = file_open_dir,

    .url_read_dir        = file_read_dir,

    .url_close_dir       = file_close_dir,

    .default_whitelist   = "file,crypto"

};

那就是最终调到file_read()函数。

下面继续分析:密切关注这三个函数指针的去向哈,还有URLContext的去向

//分配AVIOContext,传入三个函数指针:

    *s = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE,

                            internal, io_read_packet, io_write_packet, io_seek);

AVIOContext *avio_alloc_context(

                  unsigned char *buffer,

                  int buffer_size,

                  int write_flag,

                  void *opaque,

                  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))

{

    AVIOContext *s = av_mallocz(sizeof(AVIOContext));

    if (!s)

        return NULL;

    ffio_init_context(s, buffer, buffer_size, write_flag, opaque,

                  read_packet, write_packet, seek);

    return s;

}

int ffio_init_context(AVIOContext *s,

                  unsigned char *buffer,

                  int buffer_size,

                  int write_flag,

                  void *opaque,

                  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))

{

    s->buffer      = buffer;

    s->orig_buffer_size =

    s->buffer_size = buffer_size;

    s->buf_ptr     = buffer;

    s->opaque      = opaque;           //URLContext对象到了这里,叼

    s->direct      = 0;

    url_resetbuf(s, write_flag ? AVIO_FLAG_WRITE : AVIO_FLAG_READ);

    s->write_packet    = write_packet;     //三个函数指针到了这里,叼

    s->read_packet     = read_packet;

    s->seek            = seek;

    s->pos             = 0;

    s->must_flush      = 0;

    s->eof_reached     = 0;

    s->error           = 0;

    s->seekable        = seek ? AVIO_SEEKABLE_NORMAL : 0;

    s->max_packet_size = 0;

    s->update_checksum = NULL;

    s->short_seek_threshold = SHORT_SEEK_THRESHOLD;

    if (!read_packet && !write_flag) {

        s->pos     = buffer_size;

        s->buf_end = s->buffer + buffer_size;

    }

    s->read_pause = NULL;

    s->read_seek  = NULL;

    s->write_data_type       = NULL;

    s->ignore_boundary_point = 0;

    s->current_type          = AVIO_DATA_MARKER_UNKNOWN;

    s->last_time             = AV_NOPTS_VALUE;

    s->short_seek_get        = NULL;

    return 0;

}

这里总结一下:AVIOContext里面最重要的两个成员:URLContext:s->opaque

以及一堆函数指针:

                  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))

调用这些函数都必须通过URLContext才行,因为URLContext里面有这些函数调用需要的上下文:URLContext-〉priv_data

例如file格式:

const URLProtocol ff_file_protocol = {

    .name                = "file",

    .url_open            = file_open,

    .url_read            = file_read,

    .url_write           = file_write,

    .url_seek            = file_seek,

    .url_close           = file_close,

    .url_get_file_handle = file_get_handle,

    .url_check           = file_check,

    .url_delete          = file_delete,

    .url_move            = file_move,

    .priv_data_size      = sizeof(FileContext),

    .priv_data_class     = &file_class,

    .url_open_dir        = file_open_dir,

    .url_read_dir        = file_read_dir,

    .url_close_dir       = file_close_dir,

    .default_whitelist   = "file,crypto"

};

他就需要上下文:

typedef struct FileContext {

    const AVClass *class;

    int fd;

    int trunc;

    int blocksize;

    int follow;

#if HAVE_DIRENT_H

    DIR *dir;

#endif

} FileContext;

到此,AVIO全部分析完毕,简直透彻简单:

AVIOContext  —— URLContext——URLProcotol——具体某种协议格式的函数指针

至此整个AVIO_open函数分析完毕,核心如下:

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获取URLProtocol,然后根据URLProcol初始化URLContext

    err = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL);

    if (err < 0)

        return err;

//根据URLContext初始化AVIOContext,之后AVIOContext的所有函数其实都是调URLProtocol的函数干活,实际上AVIOContext是对URLProcol函数的封装,做了错误处理和缓冲

    err = ffio_fdopen(s, h); 

    if (err < 0) {

        ffurl_close(h);

        return err;

    }

    return 0;

}

好像前面漏了一点东西,现在补上去:第一个API其实还有connect的动作

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)

{

int ret = ffurl_alloc(puc, filename, flags, int_cb);

ret = ffurl_connect(*puc, options);

}

实际上很简单就是调用具体URLProtocol的open函数:

int ffurl_connect(URLContext *uc, AVDictionary **options)

{

    err =

        uc->prot->url_open2 ? uc->prot->url_open2(uc,

                                                  uc->filename,

                                                  uc->flags,

                                                  options) :

        uc->prot->url_open(uc, uc->filename, uc->flags);

}

到这里为止,其实init_input并没有走完,我们只是初始化了AVIOContext,但是并没有获得AVInputFormat,这个是具体的某种文件格式的Parser:

/* 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->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. ");

        return 0;

    }

    if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||

        (!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))

        return score;

//初始化AVIOContext

    if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)

        return ret;

    if (s->iformat)

        return 0;

//初始化AVInputFormat

    return av_probe_input_buffer2(s->pb, &s->iformat, filename,

                                 s, 0, s->format_probesize);

}

再来看看AVInputFormat的初始化过程:

其实也是简单,通过probedata去获取得分最高的AVInputFormat:

int av_probe_input_buffer2(AVIOContext *pb, AVInputFormat **fmt,

                          const char *filename, void *logctx,

                          unsigned int offset, unsigned int 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. */

        if ((ret = av_reallocp(&buf, probe_size + AVPROBE_PADDING_SIZE)) < 0)

        if ((ret = avio_read(pb, buf + buf_offset,

                             probe_size - buf_offset)) < 0) {

            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];

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

        /* Guess file format. */

        *fmt = av_probe_input_format2(&pd, 1, &score);

        if (*fmt) {

    return ret < 0 ? ret : score;

}

假如我们probe到的格式是ts:

AVInputFormat ff_mpegts_demuxer = {

    .name           = "mpegts",

    .long_name      = NULL_IF_CONFIG_SMALL("MPEG-TS (MPEG-2 Transport Stream)"),

    .priv_data_size = sizeof(MpegTSContext),

    .read_probe     = mpegts_probe,

    .read_header    = mpegts_read_header,

    .read_packet    = mpegts_read_packet,

    .read_close     = mpegts_read_close,

    .read_timestamp = mpegts_get_dts,

    .flags          = AVFMT_SHOW_IDS | AVFMT_TS_DISCONT,

    .priv_class     = &mpegts_class,

};

至此init_input分析完毕:

AVFormatContext两个成员都成功初始化:s->pb, &s->iformat,

一个是AVIOContext类型,一个是AVInputFormat类型

继续分析:avformat_open_input()函数

——ret = s->iformat->read_header(s):这个read_header()函数非常重要,不但初始化了该格式的上下文结构体如:MpegTSContext,同时还创建了该文件中包含的视频和音频流实体:

AVStream

换ts来分析:

AVInputFormat ff_mpegtsraw_demuxer = {

    .name           = "mpegtsraw",

    .long_name      = NULL_IF_CONFIG_SMALL("raw MPEG-TS (MPEG-2 Transport Stream)"),

    .priv_data_size = sizeof(MpegTSContext),

    .read_header    = mpegts_read_header,

    .read_packet    = mpegts_raw_read_packet,

    .read_close     = mpegts_read_close,

    .read_timestamp = mpegts_get_dts,

    .flags          = AVFMT_SHOW_IDS | AVFMT_TS_DISCONT,

    .priv_class     = &mpegtsraw_class,

};

//这个API超级重要,主要功能是初始化MpegTSContext数据结构,方便后面调用AVInputFormat ff_mpegts_demuxer的函数获取上下文信息

static int mpegts_read_header(AVFormatContext *s)

{

    MpegTSContext *ts = s->priv_data;    //AVFormatContext持有MpegTSContext指针,初始化的就是AVFormatContext里面的priv_data成员

    AVIOContext *pb   = s->pb;

    uint8_t buf[8 * 1024] = {0};

    int len;

    int64_t pos, probesize = s->probesize;

    if (ffio_ensure_seekback(pb, probesize) < 0)

        av_log(s, AV_LOG_WARNING, "Failed to allocate buffers for seekback ");

    /* read the first 8192 bytes to get packet size */

    pos = avio_tell(pb);

    len = avio_read(pb, buf, sizeof(buf));

    ts->raw_packet_size = get_packet_size(buf, len);

    if (ts->raw_packet_size <= 0) {

        av_log(s, AV_LOG_WARNING, "Could not detect TS packet size, defaulting to non-FEC/DVHS ");

        ts->raw_packet_size = TS_PACKET_SIZE;

    }

    ts->stream     = s;

    ts->auto_guess = 0;

    if (s->iformat == &ff_mpegts_demuxer) {

        /* normal demux */

        /* first do a scan to get all the services */

        seek_back(s, pb, pos);

//这里会根据PMT创建具体的video、audio AVStream

        mpegts_open_section_filter(ts, SDT_PID, sdt_cb, ts, 1);

 

        mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1);

        handle_packets(ts, probesize / ts->raw_packet_size);

        /* if could not find service, enable auto_guess */

        ts->auto_guess = 1;

        av_log(ts->stream, AV_LOG_TRACE, "tuning done ");

        s->ctx_flags |= AVFMTCTX_NOHEADER;

    }

    seek_back(s, pb, pos);

    return 0;

}

重点讲讲这里:因为这里非常重要。如果媒体格式有header,那么在read_header()创建AVStream,如果没有header,那么在read_packet()创建AVStream

创建AVStream的函数是:

AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c)

{

}

看一下这个函数的说明:

/**

 * Add a new stream to a media file.

 *

 * When demuxing, it is called by the demuxer in read_header(). If the

 * flag AVFMTCTX_NOHEADER is set in s.ctx_flags, then it may also

 * be called in read_packet().

 *

 * When muxing, should be called by the user before avformat_write_header().

 *

 * User is required to call avcodec_close() and avformat_free_context() to

 * clean up the allocation by avformat_new_stream().

AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);

###############################################################################

另外强调一点:AVInputFormat内部持有AVFormatContext指针,所以所有关于读写packet的函数,最终都是调用AVFormatContext的AVIOContext的读写函数,都是调用URLContext的URLProtocol的读写函数。

另外:Aviobuf.c是对Avio.c的读写函数封装,添加了错误检测机制和缓冲机制。

继续分析:avformat_open_input()剩余的部分

接下来我们关注一下这个变量:struct AVFormatInternal

在分配AVFormatContext的时候:

AVFormatContext *avformat_alloc_context(void)

{

    AVFormatContext *ic;

    ic = av_malloc(sizeof(AVFormatContext));

    if (!ic) return 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;

}

这个结构体在avformat_open_input内部还会做一些初始化:

    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. ");

        av_dict_free(&s->internal->id3v2_meta);

        if (s->error_recognition & AV_EF_EXPLODE)

            return AVERROR_INVALIDDATA;

    }

    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->internal->data_offset)

        s->internal->data_offset = avio_tell(s->pb);

    s->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;

感觉没什么内容,先略过。

继续分析:avformat_open_input()函数的最后一部分

    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;

    if (options) {

        av_dict_free(options);

        *options = tmp;

    }

    *ps = s;

return 0;

//个人认为这个函数在第一次avformat_open_input()的时候是不会执行的,因为s->nb_streams是0,以后打开时,s->nb_streams不为0,则会更新avstream的信息

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;

}

至此:avformat_open_input()函数的全部过程分析完毕,收获很大阿。

2、avformat_find_stream_info(fmt_ctx, NULL)

这个函数貌似也挺厉害的样子,开搞:

主要是对AVFormatContext的所有AVStream成员进行初始化,可以参考雷神的博客。我们这里只重点分析主要成员的初始化:

int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options)

{

    AVStream *st;

    AVCodecContext *avctx;

  for (i = 0; i < ic->nb_streams; i++) {

        const AVCodec *codec;

        st = ic->streams[i];

        avctx = st->internal->avctx;

//初始化AVStream的parser成员

        st->parser = av_parser_init(st->codecpar->codec_id);

        ret = avcodec_parameters_to_context(avctx, st->codecpar);

//初始化AVCodec和AVCodecContext

        codec = find_probe_decoder(ic, st, st->codecpar->codec_id);

        // Try to just open decoders, in case this is enough to get parameters.

        if (!has_codec_parameters(st, NULL) && st->request_probe <= 0) {

            if (codec && !avctx->codec)

                if (avcodec_open2(avctx, codec, options ? &options[i] : &thread_opt) < 0)

        }

   }

//为了初始化AVCodecContext,有时候需要解码一些帧

            if (st->info->found_decoder == 1) {

                do {

                    err = try_decode_frame(ic, st, &empty_pkt,

                                            (options && i < orig_nb_streams)

                                            ? &options[i] : NULL);

                } while (err > 0 && !has_codec_parameters(st, NULL));

// close codecs which were opened in try_decode_frame()

    for (i = 0; i < ic->nb_streams; i++) {

        st = ic->streams[i];

        avcodec_close(st->internal->avctx);

    }

/* update the stream parameters from the internal codec contexts */

    for (i = 0; i < ic->nb_streams; i++) {

        st = ic->streams[i];

         ret = avcodec_parameters_to_context(st->codec, st->codecpar);

  }

}

至此整个函数分析完毕:主要工作就是初始化AVStream和AVCodecContext

这里重点看一下AVStream的数据结构:

typedef struct AVStream {

    int index;    /**< stream index in AVFormatContext */

    int id;

#if FF_API_LAVF_AVCTX

    /**

     * @deprecated use the codecpar struct instead

     */

    attribute_deprecated

    AVCodecContext *codec;

#endif

    void *priv_data;

    AVRational time_base;

    int64_t start_time;

    int64_t duration;

    int64_t nb_frames;                 ///< number of frames in this stream if known or 0

    AVRational sample_aspect_ratio;

    AVDictionary *metadata;

    AVRational avg_frame_rate;

    AVPacketSideData *side_data;

    int pts_wrap_bits; /**< number of bits in pts (used for wrapping control) */

    int64_t first_dts;

    int64_t cur_dts;

    int64_t last_IP_pts;

    int last_IP_duration;

    int codec_info_nb_frames;

    /* av_read_frame() support */

    enum AVStreamParseType need_parsing;

    struct AVCodecParserContext *parser;

    struct AVPacketList *last_in_packet_buffer;

    AVProbeData probe_data;

    int nb_index_entries;

    unsigned int index_entries_allocated_size;

    AVRational r_frame_rate;

    AVStreamInternal *internal;

    AVCodecParameters *codecpar;

} AVStream;

3、open_codec_context()

这个函数是自己定义的,做得事情主要是初始化AVCodecContext和打开AVCodec

static int open_codec_context(int *stream_idx,

                              AVCodecContext **dec_ctx, AVFormatContext *fmt_ctx, enum AVMediaType type)

{

    int ret, stream_index;

    AVStream *st;

    AVCodec *dec = NULL;

    AVDictionary *opts = NULL;

    ret = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0);

    if (ret < 0) {

        fprintf(stderr, "Could not find %s stream in input file '%s' ",

                av_get_media_type_string(type), src_filename);

        return ret;

    } else {

        stream_index = ret;

//如果有多个streams,则通过av_find_best_stream找到最匹配stream的stream_index

        st = fmt_ctx->streams[stream_index]; 

        /* find decoder for the stream */

        dec = avcodec_find_decoder(st->codecpar->codec_id);

        if (!dec) {

            fprintf(stderr, "Failed to find %s codec ",

                    av_get_media_type_string(type));

            return AVERROR(EINVAL);

        }

        /* Allocate a codec context for the decoder */

        *dec_ctx = avcodec_alloc_context3(dec);

        if (!*dec_ctx) {

            fprintf(stderr, "Failed to allocate the %s codec context ",

                    av_get_media_type_string(type));

            return AVERROR(ENOMEM);

        }

        /* Copy codec parameters from input stream to output codec context */

        if ((ret = avcodec_parameters_to_context(*dec_ctx, st->codecpar)) < 0) {

            fprintf(stderr, "Failed to copy %s codec parameters to decoder context ",

                    av_get_media_type_string(type));

            return ret;

        }

        /* Init the decoders, with or without reference counting */

        av_dict_set(&opts, "refcounted_frames", refcount ? "1" : "0", 0);

        if ((ret = avcodec_open2(*dec_ctx, dec, &opts)) < 0) {

            fprintf(stderr, "Failed to open %s codec ",

                    av_get_media_type_string(type));

            return ret;

        }

        *stream_idx = stream_index;

    }

    return 0;

}

4、decode_packet()

这个函数也是个自己定义的函数,和之前分析的差不多:

static int decode_packet(int *got_frame, int cached)

{

    int ret = 0;

    int decoded = pkt.size;

    *got_frame = 0;

    if (pkt.stream_index == video_stream_idx) {

        /* decode video frame */

        ret = avcodec_decode_video2(video_dec_ctx, frame, got_frame, &pkt);

        if (ret < 0) {

            fprintf(stderr, "Error decoding video frame (%s) ", av_err2str(ret));

            return ret;

        }

}

很简单,这里就不重复分析了。

5、资源回收

最后看一下资源释放:

    avcodec_free_context(&video_dec_ctx);  // AVCodecContext释放

    avcodec_free_context(&audio_dec_ctx);  // AVCodecContext释放

    avformat_close_input(&fmt_ctx);       // AVFormatContext释放

    av_frame_free(&frame);              // AVFrame释放

至此整个demuxing再解码的详细过程分析完毕:demuxing_decoding.c

附录:

新版API只有以下几种格式能用:

    int (*send_frame)(AVCodecContext *avctx, const AVFrame *frame);

    int (*send_packet)(AVCodecContext *avctx, const AVPacket *avpkt);

    int (*receive_frame)(AVCodecContext *avctx, AVFrame *frame);

    int (*receive_packet)(AVCodecContext *avctx, AVPacket *avpkt);

#define DEFINE_CRYSTALHD_DECODER(x, X)

    static const AVClass x##_crystalhd_class = {

        .class_name = #x "_crystalhd",

        .item_name = av_default_item_name,

        .option = options,

        .version = LIBAVUTIL_VERSION_INT,

    };

    AVCodec ff_##x##_crystalhd_decoder = {

        .name           = #x "_crystalhd",

        .long_name      = NULL_IF_CONFIG_SMALL("CrystalHD " #X " decoder"),

        .type           = AVMEDIA_TYPE_VIDEO,

        .id             = AV_CODEC_ID_##X,

        .priv_data_size = sizeof(CHDContext),

        .priv_class     = &x##_crystalhd_class,

        .init           = init,

        .close          = uninit,

        .send_packet    = crystalhd_decode_packet,

        .receive_frame  = crystalhd_receive_frame,

        .flush          = flush,

        .capabilities   = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_AVOID_PROBING,

        .pix_fmts       = (const enum AVPixelFormat[]){AV_PIX_FMT_YUYV422, AV_PIX_FMT_NONE},

    };

#if CONFIG_H264_CRYSTALHD_DECODER

DEFINE_CRYSTALHD_DECODER(h264, H264)

#endif

#if CONFIG_MPEG2_CRYSTALHD_DECODER

DEFINE_CRYSTALHD_DECODER(mpeg2, MPEG2VIDEO)

#endif

#if CONFIG_MPEG4_CRYSTALHD_DECODER

DEFINE_CRYSTALHD_DECODER(mpeg4, MPEG4)

#endif

#if CONFIG_MSMPEG4_CRYSTALHD_DECODER

DEFINE_CRYSTALHD_DECODER(msmpeg4, MSMPEG4V3)

#endif

#if CONFIG_VC1_CRYSTALHD_DECODER

DEFINE_CRYSTALHD_DECODER(vc1, VC1)

#endif

#if CONFIG_WMV3_CRYSTALHD_DECODER

DEFINE_CRYSTALHD_DECODER(wmv3, WMV3)

#endif

原文地址:https://www.cnblogs.com/stnlcd/p/7149919.html