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

问题描述 投票:10回答:4

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

我知道ffmpeg可以使用-sample_fmt轻松地做到这一点。我想对代码做同样的事情,但是我仍然无法弄清楚。

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

android-ndk ffmpeg sample pcm libav
4个回答
38
投票

EDIT 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!

为了清楚起见,我省去了几件事...理想情况下,单声道路径中的钳位应该在立体声路径中复制。并且可以轻松地优化代码。


6
投票

我从FFMPEG找到2个重采样功能。性能可能更好。

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

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之外不支持任何其他功能。


0
投票

我已尝试使用swr_convert的Reubens代码将ffmpeg / libav帧播放到Decklink板上(这需要16位PCM交错),但是音频听起来不对。听起来好像缺少样本。当我将样本(8秒)记录到原始音频文件中并用Audacity(16位PCM,2 ch,48kHz)打开时,它作为4秒钟的剪辑播放。当我将其作为48kHz的1通道16位PCM导入时,长度很好(听起来还是错误的)。

我也尝试过“手动”转换,但不幸的是,结果相同。

这是我的代码片段

swr_ctx = swr_alloc();
av_opt_set_int(swr_ctx, "in_channel_count", pAudioCodecCtx->channels, 0);
av_opt_set_int(swr_ctx, "in_sample_rate", pAudioCodecCtx->sample_rate, 0);
av_opt_set_int(swr_ctx, "in_channel_layout", pAudioCodecCtx->channel_layout, 0);
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", pAudioCodecCtx->sample_fmt, 0);
av_opt_set_int(swr_ctx, "out_channel_count", 2, 0);
av_opt_set_int(swr_ctx, "out_sample_rate", 48000, 0);
av_opt_set_int(swr_ctx, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);

if (swr_init(swr_ctx))
{
    printf("Error SWR");
}

///

ret = avcodec_decode_audio4(pAudioCodecCtx, pFrame, &frameFinished, &packet);

if (ret < 0) {
    printf("Error in decoding audio frame.\n");
}

swr_convert(swr_ctx, (uint8_t**)&m_audioBuffer, pFrame->nb_samples, (const uint8_t *)pFrame->extended_data, pFrame->nb_samples);

感谢您的任何帮助。

© www.soinside.com 2019 - 2024. All rights reserved.