FFmpeg 转码声音 (AAC) 在视频时间过半后停止
FFmpeg transcoded sound (AAC) stops after half video time
我的 C/C++ FFmpeg 转码器有一个奇怪的问题,它采用输入 MP4(不同的输入编解码器)并生成和输出 MP4(x264、基线和 AAC LC @44100 采样率 libfdk_aac):
生成的 mp4 视频具有精美的图像 (x264),音频 (AAC LC) 也工作正常,但只播放到视频的一半。
音频没有放慢、没有拉伸,也没有卡顿。它刚好停在视频中间。
一个提示可能是输入文件的采样率为22050,22050/44100为0.5,但我真的不明白为什么这会使声音在一半时间后停止。我预计这样的错误会导致声音速度错误。如果我不尝试强制执行 44100 而只是使用传入的 sample_rate.
,一切都很好
另一种猜测是 pts 计算不起作用。但是音频听起来很好(直到它停止),我对视频部分完全做同样的事情,它完美地工作。 "Exactly",与在相同的代码中一样,但是 "audio"-变量替换为 "video"-变量。
FFmpeg在整个过程中没有报错。在完成从输入中读取的所有包后,我还刷新 decoders/encoders/interleaved_writing 。它适用于视频,所以我怀疑我的一般方法有很多错误。
这是我的代码的功能(去掉了错误处理和其他 class 内容):
AudioCodecContext 设置
outContext->_audioCodec = avcodec_find_encoder(outContext->_audioTargetCodecID);
outContext->_audioStream =
avformat_new_stream(outContext->_formatContext, outContext->_audioCodec);
outContext->_audioCodecContext = outContext->_audioStream->codec;
outContext->_audioCodecContext->channels = 2;
outContext->_audioCodecContext->channel_layout = av_get_default_channel_layout(2);
outContext->_audioCodecContext->sample_rate = 44100;
outContext->_audioCodecContext->sample_fmt = outContext->_audioCodec->sample_fmts[0];
outContext->_audioCodecContext->bit_rate = 128000;
outContext->_audioCodecContext->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
outContext->_audioCodecContext->time_base =
(AVRational){1, outContext->_audioCodecContext->sample_rate};
outContext->_audioStream->time_base = (AVRational){1, outContext->_audioCodecContext->sample_rate};
int retVal = avcodec_open2(outContext->_audioCodecContext, outContext->_audioCodec, NULL);
重采样器设置
outContext->_audioResamplerContext =
swr_alloc_set_opts( NULL, outContext->_audioCodecContext->channel_layout,
outContext->_audioCodecContext->sample_fmt,
outContext->_audioCodecContext->sample_rate,
_inputContext._audioCodecContext->channel_layout,
_inputContext._audioCodecContext->sample_fmt,
_inputContext._audioCodecContext->sample_rate,
0, NULL);
int retVal = swr_init(outContext->_audioResamplerContext);
解码
decodedBytes = avcodec_decode_audio4( _inputContext._audioCodecContext,
_inputContext._audioTempFrame,
&p_gotAudioFrame, &_inputContext._currentPacket);
正在转换(当然只有在解码产生帧的情况下)
int retVal = swr_convert( outContext->_audioResamplerContext,
outContext->_audioConvertedFrame->data,
outContext->_audioConvertedFrame->nb_samples,
(const uint8_t**)_inputContext._audioTempFrame->data,
_inputContext._audioTempFrame->nb_samples);
编码(当然只有解码产生一帧)
outContext->_audioConvertedFrame->pts =
av_frame_get_best_effort_timestamp(_inputContext._audioTempFrame);
// Init the new packet
av_init_packet(&outContext->_audioPacket);
outContext->_audioPacket.data = NULL;
outContext->_audioPacket.size = 0;
// Encode
int retVal = avcodec_encode_audio2( outContext->_audioCodecContext,
&outContext->_audioPacket,
outContext->_audioConvertedFrame,
&p_gotPacket);
// Set pts/dts time stamps for writing interleaved
av_packet_rescale_ts( &outContext->_audioPacket,
outContext->_audioCodecContext->time_base,
outContext->_audioStream->time_base);
outContext->_audioPacket.stream_index = outContext->_audioStream->index;
写入(当然只有编码产生数据包)
int retVal = av_interleaved_write_frame(outContext->_formatContext, &outContext->_audioPacket);
我完全不知道什么会导致这种行为。
所以,我终于自己弄明白了。
问题确实出在 sample_rate 的差异上。
您假设调用 swr_convert() 会像我一样为您提供转换音频帧所需的所有样本。
当然,那太容易了。
相反,您需要多次调用 swr_convert(可能)每帧 并在需要时缓冲其输出。然后你需要从缓冲区中抓取一个帧,这就是你必须编码的。
这是我的新 convertAudioFrame 函数:
// Calculate number of output samples
int numOutputSamples = av_rescale_rnd(
swr_get_delay(outContext->_audioResamplerContext, _inputContext._audioCodecContext->sample_rate)
+ _inputContext._audioTempFrame->nb_samples,
outContext->_audioCodecContext->sample_rate,
_inputContext._audioCodecContext->sample_rate,
AV_ROUND_UP);
if (numOutputSamples == 0)
{
return;
}
uint8_t* tempSamples;
av_samples_alloc( &tempSamples, NULL,
outContext->_audioCodecContext->channels, numOutputSamples,
outContext->_audioCodecContext->sample_fmt, 0);
int retVal = swr_convert( outContext->_audioResamplerContext,
&tempSamples,
numOutputSamples,
(const uint8_t**)_inputContext._audioTempFrame->data,
_inputContext._audioTempFrame->nb_samples);
// Write to audio fifo
if (retVal > 0)
{
retVal = av_audio_fifo_write(outContext->_audioFifo, (void**)&tempSamples, retVal);
}
av_freep(&tempSamples);
// Get a frame from audio fifo
int samplesAvailable = av_audio_fifo_size(outContext->_audioFifo);
if (samplesAvailable > 0)
{
retVal = av_audio_fifo_read(outContext->_audioFifo,
(void**)outContext->_audioConvertedFrame->data,
outContext->_audioCodecContext->frame_size);
// We got a frame, so also set its pts
if (retVal > 0)
{
p_gotConvertedFrame = 1;
if (_inputContext._audioTempFrame->pts != AV_NOPTS_VALUE)
{
outContext->_audioConvertedFrame->pts = _inputContext._audioTempFrame->pts;
}
else if (_inputContext._audioTempFrame->pkt_pts != AV_NOPTS_VALUE)
{
outContext->_audioConvertedFrame->pts = _inputContext._audioTempFrame->pkt_pts;
}
}
}
我基本上会调用这个函数,直到音频 fifo 缓冲区中没有更多帧为止。
因此,音频只有一半长,因为我编码的帧数与解码的帧数一样多。由于 sample_rate 的 2 倍,我实际上需要编码 2 倍的帧。
我的 C/C++ FFmpeg 转码器有一个奇怪的问题,它采用输入 MP4(不同的输入编解码器)并生成和输出 MP4(x264、基线和 AAC LC @44100 采样率 libfdk_aac):
生成的 mp4 视频具有精美的图像 (x264),音频 (AAC LC) 也工作正常,但只播放到视频的一半。
音频没有放慢、没有拉伸,也没有卡顿。它刚好停在视频中间。
一个提示可能是输入文件的采样率为22050,22050/44100为0.5,但我真的不明白为什么这会使声音在一半时间后停止。我预计这样的错误会导致声音速度错误。如果我不尝试强制执行 44100 而只是使用传入的 sample_rate.
,一切都很好另一种猜测是 pts 计算不起作用。但是音频听起来很好(直到它停止),我对视频部分完全做同样的事情,它完美地工作。 "Exactly",与在相同的代码中一样,但是 "audio"-变量替换为 "video"-变量。
FFmpeg在整个过程中没有报错。在完成从输入中读取的所有包后,我还刷新 decoders/encoders/interleaved_writing 。它适用于视频,所以我怀疑我的一般方法有很多错误。
这是我的代码的功能(去掉了错误处理和其他 class 内容):
AudioCodecContext 设置
outContext->_audioCodec = avcodec_find_encoder(outContext->_audioTargetCodecID);
outContext->_audioStream =
avformat_new_stream(outContext->_formatContext, outContext->_audioCodec);
outContext->_audioCodecContext = outContext->_audioStream->codec;
outContext->_audioCodecContext->channels = 2;
outContext->_audioCodecContext->channel_layout = av_get_default_channel_layout(2);
outContext->_audioCodecContext->sample_rate = 44100;
outContext->_audioCodecContext->sample_fmt = outContext->_audioCodec->sample_fmts[0];
outContext->_audioCodecContext->bit_rate = 128000;
outContext->_audioCodecContext->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
outContext->_audioCodecContext->time_base =
(AVRational){1, outContext->_audioCodecContext->sample_rate};
outContext->_audioStream->time_base = (AVRational){1, outContext->_audioCodecContext->sample_rate};
int retVal = avcodec_open2(outContext->_audioCodecContext, outContext->_audioCodec, NULL);
重采样器设置
outContext->_audioResamplerContext =
swr_alloc_set_opts( NULL, outContext->_audioCodecContext->channel_layout,
outContext->_audioCodecContext->sample_fmt,
outContext->_audioCodecContext->sample_rate,
_inputContext._audioCodecContext->channel_layout,
_inputContext._audioCodecContext->sample_fmt,
_inputContext._audioCodecContext->sample_rate,
0, NULL);
int retVal = swr_init(outContext->_audioResamplerContext);
解码
decodedBytes = avcodec_decode_audio4( _inputContext._audioCodecContext,
_inputContext._audioTempFrame,
&p_gotAudioFrame, &_inputContext._currentPacket);
正在转换(当然只有在解码产生帧的情况下)
int retVal = swr_convert( outContext->_audioResamplerContext,
outContext->_audioConvertedFrame->data,
outContext->_audioConvertedFrame->nb_samples,
(const uint8_t**)_inputContext._audioTempFrame->data,
_inputContext._audioTempFrame->nb_samples);
编码(当然只有解码产生一帧)
outContext->_audioConvertedFrame->pts =
av_frame_get_best_effort_timestamp(_inputContext._audioTempFrame);
// Init the new packet
av_init_packet(&outContext->_audioPacket);
outContext->_audioPacket.data = NULL;
outContext->_audioPacket.size = 0;
// Encode
int retVal = avcodec_encode_audio2( outContext->_audioCodecContext,
&outContext->_audioPacket,
outContext->_audioConvertedFrame,
&p_gotPacket);
// Set pts/dts time stamps for writing interleaved
av_packet_rescale_ts( &outContext->_audioPacket,
outContext->_audioCodecContext->time_base,
outContext->_audioStream->time_base);
outContext->_audioPacket.stream_index = outContext->_audioStream->index;
写入(当然只有编码产生数据包)
int retVal = av_interleaved_write_frame(outContext->_formatContext, &outContext->_audioPacket);
我完全不知道什么会导致这种行为。
所以,我终于自己弄明白了。
问题确实出在 sample_rate 的差异上。 您假设调用 swr_convert() 会像我一样为您提供转换音频帧所需的所有样本。 当然,那太容易了。
相反,您需要多次调用 swr_convert(可能)每帧 并在需要时缓冲其输出。然后你需要从缓冲区中抓取一个帧,这就是你必须编码的。
这是我的新 convertAudioFrame 函数:
// Calculate number of output samples
int numOutputSamples = av_rescale_rnd(
swr_get_delay(outContext->_audioResamplerContext, _inputContext._audioCodecContext->sample_rate)
+ _inputContext._audioTempFrame->nb_samples,
outContext->_audioCodecContext->sample_rate,
_inputContext._audioCodecContext->sample_rate,
AV_ROUND_UP);
if (numOutputSamples == 0)
{
return;
}
uint8_t* tempSamples;
av_samples_alloc( &tempSamples, NULL,
outContext->_audioCodecContext->channels, numOutputSamples,
outContext->_audioCodecContext->sample_fmt, 0);
int retVal = swr_convert( outContext->_audioResamplerContext,
&tempSamples,
numOutputSamples,
(const uint8_t**)_inputContext._audioTempFrame->data,
_inputContext._audioTempFrame->nb_samples);
// Write to audio fifo
if (retVal > 0)
{
retVal = av_audio_fifo_write(outContext->_audioFifo, (void**)&tempSamples, retVal);
}
av_freep(&tempSamples);
// Get a frame from audio fifo
int samplesAvailable = av_audio_fifo_size(outContext->_audioFifo);
if (samplesAvailable > 0)
{
retVal = av_audio_fifo_read(outContext->_audioFifo,
(void**)outContext->_audioConvertedFrame->data,
outContext->_audioCodecContext->frame_size);
// We got a frame, so also set its pts
if (retVal > 0)
{
p_gotConvertedFrame = 1;
if (_inputContext._audioTempFrame->pts != AV_NOPTS_VALUE)
{
outContext->_audioConvertedFrame->pts = _inputContext._audioTempFrame->pts;
}
else if (_inputContext._audioTempFrame->pkt_pts != AV_NOPTS_VALUE)
{
outContext->_audioConvertedFrame->pts = _inputContext._audioTempFrame->pkt_pts;
}
}
}
我基本上会调用这个函数,直到音频 fifo 缓冲区中没有更多帧为止。
因此,音频只有一半长,因为我编码的帧数与解码的帧数一样多。由于 sample_rate 的 2 倍,我实际上需要编码 2 倍的帧。