重采样
所谓的重采样,就是改变⾳频的采样率、sample format、声道数等参数,使之按照我们期望的参数输出。比如降低采样率 减少文件占用内存
为什么要重采样?
当然是原有的⾳频参数不满⾜我们的需求,⽐如在FFmpeg解码⾳频的时候,不同的⾳ 源有不同的格式,采样率等,在解码后的数据中的这些参数也会不⼀致。下列举例2个重要原因:
-
声卡硬件要求:不同的声卡可能对音频数据的采样率、采样格式和通道数有不同的要求。例如,如果一个声卡需要音频数据以48kHz的采样率、16位的采样格式和双通道(立体声)输出,而原始音频文件的参数是44.1kHz采样率、浮点平面格式(float planar format)和双通道,那么就需要对原始音频进行重采样以满足声卡的要求。
-
两个音源混音:当需要将两个不同采样率和/或采样格式的音频文件混合在一起时,也需要进行重采样。例如,如果一个音频文件的参数是48kHz采样率、16位采样格式和双通道,而另一个音频文件是44.1kHz采样率、浮点平面格式和双通道,为了将这两个音频文件混合在一起,需要将它们都转换到相同的采样率和采样格式,例如48kHz、双通道、16位格式。
可调节的参数
通过重采样,我们可以对:
-
sample rate(采样率)
-
sample format(采样格式)
-
channel layout(通道布局,可以通过此参数获取声道数
回顾音频基本概念
采样率:每秒钟采样的点的个数。
常用的采样频率有: 22000(22kHz): 无线广播。 44100(44.1kHz): CD音质。 48000(48kHz): 数字电视,DVD。 96000(96kHz): 蓝光,高清DVD。 192000(192kHz): 蓝光,高清DVD
帧:每次编码的采样单元数,比如MP3通常是1152个采样点作为一个编码单元,AAC通常是1024个采样点作为一个编码单元。
帧长:可以指每帧播放持续的时间也可以指压缩后每帧的数据长度。
分⽚(plane)和打包(packed):以双声道为例,带P(plane)的数据格式在存储时,其左声道和右声道的数据是分开存储的,左声道的 数据存储在data[0],右声道的数据存储在data[1],每个声道的所占⽤的字节数为linesize[0]和 linesize[1]; 不带P(packed)的⾳频数据在存储时,是按照LRLRLR...的格式交替存储在data[0]中,linesize[0] 表示总的数据量。
采样精度(采样深度):每个“样本点”的大小,常用的大小为8bit, 16bit,24bit。表示值越精确,声⾳表现⾃然就越精准。
FFMpeg中⾳频格式有以下⼏种,每种格式有其占⽤的字节数信息(libavutil/samplefmt.h): enum AVSampleFormat { packed格式 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 planar格式 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 }
比特率:表示每秒传输的比特数,单位是比特每秒(bps),用于描述数据传输速度或编码速率。 它反映了音频流每秒所需的数据量。
音频帧数据量:指的是单个音频帧(或音频包)所包含的数据大小,通常以字节为单位。
通道数:指的是音频信号中独立的声道数量。例如,立体声(Stereo)有两个通道,5.1环绕声有六个通道(五个扬声器和一个低音炮)。
通道布局:描述了各个通道在音频场景中的具体位置。它定义了每个通道的相对位置和角色,例如左前、右前、中置、环绕等。通道布局通常用一个位掩码来表示,每个位对应一个特定的位置。
以下是一些常见的通道布局和它们对应的通道数: AV_CH_LAYOUT_MONO:单声道(Mono),1个通道。 AV_CH_LAYOUT_STEREO:立体声(Stereo),2个通道,通常为左(L)和右(R)。 AV_CH_LAYOUT_5POINT1:5.1环绕声,6个通道,包括左前(L)、右前(R)、中置(C)、左环绕(Ls)、右环绕(Rs)和一个低音炮(LFE)。 AV_CH_LAYOUT_7POINT1:7.1环绕声,8个通道,增加了后左环绕(Lrs)和后右环绕(Rrs)。
常用的公式
每帧采样点数即是帧 1.比特率 = 采样率 * 采样精度 * 声道数 例如,如果采样率是44100Hz,采样精度是16位,双声道立体声,则比特率为 44100 × 16 × 2 = 1,372,800bps。 2.⾳频帧数据量 = 每帧采样点数 * 采样精度 * 声道数 例如,一个音频帧如果有1024个样本,每个样本是16位(2字节),并且有两个声道,则该帧的数据量为1024样本 × 2字节样本 × 2声道 = 4096字节。 以上两者关系: 音频帧数据量 * 播放时长 = 比特率 3.⾳频播放时间计算: 每帧持续时间(秒) = 每帧采样点数 / 采样频率(HZ) 以采样率44100Hz来计算,每秒44100个sample,⽽正常⼀帧为1024个sample,可知每帧播放时间/1024=44100,得到 每帧播放时间=1024/44100=0.02321995464852608s (23.21995464852608 ms)。 如果累计10万帧,误差>1199毫秒,如果有视频⼀起的就会有⾳视频同步的问题。 如果按着23.2去计算pts(0 23.2 46.4 )就会有累积误差。 4.输出采样点 = (输入采样点 * 输出采样率/ 输入采样率) 5.码率 = 音频文件大小/时长 上列这些公式在编程中经常使用,举个例子,我想malloc一帧内存大小,那就需要提前计算出一帧大小,结合公式即可得出。
ffmpeg提供的重采样api
根据格式计算出输入源的通道数量
int av_get_channel_layout_nb_channels(uint64_t channel_layout);
给输入源分配内存空间 根据采样分配内存 lrlrlr data[0] data[0]lll data[1]rrr
分配一个数组的指针以及音频样本缓冲区的内存 Plane模式 这里是三级指针
也就是说三级指针内有多个指针数组 data[0] data[1]
int av_samples_alloc_array_and_samples(
uint8_t ***audio_data, int *linesize, int nb_channels,
int nb_samples, enum AVSampleFormat sample_fmt, int align);
分配音频样本缓冲区的内存 packet模式(交错模式) 这里参1是二级指针
也就是说只有一个通道!data[0]
int av_samples_alloc(uint8_t **audio_data,
int *linesize, int nb_channels, int nb_samples,
enum AVSampleFormat sample_fmt, int align);
计算输出采样数量 向上取整 940.8 941 a * b / c
输出采样点 = (输入采样点 * 输出采样率/ 输入采样率)
int64_t av_rescale_rnd(
int64_t a, int64_t b, int64_t c
, enum AVRounding rnd) av_const;
确定当前重采样器的延迟样本数
int64_t swr_get_delay(struct SwrContext *s
, int64_t base);
执行音频样本的转换操作 上下文 输出缓冲区 长度 输入...
int swr_convert(struct SwrContext *s,
uint8_t **out, int out_count,
const uint8_t **in , int in_count);
计算音频帧的占用量
⾳频帧数据量 = 每帧采样点数 * 采样精度 * 声道数
linesize 将被设置为每个声道的字节大小
int av_samples_get_buffer_size(
int *linesize, int nb_channels,
int nb_samples,enum AVSampleFormat sample_fmt
, int align);
使用流程
举个例子从plane重采样pakcet
1.创建重采样器
swr_ctx = swr_alloc();
2.设置重采样参数
// 输入参数
av_opt_set_int(swr_ctx, "in_channel_layout", src_ch_layout, 0);
av_opt_set_int(swr_ctx, "in_sample_rate", src_rate, 0);
av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", src_sample_fmt, 0);
// 输出参数
av_opt_set_int(swr_ctx, "out_channel_layout", dst_ch_layout, 0);
av_opt_set_int(swr_ctx, "out_sample_rate", dst_rate, 0);
av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", dst_sample_fmt, 0);
if ((ret = swr_init(swr_ctx)) < 0) {
fprintf(stderr, "Failed to initialize the resampling context\n");
goto end;
}
下了就是分别计算输入和输出的音频样本内存大小和输出采样点数量。
3.给输入源分配内存空间 根据采样分配内存 所以需要计算出输入源的通道数量
src_nb_channels = av_get_channel_layout_nb_channels(src_ch_layout);
ret = av_samples_alloc_array_and_samples(&src_data, &src_linesize,
src_nb_channels,src_nb_samples
, src_sample_fmt, 0);
4.计算输出采样点数量
dst_nb_samples =
av_rescale_rnd(src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
while(){
//确定当前重采样器的延迟样本数
int64_t delay = swr_get_delay(swr_ctx, src_rate);
//重新计算输出采样点数量 可能增大了,就需要free之前申请的,然后再调用 av_samples_alloc
dst_nb_samples = av_rescale_rnd(delay + src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
if (dst_nb_samples > max_dst_nb_samples) {//容量不足 重新分配更大的
av_freep(&dst_data[0]);
//扩容了 重新申请packet模式连续内存块
ret = av_samples_alloc(dst_data, &dst_linesize, dst_nb_channels,
dst_nb_samples, dst_sample_fmt, 1);
if (ret < 0)
break;
max_dst_nb_samples = dst_nb_samples;
}
//执行音频样本的转换操作 上下文 输出缓冲区 长度 输入...
ret = swr_convert(swr_ctx, dst_data, dst_nb_samples
, (const uint8_t **)src_data, src_nb_samples);
//计算音频帧的占用量 ⾳频帧数据量 = 每帧采样点数 * 采样精度 * 声道数 linesize 将被设置为每个声道的字节大小
dst_bufsize = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels,
ret, dst_sample_fmt, 1);
fwrite(dst_data[0], 1, dst_bufsize, dst_file);
}
//把剩余缓存数据写入
ret = swr_convert(swr_ctx, dst_data, dst_nb_samples, NULL, 0);
dst_bufsize = av_samples_get_buffer_size(&dst_linesize, dst_nb_channels,
ret, dst_sample_fmt, 1);
fwrite(dst_data[0], 1, dst_bufsize, dst_file);
程序中出现了程序采样点数增大情况,为什么会出现这种情况?
当从44.1khz转化成 48khz ,如果不变动采样点,那就会造成播放时间变短。想要播放时间不变的情况下,那每帧的采样点数量肯定发生变化了。44转48,这里采样点就会变大。当采样率从大转1小,反之帧就会减小。