最近撰寫程式,使用 ffmpeg 將 AAC檔案 轉為 WAV檔案,在實作過程中發現轉換後的聲音品質很差。
主要是因為ffmpeg解開AAC之後的格式與預期不同,因此需要做 resample 的動作。
造成問題原因:
ffmpeg 會將 AAC 解成 AV_SAMPLE_FMT_FLTP 格式 (32bits per sample)
但是製作 PCM 時所需要用的是 AV_SAMPLE_FMT_S16 格式 (16bits per sample),因此需要進行轉換
解決方式:
方法1.
手動將 samples 由 32bits 轉為 16bits. 參考 ffmpeg samplefmt.h
若 sample 屬於 AV_SAMPLE_FMT_FLTP,則 sample 會是 float 格式,且值域為 [-1.0, 1.0]
若 sample 屬於 AV_SAMPLE_FMT_S16, 則 sample 會是 int16 格式,且值域為 [-32767, +32767]
直接撰寫程式碼轉換的方式如下:
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);
}
}
}
方法2.
使用 ffmpeg 提供的 swr_convert() 功能,參考 swresample-test.c,摘錄如下
in_sample_fmt = AV_SAMPLE_FMT_FLTP;
out_sample_fmt = AV_SAMPLE_FMT_16; swr = swr_alloc_set_opts(swr, out_ch_layout, out_sample_fmt, out_sample_rate,
in_ch_layout, in_sample_fmt, in_sample_rate,
0, 0);
swr_init(swr)
swr_convert(swr, pOutBuffer, out_count, pInBuffer, frame->nb_samples);
方法3.
使用 ffmpeg 提供的 avresample_convert 功能,參考 avresample-test.c,摘錄如下
AVAudioResampleContext *s;
in_fmt = AV_SAMPLE_FMT_FLTP;
out_fmt = AV_SAMPLE_FMT_16;
s = avresample_alloc_context();
av_opt_set_int(s, "in_channel_layout", in_ch_layout, 0);
av_opt_set_int(s, "in_sample_fmt", in_fmt, 0);
av_opt_set_int(s, "in_sample_rate", in_rate, 0);
av_opt_set_int(s, "out_channel_layout", out_ch_layout, 0);
av_opt_set_int(s, "out_sample_fmt", out_fmt, 0);
av_opt_set_int(s, "out_sample_rate", out_rate, 0);
av_opt_set_int(s, "internal_sample_fmt", AV_SAMPLE_FMT_FLTP, 0);
avresample_open(),
avresample_convert(),
avresample_close(s);
註:根據 ffmpeg 的說明,在解碼時會將AAC解開為 AV_SAMPLE_FMT_FLTP,此部分是無法透過命令修改的
摘錄 http://www.mail-archive.com/libav-user@ffmpeg.org/msg03612.html
This is not a solution because most decoders only support one sample_fmt.
Use the aconvert filter to convert between sample formats.
參考 aacdec.c
aac_decode_init(AVCodecContext *avctx)
{
avctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
}
參考資料
- http://ffmpeg.org/doxygen/trunk/samplefmt_8h.html
- http://patches.libav.org/patch/34228/
- http://www.mail-archive.com/libav-user@ffmpeg.org/msg03612.html
- http://stackoverflow.com/questions/14989397/how-to-convert-sample-rate-from-av-sample-fmt-fltp-to-av-sample-fmt-s16
- https://ccrma.stanford.edu/courses/422/projects/WaveFormat/