FFMPEG:多路复用具有不同持续时间的流
FFMPEG: multiplexing streams with different duration
我正在复用视频和音频流。视频流来自生成的图像数据。音频流来自 aac 文件。有些音频文件比我设置的总视频时间长,所以我的策略是当音频流复用器的时间大于总视频时间(我通过数字编码视频帧控制的最后一个)时停止音频流复用器。
我不会把整个设置代码放在这里,但它类似于来自最新 FFMPEG 存储库的 muxing.c 示例。唯一的区别是,正如我所说,我使用的是来自文件的音频流,而不是来自合成生成的编码帧的音频流。我很确定问题是在 muxer loop.Here 期间我的错误同步是我所做的:
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%,而且很明显,音频对此有所贡献,因为视频帧正好在应该出现的位置完成。
我最接近的'hack'是这部分:
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。至少那是我相信我正在做的。通过这个 hack,我在使用标准 AAC 文件时与正确的电影长度有很小的偏差,即 44100 的采样率,立体声。但是,如果我使用更多有问题的样本进行测试,例如 AAC 采样率 16000、单声道 - 那么视频文件的大小几乎会增加整整一秒。
如果有人能指出我在这里做错了什么,我将不胜感激。
重要说明:我没有为任何上下文设置持续时间。我控制基于视频帧的多路复用会话的终止 count.The 音频输入流当然有持续时间,但这对我没有帮助,因为视频持续时间定义了电影长度。
更新:
这是第二次赏金尝试。
更新 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_base
为 1/25
并且流的播放时间为 6
秒,这超出了您的预期,因此您希望 av_compare_ts
return 0
或 1
。鉴于这些条件,您将具有以下值:
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
复制整个函数体,因为它很长,但你可以看一下 here。)
return (a * b) / c;
这将return(150 * 1) / 1500
,也就是0
.
因此 av_rescale_rnd(ts_a, a, b, AV_ROUND_DOWN) < ts_b
将解析为 true
,因为 0
小于 ts_b
(5
),因此 av_compare_ts
将return-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
这将return(150 * 1) / 25
,即6
。
6
大于5
,条件不成立,再次调用av_rescale_rnd
,这次with:
a = 5
b = 25
c = 1
rnd = AV_ROUND_DOWN
这将 return (5 * 25) / 1
,即 125
。它小于 150
,因此 1
被 return 编辑,瞧,你的问题就解决了。
如果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
.
我正在复用视频和音频流。视频流来自生成的图像数据。音频流来自 aac 文件。有些音频文件比我设置的总视频时间长,所以我的策略是当音频流复用器的时间大于总视频时间(我通过数字编码视频帧控制的最后一个)时停止音频流复用器。
我不会把整个设置代码放在这里,但它类似于来自最新 FFMPEG 存储库的 muxing.c 示例。唯一的区别是,正如我所说,我使用的是来自文件的音频流,而不是来自合成生成的编码帧的音频流。我很确定问题是在 muxer loop.Here 期间我的错误同步是我所做的:
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%,而且很明显,音频对此有所贡献,因为视频帧正好在应该出现的位置完成。
我最接近的'hack'是这部分:
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。至少那是我相信我正在做的。通过这个 hack,我在使用标准 AAC 文件时与正确的电影长度有很小的偏差,即 44100 的采样率,立体声。但是,如果我使用更多有问题的样本进行测试,例如 AAC 采样率 16000、单声道 - 那么视频文件的大小几乎会增加整整一秒。
如果有人能指出我在这里做错了什么,我将不胜感激。
重要说明:我没有为任何上下文设置持续时间。我控制基于视频帧的多路复用会话的终止 count.The 音频输入流当然有持续时间,但这对我没有帮助,因为视频持续时间定义了电影长度。
更新:
这是第二次赏金尝试。
更新 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_base
为 1/25
并且流的播放时间为 6
秒,这超出了您的预期,因此您希望 av_compare_ts
return 0
或 1
。鉴于这些条件,您将具有以下值:
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
复制整个函数体,因为它很长,但你可以看一下 here。)
return (a * b) / c;
这将return(150 * 1) / 1500
,也就是0
.
因此 av_rescale_rnd(ts_a, a, b, AV_ROUND_DOWN) < ts_b
将解析为 true
,因为 0
小于 ts_b
(5
),因此 av_compare_ts
将return-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
这将return(150 * 1) / 25
,即6
。
6
大于5
,条件不成立,再次调用av_rescale_rnd
,这次with:
a = 5
b = 25
c = 1
rnd = AV_ROUND_DOWN
这将 return (5 * 25) / 1
,即 125
。它小于 150
,因此 1
被 return 编辑,瞧,你的问题就解决了。
如果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
.