avformat的简单应用

注意:本文是基因FFMPEG的3.3.1 版本,如有出入请先核对版本是否相同

一、简介

avformat是包含复用(mux),解复用(demux)的多媒体容器库,它是ffmpeg框架中比较重要的两个library之一,另一个是avcodec(编解码库)。

avformat当中包含了非常之多的容器格式,有很老的偏门格式,也有当今主流的多媒体容器格式。如果要实现一个多媒体播放器的话,基本上只需要ffmpeg的avformat作为容器部分就完全能够满足需求了。

接下来将会分为demux和mux两个部分来进行阐述,在此之前先看一下demux和mux之外的公共部分,属于avformat的部分API:

unsigned avformat_version(void); /**获取avformat的版本号*/

const char *avformat_configuration(void);/**获取编译时的配置信息*/

const char *avformat_license(void);/**获取avformat的许可证信息*/

/** 初始化avformat并注册编译进avformat库里面所有的复用器(muxers),
  * 解复用器(demuxers)和协议模块
  */
void av_register_all(void);

/**网络功能的全局初始化(可选的,在使用网络协议时有必要调用)*/
int avformat_network_init(void);

/**释放网络相关的全局资源,与avformat_network_init函数相对应*/
int avformat_network_deinit(void);

二、demux部分

2.1 AVInputFormat结构体(demuxer的接口结构体)

关于demux这部分,首先需要说明的是其比较核心的结构体:AVInputFormat, 它是容器API以及容器名等信息的数据结构体:

typedef struct AVInputFormat {

    const char *name; /**容器名,它可以是一个以逗号分隔的列表 */
    const char *long_name; /**容器详细的名称*/
    int flags; /**标志*/

    /**容器文件的后缀名,很少用,因为使用后缀名来判断容器类型并不太准确 */
    const char *extensions;

    /**容器包含的元码流格式,解复用容器定义这一项的比较少,复用(mux)容器相对多一些*/
    const struct AVCodecTag * const *codec_tag;

    const AVClass *priv_class; /**指向容器私有上下文(context)的指针*/
    /** MIME (Multipurpose Internet Mail Extensions)类型,可以是逗号分隔的列表,
      * 在检测容器类型时会用到 
      */
    const char *mime_type;

    struct AVInputFormat *next;/**指向下一个解复用容器结构体的指针*/

    /**元码流格式的容器的编码类型(如AAC容器的这个成员为:AV_CODEC_ID_AAC)*/
    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);

    /**开始或回复播放,仅用于基于网络类型的容器(如RTSP)*/
    int (*read_play)(struct AVFormatContext *);

    /**暂停播放,仅用于基于网络类型的容器(如RTSP)*/
    int (*read_pause)(struct AVFormatContext *);

    /** 也容器的跳转函数,与read_seek区别是:这里要求跳转的时间戳逼近‘ts’,且需要
      * 在min_ts和max_ts时间戳之间
      */
    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;

这个解复用的结构体内容还是比较多,但在实现一个容器的时候并不一定需要所有内容都去实现。比如在avformat中,mp3容器中填充的内容如下:

/**mp3容器的设置项*/
static const AVOption options[] = {
    { "usetoc", "use table of contents", offsetof(MP3DecContext, usetoc), 
    AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, AV_OPT_FLAG_DECODING_PARAM},
    { NULL },
};

/**mp3容器的是有上下文*/
static const AVClass demuxer_class = {
    .class_name = "mp3",
    .item_name  = av_default_item_name,
    .option     = options,
    .version    = LIBAVUTIL_VERSION_INT,
    .category   = AV_CLASS_CATEGORY_DEMUXER,
};

/**mp3容器填充解复用结构体*/
AVInputFormat ff_mp3_demuxer = {
    .name           = "mp3",/**容器简短名称*/
    .long_name      = NULL_IF_CONFIG_SMALL("MP2/3 (MPEG audio layer 2/3)"),/**详细长名称*/
    .read_probe     = mp3_read_probe, /**探查容器格式*/
    .read_header    = mp3_read_header,/**解析容器头信息*/
    .read_packet    = mp3_read_packet,/**读取元码流数据包*/
    .read_seek      = mp3_seek,/**跳转*/
    .priv_data_size = sizeof(MP3DecContext),/**mp3容器是有数据结构体MP3DecContext的大小*/
    .flags          = AVFMT_GENERIC_INDEX,/**标志,常用的索引类型,用于跳转*/
    .extensions     = "mp2,mp3,m2a,mpa", /* 后缀名,用于probe */
    .priv_class     = &demuxer_class,/**指向mp3容器私有上下文*/
};

2.2 解复用(demux)相关的API

在avformat.h中关于demux的API有不少,但其中有很多都只是获取属性信息等小功能API,这里只说几个主要的API,基本上就是和AVInputFormat结构体中的接口相对应。

/**打开传入的url,创建一个解复用实例,也可外面指定容器类型或其他设置项 */
int avformat_open_input(AVFormatContext **ps, const char *url, 
                        AVInputFormat *fmt, AVDictionary **options);

/**根据传入参数寻找最好(best,或者这里可以说最匹配的 )的元码流的索引(index)*/
int av_find_best_stream(AVFormatContext *ic,
                        enum AVMediaType type,
                        int wanted_stream_nb,
                        int related_stream,
                        AVCodec **decoder_ret,
                        int flags);

/**从容器中读取一帧数据*/
int av_read_frame(AVFormatContext *s, AVPacket *pkt);

/**跳转到对应的时间点(对应read_seek)*/
int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, 
                  int flags);


/**跳转到逼近ts时间点,并且在min_ts和max_ts范围之内(对应read_seek2)*/
int avformat_seek_file(AVFormatContext *s, int stream_index, int64_t min_ts,
                       int64_t ts, int64_t max_ts, int flags);

/**清空内部缓存数据*/
int avformat_flush(AVFormatContext *s);

/**开始播放一个基于网络的流(如RTSP流)*/
int av_read_play(AVFormatContext *s);

/**暂停一个基于网络的流*/
int av_read_pause(AVFormatContext *s);

/**关闭一个打开的容器,并释放资源 */
void avformat_close_input(AVFormatContext **s);

这里的API并不是简单的直接调用AVInputFormat结构体中的接口,比如av_read_frame函数,其内部就可能包含一个缓冲buffer来暂存解复用解析出来的元码流,以及根据需要将从容器中解析出来的元码流进行二次parse获取完整帧(因为有些容器中的一个数据包并不是一个完整的音频或视频帧,它可能一个数据包含有多个帧,也可能几个数据包才凑齐一个帧)。

这个API的实现这些内容比较多,这里就只看一下av_read_frame接口的实现,其他的就不一一呈现了。

int av_read_frame(AVFormatContext *s, AVPacket *pkt)
{
/**
 *#define AVFMT_FLAG_GENPTS 0x0001 ///< Generate missing pts even if it 
                                   // requires parsing future frames.
 * 要生成丢失的显示时间戳,即使这需要读取后面的帧阿里确定(因为有些容器只提供了
 * dts(解码时间戳),没提供pts)
 */
    const int genpts = s->flags & AVFMT_FLAG_GENPTS;
    int eof = 0;
    int ret;
    AVStream *st;

    if (!genpts) {/**如果没有AVFMT_FLAG_GENPTS标志,则直接读取数据*/

    /**如果有内部暂存数据包的buffer,就从这个buffer中读取,否则就直接从容器中读取*/
        ret = s->internal->packet_buffer
              ? read_from_packet_buffer(&s->internal->packet_buffer,
                                        &s->internal->packet_buffer_end, pkt)
              : read_frame_internal(s, pkt);
        if (ret < 0)
            return ret;
        goto return_packet;
    }

    for (;;) {
        AVPacketList *pktl = s->internal->packet_buffer;

        if (pktl) {
            AVPacket *next_pkt = &pktl->pkt;

            if (next_pkt->dts != AV_NOPTS_VALUE) {/**判断dts是否有效*/
                int wrap_bits = s->streams[next_pkt->stream_index]->pts_wrap_bits;
                // last dts seen for this stream. if any of packets following
                // current one had no dts, we will set this to AV_NOPTS_VALUE.
                int64_t last_dts = next_pkt->dts;
        /**根据当前包的pts和dts以及下一个包的dts来确定下一个包的pts */
                while (pktl && next_pkt->pts == AV_NOPTS_VALUE) {
                    if (pktl->pkt.stream_index == next_pkt->stream_index &&
                        2LL << (wrap_bits - 1)) < 0)) {
                        if (av_compare_mod(pktl->pkt.pts, pktl->pkt.dts,
                            2LL << (wrap_bits - 1))) {
                            // not B-frame
                            next_pkt->pts = pktl->pkt.dts;
                        }
                        if (last_dts != AV_NOPTS_VALUE) {
                            // Once last dts was set to AV_NOPTS_VALUE, we don't change it.
                            last_dts = pktl->pkt.dts;
                        }
                    }
                    pktl = pktl->next;
                }

                if (eof && next_pkt->pts == AV_NOPTS_VALUE && 
                   last_dts != AV_NOPTS_VALUE) {
                    // Fixing the last reference frame had none pts issue (For MXF etc).
                    // We only do this when
                    // 1. eof.
                    // 2. we are not able to resolve a pts value for current packet.
                    // 3. the packets for this stream at the end of the files had valid dts.
                    next_pkt->pts = last_dts + next_pkt->duration;
                }
                pktl = s->internal->packet_buffer;
            }

            /* read packet from packet buffer, if there is data */
            st = s->streams[next_pkt->stream_index];
            if (!(next_pkt->pts == AV_NOPTS_VALUE && st->discard < AVDISCARD_ALL &&
                  next_pkt->dts != AV_NOPTS_VALUE && !eof)) {
                ret = read_from_packet_buffer(&s->internal->packet_buffer,
                                               &s->internal->packet_buffer_end, pkt);
                goto return_packet;
            }
        }

    /**从容器中直接读取数据包*/
        ret = read_frame_internal(s, pkt);
        if (ret < 0) {
            if (pktl && ret != AVERROR(EAGAIN)) {
                eof = 1;
                continue;
            } else
                return ret;
        }

    /**将数据包添加到暂存数据包的buffer当中*/
        ret = add_to_pktbuf(&s->internal->packet_buffer, pkt,
                            &s->internal->packet_buffer_end, 1);
        av_packet_unref(pkt);
        if (ret < 0)
            return ret;
    }

return_packet:

    st = s->streams[pkt->stream_index];
    if ((s->iformat->flags & AVFMT_GENERIC_INDEX) && pkt->flags & AV_PKT_FLAG_KEY) {
    /**如果当前容器不支持直接seek(如没有索引信息等)且当前数据包为关键帧时*/

        ff_reduce_index(s, st->index);
    /**将关键帧的解码时间戳和在文件中的起始位置存入索引列表中,用于提高跳转速度 */
        av_add_index_entry(st, pkt->pos, pkt->dts, 0, 0, AVINDEX_KEYFRAME);
    }

    if (is_relative(pkt->dts))
        pkt->dts -= RELATIVE_TS_BASE;
    if (is_relative(pkt->pts))
        pkt->pts -= RELATIVE_TS_BASE;

    return ret;
}

接下来就是内部读取数据包的函数read_frame_internal(严格来说,这里也没有直接调用到AVInputFormat中的read_packet接口,而是在ff_read_packet函数里面调用的):

static int read_frame_internal(AVFormatContext *s, AVPacket *pkt)
{
    int ret = 0, i, got_packet = 0;
    AVDictionary *metadata = NULL;

    av_init_packet(pkt);

    while (!got_packet && !s->internal->parse_queue) {
    /** 如果还没有获取到一个数据包,并且解析器的解析队列为空(也就是说解析队列里
      * 面也没有一个完整的帧或者不需要解析器的情况)
      */
        AVStream *st;
        AVPacket cur_pkt;

        /* read next packet */
        ret = ff_read_packet(s, &cur_pkt);/**真正的从容器中读取数据包*/
        if (ret < 0) {/**如果读取失败*/
            if (ret == AVERROR(EAGAIN))
                return ret;
            /* flush the parsers(情况解析器中的数据,数据会被丢弃) */
            for (i = 0; i < s->nb_streams; i++) {
                st = s->streams[i];
                if (st->parser && st->need_parsing)
                    parse_packet(s, NULL, st->index);
            }
            /* all remaining packets are now in parse_queue =>
             * really terminate parsing */
            break;
        }
        ret = 0;
        st  = s->streams[cur_pkt.stream_index];

        /* update context if required */
        if (st->internal->need_context_update) {
    /**如果需要更新上下文信息*/
            if (avcodec_is_open(st->internal->avctx)) {
    /** 如果当前读取数据包对应的元码流的解码器已经打开(有时会需要使
      * 用解码器去分析元码流的信息)
      */
                av_log(s, AV_LOG_DEBUG, 
                "Demuxer context update while decoder is open,"
                " closing and trying to re-open\n");
                avcodec_close(st->internal->avctx);/**关闭解码器*/
                st->info->found_decoder = 0;
            }

            /* close parser, because it depends on the codec */
            if (st->parser && 
               st->internal->avctx->codec_id != st->codecpar->codec_id) {
        /**如果当前元码流已有解析器实例,且新的解码器参数中的解码器id也不同,就关闭解析器*/
                av_parser_close(st->parser);
                st->parser = NULL;
            }

        /**根据解码器参数创建解码器上下文实例*/
            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;
        }

        if (cur_pkt.pts != AV_NOPTS_VALUE &&
            cur_pkt.dts != AV_NOPTS_VALUE &&
            cur_pkt.pts < cur_pkt.dts) {
            av_log(s, AV_LOG_WARNING,
                   "Invalid timestamps stream=%d, pts=%s, dts=%s, size=%d\n",
                   cur_pkt.stream_index,
                   av_ts2str(cur_pkt.pts),
                   av_ts2str(cur_pkt.dts),
                   cur_pkt.size);
        }
        if (s->debug & FF_FDEBUG_TS)
            av_log(s, AV_LOG_DEBUG,
                   "ff_read_packet stream=%d, pts=%s, dts=%s,"
                   " size=%d, duration=%"PRId64", flags=%d\n",
                   cur_pkt.stream_index,
                   av_ts2str(cur_pkt.pts),
                   av_ts2str(cur_pkt.dts),
                   cur_pkt.size, cur_pkt.duration, cur_pkt.flags);

        if (st->need_parsing && !st->parser && 
            !(s->flags & AVFMT_FLAG_NOPARSE)) {
    /** 如果需要进行二次解析,且解析器实例尚未创建,
      * 且avformat的标志中并未设定不进行二次解析的标志
      */
            st->parser = av_parser_init(st->codecpar->codec_id);/**创建解析器实例*/
            if (!st->parser) {
                av_log(s, AV_LOG_VERBOSE, "parser not found for codec "
                       "%s, packets or times may be invalid.\n",
                       avcodec_get_name(st->codecpar->codec_id));
                /* no parser available: just output the raw packets */
                st->need_parsing = AVSTREAM_PARSE_NONE;
            } else if (st->need_parsing == AVSTREAM_PARSE_HEADERS)
                st->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
            else if (st->need_parsing == AVSTREAM_PARSE_FULL_ONCE)
                st->parser->flags |= PARSER_FLAG_ONCE;
            else if (st->need_parsing == AVSTREAM_PARSE_FULL_RAW)
                st->parser->flags |= PARSER_FLAG_USE_CODEC_TS;
        }

        if (!st->need_parsing || !st->parser) {
    /**如果不需要二次解析或解析器实例为空,就直接输出当前数据包*/
            /* no parsing needed: we just output the packet as is */
            *pkt = cur_pkt;
        /**处理当前数据包的时间戳相关信息*/
            compute_pkt_fields(s, st, NULL, pkt, AV_NOPTS_VALUE, AV_NOPTS_VALUE);

            if ((s->iformat->flags & AVFMT_GENERIC_INDEX) &&
                (pkt->flags & AV_PKT_FLAG_KEY) && pkt->dts != AV_NOPTS_VALUE) {
        /** 如果当前容器不支持直接seek(如没有索引信息等)且当前数据包为关键帧,
          * 且解码时间戳为有效值时
          */
                ff_reduce_index(s, st->index);
        /**将当前数据包的解码时间戳以及位置信息假如到索引列表当中*/
                av_add_index_entry(st, pkt->pos, pkt->dts,
                                   0, 0, AVINDEX_KEYFRAME);
            }
            got_packet = 1;
        } else if (st->discard < AVDISCARD_ALL) {
    /**如果当前元码流没有设置为丢弃全部数据包,就进行二次解析*/
            if ((ret = parse_packet(s, &cur_pkt, cur_pkt.stream_index)) < 0)
                return ret;
            st->codecpar->sample_rate = st->internal->avctx->sample_rate;
            st->codecpar->bit_rate = st->internal->avctx->bit_rate;
            st->codecpar->channels = st->internal->avctx->channels;
            st->codecpar->channel_layout = st->internal->avctx->channel_layout;
            st->codecpar->codec_id = st->internal->avctx->codec_id;
        } else {
            /* free packet */
            av_packet_unref(&cur_pkt);
        }
        if (pkt->flags & AV_PKT_FLAG_KEY)
            st->skip_to_keyframe = 0;
        if (st->skip_to_keyframe) {
    /**如果跳过所有数据直到关键帧出现的标志被置位*/
            av_packet_unref(&cur_pkt);/**释放当前数据包的一个引用*/
            if (got_packet) {/**如果已获取到数据包的标志置位*/
                *pkt = cur_pkt;/**将当前数据包赋值给输出数据包*/
            }
            got_packet = 0;
        }
    }
    /**如果未获取到数据包,且有解析队列,就在解析队列中去读取数据包*/
    if (!got_packet && s->internal->parse_queue)
        ret = read_from_packet_buffer(&s->internal->parse_queue, 
                                      &s->internal->parse_queue_end, pkt);

    if (ret >= 0) {
        AVStream *st = s->streams[pkt->stream_index];
        int discard_padding = 0;
        if (st->first_discard_sample && pkt->pts != AV_NOPTS_VALUE) {
    /**如果需要丢要第一个音频样本,且时间戳有效*/
            int64_t pts = pkt->pts - (is_relative(pkt->pts) ? RELATIVE_TS_BASE : 0);
            int64_t sample = ts_to_samples(st, pts);
            int duration = ts_to_samples(st, pkt->duration);
            int64_t end_sample = sample + duration;
            if (duration > 0 && end_sample >= st->first_discard_sample &&
                sample < st->last_discard_sample)
                discard_padding = FFMIN(end_sample - st->first_discard_sample, duration);
        }
        /**如果需要被跳过的其实样本的数量不为0*/
        if (st->start_skip_samples && (pkt->pts == 0 || 
            pkt->pts == RELATIVE_TS_BASE))
            st->skip_samples = st->start_skip_samples;

        if (st->skip_samples || discard_padding) {
    /**需要跳过样本或者衬垫数据*/
            uint8_t *p = av_packet_new_side_data(pkt, AV_PKT_DATA_SKIP_SAMPLES, 10);
            if (p) {
                AV_WL32(p, st->skip_samples);
                AV_WL32(p + 4, discard_padding);
                av_log(s, AV_LOG_DEBUG, "demuxer injecting skip %d / discard %d\n",
                 st->skip_samples, discard_padding);
            }
            st->skip_samples = 0;
        }

        if (st->inject_global_side_data) {
    /** 如果需要将内部数据注入到全局侧数据当中(不太清楚这里的
      * 侧数据是什么东东,没有用过)
      */
            for (i = 0; i < st->nb_side_data; i++) {
                AVPacketSideData *src_sd = &st->side_data[i];
                uint8_t *dst_data;

                if (av_packet_get_side_data(pkt, src_sd->type, NULL))
                    continue;

                dst_data = av_packet_new_side_data(pkt, src_sd->type, src_sd->size);
                if (!dst_data) {
                    av_log(s, AV_LOG_WARNING, "Could not inject global side data\n");
                    continue;
                }

                memcpy(dst_data, src_sd->data, src_sd->size);
            }
            st->inject_global_side_data = 0;
        }

#if FF_API_LAVF_MERGE_SD
FF_DISABLE_DEPRECATION_WARNINGS
        if (!(s->flags & AVFMT_FLAG_KEEP_SIDE_DATA))
            av_packet_merge_side_data(pkt);
FF_ENABLE_DEPRECATION_WARNINGS
#endif
    }

    av_opt_get_dict_val(s, "metadata", AV_OPT_SEARCH_CHILDREN, &metadata);
    if (metadata) {/**更新元数据*/
        s->event_flags |= AVFMT_EVENT_FLAG_METADATA_UPDATED;
        av_dict_copy(&s->metadata, metadata, 0);
        av_dict_free(&metadata);
        av_opt_set_dict_val(s, "metadata", NULL, AV_OPT_SEARCH_CHILDREN);
    }

#if FF_API_LAVF_AVCTX
    update_stream_avctx(s);
#endif

    if (s->debug & FF_FDEBUG_TS)
        av_log(s, AV_LOG_DEBUG,
               "read_frame_internal stream=%d, pts=%s, dts=%s, "
               "size=%d, duration=%"PRId64", flags=%d\n",
               pkt->stream_index,
               av_ts2str(pkt->pts),
               av_ts2str(pkt->dts),
               pkt->size, pkt->duration, pkt->flags);

    return ret;
}

最后就是直接从容器中读取数据的函数ff_read_packet:

int ff_read_packet(AVFormatContext *s, AVPacket *pkt)
{
    int ret, i, err;
    AVStream *st;

    for (;;) {
        AVPacketList *pktl = s->internal->raw_packet_buffer;

        if (pktl) {/**如果存在元码流数据包暂存buffer*/
            *pkt = pktl->pkt;/**将暂存buffer中的第一个数据包赋值给输出数据包*/
            st   = s->streams[pkt->stream_index];

            /**元码流数据包暂存buffer的剩余空间大小<=0*/
            if (s->internal->raw_packet_buffer_remaining_size <= 0)
                if ((err = probe_codec(s, st, NULL)) < 0)
                    return err;

            if (st->request_probe <= 0) {/**不需要今夕probe或者probe已经完成*/
        /**将当前数据包从暂存buffer的链表中移除*/
                s->internal->raw_packet_buffer                 = pktl->next;
                s->internal->raw_packet_buffer_remaining_size += pkt->size;
                av_free(pktl);
                return 0;/**返回*/
            }
        }

        pkt->data = NULL;
        pkt->size = 0;
        av_init_packet(pkt);
        /**调用AVInputFormat中的read_packet函数读取数据包*/
        ret = s->iformat->read_packet(s, pkt);
        if (ret < 0) {/**读取失败*/
            /* Some demuxers return FFERROR_REDO when they consume
               data and discard it (ignored streams, junk, extradata).
               We must re-call the demuxer to get the real packet. */
    /**如果是遇到容器消耗或者丢弃某些数据,并需要进行re-call来读取真实数据包时,就继续循环*/
            if (ret == FFERROR_REDO)
                continue;
            if (!pktl || ret == AVERROR(EAGAIN))
                return ret;/**返回错误*/
            for (i = 0; i < s->nb_streams; i++) {/**有读取到数据包,但返回错误号时*/
                st = s->streams[i];
                if (st->probe_packets || st->request_probe > 0)
                    if ((err = probe_codec(s, st, NULL)) < 0)/**需要对数据包进行探查时*/
                        return err;
                av_assert0(st->request_probe <= 0);
            }
            continue;/**继续循环*/
        }

        if (!pkt->buf) {/**如果容器中不是使用引用计数的buffer(即AVBuffer)来存储数据*/
            AVPacket tmp = { 0 };
            ret = av_packet_ref(&tmp, pkt);/**以引用计数的buffer来存储数据*/
            if (ret < 0)
                return ret;
            *pkt = tmp;
        }

        if ((s->flags & AVFMT_FLAG_DISCARD_CORRUPT) &&
            (pkt->flags & AV_PKT_FLAG_CORRUPT)) {
    /**如果丢掉换数据的标志置位,且当前数据包为坏数据包*/
            av_log(s, AV_LOG_WARNING,
                   "Dropped corrupted packet (stream = %d)\n",
                   pkt->stream_index);
            av_packet_unref(pkt);
            continue;
        }

        if (pkt->stream_index >= (unsigned)s->nb_streams) {/**无效的元码流索引值*/
            av_log(s, AV_LOG_ERROR, "Invalid stream index %d\n", pkt->stream_index);
            continue;
        }

        st = s->streams[pkt->stream_index];

    /**处理pts相关信息*/
        if (update_wrap_reference(s, st, pkt->stream_index, pkt) && 
            st->pts_wrap_behavior == AV_PTS_WRAP_SUB_OFFSET) {
            // correct first time stamps to negative values
            if (!is_relative(st->first_dts))
                st->first_dts = wrap_timestamp(st, st->first_dts);
            if (!is_relative(st->start_time))
                st->start_time = wrap_timestamp(st, st->start_time);
            if (!is_relative(st->cur_dts))
                st->cur_dts = wrap_timestamp(st, st->cur_dts);
        }

        pkt->dts = wrap_timestamp(st, pkt->dts);
        pkt->pts = wrap_timestamp(st, pkt->pts);

        force_codec_ids(s, st);/**用当前元码流的解码器信息更新avformat上下文中的解码器信息*/

        /* TODO: audio: time filter; video: frame reordering (pts != dts) */
        if (s->use_wallclock_as_timestamps)
            pkt->dts = pkt->pts = av_rescale_q(av_gettime(), AV_TIME_BASE_Q, st->time_base);

        if (!pktl && st->request_probe <= 0)
            return ret;/**如果没有元码流暂存buffer且不需要或已经完成probe就返回*/

    /**将当前数据包添加到元码流暂存buffer链表当中*/
        err = add_to_pktbuf(&s->internal->raw_packet_buffer, pkt,
                            &s->internal->raw_packet_buffer_end, 0);
        if (err)
            return err;/**如果发生错误就返回*/
        /**更新元码流暂存buffer的剩余空间的大小*/    
        s->internal->raw_packet_buffer_remaining_size -= pkt->size; 

        if ((err = probe_codec(s, st, pkt)) < 0)/**探查数据包*/
            return err;
    }
}

2.3 demux的简单例子

这个例子是截取自ffmpeg源代码中doc/examples/demuxing_decoding.c文件中的例子,剔除了其中与codec相关的部分,其代码如下:

/**因为是在visual studio下写的代码,所以这里有一个消除因为安全警告而报错的宏*/
#define _CRT_SECURE_NO_WARNINGS

#include <libavformat/avformat.h>


static AVFormatContext *fmt_ctx = NULL;/**avformat上下文实例指针*/

static const char *src_filename = NULL;/**要解复用的文件*/

static int video_stream_idx = -1, audio_stream_idx = -1;
static AVPacket pkt;
static int video_pkt_count = 0;
static int audio_pkt_count = 0;


int main (int argc, char **argv)
{
    int ret = 0;

    src_filename = "south_mountain_south.mp4";

    /* 注册所有的容器和编解码器 */
    av_register_all();

    /* 打开输入文件,并分配avformat的上下文实例 */
    if (avformat_open_input(&fmt_ctx, src_filename, NULL, NULL) < 0)
    {
        fprintf(stderr, "Could not open source file %s\n", src_filename);
        exit(1);
    }

    /* 检索流的信息 */
    if (avformat_find_stream_info(fmt_ctx, NULL) < 0) 
    {
        fprintf(stderr, "Could not find stream information\n");
        exit(1);
    }
    /**获取一条最好的视频流(如果输入文件没有视频流,将返回 -1)*/
    video_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if (video_stream_idx < 0) 
    {
        fprintf(stderr, "Could not find %s stream in input file '%s'\n",
            av_get_media_type_string(AVMEDIA_TYPE_VIDEO), src_filename);
    }

    /**获取一条最好的音频流(如果输入文件没有音频流,将返回 -1)*/
    audio_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    if (audio_stream_idx < 0) 
    {
        fprintf(stderr, "Could not find %s stream in input file '%s'\n",
            av_get_media_type_string(AVMEDIA_TYPE_AUDIO), src_filename);
    }


    /* dump input information to stderr */
    av_dump_format(fmt_ctx, 0, src_filename, 0);/**在控制台显示流的信息*/

    if (!(video_stream_idx >= 0) && !(audio_stream_idx >= 0)) 
    {/**如果输入文件,既没有发下有效的音频流,也没发现有效的视频流,那就结束解析*/
        fprintf(stderr, "Could not find audio or video stream in the input, aborting\n");
        ret = 1;
        goto end;
    }

    /* initialize packet, set data to NULL, let the demuxer fill it */
    av_init_packet(&pkt);/**初始化元码流数据包结构*/
    pkt.data = NULL;
    pkt.size = 0;

    /* read frames from the file */
    while (av_read_frame(fmt_ctx, &pkt) >= 0)/**从avformat中读取一帧数据*/
    {
        //todo print 
        if (pkt.stream_index == video_stream_idx)
        {/**如果是视频数据包,视频包的计数值加1,并打印包的序号和大小*/
            video_pkt_count++;
            printf("video pkt number: %d, size: %d\n", video_pkt_count, pkt.size);
        }
        else if (pkt.stream_index == audio_stream_idx)
        {/**如果是音频数据包,音频包的计数值加1,并打印包的序号和大小*/
            audio_pkt_count++;
            printf("audio pkt number: %d, size: %d\n", audio_pkt_count, pkt.size);
        }


        av_packet_unref(&pkt);/**接触数据包的引用*/
    }

    printf("Demuxing succeeded.got video pkt: %d, audio pkt: %d\n", 
           video_pkt_count, audio_pkt_count);


end:
    /**关闭avformat*/
    avformat_close_input(&fmt_ctx);

    getchar();
    return ret < 0;
}

三、mux部分

3.1 AVOutputFormat结构体(Muxer的接口结构体)

和demux部分一样,mux部分也有一个核心的接口结构体——AVOutputFormat:

typedef struct AVOutputFormat {
    const char *name;/**容器短名*/
    const char *long_name;/**容器详细的长名*/
    const char *mime_type;/**MIME (Multipurpose Internet Mail Extensions)类型*/
    const char *extensions; /**容器文件的后缀名,可以是逗号隔开的列表 */

    enum AVCodecID audio_codec;    /**< 默认的音频编码器类型 */
    enum AVCodecID video_codec;    /**< 默认的视频编码器类型 */
    enum AVCodecID subtitle_codec; /**< 默认的字幕编码器类型 */

    int flags; /**标志*/

    /**容器支持的编码器数组,越靠前的就是对于当前容器越好的选择,并以AV_CODEC_ID_NONE结尾*/
    const struct AVCodecTag * const *codec_tag;


    const AVClass *priv_class; /**指向容器私有上下文结构的指针*/

    struct AVOutputFormat *next;/**指向下一个复用(mux)容器的指针*/

    int priv_data_size;/**创建容器实例时,容器内部私有数据结构的大小*/

    /**向容器中写入头部数据,比如容器类型,包含的元码流的基本信息等等*/
    int (*write_header)(struct AVFormatContext *);
    /**向容器中写入一个元码流数据包(可能是:音频,视频或字母等)*/
    int (*write_packet)(struct AVFormatContext *, AVPacket *pkt);

    /**向容器中写入尾部数据,比如更新索引表,完善容器基本信息等等*/
    int (*write_trailer)(struct AVFormatContext *);


    /** 交织一个包(不清楚这样注释是否准确,在使用AVOutputformat的时候
      * 并未用过这个API,avformat的注释上也说,当前只是在像素格式不是
      * YUV420P的时候来设置像素格式用的)         
      */
    int (*interleave_packet)(struct AVFormatContext *, AVPacket *out,
                             AVPacket *in, int flush);

    /**判断当前mux容器是否支持参数中的编码器类型以及标准*/
    int (*query_codec)(enum AVCodecID id, int std_compliance);

    /**获取直到上一个输出数据包的时间戳*/
    void (*get_output_timestamp)(struct AVFormatContext *s, int stream,
                                 int64_t *dts, int64_t *wall);

    /**上层应用向mux容器发送控制消息*/
    int (*control_message)(struct AVFormatContext *s, int type,
                           void *data, size_t data_size);

    /**向容器中写入一帧未编码的数据*/
    int (*write_uncoded_frame)(struct AVFormatContext *, int stream_index,
                               AVFrame **frame, unsigned 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);

    enum AVCodecID data_codec; /** 默认的数据编码格式*/

    int (*init)(struct AVFormatContext *);/**初始化mux容器实例*/

    void (*deinit)(struct AVFormatContext *);/**销毁mux容器实例*/

    /**为全局的头部信息设置所有需要的过滤器和提取所有的扩展数据*/
    int (*check_bitstream)(struct AVFormatContext *, const AVPacket *pkt);
} AVOutputFormat;

可以看出,AVOutputformat结构体和AVInputFormat结构体还是有很多的相似之处的,同样这里也拿出一个mux容器的实例来看看需要如何填充这个AVOutputformat结构体。

MOV_CLASS(mp4) /**定义mp4 mux容器的私有长下文结构体*/
AVOutputFormat ff_mp4_muxer = {
    .name              = "mp4", /**mux容器缩写名(短名)*/
    .long_name         = NULL_IF_CONFIG_SMALL("MP4 (MPEG-4 Part 14)"),/**详细信息的长名*/
    .mime_type         = "video/mp4",/**mime 类型*/
    .extensions        = "mp4",/**mux容器对应文件的扩展名*/
    .priv_data_size    = sizeof(MOVMuxContext),/**mp4 mux容器的私有数据结构的大小*/
    .audio_codec       = AV_CODEC_ID_AAC,/**默认音频编码格式为 AAC*/
    /** 根据编译时的配置决定默认视频编码是H264还是mpeg4
      *(LIBX264为第三个库,不属于ffmpeg)
      */
    .video_codec       = CONFIG_LIBX264_ENCODER ?
                         AV_CODEC_ID_H264 : AV_CODEC_ID_MPEG4,
    .init              = mov_init,/**容器初始化接口*/
    .write_header      = mov_write_header,/**向容器中写入头部信息的接口*/
    .write_packet      = mov_write_packet,/**向容器中写入元码流数据包的接口*/
    .write_trailer     = mov_write_trailer,/**向容器中写入尾部数据的接口*/
    .deinit            = mov_free,/**容器释放接口*/
    .flags             = AVFMT_GLOBALHEADER | AVFMT_ALLOW_FLUSH 
                         | AVFMT_TS_NEGATIVE,/**标志*/
     /**容器支持的编码格式数组*/
    .codec_tag         = (const AVCodecTag* const []){ ff_mp4_obj_type, 0 },
    /**为容器全局头部添加所需过滤器和提取所需扩展数据的接口*/
    .check_bitstream   = mov_check_bitstream,
    .priv_class        = &mp4_muxer_class,/**mp4 mux容器的私有上下文结构体实例*/
};

其中MOV_CLASS是一个宏定义,其内容如下:

#define MOV_CLASS(flavor)\
static const AVClass flavor ## _muxer_class = {\
    .class_name = #flavor " muxer",\
    .item_name  = av_default_item_name,\
    .option     = options,\
    .version    = LIBAVUTIL_VERSION_INT,\
};

其中options的内容太多就不贴出来了,可以到ffmpeg代码中的movenc.c(位于libavformat目录下)去查看。

然后是mp4 mux容器所支持的编码格式数据:

const AVCodecTag ff_mp4_obj_type[] = {
    { AV_CODEC_ID_MOV_TEXT    , 0x08 },
    { AV_CODEC_ID_MPEG4       , 0x20 },
    { AV_CODEC_ID_H264        , 0x21 },
    { AV_CODEC_ID_HEVC        , 0x23 },
    { AV_CODEC_ID_AAC         , 0x40 },
    { AV_CODEC_ID_MP4ALS      , 0x40 }, /* 14496-3 ALS */
    { AV_CODEC_ID_MPEG2VIDEO  , 0x61 }, /* MPEG-2 Main */
    { AV_CODEC_ID_MPEG2VIDEO  , 0x60 }, /* MPEG-2 Simple */
    { AV_CODEC_ID_MPEG2VIDEO  , 0x62 }, /* MPEG-2 SNR */
    { AV_CODEC_ID_MPEG2VIDEO  , 0x63 }, /* MPEG-2 Spatial */
    { AV_CODEC_ID_MPEG2VIDEO  , 0x64 }, /* MPEG-2 High */
    { AV_CODEC_ID_MPEG2VIDEO  , 0x65 }, /* MPEG-2 422 */
    { AV_CODEC_ID_AAC         , 0x66 }, /* MPEG-2 AAC Main */
    { AV_CODEC_ID_AAC         , 0x67 }, /* MPEG-2 AAC Low */
    { AV_CODEC_ID_AAC         , 0x68 }, /* MPEG-2 AAC SSR */
    { AV_CODEC_ID_MP3         , 0x69 }, /* 13818-3 */
    { AV_CODEC_ID_MP2         , 0x69 }, /* 11172-3 */
    { AV_CODEC_ID_MPEG1VIDEO  , 0x6A }, /* 11172-2 */
    { AV_CODEC_ID_MP3         , 0x6B }, /* 11172-3 */
    { AV_CODEC_ID_MJPEG       , 0x6C }, /* 10918-1 */
    { AV_CODEC_ID_PNG         , 0x6D },
    { AV_CODEC_ID_JPEG2000    , 0x6E }, /* 15444-1 */
    { AV_CODEC_ID_VC1         , 0xA3 },
    { AV_CODEC_ID_DIRAC       , 0xA4 },
    { AV_CODEC_ID_AC3         , 0xA5 },
    { AV_CODEC_ID_EAC3        , 0xA6 },
    { AV_CODEC_ID_DTS         , 0xA9 }, /* mp4ra.org */

    /* nonstandard, update when there is a standard value */
    { AV_CODEC_ID_VP9         , 0xC0 }, 

     /* nonstandard, update when there is a standard value */
    { AV_CODEC_ID_FLAC        , 0xC1 },


    { AV_CODEC_ID_TSCC2       , 0xD0 },/* nonstandard, camtasia uses it */    
    { AV_CODEC_ID_EVRC        , 0xD1 }, /* nonstandard, pvAuthor uses it */    
    { AV_CODEC_ID_VORBIS      , 0xDD }, /* nonstandard, gpac uses it */

     /* nonstandard, see unsupported-embedded-subs-2.mp4 */
    { AV_CODEC_ID_DVD_SUBTITLE, 0xE0 },

    { AV_CODEC_ID_QCELP       , 0xE1 },
    { AV_CODEC_ID_MPEG4SYSTEMS, 0x01 },
    { AV_CODEC_ID_MPEG4SYSTEMS, 0x02 },
    { AV_CODEC_ID_NONE        ,    0 },
};

里面包含了,音频,视频以及字幕的编码格式,内容还是挺丰富的。

3.2 复用(mux)相关的API

和上一章一样,这里也只是介绍mux相关的几个主要的API,其他的API可到ffmpeg代码中查看。

/** 向mux容器中写入头部数据,对应AVOutputformat中的write_header接口*/
int avformat_write_header(AVFormatContext *s, AVDictionary **options);

/**初始化mux容器,可选的调用接口,因为如果在调用avformat_write_header时,容器还未初始化,
 * avformat_write_header函数内部就会去调用avformat_init_output接口。
 * 对应AVOutputformat中的init接口
 */
int avformat_init_output(AVFormatContext *s, AVDictionary **options);

/**向容器中写入一帧元码流数据,对应AVOutputformat中的write_packet接口*/
int av_write_frame(AVFormatContext *s, AVPacket *pkt);

/** 向容器中写入一个元码流数据包,但会确保各条元码流能较好的交织存储(不让音视频时
  * 间戳差异太大),这样有利于更好的解复用.
  * 它以dts(解码时间戳)递增的顺序向容器中写入数据包,为了实现这一目标,内部会
  * 暂存不符合条件的数据包,直到写入条件满足
  */
int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);

/**写入一帧未编码的元码流数据帧,行为类似av_write_frame*/
int av_write_uncoded_frame(AVFormatContext *s, int stream_index,
                           AVFrame *frame);

/**写入一帧未编码的元码流数据帧,行为类似av_interleaved_write_frame*/
int av_interleaved_write_uncoded_frame(AVFormatContext *s, int stream_index,
                                       AVFrame *frame);

/**检测mux容器的第stream_index条元码流是否支持未编码的数据包*/
int av_write_uncoded_frame_query(AVFormatContext *s, int stream_index);

/** 向容器中写入尾部数据,并更新相应的信息(如容器基本信息,索引表,元码流基本信息等)
 *  这里没有单独的avformat_deinit_output函数,因为在本接口中就会调用到
 *  AVOutputformat中的deinit接口
 */
int av_write_trailer(AVFormatContext *s);

因为这篇文章的目标是简单应用,所以这里就不再一一去看这些主要接口的具体实现了,有兴趣的可以下载ffmpeg代码来慢慢研究,地址在前一篇文章《ffmpeg简介》当中标注。

3.3 mux的简单例子

因为这里会涉及到原始数据的来源(不管是从摄像头,麦克风,还是自动生成的数据),以及编码动作等较多环节。 而这里主要是说明mux的API的简单应用,故而这个例子只演示了muxer部分,其他部分均省略掉了,要看完整版的可以到ffmpeg的源代码中“doc/examples/”目录下的muxing.c中去查看较为完整的例子代码,其中使用的ffmpeg中avcodec的部分作为编码器。

关于mux的API的简单使用方式的代码如下:

    int ret;
    const char *filename = "out.mp4";/**最终输出文件的名字,可以加上路径*/
    AVFormatContext *oc;
    AVStream *audioSt, *videoSt;
    AVDictionary *opt = NULL;
    AVPacket pkt;
    int isRequestExit = 0;

    /* Initialize libavcodec, and register all codecs and formats. */
    av_register_all();/**注册所有组件*/

    /**创建输出类型的avformat上下文实例*/
    avformat_alloc_output_context2(&oc, NULL, NULL, filename);


    videoSt = avformat_new_stream(oc, NULL);/**添加视频流*/

    /**可在这个地方对视频流进行初始化,如创建视频编码器等*/

    audioSt = avformat_new_stream(oc, NULL);/**添加音频流*/

     /**可在这个地方对音频流进行初始化,如创建音频编码器等*/

    /* Write the stream header, if any. */
    ret = avformat_write_header(oc, &opt);/**开始写入头部数据 */

    while (isRequestExit == 0)
    {
        /**这里省略数据获取的过程,如从camera读取数据,然后经过视频编码,得到编码后的数据*/

        /**向容器中写入一个元码流数据包,在此将进行“mux”动作 */
        av_interleaved_write_frame(oc, &pkt); 
    }


    av_write_trailer(oc); /**写入尾部数据,更新头部数据,索引表等信息*/


    /* free the stream */
    avformat_free_context(oc);  /**释放输出类型的avformat上下文实例*/

猜你喜欢

转载自blog.csdn.net/xuanwolanxue/article/details/73498744