我用avcodec_decode_audio3用ffmpeg将aac解码为pcm。然而,它解码为AV_SAMPLE_FMT_FLTP样本格式(PCM 32位浮点平面),我需要AV_SAMPLE_FMT_S16(PCM 16位签名 - S16LE)。
我知道ffmpeg可以使用-sample_fmt轻松完成此操作。我想对代码做同样的事情,但我仍然无法理解。
audio_resample不起作用:它失败并显示错误消息:....转换失败。
答案 0 :(得分:37)
编辑2013年4月9日:了解了如何使用libswresample来实现这一目标......更快!
在过去的2 - 3年中,FFmpeg的AAC解码器的输出格式从AV_SAMPLE_FMT_S16变为AV_SAMPLE_FMT_FLTP。这意味着每个音频通道都有自己的缓冲区,每个采样值都是从-1.0到+1.0的32位浮点值。
对于AV_SAMPLE_FMT_S16,数据在单个缓冲区中,样本交错,每个样本是从-32767到+32767的有符号整数。
如果您真的需要音频为AV_SAMPLE_FMT_S16,那么您必须自己进行转换。我想出了两种方法:
<强> 1。使用libswresample (推荐)
#include "libswresample/swresample.h"
...
SwrContext *swr;
...
// Set up SWR context once you've got codec information
swr = swr_alloc();
av_opt_set_int(swr, "in_channel_layout", audioCodec->channel_layout, 0);
av_opt_set_int(swr, "out_channel_layout", audioCodec->channel_layout, 0);
av_opt_set_int(swr, "in_sample_rate", audioCodec->sample_rate, 0);
av_opt_set_int(swr, "out_sample_rate", audioCodec->sample_rate, 0);
av_opt_set_sample_fmt(swr, "in_sample_fmt", AV_SAMPLE_FMT_FLTP, 0);
av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
swr_init(swr);
...
// In your decoder loop, after decoding an audio frame:
AVFrame *audioFrame = ...;
int16_t* outputBuffer = ...;
swr_convert(&outputBuffer, audioFrame->nb_samples, audioFrame->extended_data, audioFrame->nb_samples);
这就是你所要做的一切!
<强> 2。在C中手动执行(原始答案,不推荐)
所以在你的解码循环中,当你有一个音频数据包时,你就像这样解码它:
AVCodecContext *audioCodec; // init'd elsewhere
AVFrame *audioFrame; // init'd elsewhere
AVPacket packet; // init'd elsewhere
int16_t* outputBuffer; // init'd elsewhere
int out_size = 0;
...
int len = avcodec_decode_audio4(audioCodec, audioFrame, &out_size, &packet);
然后,如果你有一整帧音频,你可以很容易地转换它:
// Convert from AV_SAMPLE_FMT_FLTP to AV_SAMPLE_FMT_S16
int in_samples = audioFrame->nb_samples;
int in_linesize = audioFrame->linesize[0];
int i=0;
float* inputChannel0 = (float*)audioFrame->extended_data[0];
// Mono
if (audioFrame->channels==1) {
for (i=0 ; i<in_samples ; i++) {
float sample = *inputChannel0++;
if (sample<-1.0f) sample=-1.0f; else if (sample>1.0f) sample=1.0f;
outputBuffer[i] = (int16_t) (sample * 32767.0f);
}
}
// Stereo
else {
float* inputChannel1 = (float*)audioFrame->extended_data[1];
for (i=0 ; i<in_samples ; i++) {
outputBuffer[i*2] = (int16_t) ((*inputChannel0++) * 32767.0f);
outputBuffer[i*2+1] = (int16_t) ((*inputChannel1++) * 32767.0f);
}
}
// outputBuffer now contains 16-bit PCM!
为了清楚起见,我已经留下了一些东西......理想情况下,单声道路径中的钳位应该在立体声路径中重复。并且可以轻松优化代码。
答案 1 :(得分:5)
我从FFMPEG找到了2个重采样功能。表现可能更好。
答案 2 :(得分:2)
感谢Reuben提供解决方案。我确实发现,与直接ffmpeg -i file.wav相比,某些样本值略有偏差。似乎在转换中,它们在值上使用round()。
为了进行转换,我做了一些修改后的投标,以适用于任意数量的频道:
if (audioCodecContext->sample_fmt == AV_SAMPLE_FMT_FLTP)
{
int nb_samples = decoded_frame->nb_samples;
int channels = decoded_frame->channels;
int outputBufferLen = nb_samples & channels * 2;
short* outputBuffer = new short[outputBufferLen/2];
for (int i = 0; i < nb_samples; i++)
{
for (int c = 0; c < channels; c++)
{
float* extended_data = (float*)decoded_frame->extended_data[c];
float sample = extended_data[i];
if (sample < -1.0f) sample = -1.0f;
else if (sample > 1.0f) sample = 1.0f;
outputBuffer[i * channels + c] = (short)round(sample * 32767.0f);
}
}
// Do what you want with the data etc.
}
我从ffmpeg 0.11.1开始 - &gt; 1.1.3并发现样本格式的变化很烦人。我查看了将request_sample_fmt设置为AV_SAMPLE_FMT_S16,但似乎aac解码器不支持AV_SAMPLE_FMT_FLTP以外的其他任何内容。