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上下文實例*/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章