注意:本文是基因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上下文實例*/