2013年4月10日 星期三

FFMPEG -- AAC 轉換為 WAV


最近撰寫程式,使用 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;
}


參考資料
  1. http://ffmpeg.org/doxygen/trunk/samplefmt_8h.html
  2. http://patches.libav.org/patch/34228/
  3. http://www.mail-archive.com/libav-user@ffmpeg.org/msg03612.html
  4. http://stackoverflow.com/questions/14989397/how-to-convert-sample-rate-from-av-sample-fmt-fltp-to-av-sample-fmt-s16
  5. https://ccrma.stanford.edu/courses/422/projects/WaveFormat/