|
本帖最后由 由崎星空 于 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[j];
itemp *= adj_tmp ;
itemp >>= 7;
if (itemp > 32767) itemp = 32767;
else if (itemp < -32768) itemp = -32768;
out[j] = (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[j];
itemp *= rx_adj_level;
itemp >>= 7;
if (itemp > MAX_LEVEL) itemp = MAX_LEVEL;
else if (itemp < MIN_LEVEL) itemp = MIN_LEVEL;
p_out[j] = (int16_t)itemp;
}
}
int main(int argv, char* argc[])
{
int16_t input_raw[160], intput_raw2[160], output_raw[160];
int32_t mix_buf[160];
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[1], "rb");
if (fin == NULL)
{
printf("file %s open filed (%s)\r\n", argc[1], strerror(errno));
return 1;
}
FILE* fin2 = fopen(argc[2], "rb");
if (fin2 == NULL)
{
printf("file %s open filed (%s)\r\n", argc[1], strerror(errno));
return 1;
}
FILE* fout = fopen(argc[3], "wb");
if (fout == NULL)
{
printf("file %s open filed (%s)\r\n", argc[2], 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[k] = (int32_t)input_raw[k];
}
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[k] = (int32_t)mix_buf[k] + intput_raw2[k];
if (mix_buf[k] < mix_buf_min)
mix_buf_min = mix_buf[k];
if (mix_buf[k] > mix_buf_max)
mix_buf_max = mix_buf[k];
}
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[j] = (int16_t)mix_buf[j];
}
}
fwrite(output_raw, 1, 320, fout);
}
fclose(fin);
fclose(fin2);
fclose(fout);
return 0;
}
这个算法其实也是在很早之前刚学习音频传输处理 时候,在网上找很多资料才找到的,这个是开源项目pjproject (微型sip协议栈,里面有很多经典的算法和处理,感兴趣的同学可以关注、
pjsip/pjproject: PJSIP project (github.com),代码耦合有点严重,后来分离出来之后,就一直在这样使用(demo不是,可以自己把它封装成函数)
就到此吧
|
评分
-
查看全部评分
|