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