av_interleaved_write_frame
的主要作用是将编码后的数据写入输出文件,并保证数据的正确排序,这个函数会在写入数据时根据时间戳对数据进行排序,确保数据的正确性和同步性,这次我们就针对这个函数进行源码的解析。
一、流程解析
首先我们先给出av_interleaved_write_frame
的源码:
cpp
int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt)
{
int ret, flush = 0;
ret = prepare_input_packet(s, pkt);
if (ret < 0)
goto fail;
if (pkt) {
AVStream *st = s->streams[pkt->stream_index];
ret = do_packet_auto_bsf(s, pkt);
if (ret == 0)
return 0;
else if (ret < 0)
goto fail;
if (s->debug & FF_FDEBUG_TS)
av_log(s, AV_LOG_TRACE, "av_interleaved_write_frame size:%d dts:%s pts:%s\n",
pkt->size, av_ts2str(pkt->dts), av_ts2str(pkt->pts));
#if FF_API_COMPUTE_PKT_FIELDS2 && FF_API_LAVF_AVCTX
if ((ret = compute_muxer_pkt_fields(s, st, pkt)) < 0 && !(s->oformat->flags & AVFMT_NOTIMESTAMPS))
goto fail;
#endif
if (pkt->dts == AV_NOPTS_VALUE && !(s->oformat->flags & AVFMT_NOTIMESTAMPS)) {
ret = AVERROR(EINVAL);
goto fail;
}
} else {
av_log(s, AV_LOG_TRACE, "av_interleaved_write_frame FLUSH\n");
flush = 1;
}
for (;; ) {
AVPacket opkt;
int ret = interleave_packet(s, &opkt, pkt, flush);
if (pkt) {
memset(pkt, 0, sizeof(*pkt));
av_init_packet(pkt);
pkt = NULL;
}
if (ret <= 0) //FIXME cleanup needed for ret<0 ?
return ret;
ret = write_packet(s, &opkt);
if (ret >= 0)
s->streams[opkt.stream_index]->nb_frames++;
av_packet_unref(&opkt);
if (ret < 0)
return ret;
if(s->pb && s->pb->error)
return s->pb->error;
}
fail:
av_packet_unref(pkt);
return ret;
}
我们可以发现av_interleaved_write_frame
主要逻辑为:
prepare_input_packet
:准备输入的数据包,包括复制数据包,以及根据需要调整数据包的时间戳和持续时间do_packet_auto_bsf
:自动应用比特流过滤器(BitStream Filter),在某些情况下,我们需要对比特流进行一些额外的处理,比如添加SPS/PPS头,这个函数就是用来完成这些处理的。compute_muxer_pkt_fields
:这个函数主要是计算复用器(Muxer)的数据包字段,包括PTS(Presentation Time Stamp)、DTS(Decoding Time Stamp)等。这些字段对于视频的同步播放非常重要interleave_packet
:主要作用是将数据包插入到一个队列中,并且保证队列中的数据包按照DTS排序,这是为了保证数据的正确性和同步性。write_packet
:这个函数的作用是将数据包写入到输出文件中,它会调用底层的I/O函数,将数据实际写入到文件中。av_packet_unref
:这个函数的作用是释放数据包,在数据包被处理完毕后,我们需要调用这个函数来释放函数包占用内存。
二、解析prepare_input_packet
首先贴出源码
cpp
static int prepare_input_packet(AVFormatContext *s, AVPacket *pkt)
{
int ret;
ret = check_packet(s, pkt);
if (ret < 0)
return ret;
#if !FF_API_COMPUTE_PKT_FIELDS2 || !FF_API_LAVF_AVCTX
/* sanitize the timestamps */
if (!(s->oformat->flags & AVFMT_NOTIMESTAMPS)) {
AVStream *st = s->streams[pkt->stream_index];
/* when there is no reordering (so dts is equal to pts), but
* only one of them is set, set the other as well */
if (!st->internal->reorder) {
if (pkt->pts == AV_NOPTS_VALUE && pkt->dts != AV_NOPTS_VALUE)
pkt->pts = pkt->dts;
if (pkt->dts == AV_NOPTS_VALUE && pkt->pts != AV_NOPTS_VALUE)
pkt->dts = pkt->pts;
}
/* check that the timestamps are set */
if (pkt->pts == AV_NOPTS_VALUE || pkt->dts == AV_NOPTS_VALUE) {
av_log(s, AV_LOG_ERROR,
"Timestamps are unset in a packet for stream %d\n", st->index);
return AVERROR(EINVAL);
}
/* check that the dts are increasing (or at least non-decreasing,
* if the format allows it */
if (st->cur_dts != AV_NOPTS_VALUE &&
((!(s->oformat->flags & AVFMT_TS_NONSTRICT) && st->cur_dts >= pkt->dts) ||
st->cur_dts > pkt->dts)) {
av_log(s, AV_LOG_ERROR,
"Application provided invalid, non monotonically increasing "
"dts to muxer in stream %d: %" PRId64 " >= %" PRId64 "\n",
st->index, st->cur_dts, pkt->dts);
return AVERROR(EINVAL);
}
if (pkt->pts < pkt->dts) {
av_log(s, AV_LOG_ERROR, "pts %" PRId64 " < dts %" PRId64 " in stream %d\n",
pkt->pts, pkt->dts, st->index);
return AVERROR(EINVAL);
}
}
#endif
return 0;
}
对照上述源码我们可以发现,其首先调用check_packet
检查:
- AVPacket是否为空、其Stream流的index是否不合法(index本身大于0,且不超过AVFormatContext流的总数)
- 检查AVFormatContext里AVStream流对应的编解码器是否有问题
然后会对数据包的时间戳进行检查处理,也就是如下的代码:
cpp
#if !FF_API_COMPUTE_PKT_FIELDS2 || !FF_API_LAVF_AVCTX
/* sanitize the timestamps */
if (!(s->oformat->flags & AVFMT_NOTIMESTAMPS)) {
AVStream *st = s->streams[pkt->stream_index];
/* when there is no reordering (so dts is equal to pts), but
* only one of them is set, set the other as well */
if (!st->internal->reorder) {
if (pkt->pts == AV_NOPTS_VALUE && pkt->dts != AV_NOPTS_VALUE)
pkt->pts = pkt->dts;
if (pkt->dts == AV_NOPTS_VALUE && pkt->pts != AV_NOPTS_VALUE)
pkt->dts = pkt->pts;
}
/* check that the timestamps are set */
if (pkt->pts == AV_NOPTS_VALUE || pkt->dts == AV_NOPTS_VALUE) {
av_log(s, AV_LOG_ERROR,
"Timestamps are unset in a packet for stream %d\n", st->index);
return AVERROR(EINVAL);
}
/* check that the dts are increasing (or at least non-decreasing,
* if the format allows it */
if (st->cur_dts != AV_NOPTS_VALUE &&
((!(s->oformat->flags & AVFMT_TS_NONSTRICT) && st->cur_dts >= pkt->dts) ||
st->cur_dts > pkt->dts)) {
av_log(s, AV_LOG_ERROR,
"Application provided invalid, non monotonically increasing "
"dts to muxer in stream %d: %" PRId64 " >= %" PRId64 "\n",
st->index, st->cur_dts, pkt->dts);
return AVERROR(EINVAL);
}
if (pkt->pts < pkt->dts) {
av_log(s, AV_LOG_ERROR, "pts %" PRId64 " < dts %" PRId64 " in stream %d\n",
pkt->pts, pkt->dts, st->index);
return AVERROR(EINVAL);
}
}
#endif
对照上述代码我们可以发现其会判断输出格式是需要时间戳的(即没有设置AVFMT_NOTIMESTAMPS标志),那么就进行以下操作:
- 获取数据包对应的流(AVStream)
- 检查是否需要对数据包进行重排序。如果不需要重排序(即DTS等于PTS),但是只设置了其中一个时间戳,那么就将另一个时间戳也设置为相同的值(st->internal->reorder是什么)
- 检查数据包的PTS和DTS是否都已设置。如果有任意没有设置,则会打印报错日志并返回错误
- 检查数据包的DTS是否是递增的(或者至少是非递减的,如果输出格式允许)。如果不是,则会打印报错日志并返回错误
- 检查数据包的PTS是否大于DTS。如果不是,则会打印报错日志并返回错误
这段代码的主要目的是确保数据包的时间戳符合要求,因为时间戳对于数据的正确播放非常重要。如果时间戳有任何问题,那么可能会导致数据的播放顺序混乱,或者无法正确播放。
三、解析do_packet_auto_bsf
首先我们贴出实现源码
cpp
static int do_packet_auto_bsf(AVFormatContext *s, AVPacket *pkt) {
AVStream *st = s->streams[pkt->stream_index];
int i, ret;
if (!(s->flags & AVFMT_FLAG_AUTO_BSF))
return 1;
if (s->oformat->check_bitstream) {
if (!st->internal->bitstream_checked) {
if ((ret = s->oformat->check_bitstream(s, pkt)) < 0)
return ret;
else if (ret == 1)
st->internal->bitstream_checked = 1;
}
}
#if FF_API_LAVF_MERGE_SD
FF_DISABLE_DEPRECATION_WARNINGS
if (st->internal->nb_bsfcs) {
ret = av_packet_split_side_data(pkt);
if (ret < 0)
av_log(s, AV_LOG_WARNING, "Failed to split side data before bitstream filter\n");
}
FF_ENABLE_DEPRECATION_WARNINGS
#endif
for (i = 0; i < st->internal->nb_bsfcs; i++) {
AVBSFContext *ctx = st->internal->bsfcs[i];
// TODO: when any bitstream filter requires flushing at EOF, we'll need to
// flush each stream's BSF chain on write_trailer.
if ((ret = av_bsf_send_packet(ctx, pkt)) < 0) {
av_log(ctx, AV_LOG_ERROR,
"Failed to send packet to filter %s for stream %d\n",
ctx->filter->name, pkt->stream_index);
return ret;
}
// TODO: when any automatically-added bitstream filter is generating multiple
// output packets for a single input one, we'll need to call this in a loop
// and write each output packet.
if ((ret = av_bsf_receive_packet(ctx, pkt)) < 0) {
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
return 0;
av_log(ctx, AV_LOG_ERROR,
"Failed to send packet to filter %s for stream %d\n",
ctx->filter->name, pkt->stream_index);
return ret;
}
}
return 1;
}
对照上述代码我们会发现,其逻辑是:
AVStream *st = s->streams[pkt->stream_index];
:获取数据包对应的流AVStream
- 判断AVStream流里的Flag是否为
AVFMT_FLAG_AUTO_BSF
,如果没有设置自动比特流过滤器的标志,那么就直接返回 if (s->oformat->check_bitstream) {...}
:如果存在检查比特流的函数,那么就调用这个函数进行检查,如果检查出错,那么就返回错误。如果检查成功,那么就设置比特流为已经被检查的标志。下次就不会再检查了if (st->internal->nb_bsfcs) {...}
:如果存在比特流过滤器,那么就分离数据包的边际数据。如果分离出错,那么就会打印错误告警信息。for (i = 0; i < st->internal->nb_bsfcs; i++) {...}
:对每一个比特流过滤器,都将数据包送入过滤器进行处理。如果送入过滤器出错,那么就打印错误信息并返回错误。然后,从过滤器接受处理后的数据包。如果接受出错,那么就打印错误信息并返回错误。如果接受成功,那么就继续处理下一个过滤器return 1
:如果所有的过滤器都处理成功,那么就返回1,表示处理成功。
这个函数的主要作用是对数据包应用比特流过滤器。比特流过滤器可以对数据包进行一些额外的处理,比如添加SPS/PPS头,或者进行其他的一些转换。这个函数就是用来完成这些处理的。
四、解析compute_muxer_pkt_fields
执行compute_muxer_pkt_fields
会先做如下的检查:
cpp
#if FF_API_COMPUTE_PKT_FIELDS2 && FF_API_LAVF_AVCTX
ret = compute_muxer_pkt_fields(s, s->streams[pkt->stream_index], pkt);
if (ret < 0 && !(s->oformat->flags & AVFMT_NOTIMESTAMPS))
return ret;
#endif
结合上述代码,它会检查FF_API_COMPUTE_PKT_FIELDS2
和FF_API_LAVF_AVCTX
是否被定义,如果执行compute_muxer_pkt_fields出现问题且当前s->oformat->flags
是需要时间戳的时候,会返回错误。接下来我们浅浅分析下compute_muxer_pkt_fields
的源码:
cpp
static int compute_muxer_pkt_fields(AVFormatContext *s, AVStream *st, AVPacket *pkt)
{
int delay = FFMAX(st->codecpar->video_delay, st->internal->avctx->max_b_frames > 0);
int num, den, i;
int frame_size;
if (!s->internal->missing_ts_warning &&
!(s->oformat->flags & AVFMT_NOTIMESTAMPS) &&
(!(st->disposition & AV_DISPOSITION_ATTACHED_PIC) || (st->disposition & AV_DISPOSITION_TIMED_THUMBNAILS)) &&
(pkt->pts == AV_NOPTS_VALUE || pkt->dts == AV_NOPTS_VALUE)) {
av_log(s, AV_LOG_WARNING,
"Timestamps are unset in a packet for stream %d. "
"This is deprecated and will stop working in the future. "
"Fix your code to set the timestamps properly\n", st->index);
s->internal->missing_ts_warning = 1;
}
if (s->debug & FF_FDEBUG_TS)
av_log(s, AV_LOG_TRACE, "compute_muxer_pkt_fields: pts:%s dts:%s cur_dts:%s b:%d size:%d st:%d\n",
av_ts2str(pkt->pts), av_ts2str(pkt->dts), av_ts2str(st->cur_dts), delay, pkt->size, pkt->stream_index);
if (pkt->duration < 0 && st->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
av_log(s, AV_LOG_WARNING, "Packet with invalid duration %"PRId64" in stream %d\n",
pkt->duration, pkt->stream_index);
pkt->duration = 0;
}
/* duration field */
if (pkt->duration == 0) {
ff_compute_frame_duration(s, &num, &den, st, NULL, pkt);
if (den && num) {
pkt->duration = av_rescale(1, num * (int64_t)st->time_base.den * st->codec->ticks_per_frame, den * (int64_t)st->time_base.num);
}
}
if (pkt->pts == AV_NOPTS_VALUE && pkt->dts != AV_NOPTS_VALUE && delay == 0)
pkt->pts = pkt->dts;
//XXX/FIXME this is a temporary hack until all encoders output pts
if ((pkt->pts == 0 || pkt->pts == AV_NOPTS_VALUE) && pkt->dts == AV_NOPTS_VALUE && !delay) {
static int warned;
if (!warned) {
av_log(s, AV_LOG_WARNING, "Encoder did not produce proper pts, making some up.\n");
warned = 1;
}
pkt->dts =
// pkt->pts= st->cur_dts;
pkt->pts = st->priv_pts->val;
}
//calculate dts from pts
if (pkt->pts != AV_NOPTS_VALUE && pkt->dts == AV_NOPTS_VALUE && delay <= MAX_REORDER_DELAY) {
st->pts_buffer[0] = pkt->pts;
for (i = 1; i < delay + 1 && st->pts_buffer[i] == AV_NOPTS_VALUE; i++)
st->pts_buffer[i] = pkt->pts + (i - delay - 1) * pkt->duration;
for (i = 0; i<delay && st->pts_buffer[i] > st->pts_buffer[i + 1]; i++)
FFSWAP(int64_t, st->pts_buffer[i], st->pts_buffer[i + 1]);
pkt->dts = st->pts_buffer[0];
}
if (st->cur_dts && st->cur_dts != AV_NOPTS_VALUE &&
((!(s->oformat->flags & AVFMT_TS_NONSTRICT) &&
st->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE &&
st->codecpar->codec_type != AVMEDIA_TYPE_DATA &&
st->cur_dts >= pkt->dts) || st->cur_dts > pkt->dts)) {
av_log(s, AV_LOG_ERROR,
"Application provided invalid, non monotonically increasing dts to muxer in stream %d: %s >= %s\n",
st->index, av_ts2str(st->cur_dts), av_ts2str(pkt->dts));
return AVERROR(EINVAL);
}
if (pkt->dts != AV_NOPTS_VALUE && pkt->pts != AV_NOPTS_VALUE && pkt->pts < pkt->dts) {
av_log(s, AV_LOG_ERROR,
"pts (%s) < dts (%s) in stream %d\n",
av_ts2str(pkt->pts), av_ts2str(pkt->dts),
st->index);
return AVERROR(EINVAL);
}
if (s->debug & FF_FDEBUG_TS)
av_log(s, AV_LOG_TRACE, "av_write_frame: pts2:%s dts2:%s\n",
av_ts2str(pkt->pts), av_ts2str(pkt->dts));
st->cur_dts = pkt->dts;
st->priv_pts->val = pkt->dts;
/* update pts */
switch (st->codecpar->codec_type) {
case AVMEDIA_TYPE_AUDIO:
frame_size = (pkt->flags & AV_PKT_FLAG_UNCODED_FRAME) ?
((AVFrame *)pkt->data)->nb_samples :
av_get_audio_frame_duration(st->codec, pkt->size);
/* HACK/FIXME, we skip the initial 0 size packets as they are most
* likely equal to the encoder delay, but it would be better if we
* had the real timestamps from the encoder */
if (frame_size >= 0 && (pkt->size || st->priv_pts->num != st->priv_pts->den >> 1 || st->priv_pts->val)) {
frac_add(st->priv_pts, (int64_t)st->time_base.den * frame_size);
}
break;
case AVMEDIA_TYPE_VIDEO:
frac_add(st->priv_pts, (int64_t)st->time_base.den * st->time_base.num);
break;
}
return 0;
}
结合上述源码,我们可以发现其流程如下:
FFMAX(st->codecpar->video_delay, st->internal->avctx->max_b_frames > 0)
计算延迟,取视频延迟和B帧数量大于0的最大值- 一系列检查:
- 检查时间戳的设置:如果数据包的pts和dts都没有设置,并且输出格式需要时间戳,那么就会打印一条告警信息,并设置
missing_ts_warning
标志 - 如果开启了时间戳调试,那么就打印一些调试信息,包括pts、dts、当前dts、延迟、数据包大小和流索引
- 检查时间戳的设置:如果数据包的pts和dts都没有设置,并且输出格式需要时间戳,那么就会打印一条告警信息,并设置
- 计算数据包的持续时间
- 如果数据包的持续时间小于0,并且编码类型不是字幕,那么就打印一条告警信息,并将持续时间设置为0
- 计算数据包的持续时间,如果持续时间为0,那么就调用
ff_compute_frame_duration
函数计算持续时间,并根据计算结果更新数据包的持续时间
- 校正数据包的pts(Presentation Time Stamp,显示时间戳)和dts(Decoding TimeStamp,解码时间戳)
- 如果数据包的pts没有设置,但dts设置了,并且延迟为0,那么就将pts设置成dts。
- 如果pts没有设置,并且dts也没有设置,并且延迟为0,则打印一条警告信息,并将pts和dts设置成当前dts
- 然后继续检查数据包(pkt)的pts和dts,如果pts存在,而dts不存在,且延迟小于等于最大重排序延迟,那么就会计算dts。计算方式为,先将pts存入缓冲区,然后根据延迟和持续时间计算出后续的dts,最后将缓冲区的数据将进行排序,取出最小的作为dts。
- 然后会检查当前dts(st->cur_dts)是否有效,以及是否满足一定的条件。如果不满足,就会记录错误日志,并返回错误码
- 在之后就会检查数据包的pts和dts是否都存在,以及pts是否小于dts。如果pts小于dts,那么就会记录错误日志,并返回错误码
- 接着,如果开启了时间戳调试,就会记录pts和dts的日志
- 然后,更新当前的dts和私有pts
- 最后,会根据编解码器的类型,更新pts。对于音频数据,会根据帧大小和时间基准来计算pts。对于视频数据,会根据时间基准来计算pts
五、解析write_packet
首先贴出源码,然后我们一步一步拆解
cpp
static int write_packet(AVFormatContext *s, AVPacket *pkt)
{
int ret, did_split;
int64_t pts_backup, dts_backup;
pts_backup = pkt->pts;
dts_backup = pkt->dts;
// If the timestamp offsetting below is adjusted, adjust
// ff_interleaved_peek similarly.
if (s->output_ts_offset) {
AVStream *st = s->streams[pkt->stream_index];
int64_t offset = av_rescale_q(s->output_ts_offset, AV_TIME_BASE_Q, st->time_base);
if (pkt->dts != AV_NOPTS_VALUE)
pkt->dts += offset;
if (pkt->pts != AV_NOPTS_VALUE)
pkt->pts += offset;
}
if (s->avoid_negative_ts > 0) {
AVStream *st = s->streams[pkt->stream_index];
int64_t offset = st->mux_ts_offset;
int64_t ts = s->internal->avoid_negative_ts_use_pts ? pkt->pts : pkt->dts;
if (s->internal->offset == AV_NOPTS_VALUE && ts != AV_NOPTS_VALUE &&
(ts < 0 || s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_MAKE_ZERO)) {
s->internal->offset = -ts;
s->internal->offset_timebase = st->time_base;
}
if (s->internal->offset != AV_NOPTS_VALUE && !offset) {
offset = st->mux_ts_offset =
av_rescale_q_rnd(s->internal->offset,
s->internal->offset_timebase,
st->time_base,
AV_ROUND_UP);
}
if (pkt->dts != AV_NOPTS_VALUE)
pkt->dts += offset;
if (pkt->pts != AV_NOPTS_VALUE)
pkt->pts += offset;
if (s->internal->avoid_negative_ts_use_pts) {
if (pkt->pts != AV_NOPTS_VALUE && pkt->pts < 0) {
av_log(s, AV_LOG_WARNING, "failed to avoid negative "
"pts %s in stream %d.\n"
"Try -avoid_negative_ts 1 as a possible workaround.\n",
av_ts2str(pkt->pts),
pkt->stream_index
);
}
} else {
av_assert2(pkt->dts == AV_NOPTS_VALUE || pkt->dts >= 0 || s->max_interleave_delta > 0);
if (pkt->dts != AV_NOPTS_VALUE && pkt->dts < 0) {
av_log(s, AV_LOG_WARNING,
"Packets poorly interleaved, failed to avoid negative "
"timestamp %s in stream %d.\n"
"Try -max_interleave_delta 0 as a possible workaround.\n",
av_ts2str(pkt->dts),
pkt->stream_index
);
}
}
}
#if FF_API_LAVF_MERGE_SD
FF_DISABLE_DEPRECATION_WARNINGS
did_split = av_packet_split_side_data(pkt);
FF_ENABLE_DEPRECATION_WARNINGS
#endif
if (!s->internal->header_written) {
ret = s->internal->write_header_ret ? s->internal->write_header_ret : write_header_internal(s);
if (ret < 0)
goto fail;
}
if ((pkt->flags & AV_PKT_FLAG_UNCODED_FRAME)) {
AVFrame *frame = (AVFrame *)pkt->data;
av_assert0(pkt->size == UNCODED_FRAME_PACKET_SIZE);
ret = s->oformat->write_uncoded_frame(s, pkt->stream_index, &frame, 0);
av_frame_free(&frame);
} else {
ret = s->oformat->write_packet(s, pkt);
}
if (s->pb && ret >= 0) {
flush_if_needed(s);
if (s->pb->error < 0)
ret = s->pb->error;
}
fail:
#if FF_API_LAVF_MERGE_SD
FF_DISABLE_DEPRECATION_WARNINGS
if (did_split)
av_packet_merge_side_data(pkt);
FF_ENABLE_DEPRECATION_WARNINGS
#endif
if (ret < 0) {
pkt->pts = pts_backup;
pkt->dts = dts_backup;
}
return ret;
}
我们会发现:
- 首先,它备份了数据的dts和pts值,pts是presentation timestamp,表示这个数据包应该何时被播放;dts是decoding timestamp,表示这个数据包何时被解码
- 如果设置了
output_ts_offset
,那么将这个偏移量加到pts和dts上。 - 如果设置了
avoid_negative_ts
,那么会尝试避免pts和dts的值为负。如果pts和dts的值为负,那么会计算一个offset值,并将其加到pts和dts上。如果加上offset后pts或dts仍然为负,那么会打印一条警告日志。 - 然后,如果数据包包含了未编码的帧,那么会调用
write_uncoded_frame
函数将这个帧写入输出;否则,会调用write_packet
函数将数据包写入输出。 - 如果在写入过程中出现了错误,那么会恢复数据包的pts和dts为备份的值,并返回错误码。