PCM 音频混音算法分享
本帖最后由 由崎星空 于 2022-1-10 10:40 编辑最近忙了一年,终于有点空闲时间,想写点什么分享给大家,刚好在安富莱微信公众号看到了,有想了解音频处理的朋友,所以就写一些自己知道的分享一下。---------------------------------------------------------------------------------------------------------------------------------------------------
由于I2S在播放音频数据的时候,只能播放一帧数据,不像PCM接口可以通过设置不同的SLOT来播放N个不同源的帧,那么设备要播放一个文件/数据流的时候,会打断另一个文件/数据流的播放,或者在VOIP里面多方通信的情况下,想要I2S同时播放N个文件/数据流需要怎么做呢?
答案就是需要将音频混合在一起,而混音算法最基本的原理其实就是简单的将 N个不同的音频文件通过加法叠加在一起而已(同采样率的情况下,如果不同需要进行重采样)。最基础的最核心的部分就已经讲完了,就是简单的 + 在一起就可以了,但是由于采样深度的问题,常用的 16/24/32 bit (通信里面还是常用16bit)来说。2个16bit 相加肯定会有机率 超过 -32768 ~ 32767,那么需要在叠在之后,进行判断当前的采样点的幅值是否超过了 范围,并且在 叠加的时候 需要根据采样深度向上扩展。
int16_t A ,B; int32_t C; C=A+B; (最后为叠在之后的点的值,如果 C 超过了 -32768 ~ 32767 那么需要将 C 值进行缩放到 -32768 ~ 32767的范围内,那么可以通过简单的 " >> " 来实现,那么右移多少比较合适呢。这个值可以是固定的,也可以是动态的。但如果只是对一个值进行 缩放,那么就会听起来很机械,那么需要查找一帧数据中 叠加之后的最大值。
在缩放中还有一个小知识就是 无论是16/24/32bit的其实最后改变声音效果的 都是最高位的8bit而已,例如你有16bit的I2S数据,你把底8bit 砍掉(不存储,在播放的时候,把低8位填0也好填其它的也好,播放出来也跟之前 录音16bit的效果差不多)。跟a/μ率有些许不同。那么由于这个小知识点其实处理 幅值只需要处理最高7bit就行了(符号位不算)。
通过统计出了 一帧数据叠加之后的最大值,adj_tmp = (32767 << 7) / max_tmp(为正数,需要将采样点的数据取绝对值进行比较); 那么 就是所需要的缩放的 数值 , 然后将一帧数据进行缩放,假设2个数据信号都为 最小的15bit的 -32768,相加就为-65536 如果直接将这个数据进行播放播放的效果就完全不同了,整个音频数据被静音了 16bit (0x0000了)。所以需要缩放到 范围内,范围就是 adj_tmp = (32767 << 7) / 65536(当前音频这个帧的最大值);
最后得到adj_tmp = 63。63为0~127范围内的中间点(熟悉a/μ率的同学可能理解比较快)最后计算出来的结果就是叠加之后并且缩放之后的音频数据了。
in为32bit的数据,out为16bit的数据:
for (int32_t j = 0; j < length; ++j) {
int32_t itemp;
itemp = in;
itemp *= adj_tmp ;
itemp >>= 7;
if (itemp > 32767) itemp = 32767;
else if (itemp < -32768) itemp = -32768;
out = (int16_t)itemp;
}
最后实现效果可以为:
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#define NORMAL_LEVEL 128
#define MAX_LEVEL (32767)
#define MIN_LEVEL (-32768)
#define IS_OVERFLOW(s) ((s > MAX_LEVEL) || (s < MIN_LEVEL))
void adjust_level(int32_t* p_in, int16_t* p_out, int32_t length, int16_t rx_adj_level)
{
for (int32_t j = 0; j < length; ++j) {
int32_t itemp;
itemp = p_in;
itemp *= rx_adj_level;
itemp >>= 7;
if (itemp > MAX_LEVEL) itemp = MAX_LEVEL;
else if (itemp < MIN_LEVEL) itemp = MIN_LEVEL;
p_out = (int16_t)itemp;
}
}
int main(int argv, char* argc[])
{
int16_t input_raw, intput_raw2, output_raw;
int32_t mix_buf;
int32_t clock_rate;
int32_t samples_per_frame;
int32_t adj_level;
int32_t mix_buf_min = 0;
int32_t mix_buf_max = 0;
int32_t mix_adj;
clock_rate = 8000;
samples_per_frame = 160;
FILE* fin = fopen(argc, "rb");
if (fin == NULL)
{
printf("file %s open filed (%s)\r\n", argc, strerror(errno));
return 1;
}
FILE* fin2 = fopen(argc, "rb");
if (fin2 == NULL)
{
printf("file %s open filed (%s)\r\n", argc, strerror(errno));
return 1;
}
FILE* fout = fopen(argc, "wb");
if (fout == NULL)
{
printf("file %s open filed (%s)\r\n", argc, strerror(errno));
return 1;
}
mix_adj = NORMAL_LEVEL;
while (fread(input_raw, 1, samples_per_frame * sizeof(int16_t), fin) == samples_per_frame * sizeof(int16_t))
{
mix_buf_min = 0;
mix_buf_max = 0;
for (int32_t k = 0; k < samples_per_frame; ++k) {
mix_buf = (int32_t)input_raw;
}
if (fread(intput_raw2, 1, samples_per_frame * sizeof(int16_t), fin2) != samples_per_frame * sizeof(int16_t))
{
break;
}
for (int32_t k = 0; k < samples_per_frame; ++k) {
mix_buf = (int32_t)mix_buf + intput_raw2;
if (mix_buf < mix_buf_min)
mix_buf_min = mix_buf;
if (mix_buf > mix_buf_max)
mix_buf_max = mix_buf;
}
mix_adj = NORMAL_LEVEL;
if (mix_buf_min < MIN_LEVEL || mix_buf_max >= MAX_LEVEL) {
int tmp_adj;
if (-mix_buf_min > mix_buf_max)
mix_buf_max = -mix_buf_min;
tmp_adj = (MAX_LEVEL << 7) / mix_buf_max;
if (tmp_adj < NORMAL_LEVEL)
mix_adj = tmp_adj;
}
adj_level = NORMAL_LEVEL * mix_adj;
adj_level >>= 7;
if (adj_level != NORMAL_LEVEL) {
adjust_level((int16_t*)mix_buf, (int16_t*)output_raw, 160, adj_level);
}
else
{
for (int32_t j = 0; j < 160; ++j) {
output_raw = (int16_t)mix_buf;
}
}
fwrite(output_raw, 1, 320, fout);
}
fclose(fin);
fclose(fin2);
fclose(fout);
return 0;
}
这个算法其实也是在很早之前刚学习音频传输处理 时候,在网上找很多资料才找到的,这个是开源项目pjproject (微型sip协议栈,里面有很多经典的算法和处理,感兴趣的同学可以关注、
pjsip/pjproject: PJSIP project (github.com),代码耦合有点严重,后来分离出来之后,就一直在这样使用(demo不是,可以自己把它封装成函数)
就到此吧
非常感谢楼主分享。 eric2013 发表于 2022-1-10 09:41
非常感谢楼主分享。
:lol:lol感谢 哥 天天在公众号分享知识 由崎星空 发表于 2022-1-10 10:42
感谢 哥 天天在公众号分享知识
发帖的时候我还以为有图没有贴上,原来是代码没有帖上,感谢 eric2013 发表于 2022-1-11 08:12
发帖的时候我还以为有图没有贴上,原来是代码没有帖上,感谢
我也是临时想到分享一下,所以也没有做很多的准备,就简单的把 原理讲一下,然后把代码贴上来了:lol {:8:}谢谢分享 本帖最后由 glory 于 2022-7-18 14:03 编辑
什么时候楼主分享一下适合单片机的高效重采样算法啊,比如音频流A,B,混合成C。A采样率为fA,B为fB,C为fC,fX各不相同。 glory 发表于 2022-7-18 14:01
什么时候楼主分享一下适合单片机的高效重采样算法啊,比如音频流A,B,混合成C。A采样率为fA,B为fB,C为fC ...
现在常用都是统一的将3个不同频段的音频转换成一个,然后在混音的,重采样可以用 webrtc 或者 用 speex 的重采样算法都很好的。 非常及时!很给力,这个我根本没去仔细研究音频这块,硬汉,给力! 收藏 学习之~~~~~ 由崎星空 发表于 2022-7-24 19:01
现在常用都是统一的将3个不同频段的音频转换成一个,然后在混音的,重采样可以用 webrtc 或者 用 speex...
楼主,是做音频产品的吗 ccc228 发表于 2023-10-17 12:05
楼主,是做音频产品的吗
大部分是做 音频的,其它的也在做,看需求了。 由崎星空 发表于 2023-10-17 15:09
大部分是做 音频的,其它的也在做,看需求了。
楼主做音频用过ADI的音频处理芯片吗 lishang4650 发表于 2023-11-20 15:35
楼主做音频用过ADI的音频处理芯片吗
用过 ADI 17 系列和最新的 ADI 184X系列。不过ADI的产品大多都是 寄给 ADI 让他们帮忙调。 由崎星空 发表于 2023-11-29 10:38
用过 ADI 17 系列和最新的 ADI 184X系列。不过ADI的产品大多都是 寄给 ADI 让他们帮忙调。
楼主,我们现在也是第一次用ADI 14系列,请教下你说的ADI帮忙调 是通过什么渠道或方式。我看了看国内的技术支持不是太多,目前资料都是看国外论坛,效率有点慢,感谢!{:20:} lishang4650 发表于 2023-12-4 09:08
楼主,我们现在也是第一次用ADI 14系列,请教下你说的ADI帮忙调 是通过什么渠道或方式。我看了看国内的技 ...
ADADU 14系列的 比较经典,而且资源比较多,应该不那么难调吧,我用ADAU 1860资料不多,所以才找的。我们是直接联系 深圳的ADI 办事处,让他们到公司来协助,买了开发板,买了配套的一些东西。 由崎星空 发表于 2023-12-4 11:32
ADADU 14系列的 比较经典,而且资源比较多,应该不那么难调吧,我用ADAU 1860资料不多,所以才找的。我们 ...
了解了,多谢!第一次用SigmaStudio编程,不太熟悉如何搭工程调参数。我们产品主要功能是AGC,从论坛下了点参考例程,效果不是太好,有杂音啥的:) lishang4650 发表于 2023-12-5 15:14
了解了,多谢!第一次用SigmaStudio编程,不太熟悉如何搭工程调参数。我们产品主要功能是AGC,从论坛下了 ...
不客气,是什么样的杂音呢,可以输入个正弦波,看看是不是 限幅器 值设置 小了。 由崎星空 发表于 2023-12-6 09:06
不客气,是什么样的杂音呢,可以输入个正弦波,看看是不是 限幅器 值设置 小了。
就是从论坛下载了个AGC工程自己跑了下,没有输入的时候就有底噪。还不了解哪里出来的
页:
[1]