FFmpeg学习:音频重采样

重采样:

将音频进行SDL播放的时候,因为当前的SDL2.0不支持plannar格式,也不支持浮点型的,而最新的FFpemg会将音频解码为AV_SAMPLE_FMT_FLTP,这个时候进行对它重采样的话,就可以在SDL2.0上进行播放这个音频了。

重采样参数
  • 1、sample rate(采样率):采样设备每秒抽取样本的次数
  • 2、sample format(采样格式)和量化精度:每种⾳频格式有不同的量化精度(位宽),位数越多,表示值就越精确,声⾳表现⾃然就越精准。
  • 3、channel layout(通道布局,也就是声道数):这个就是采样的声道数

在FFmpeg里面主要有两种采样格式:floating-point formats 和 planar sample formats;具体采样参数如下(在(libavutil/samplefmt.h头文件里面):

enum AVSampleFormat {
    AV_SAMPLE_FMT_NONE = -1,
    AV_SAMPLE_FMT_U8,          ///< unsigned 8 bits
    AV_SAMPLE_FMT_S16,         ///< signed 16 bits
    AV_SAMPLE_FMT_S32,         ///< signed 32 bits
    AV_SAMPLE_FMT_FLT,         ///< float
    AV_SAMPLE_FMT_DBL,         ///< double

    AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planar
    AV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planar
    AV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planar
    AV_SAMPLE_FMT_FLTP,        ///< float, planar
    AV_SAMPLE_FMT_DBLP,        ///< double, planar
    AV_SAMPLE_FMT_S64,         ///< signed 64 bits
    AV_SAMPLE_FMT_S64P,        ///< signed 64 bits, planar

    AV_SAMPLE_FMT_NB           ///< Number of sample formats. DO NOT USE if linking dynamically
};

说明:

  • 1.U8(无符号整型8bit)、S16(整型16bit)、S32(整型32bit)、FLT(单精度浮点类型)、DBL(双精度浮点类型)、S64(整型64bit),不以P为结尾的都是interleaved(packed)结构,以P为结尾的是planar结构。
  • 2.Planar模式是FFmpeg内部存储模式,我们实际使用的音频文件都是Packed模式的。
  • 3.FFmpeg解码不同格式的音频输出的音频采样格式不是一样。测试发现,其中AAC解码输出的数据为浮点型的 AV_SAMPLE_FMT_FLTP 格式,MP3解码输出的数据为 AV_SAMPLE_FMT_S16P 格式(使用的mp3文件为16位深)。具体采样格式可以查看解码后的AVFrame中的 format 成员或解码器的AVCodecContext中的 sample_fmt 成员。

还有就是声道分布参数,这个在FFmpeg也有说明(声道分布在FFmpeg\libavutil\channel_layout.h中有定义,⼀般来说⽤的⽐较多的是 AV_CH_LAYOUT_STEREO(双声道)和AV_CH_LAYOUT_SURROUND(三声道),这两者的定义如下):

#define AV_CH_LAYOUT_STEREO (AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT) 

#define AV_CH_LAYOUT_SURROUND (AV_CH_LAYOUT_STEREO|AV_CH_FRONT_CENTER)

FFmpeg重采样流程

重采样的处理流程:
1、创建上下文环境:重采样过程上下文环境为SwrContext数据结构。
2、参数设置:转换的参数设置到SwrContext中。SwrContext初始化:swr_init()。
3、分配样本数据内存空间:使用av_samples_alloc_array_and_samples、av_samples_alloc等工具函数。
4、开启重采样转换:通过重复地调用swr_convert来完成。
5、重采样转换完成, 释放相关资源:通过swr_free()释放SwrContext。

流程图如下:
image

FFmpeg重采样流程(方法二)(采用swr_convert_frame()进行重采样)

重采样的处理流程:
1、创建上下文环境:重采样过程上下文环境为SwrContext数据结构。(和上面一样)
2、参数设置:转换的参数设置到SwrContext中。SwrContext初始化:swr_init()。
3、直接采用swr_convert_frame()对输入帧和输出帧进行重采样。(不需要提前申请空间,详细看函数介绍)
4、重采样转换完成, 释放相关资源:通过swr_free()释放SwrContext。

FFmpeg采样流程相关api介绍

【第一】音频重采样上下文——结构体SwrContext

【结构声明】

点击查看代码
struct SwrContext {
    const AVClass *av_class;                        ///< AVClass used for AVOption and av_log()
    int log_level_offset;                           ///< logging level offset
    void *log_ctx;                                  ///< parent logging context
    enum AVSampleFormat  in_sample_fmt;             ///< input sample format
    enum AVSampleFormat int_sample_fmt;             ///< internal sample format (AV_SAMPLE_FMT_FLTP or AV_SAMPLE_FMT_S16P)
    enum AVSampleFormat out_sample_fmt;             ///< output sample format
    int64_t  in_ch_layout;                          ///< input channel layout
    int64_t out_ch_layout;                          ///< output channel layout
    int      in_sample_rate;                        ///< input sample rate
    int     out_sample_rate;                        ///< output sample rate
    int flags;                                      ///< miscellaneous flags such as SWR_FLAG_RESAMPLE
    float slev;                                     ///< surround mixing level
    float clev;                                     ///< center mixing level
    float lfe_mix_level;                            ///< LFE mixing level
    float rematrix_volume;                          ///< rematrixing volume coefficient
    float rematrix_maxval;                          ///< maximum value for rematrixing output
    int matrix_encoding;                            /**< matrixed stereo encoding */
    const int *channel_map;                         ///< channel index (or -1 if muted channel) map
    int used_ch_count;                              ///< number of used input channels (mapped channel count if channel_map, otherwise in.ch_count)
    int engine;

    int user_in_ch_count;                           ///< User set input channel count
    int user_out_ch_count;                          ///< User set output channel count
    int user_used_ch_count;                         ///< User set used channel count
    int64_t user_in_ch_layout;                      ///< User set input channel layout
    int64_t user_out_ch_layout;                     ///< User set output channel layout
    enum AVSampleFormat user_int_sample_fmt;        ///< User set internal sample format
    int user_dither_method;                         ///< User set dither method

    struct DitherContext dither;

    int filter_size;                                /**< length of each FIR filter in the resampling filterbank relative to the cutoff frequency */
    int phase_shift;                                /**< log2 of the number of entries in the resampling polyphase filterbank */
    int linear_interp;                              /**< if 1 then the resampling FIR filter will be linearly interpolated */
    int exact_rational;                             /**< if 1 then enable non power of 2 phase_count */
    double cutoff;                                  /**< resampling cutoff frequency (swr: 6dB point; soxr: 0dB point). 1.0 corresponds to half the output sample rate */
    int filter_type;                                /**< swr resampling filter type */
    double kaiser_beta;                                /**< swr beta value for Kaiser window (only applicable if filter_type == AV_FILTER_TYPE_KAISER) */
    double precision;                               /**< soxr resampling precision (in bits) */
    int cheby;                                      /**< soxr: if 1 then passband rolloff will be none (Chebyshev) & irrational ratio approximation precision will be higher */

    float min_compensation;                         ///< swr minimum below which no compensation will happen
    float min_hard_compensation;                    ///< swr minimum below which no silence inject / sample drop will happen
    float soft_compensation_duration;               ///< swr duration over which soft compensation is applied
    float max_soft_compensation;                    ///< swr maximum soft compensation in seconds over soft_compensation_duration
    float async;                                    ///< swr simple 1 parameter async, similar to ffmpegs -async
    int64_t firstpts_in_samples;                    ///< swr first pts in samples

    int resample_first;                             ///< 1 if resampling must come first, 0 if rematrixing
    int rematrix;                                   ///< flag to indicate if rematrixing is needed (basically if input and output layouts mismatch)
    int rematrix_custom;                            ///< flag to indicate that a custom matrix has been defined

    AudioData in;                                   ///< input audio data
    AudioData postin;                               ///< post-input audio data: used for rematrix/resample
    AudioData midbuf;                               ///< intermediate audio data (postin/preout)
    AudioData preout;                               ///< pre-output audio data: used for rematrix/resample
    AudioData out;                                  ///< converted output audio data
    AudioData in_buffer;                            ///< cached audio data (convert and resample purpose)
    AudioData silence;                              ///< temporary with silence
    AudioData drop_temp;                            ///< temporary used to discard output
    int in_buffer_index;                            ///< cached buffer position
    int in_buffer_count;                            ///< cached buffer length
    int resample_in_constraint;                     ///< 1 if the input end was reach before the output end, 0 otherwise
    int flushed;                                    ///< 1 if data is to be flushed and no further input is expected
    int64_t outpts;                                 ///< output PTS
    int64_t firstpts;                               ///< first PTS
    int drop_output;                                ///< number of output samples to drop
    double delayed_samples_fixup;                   ///< soxr 0.1.1: needed to fixup delayed_samples after flush has been called.

    struct AudioConvert *in_convert;                ///< input conversion context
    struct AudioConvert *out_convert;               ///< output conversion context
    struct AudioConvert *full_convert;              ///< full conversion context (single conversion for input and output)
    struct ResampleContext *resample;               ///< resampling context
    struct Resampler const *resampler;              ///< resampler virtual function table

    double matrix[SWR_CH_MAX][SWR_CH_MAX];          ///< floating point rematrixing coefficients
    float matrix_flt[SWR_CH_MAX][SWR_CH_MAX];       ///< single precision floating point rematrixing coefficients
    uint8_t *native_matrix;
    uint8_t *native_one;
    uint8_t *native_simd_one;
    uint8_t *native_simd_matrix;
    int32_t matrix32[SWR_CH_MAX][SWR_CH_MAX];       ///< 17.15 fixed point rematrixing coefficients
    uint8_t matrix_ch[SWR_CH_MAX][SWR_CH_MAX+1];    ///< Lists of input channels per output channel that have non zero rematrixing coefficients
    mix_1_1_func_type *mix_1_1_f;
    mix_1_1_func_type *mix_1_1_simd;

    mix_2_1_func_type *mix_2_1_f;
    mix_2_1_func_type *mix_2_1_simd;

    mix_any_func_type *mix_any_f;

    /* TODO: callbacks for ASM optimizations */
};
比较重要的参数: + 采样格式 + 声道类型

【结构体定义和申请空间】

SwrContext* audio_convert_ctx = NULL;
audio_convert_ctx = swr_alloc();
【第二】上下文结构体参数设置

参数设置有两种方式:
【方法一】:采用AVOptions的api,举例如下

av_opt_set_channel_layout(audio_convert_ctx, "in_channel_layout", AV_CH_LAYOUT_5POINT1, 0);
av_opt_set_channel_layou(audio_convert_ctx, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(audio_convert_ctx, "in_sample_rate", 44100, 0);
av_opt_set_int(audio_convert_ctx, "out_sample_rate", 16000, 0);
av_opt_set_sample_fmt(audio_convert_ctx, "in_sample_fmt", AV_SAMPLE_FMT_FLPT, 0);
av_opt_set_sample_fmt(audio_convert_ctx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);

【方法二】:采用swr_alloc_set_opts():如果第一个参数为NULL则创建一个新的SwrContext,否则对已有的SwrContext进行参数设置。

struct SwrContext *swr_alloc_set_opts(struct SwrContext *s, // ⾳频重采样上下⽂ 
int64_t out_ch_layout, // 输出的layout, 如:5.1声道 
enum AVSampleFormat out_sample_fmt, // 输出的采样格式。
Float, S16,⼀般 选⽤是s16 绝⼤部分声卡⽀持 
int out_sample_rate, //输出采样率 
int64_t in_ch_layout, // 输⼊的layout 
enum AVSampleFormat in_sample_fmt, // 输⼊的采样格式 
int in_sample_rate, // 输⼊的采样率 
int log_offset, // ⽇志相关,不⽤管先,直接为0 void *log_ctx // ⽇志相关,不⽤管先,直接为NULL 
);
【第三】初始化SwrContext结构体

参数设置好之后必须调用swr_init()对SwrContext进行初始化。

int swr_init(struct SwrContext *s);

如果需要修改转换的参数:

  • 1、重新进行参数设置。
  • 2、再次调用swr_init()。
【第四】分配采样内存空间

转换之前需要分配内存空间用于保存重采样的输出数据,内存空间的大小跟通道个数、样本格式需要、容纳的样本个数都有关系。主要有两种方式:
【方式一】:输出目标是Frame->data
采用av_frame_get_buffer()函数,举例如下:

AVFrame* pFrame_new;
			pFrame_new = av_frame_alloc();
			pFrame_new->channel_layout = audio_decodec_ctx->channel_layout;
			pFrame_new->channels = av_get_channel_layout_nb_channels(audio_decodec_ctx->channel_layout);
			pFrame_new->format = t->inner_sample_format;
			pFrame_new->sample_rate = t->inner_sample_rate;
			int64_t nb_samples = av_rescale_rnd(pFrame->nb_samples, pFrame_new->sample_rate, pFrame->sample_rate, AV_ROUND_UP);
			pFrame_new->nb_samples = nb_samples; //该帧采样点数
			av_frame_get_buffer(pFrame_new, 0);
			pFrame_new->pts = av_rescale_rnd(pFrame->pts, pFrame_new->sample_rate, pFrame->sample_rate, AV_ROUND_UP);

【方式二】:输出目标直接是内存空间
libavutil中的samples处理API提供了一些函数方便管理样本数据,例如av_samples_alloc()函数用于分配存储sample的buffer。

/**
 * @param[out] audio_data  输出数组,每个元素是指向一个通道的数据的指针。
 * @param[out] linesize    aligned size for audio buffer(s), may be NULL
 * @param nb_channels      通道的个数。
 * @param nb_samples       每个通道的样本个数。
 * @param align            buffer size alignment (0 = default, 1 = no alignment)
 * @return                 成功返回大于0的数,错误返回负数。
 */
int av_samples_alloc(uint8_t **audio_data, int *linesize, int nb_channels,
                     int nb_samples, enum AVSampleFormat sample_fmt, int align);

也有计算所需内存空间的函数av_samples_get_buffer_size()

/*
 *获取给定音频参数所需的缓冲区大小。
 * @param [out] linesize计算的lineize,可能为NULL
 * @param nb_channels频道数
 * @param nb_samples单个通道中的样本数
 * @param sample_fmt样本格式
 * @param对齐缓冲区大小对齐(0 =默认,1 =无对齐)
 * @return需要缓冲区大小,或失败时出现负错误代码
*/
int av_samples_get_buffer_size(int *linesize, int nb_channels, int nb_samples,
                               enum AVSampleFormat sample_fmt, int align);
【第五-1】将输⼊的⾳频按照定义的参数进⾏转换并输出 Swr_convert()

【函数定义】

int swr_convert(struct SwrContext *s, // ⾳频重采样的上下⽂ 
uint8_t **out, // 输出的指针。传递的输出的数组(frame的数据指针)
int out_count, //输出缓冲区每通道样本数据数量(对于音频,每个通道数据长度都相同),注意这里不是以字节为单位
const uint8_t **in , //输⼊的数组,AVFrame解码出来的DATA
int in_count // 输⼊的单通道的样本数量。这里填入frame->nb_samples即可
);

【函数说明】

  • 0、返回值:转换成功后每个通道的输出样本数,出错则为负值
  • 1、如果没有提供足够的空间用于保存输出数据,采样数据会缓存在swr中。可以通过 swr_get_out_samples()来获取下一次调用swr_convert在给定输入样本数量下输出样本数量的上限,来提供足够的空间。
  • 2、如果是采样频率转换,转换完成后采样数据可能会缓存在swr中,它期待你提供更多的输入数据。
  • 3、如果实际上并不需要更多输入数据,通过调用swr_convert(),其中参数in_count设置为0来获取缓存在swr中的数据。
  • 4、转换结束之后需要冲刷swr_context的缓冲区,通过调用swr_convert(),其中参数in设置为NULL,参数in_count设置为0。
    注释里面有一句:
    in and in_count can be set to 0 to flush the last few samples out at the
    end.
    【举例】
ret = swr_convert(audio_convert_ctx, 
				&outBuff, nb_samples,
				(const uint8_t**)audio_frame->data, audio_frame->nb_samples);
			while (ret > 0) {	// 把最后几个样本冲洗出来
				ret = swr_convert(audio_convert_ctx, &outBuff, nb_samples,
					NULL, 0);
			}
【第五-2】采用swr_convert_frame() 输入输出帧的转换

【函数定义】

int swr_convert_frame(SwrContext *swr,
                      AVFrame *output, const AVFrame *input);

【参数说明】
输入:

  • swr:重采样上下文结构体
  • output:输出帧
  • input:输入帧
    输出:
  • 0 成功,AVERROR 失败或不匹配的配置。

【函数说明】

  • 转换输入 AVFrame 中的样本,并将其写入输出 AVFrame。
  • 输入和输出 AVFrames 必须设置 channel_layout、sample_rate 和 format。
  • 如果输出 AVFrame 没有分配数据指针,则将自动调用 av_frame_get_buffer() 来设置 nb_samples 字段以分配帧。
  • 输出的 AVFrame 可以为 NULL 或分配的样本少于所需的样本。在这种情况下,任何未写入输出的剩余样本将被添加到内部 FIFO 缓冲区,以在下次调用此函数或 swr_convert() 时返回。
  • 如果转换采样率,内部重采样延迟缓冲区中可能有剩余数据。 swr_get_delay() 告诉剩余样本的数量。要将此数据作为输出,请使用 NULL 输入调用此函数或 swr_convert()。
  • 如果 SwrContext 配置与输出和输入 AVFrame 设置不匹配,则不会进行转换,取决于哪个 AVFrame 不匹配 AVERROR_OUTPUT_CHANGED、AVERROR_INPUT_CHANGED 或它们的按位或结果。
【第六】释放资源

首先释放内存(frame->data可以通过av_frame_free()时释放):

av_freep(outBuffer);

在所有转换完成之后,释放上下文:

void swr_free(struct SwrContext **s)
⾳频重采样,采样格式转换和混合库
点击查看代码
//
// 音频帧格式转换
//
int32_t AudioConvert(
  const AVFrame* pInFrame,      // 输入音频帧
  AVSampleFormat eOutSmplFmt,   // 输出音频格式
  int32_t        nOutChannels,  // 输出音频通道数 
  int32_t        nOutSmplRate,  // 输出音频采样率
  AVFrame**      ppOutFrame)    // 输出视频帧
{
  struct SwrContext* pSwrCtx   = nullptr;
  AVFrame*           pOutFrame = nullptr;


  // 创建格式转换器,
  int64_t nInChnlLayout  = av_get_default_channel_layout(pInFrame->channels);
  int64_t nOutChnlLayout = (nOutChannels == 1) ? AV_CH_LAYOUT_MONO : AV_CH_LAYOUT_STEREO;

  pSwrCtx = swr_alloc();
  if (pSwrCtx == nullptr)
  {
    LOGE("<AudioConvert> [ERROR] fail to swr_alloc()\n");
    return -1;
  }
  swr_alloc_set_opts(pSwrCtx, 
                      nOutChnlLayout, eOutSmplFmt, nOutSmplRate, nInChnlLayout, 
                      (enum AVSampleFormat)(pInFrame->format), pInFrame->sample_rate, 
                      0, nullptr  );


  // 计算重采样转换后的样本数量,从而分配缓冲区大小
  int64_t nCvtBufSamples    = av_rescale_rnd(pInFrame->nb_samples, nOutSmplRate, pInFrame->sample_rate, AV_ROUND_UP);

  // 创建输出音频帧
  pOutFrame                 = av_frame_alloc();
  pOutFrame->format         = eOutSmplFmt;
  pOutFrame->nb_samples     = (int)nCvtBufSamples;
  pOutFrame->channel_layout = (uint64_t)nOutChnlLayout;
  int res                   = av_frame_get_buffer(pOutFrame, 0); // 分配缓冲区
  if (res < 0)
  {
    LOGE("<AudioConvert> [ERROR] fail to av_frame_get_buffer(), res=%d\n", res);
    swr_free(&pSwrCtx);
    av_frame_free(&pOutFrame);
    return -2;
  }


  // 进行重采样转换处理,返回转换后的样本数量
  int nCvtedSamples = swr_convert(pSwrCtx,
                                  const_cast<uint8_t**>(pOutFrame->data),
                                  (int)nCvtBufSamples,
                                  const_cast<const uint8_t**>(pInFrame->data),
                                  pInFrame->nb_samples);
  if (nCvtedSamples <= 0)
  {
    LOGE("<AudioConvert> [ERROR] no data for swr_convert()\n");
    swr_free(&pSwrCtx);
    av_frame_free(&pOutFrame);
    return -3;
  }
  pOutFrame->nb_samples = nCvtedSamples;
  pOutFrame->pts        = pInFrame->pts;      // pts等时间戳沿用
  pOutFrame->pkt_pts    = pInFrame->pkt_pts;

  (*ppOutFrame) = pOutFrame;
  swr_free(&pSwrCtx); // 释放转换器
  return 0;
}
delay
  • @see swr_delay()
    @see swr_convert()
    @see swr_get_delay()

补充

channels 和 channel_layout 是啥
channels  为 音频的 通道数 1 2 3 4 5.....
channel_layout  为音频 通道格式类型 如 单通道 双通道 .....

相互之间获取

av_get_channel_layout_nb_channels()
av_get_default_channel_layout()

对应关系

点击查看代码
channel_layout_map[]
	{ "mono",        1,  AV_CH_LAYOUT_MONO },
    { "stereo",      2,  AV_CH_LAYOUT_STEREO },
    { "2.1",         3,  AV_CH_LAYOUT_2POINT1 },
    { "3.0",         3,  AV_CH_LAYOUT_SURROUND },
    { "3.0(back)",   3,  AV_CH_LAYOUT_2_1 },
    { "4.0",         4,  AV_CH_LAYOUT_4POINT0 },
    { "quad",        4,  AV_CH_LAYOUT_QUAD },
    { "quad(side)",  4,  AV_CH_LAYOUT_2_2 },
    { "3.1",         4,  AV_CH_LAYOUT_3POINT1 },
    { "5.0",         5,  AV_CH_LAYOUT_5POINT0_BACK },
    { "5.0(side)",   5,  AV_CH_LAYOUT_5POINT0 },
    { "4.1",         5,  AV_CH_LAYOUT_4POINT1 },
    { "5.1",         6,  AV_CH_LAYOUT_5POINT1_BACK },
    { "5.1(side)",   6,  AV_CH_LAYOUT_5POINT1 },
    { "6.0",         6,  AV_CH_LAYOUT_6POINT0 },
    { "6.0(front)",  6,  AV_CH_LAYOUT_6POINT0_FRONT },
    { "hexagonal",   6,  AV_CH_LAYOUT_HEXAGONAL },
    { "6.1",         7,  AV_CH_LAYOUT_6POINT1 },
    { "6.1",         7,  AV_CH_LAYOUT_6POINT1_BACK },
    { "6.1(front)",  7,  AV_CH_LAYOUT_6POINT1_FRONT },
    { "7.0",         7,  AV_CH_LAYOUT_7POINT0 },
    { "7.0(front)",  7,  AV_CH_LAYOUT_7POINT0_FRONT },
    { "7.1",         8,  AV_CH_LAYOUT_7POINT1 },
    { "7.1(wide)",   8,  AV_CH_LAYOUT_7POINT1_WIDE },
    { "octagonal",   8,  AV_CH_LAYOUT_OCTAGONAL },
    { "downmix",     2,  AV_CH_LAYOUT_STEREO_DOWNMIX, },

参考

FFmpeg之重采样总结
音频属性相关:声道、采样率、采样位数、样本格式、比特率
FFMPEG开发快速入坑——音频转换处理
channel_layout

posted @ 2022-07-28 19:02  小超不挑食  阅读(2009)  评论(0编辑  收藏  举报