FFMPEG:具有不同持续时间的多路复用流

FFMPEG: multiplexing streams with different duration

本文关键字:多路 复用 持续时间 FFMPEG      更新时间:2023-10-16

我正在多路复用视频和音频流。视频流来自生成的图像数据。音频流来自 aac 文件。某些音频文件比我设置的总视频时间长,因此我的策略是在音频流复用器的时间大于总视频时间时停止其策略(最后一个我通过数字编码的视频帧控制)。

我不会在这里放置整个设置代码,但它类似于最新 FFMPEG 存储库中的 muxing.c 示例。唯一的区别是我使用来自文件的音频流,正如我所说,而不是来自合成生成的编码帧。我很确定问题出在复用器循环期间我的错误同步中。这是我的工作:

void AudioSetup(const char* audioInFileName)
{
AVOutputFormat* outputF = mOutputFormatContext->oformat;
auto audioCodecId = outputF->audio_codec;
if (audioCodecId == AV_CODEC_ID_NONE) {
return false;
}
audio_codec = avcodec_find_encoder(audioCodecId);
avformat_open_input(&mInputAudioFormatContext,
audioInFileName, 0, 0);
avformat_find_stream_info(mInputAudioFormatContext, 0);
av_dump_format(mInputAudioFormatContext, 0, audioInFileName, 0);

for (size_t i = 0; i < mInputAudioFormatContext->nb_streams; i++) {
if (mInputAudioFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
inAudioStream = mInputAudioFormatContext->streams[i];
AVCodecParameters *in_codecpar = inAudioStream->codecpar;
mAudioOutStream.st = avformat_new_stream(mOutputFormatContext, NULL);
mAudioOutStream.st->id = mOutputFormatContext->nb_streams - 1;
AVCodecContext* c = avcodec_alloc_context3(audio_codec);
mAudioOutStream.enc = c;
c->sample_fmt = audio_codec->sample_fmts[0];
avcodec_parameters_to_context(c, inAudioStream->codecpar);
//copyparams from input to autput audio stream:
avcodec_parameters_copy(mAudioOutStream.st->codecpar, inAudioStream->codecpar);
mAudioOutStream.st->time_base.num = 1;
mAudioOutStream.st->time_base.den = c->sample_rate;
c->time_base = mAudioOutStream.st->time_base;
if (mOutputFormatContext->oformat->flags & AVFMT_GLOBALHEADER) {
c->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
break;
}
}
}
void Encode()
{
int cc = av_compare_ts(mVideoOutStream.next_pts, mVideoOutStream.enc->time_base,
mAudioOutStream.next_pts, mAudioOutStream.enc->time_base);
if (mAudioOutStream.st == NULL || cc <= 0) {
uint8_t* data = GetYUVFrame();//returns ready video YUV frame to work with
int ret = 0;
AVPacket pkt = { 0 };
av_init_packet(&pkt);
pkt.size = packet->dataSize;
pkt.data = data;
const int64_t duration = av_rescale_q(1, mVideoOutStream.enc->time_base, mVideoOutStream.st->time_base);
pkt.duration = duration;
pkt.pts = mVideoOutStream.next_pts;
pkt.dts = mVideoOutStream.next_pts;
mVideoOutStream.next_pts += duration;
pkt.stream_index = mVideoOutStream.st->index;
ret = av_interleaved_write_frame(mOutputFormatContext, &pkt);
} else
if(audio_time <  video_time) {
//5 -  duration of video in seconds
AVRational r = {  60, 1 };
auto cmp= av_compare_ts(mAudioOutStream.next_pts, mAudioOutStream.enc->time_base, 5, r);
if (cmp >= 0) {
mAudioOutStream.next_pts = (int64_t)std::numeric_limits<int64_t>::max();
return true; //don't mux audio anymore
}
AVPacket a_pkt = { 0 };
av_init_packet(&a_pkt);
int ret = 0;
ret = av_read_frame(mInputAudioFormatContext, &a_pkt);
//if audio file is shorter than stop muxing when at the end of the file
if (ret == AVERROR_EOF) {
mAudioOutStream.next_pts = (int64_t)std::numeric_limits<int64_t>::max(); 
return true;
}
a_pkt.stream_index = mAudioOutStream.st->index;
av_packet_rescale_ts(&a_pkt, inAudioStream->time_base, mAudioOutStream.st->time_base);
mAudioOutStream.next_pts += a_pkt.pts;
ret = av_interleaved_write_frame(mOutputFormatContext, &a_pkt);
}
}

现在,视频部分完美无缺。但是,如果音轨比视频持续时间长,我的总视频长度会延长约 5% - 20%,很明显,音频对此做出了贡献,因为视频帧正好在应该有的地方完成。

我最接近的"黑客"是这部分:

AVRational r = {  60 ,1 };
auto cmp= av_compare_ts(mAudioOutStream.next_pts, mAudioOutStream.enc->time_base, 5, r);
if (cmp >= 0) {
mAudioOutStream.next_pts = (int64_t)std::numeric_limits<int64_t>::max();
return true;
} 

在这里,我试图将音频流的next_pts与视频文件设置的总时间进行比较,即 5 秒。通过设置r = {60,1}我正在通过音频流的time_base转换这些秒数。至少我相信我正在做的事情。通过这个技巧,当使用标准 AAC 文件时,我与正确电影长度的偏差非常小,即 44100,立体声的采样率。但是,如果我使用更多有问题的样本进行测试,例如 AAC 采样率 16000,单声道 - 那么视频文件的大小几乎增加了一整秒。 如果有人能指出我在这里做错了什么,我将不胜感激。

重要说明:我没有为任何上下文设置持续时间。我控制基于视频帧数的复用会话的终止。当然,音频输入流有持续时间,但它对我没有帮助,因为视频持续时间是定义电影长度的原因。

更新:

这是第二次赏金尝试。

更新 2:

实际上,我的音频时间戳 {den,num} 是错误的,而 {1,1} 确实是要走的路,正如答案所解释的那样。阻止它工作的是这一行中的一个错误(我的坏):

mAudioOutStream.next_pts += a_pkt.pts;

必须是:

mAudioOutStream.next_pts = a_pkt.pts;

该错误导致 pts 呈指数级增长,这导致非常早地到达流的末尾(以 pts 为单位),因此导致音频流比预期提前终止。

问题是你告诉它将给定的音频时间与60 seconds per tick处的5刻度进行比较。我真的很惊讶它在某些情况下有效,但我想这真的取决于给定音频流的具体time_base

假设音频的time_base1/25,并且流为6秒,这比您想要的要多,因此您希望av_compare_ts返回01。在这些条件下,你将具有以下值:

mAudioOutStream.next_pts = 150
mAudioOutStream.enc->time_base = 1/25

因此,您可以使用以下参数调用av_compare_ts

ts_a = 150
tb_a = 1/25
ts_b = 5
tb_b = 60/1

现在我们来看看av_compare_ts的实现:

int av_compare_ts(int64_t ts_a, AVRational tb_a, int64_t ts_b, AVRational tb_b)
{
int64_t a = tb_a.num * (int64_t)tb_b.den;
int64_t b = tb_b.num * (int64_t)tb_a.den;
if ((FFABS(ts_a)|a|FFABS(ts_b)|b) <= INT_MAX)
return (ts_a*a > ts_b*b) - (ts_a*a < ts_b*b);
if (av_rescale_rnd(ts_a, a, b, AV_ROUND_DOWN) < ts_b)
return -1;
if (av_rescale_rnd(ts_b, b, a, AV_ROUND_DOWN) < ts_a)
return 1;
return 0;
}

给定上述值,您将获得:

a = 1 * 1 = 1
b = 60 * 25 = 1500

然后使用以下参数调用av_rescale_rnd

a = 150
b = 1
c = 1500
rnd = AV_ROUND_DOWN

给定我们的参数,我们实际上可以将整个函数av_rescale_rnd剥离到下一行。(我不会复制整个函数体av_rescale_rnd因为它相当长,但你可以在这里查看它。

return (a * b) / c;

这将返回(150 * 1) / 1500,即0

因此av_rescale_rnd(ts_a, a, b, AV_ROUND_DOWN) < ts_b将解析为true,因为0小于ts_b(5),所以av_compare_ts将返回-1,这完全不是你想要的。

如果您将r更改为1/1它应该可以工作,因为现在您的5实际上将被视为5 seconds

ts_a = 150
tb_a = 1/25
ts_b = 5
tb_b = 1/1

av_compare_ts,我们现在得到:

a = 1 * 1 = 1
b = 1 * 25 = 25

然后使用以下参数调用av_rescale_rnd

a = 150
b = 1
c = 25
rnd = AV_ROUND_DOWN

这将返回(150 * 1) / 25,即6

6大于5,则条件失败,并再次调用av_rescale_rnd,这次为:

a = 5
b = 25
c = 1
rnd = AV_ROUND_DOWN

这将返回(5 * 25) / 1,这是125。这比150,因此返回1,瞧,您的问题解决了。

如果step_size大于 1

如果您的音频流的step_size不是1,您需要修改您的r来解决这个问题,例如step_size = 1024

r = { 1, 1024 };

让我们快速回顾一下现在发生的事情:

在 ~6 秒时:

mAudioOutStream.next_pts = 282
mAudioOutStream.enc->time_base = 1/48000

av_compare_ts获取以下参数:

ts_a = 282
tb_a = 1/48000
ts_b = 5
tb_b = 1/1024

因此:

a = 1 * 1024 = 1024
b = 1 * 48000 = 48000

av_rescale_rnd

a = 282
b = 1024
c = 48000
rnd = AV_ROUND_DOWN

(a * b) / c将给出(282 * 1024) / 48000=288768 / 48000这是6.

有了r={1,1},你会再次得到0,因为它会计算(281 * 1) / 48000.