如何将采样率从 AV_SAMPLE_FMT_FLTP 转换为 AV_SAMPLE_FMT_S16?

Posted

技术标签:

【中文标题】如何将采样率从 AV_SAMPLE_FMT_FLTP 转换为 AV_SAMPLE_FMT_S16?【英文标题】:How to convert sample rate from AV_SAMPLE_FMT_FLTP to AV_SAMPLE_FMT_S16? 【发布时间】:2013-02-06 00:25:33 【问题描述】:

我正在使用带有 avcodec_decode_audio3 的 ffmpeg 将 aac 解码为 pcm。但是它会解码为 AV_SAMPLE_FMT_FLTP 样本格式(PCM 32 位浮点平面),我需要 AV_SAMPLE_FMT_S16(PCM 16 位签名 - S16LE)。

我知道 ffmpeg 可以通过 -sample_fmt 轻松做到这一点。我想对代码做同样的事情,但我还是想不通。

audio_resample 不适用于:它失败并显示错误消息:....转换失败。

【问题讨论】:

你有没有解决这个问题?我面临着完全相同的问题 【参考方案1】:

2013 年 4 月 9 日编辑:研究了如何使用 libswresample 来执行此操作...更快!

在过去 2-3 年的某个时间点,FFmpeg 的 AAC 解码器的输出格式从 AV_SAMPLE_FMT_S16 更改为 AV_SAMPLE_FMT_FLTP。这意味着每个音频通道都有自己的缓冲区,每个样本值都是一个 32 位浮点值,范围从 -1.0 到 +1.0。

而对于 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!

为了清楚起见,我留下了一些东西......单声道路径中的钳位理想情况下应该在立体声路径中复制。并且代码可以轻松优化。

【讨论】:

我有一个相关的问题,这次需要将S16转换成S16P。因为最新的 ffmpeg 需要 S16P 进行 libmp3lame 编码。如果你看看我会很高兴:***.com/questions/18131389/… 鲁本,你会碰巧还有这段代码吗?我正在尝试使这种转换正常工作,但我遇到了一些问题。如果您可以发布链接,我希望看到完整的工作解决方案。提前致谢。 我不再有选项 2 的代码...使用 libswresample 是解决此问题的唯一明智的方法。你有什么问题? 使用您在上面为选项 #1 “swr_init(swr);”发布的代码使用 FFmpeg 2.1 以 -1 返回码失败。你遇到过吗? 不,我没听懂。尝试使用 FFmpeg 的 1.2 分支,因为这就是我使用的。如果这不是一个选项,那么看看 swresample.c 中 swr_init() 的实现......你会看到它记录了很多错误信息(据称默认情况下会进入 stderr)。【参考方案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 -> 1.1.3 开始,发现样本格式的变化很烦人。我查看了将 request_sample_fmt 设置为 AV_SAMPLE_FMT_S16 但似乎 aac 解码器不支持除 AV_SAMPLE_FMT_FLTP 以外的任何东西。

【讨论】:

我使用 libswresample 以更好的方式更新了我的答案。这非常容易做到。 @BradMitchell 我们如何做与此相反的事情?你介意看看***.com/questions/18131389/… 吗?【参考方案3】:

我从 FFMPEG 中找到了 2 个重采样函数。性能可能更好。

    avresample_convert() http://libav.org/doxygen/master/group__lavr.html swr_convert() http://spirton.com/svn/MPlayer-SB/ffmpeg/libswresample/swresample_test.c

【讨论】:

Albert 你绝对是正确的...我今天早些时候遇到了性能投诉,所以不得不寻找一种优化的方法来进行这种转换,而 libswresample 是我最好的新朋友。我上面的答案已经用必要的代码更新了。

以上是关于如何将采样率从 AV_SAMPLE_FMT_FLTP 转换为 AV_SAMPLE_FMT_S16?的主要内容,如果未能解决你的问题,请参考以下文章

以高于 16kHz 的采样率从 AirPod Pro 录制音频

如何更改插孔音频中的采样率?

听说高通平台处理器有先天的音频采样率缺陷,是啥

ffmpeg 如何使用 AV_SAMPLE_FMT_FLT 从 AVFrame 获取 PCM 浮点数

使用 NAudio 重新采样原始音频

蝗虫负载测试 - 将孵化率从 1 秒更改为 20 秒? (每 20 秒 1 只蝗虫)