从48kHz到16kHz:音频重采样那些事儿

实时音视频

搞音频的小伙伴们都懂,采样率转换简直是家常便饭啊!不管是为了省点儿存储空间,还是让你那破网速能扛得住传输,又或者就是不同设备间互相伙伴匹配,重采样技术绝对是音频处理中的硬核技能。今天咱就来唠唠从48kHz降到16kHz的PCM音频重采样那些事儿,整几个不同的实现方法,看看哪个才是真爱。

picture.image

重采样是个啥玩意儿

picture.image

同一时间的单位区间内 48000HZ采样了3个点,16000HZ则采样了1个点,即从48000HZ的文件中每读取3个数据,就要根据这3个数据去推算得到1个数据,而这个数据对应的就是16000HZ文件中的一个数据。

重采样说白了就是把音频信号的采样率整成另一个值。从高采样率变低采样率(比如从48kHz变成16kHz)咱们业内人士管这叫"下采样"或者"抽取"。

按照那个谁...对,奈奎斯特采样定理(装个X),下采样前你得先把高频成分过滤掉,不然就会出现"混叠失真"这个魔鬼。对16kHz的采样率来说,就得把8kHz以上的频率给干掉。

三种实现方法大PK

1. 简单粗暴法

最简单粗暴的方法就是隔几个取一个样本点呗。从48kHz到16kHz,就是每3个取1个,不要太简单:

int convert_48k_to_16k_pcm(const short* input_buffer, size_t input_size, 
                           short* output_buffer, size_t output_size) {
    // 算一下有多少个输入样本(每个样本4个通道,每通道2字节)
    size_t input_samples = input_size / (4 * sizeof(short));
    
    // 输出样本数(降采样比3:1,从48k到16k)
    size_t output_samples = input_samples / 3;
    
    // 开始降采样(简单粗暴,每3个取1个)
    for (size_t i = 0; i < output_samples; i++) {
        // 输入索引:每3个取1个
        size_t in_idx = i * 3;
        
        // 对每个样本的4个通道都整一遍
        for (int ch = 0; ch < 4; ch++) {
            output_buffer[i * 4 + ch] = input_buffer[in_idx * 4 + ch];
        }
    }
    
    return output_samples * 4 * sizeof(short);
}

优点

  • 简单到爆,上手零门槛

  • 计算超快,不费劲

  • 不需要额外内存,省钱

缺点

  • 音质惨不忍睹,堪比上世纪电话线

  • 丢失信息贼多,关键细节全没了

  • 混叠失真严重,听着像鬼压床

2. 平均值法

这方法稍微靠谱点,就是把相邻几个样本平均一下,生成新样本点:

int convert_48k_to_16k_pcm_interpolated(const short* input_buffer, size_t input_size, 
                                       short* output_buffer, size_t output_size) {
    // 计算样本数啥的...
    
    // 降采样(用平均值法)
    for (size_t i = 0; i < output_samples; i++) {
        // 输入索引:每3个取平均
        size_t in_idx = i * 3;
        
        // 对每个样本的4个通道
        for (int ch = 0; ch < 4; ch++) {
            int sum = 0;
            int count = 0;
            
            // 算3个连续样本的平均值
            for (int j = 0; j < 3; j++) {
                if (in_idx + j < input_samples) {
                    sum += input_buffer[(in_idx + j) * 4 + ch];
                    count++;
                }
            }
            
            // 把平均值塞进输出缓冲区
            if (count > 0) {
                output_buffer[i * 4 + ch] = (short)(sum / count);
            }
        }
    }
    
    return output_samples * 4 * sizeof(short);
}

优点

  • 比上面那个靠谱多了

  • 实现也不复杂,小白也能整

  • 速度还行,不拖后腿

缺点

  • 混叠问题还是有点顶不住

  • 高频细节基本凉凉

  • 对语音类音频,听着跟隔了层毛玻璃似的

3. 低通滤波法

这个是学院派的正统方法,先用低通滤波器把高频干掉,再下采样:

int convert_48k_to_16k_pcm_filtered(const short* input_buffer, size_t input_size, 
                                   short* output_buffer, size_t output_size) {
    // 计算样本数啥的...
    
    // 随便整了个低通滤波器系数
    const float filter[5] = {0.1f, 0.2f, 0.4f, 0.2f, 0.1f};
    const int filter_len = 5;
    
    // 给每个通道整个临时缓冲区
    short* temp_buffer = (short*)malloc(input_samples * sizeof(short));
    
    // 对每个通道单独处理
    for (int ch = 0; ch < 4; ch++) {
        // 1. 提取单个通道数据
        for (size_t i = 0; i < input_samples; i++) {
            temp_buffer[i] = input_buffer[i * 4 + ch];
        }
        
        // 2. 上低通滤波器
        for (size_t i = 0; i < input_samples; i++) {
            float sum = 0.0f;
            for (int j = 0; j < filter_len; j++) {
                int idx = i - j + filter_len / 2;
                if (idx >= 0 && idx < (int)input_samples) {
                    sum += filter[j] * temp_buffer[idx];
                }
            }
            temp_buffer[i] = (short)sum;
        }
        
        // 3. 降采样(每3个取1个)
        for (size_t i = 0; i < output_samples; i++) {
            output_buffer[i * 4 + ch] = temp_buffer[i * 3];
        }
    }
    
    free(temp_buffer);
    return output_samples * 4 * sizeof(short);
}

优点

  • 音质贼拉好,简直就是原音重现

  • 混叠失真基本告别了

  • 符合理论,学院派认证

缺点

  • 实现复杂得要死

  • 计算量大,吃CPU

  • 还得额外整内存,费资源

性能与音质大比拼

计算复杂度

三种方法的计算复杂度对比:

  • 简单粗暴法:O(n),n是输出样本数

  • 平均值法:O(3n) ≈ O(n)

  • 低通滤波法:O(k·n),k是滤波器长度,n是输入样本数

内存占用

  • 简单粗暴法:只要输入输出缓冲区

  • 平均值法:只要输入输出缓冲区

  • 低通滤波法:还得额外整个临时缓冲区,大小跟输入样本数差不多

音质对比

我们找了几个耳朵灵的小伙伴做了主观测试,又整了点客观指标:

方法信噪比(SNR)总谐波失真(THD)主观评分(1-5)
简单粗暴法低到爆炸高到离谱2.1
平均值法还能凑合一般般3.4
低通滤波法贼高贼低4.7

缓冲区大小咋整

实现重采样时,算对输出缓冲区大小超级关键。从48kHz到16kHz(比例3:1)的转换,输出缓冲区大小跟输入缓冲区关系是这样的:

输出缓冲区大小 = 输入缓冲区大小 ÷ 3

具体点说,对于4通道、16bit PCM数据:

  • 每个采样点占:4通道 × 2字节 = 8字节

  • 输入缓冲区大小input_size字节,包含的采样点数:input_samples = input_size / 8

  • 转成16kHz后,采样点数变成:output_samples = input_samples / 3

  • 所以输出缓冲区大小:output_size = output_samples * 8 = input_size / 3

实际用途

语音识别前的准备工作

现在好多语音识别系统都是按16kHz工作的,可现在录音设备基本都是48kHz。所以在把音频塞进语音识别引擎前,得先重采样一下。

网络传输省流量

在网速拉胯的环境下,降低采样率能明显减少传输数据量,让你的语音通话不再"卡成PPT"。

让不同设备能愉快玩耍

不同设备可能要求不同采样率,重采样能确保你的音频到处都能正常播放,不会出现"格式不支持"这种尴尬事。

优化小技巧

  1. 滤波器设计:低通滤波法可以用更高级的滤波器设计工具整出更牛的频率响应。

  2. 多相滤波器:想要效率拉满,可以试试多相滤波器结构,能省不少计算量。

  3. SIMD 加速:用现代CPU的SIMD指令(SSE、AVX这些)可以让处理速度起飞。

  4. 分块处理:处理大文件时可以分块来,内存占用就不会爆炸。

最后

PCM音频重采样这活儿,说难不难说简单不简单。选哪种方法主要看你啥需求——要音质好就用低通滤波法,资源紧张就上简单粗暴法或平均值法。

关键是得理解重采样的基本原理,算对缓冲区大小。希望这篇文章能帮你少踩坑,毕竟踩坑的时间可以用来摸鱼不是?

0
0
0
0
关于作者
关于作者

文章

0

获赞

0

收藏

0

相关资源
边缘计算在视频直播场景的应用与实践
视频直播作为当前视频行业的核心场景之一,对于高清化、实时性、交互性要求较高,需要强大算力保障用户流畅观看与互动体验。本次分享主要从视频直播场景需求切入,介绍基于边缘计算的视频直播场景方案及其架构、应用与实践。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论