写点什么

音视频八股文(11)-- ffmpeg 音频重采样

  • 2023-05-11
    北京
  • 本文字数:3183 字

    阅读完需:约 10 分钟

1 重采样

1.1 什么是重采样

所谓的重采样,就是改变⾳频的采样率、sample format、声道数等参数,使之按照我们期望的参数输出。

1.2 为什么要重采样

为什么要重采样?当然是原有的⾳频参数不满⾜我们的需求,⽐如在 FFmpeg 解码⾳频的时候,不同的⾳源有不同的格式,采样率等,在解码后的数据中的这些参数也会不⼀致(最新 FFmpeg 解码⾳频后,⾳频格式为 AV_SAMPLE_FMT_FLTP,这个参数应该是⼀致的),如果我们接下来需要使⽤解码后的⾳频数据做其他操作,⽽这些参数的不⼀致导致会有很多额外⼯作,此时直接对其进⾏重采样,获取我们制定的⾳频参数,这样就会⽅便很多。


再⽐如在将⾳频进⾏SDL 播放时候,因为当前的 SDL2.0 不⽀持 planar 格式,也不⽀持浮点型的,⽽最新的 FFMPEG 16 年会将⾳频解码为 AV_SAMPLE_FMT_FLTP 格式,因此此时就需要我们对其重采样,使之可以在 SDL2.0 上进⾏播放。

2 对应参数解析

2.1 采样率

采样设备每秒抽取样本的次数

2.2 采样格式及量化精度(位宽)

每种⾳频格式有不同的量化精度(位宽),位数越多,表示值就越精确,声⾳表现⾃然就越精准。FFMpeg 中⾳频格式有以下⼏种,每种格式有其占⽤的字节数信息(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};
复制代码

2.3 分⽚(plane)和打包(packed)

以双声道为例,带 P(plane)的数据格式在存储时,其左声道和右声道的数据是分开存储的,左声道的数据存储在 data[0],右声道的数据存储在 data[1],每个声道的所占⽤的字节数为 linesize[0]和 linesize[1];


不带 P(packed)的⾳频数据在存储时,是按照 LRLRLR...的格式交替存储在 data[0]中,linesize[0]表示总的数据量。

2.4 声道分布(channel_layout)

声道分布在 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)
复制代码

2.5 ⾳频帧的数据量计算

⼀帧⾳频的数据量(字节)=channel 数 * nb_samples 样本数 * 每个样本占⽤的字节数


如果该⾳频帧是 FLTP 格式的 PCM 数据,包含 1024 个样本,双声道,那么该⾳频帧包含的⾳频数据量是 210244=8192 字节。


AV_SAMPLE_FMT_DBL : 210248 = 16384

2.6 ⾳频播放时间计算

以采样率 44100Hz 来计算,每秒 44100 个 sample,⽽正常⼀帧为 1024 个 sample,可知每帧播放时间/1024=1000ms/44100,得到每帧播放时间=1024*1000/44100=23.2ms (更精确的是 23.21995464852608)。


⼀帧播放时间(毫秒) = nb_samples 样本数 *1000/采样率 =


(1)1024*1000/44100=23.21995464852608ms ->约等于 23.2ms,精度损失了 0.011995464852608ms,如果累计 10 万帧,误差>1199 毫秒,如果有视频⼀起的就会有⾳视频同步的问题。 如果按着 23.2 去计算 pts(0 23.2 46.4 )就会有累积误差。


(2)1024*1000/48000=21.33333333333333ms

3 FFmpeg 重采样 API

分配⾳频重采样的上下⽂


struct SwrContext *swr_alloc(void);
复制代码


当设置好相关的参数后,使⽤此函数来初始化 SwrContext 结构体


int swr_init(struct SwrContext *s);
复制代码


分配 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);
复制代码


将输⼊的⾳频按照定义的参数进⾏转换并输出


int swr_convert(struct SwrContext* s, // ⾳频重采样的上下⽂    uint8_t** out, // 输出的指针。传递的输出的数组    int out_count, //输出的样本数量,不是字节数。单通道的样本数量。    const uint8_t** in, //输⼊的数组,AVFrame解码出来的DATA    int in_count // 输⼊的单通道的样本数量。);
复制代码


in 和 in_count 可以设置为 0,以最后刷新最后⼏个样本。


释放掉 SwrContext 结构体并将此结构体置为 NULL;


void swr_free(struct SwrContext **s);
复制代码


⾳频重采样,采样格式转换和混合库。与 lswr 的交互是通过 SwrContext 完成的,SwrContext 被分配给 swr_alloc()或 swr_alloc_set_opts()。 它是不透明的,所以所有参数必须使⽤AVOptions API 设置。为了使⽤lswr,你需要做的第⼀件事就是分配 SwrContext。 这可以使⽤swr_alloc()或 swr_alloc_set_opts()来完成。 如果您使⽤前者,则必须通过 AVOptions API 设置选项。 后⼀个函数提供了相同的功能,但它允许您在同⼀语句中设置⼀些常⽤选项。


例如,以下代码将设置从平⾯浮动样本格式到交织的带符号 16 位整数的转换,从 48kHz 到 44.1kHz 的下采样,以及从 5.1 声道到⽴体声的下混合(使⽤默认混合矩阵)。 这是使⽤swr_alloc()函数。


SwrContext * swr = swr_alloc();av_opt_set_channel_layout(swr, "in_channel_layout", AV_CH_LAYOUT_5POINT1, 0);av_opt_set_channel_layout(swr, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);av_opt_set_int(swr, "in_sample_rate", 48000, 0);av_opt_set_int(swr, "out_sample_rate", 44100, 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_alloc_set_opts():


SwrContext * swr = swr_alloc_set_opts(NULL, // we're allocating a new context    AV_CH_LAYOUT_STEREO, // out_ch_layout    AV_SAMPLE_FMT_S16, // out_sample_fmt    44100, // out_sample_rate    AV_CH_LAYOUT_5POINT1, // in_ch_layout    AV_SAMPLE_FMT_FLTP, // in_sample_fmt    48000, // in_sample_rate    0, // log_offset    NULL); // log_ctx
复制代码


⼀旦设置了所有值,它必须⽤swr_init()初始化。 如果需要更改转换参数,可以使⽤AVOptions 来更改参数,如上⾯第⼀个例⼦所述; 或者使⽤swr_alloc_set_opts(),但是第⼀个参数是分配的上下⽂。 您必须再次调⽤swr_init()。⼀旦设置了所有值,它必须⽤swr_init()初始化。 如果需要更改转换参数,可以使⽤AVOptions 来更改参数,如上⾯第⼀个例⼦所述; 或者使⽤swr_alloc_set_opts(),但是第⼀个参数是分配的上下⽂。 您必须再次调⽤swr_init()。转换本身通过重复调⽤swr_convert()来完成。 请注意,如果提供的输出空间不⾜或采样率转换完成后,样本可能会在 swr 中缓冲,这需要“未来”样本。 可以随时通过使⽤swr_convert()(in_count 可以设置为 0)来检索不需要将来输⼊的样本。 在转换结束时,可以通过调⽤具有 NULL in 和 in incount 的 swr_convert()来刷新重采样缓冲区。

4 go 代码

moonfdd/ffmpeg-go



发布于: 刚刚阅读数: 3
用户头像

公众号:福大大架构师每日一题 2021-02-15 加入

公众号:福大大架构师每日一题

评论

发布
暂无评论
音视频八股文(11)-- ffmpeg 音频重采样_音视频_福大大架构师每日一题_InfoQ写作社区