硬汉嵌入式论坛

 找回密码
 立即注册
查看: 3122|回复: 18
收起左侧

[信号与系统] PCM 音频混音算法分享

  [复制链接]

8

主题

104

回帖

138

积分

初级会员

积分
138
发表于 2022-1-10 09:15:12 | 显示全部楼层 |阅读模式
本帖最后由 由崎星空 于 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不是,可以自己把它封装成函数)


   就到此吧

评分

参与人数 1金币 +100 收起 理由
eric2013 + 100 很给力!

查看全部评分

回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106622
QQ
发表于 2022-1-10 09:41:29 | 显示全部楼层
非常感谢楼主分享。
回复

使用道具 举报

8

主题

104

回帖

138

积分

初级会员

积分
138
 楼主| 发表于 2022-1-10 10:42:26 | 显示全部楼层
eric2013 发表于 2022-1-10 09:41
非常感谢楼主分享。

  感谢 哥 天天在公众号分享知识
回复

使用道具 举报

1万

主题

6万

回帖

10万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
106622
QQ
发表于 2022-1-11 08:12:27 | 显示全部楼层
由崎星空 发表于 2022-1-10 10:42
感谢 哥 天天在公众号分享知识

发帖的时候我还以为有图没有贴上,原来是代码没有帖上,感谢
回复

使用道具 举报

8

主题

104

回帖

138

积分

初级会员

积分
138
 楼主| 发表于 2022-1-11 09:25:15 | 显示全部楼层
eric2013 发表于 2022-1-11 08:12
发帖的时候我还以为有图没有贴上,原来是代码没有帖上,感谢

我也是临时想到分享一下,所以也没有做很多的准备,就简单的把 原理讲一下,然后把代码贴上来了
回复

使用道具 举报

8

主题

133

回帖

167

积分

初级会员

积分
167
发表于 2022-5-26 15:17:26 | 显示全部楼层
谢谢分享
回复

使用道具 举报

3

主题

335

回帖

344

积分

高级会员

积分
344
发表于 2022-7-18 14:01:42 | 显示全部楼层
本帖最后由 glory 于 2022-7-18 14:03 编辑

什么时候楼主分享一下适合单片机的高效重采样算法啊,比如音频流A,B,混合成C。A采样率为fA,B为fB,C为fC,fX各不相同。
回复

使用道具 举报

8

主题

104

回帖

138

积分

初级会员

积分
138
 楼主| 发表于 2022-7-24 19:01:29 | 显示全部楼层
glory 发表于 2022-7-18 14:01
什么时候楼主分享一下适合单片机的高效重采样算法啊,比如音频流A,B,混合成C。A采样率为fA,B为fB,C为fC ...

现在常用都是统一的将3个不同频段的音频转换成一个,然后在混音的,重采样可以用 webrtc 或者 用 speex 的重采样算法都很好的。
回复

使用道具 举报

0

主题

1

回帖

1

积分

新手上路

积分
1
发表于 2022-8-10 16:56:23 | 显示全部楼层
非常及时!很给力,这个我根本没去仔细研究音频这块,硬汉,给力!
回复

使用道具 举报

1

主题

19

回帖

22

积分

新手上路

积分
22
发表于 2023-10-17 11:59:04 | 显示全部楼层
收藏 学习之~~~~~
回复

使用道具 举报

1

主题

19

回帖

22

积分

新手上路

积分
22
发表于 2023-10-17 12:05:41 | 显示全部楼层
由崎星空 发表于 2022-7-24 19:01
现在常用都是统一的将3个不同频段的音频转换成一个,然后在混音的,重采样可以用 webrtc 或者 用 speex  ...

楼主,是做音频产品的吗
回复

使用道具 举报

8

主题

104

回帖

138

积分

初级会员

积分
138
 楼主| 发表于 2023-10-17 15:09:18 | 显示全部楼层
ccc228 发表于 2023-10-17 12:05
楼主,是做音频产品的吗

大部分是做 音频的,其它的也在做,看需求了。
回复

使用道具 举报

10

主题

43

回帖

73

积分

初级会员

积分
73
发表于 2023-11-20 15:35:10 | 显示全部楼层
由崎星空 发表于 2023-10-17 15:09
大部分是做 音频的,其它的也在做,看需求了。

楼主做音频用过ADI的音频处理芯片吗
回复

使用道具 举报

8

主题

104

回帖

138

积分

初级会员

积分
138
 楼主| 发表于 2023-11-29 10:38:25 | 显示全部楼层
lishang4650 发表于 2023-11-20 15:35
楼主做音频用过ADI的音频处理芯片吗

用过 ADI 17 系列和最新的 ADI 184X系列。不过ADI的产品大多都是 寄给 ADI 让他们帮忙调。
回复

使用道具 举报

10

主题

43

回帖

73

积分

初级会员

积分
73
发表于 2023-12-4 09:08:36 | 显示全部楼层
由崎星空 发表于 2023-11-29 10:38
用过 ADI 17 系列和最新的 ADI 184X系列。不过ADI的产品大多都是 寄给 ADI 让他们帮忙调。

楼主,我们现在也是第一次用ADI 14系列,请教下你说的ADI帮忙调 是通过什么渠道或方式。我看了看国内的技术支持不是太多,目前资料都是看国外论坛,效率有点慢,感谢!
回复

使用道具 举报

8

主题

104

回帖

138

积分

初级会员

积分
138
 楼主| 发表于 2023-12-4 11:32:48 | 显示全部楼层
lishang4650 发表于 2023-12-4 09:08
楼主,我们现在也是第一次用ADI 14系列,请教下你说的ADI帮忙调 是通过什么渠道或方式。我看了看国内的技 ...

ADADU 14系列的 比较经典,而且资源比较多,应该不那么难调吧,我用ADAU 1860资料不多,所以才找的。我们是直接联系 深圳的ADI 办事处,让他们到公司来协助,买了开发板,买了配套的一些东西。
回复

使用道具 举报

10

主题

43

回帖

73

积分

初级会员

积分
73
发表于 2023-12-5 15:14:22 | 显示全部楼层
由崎星空 发表于 2023-12-4 11:32
ADADU 14系列的 比较经典,而且资源比较多,应该不那么难调吧,我用ADAU 1860资料不多,所以才找的。我们 ...

了解了,多谢!第一次用SigmaStudio编程,不太熟悉如何搭工程调参数。我们产品主要功能是AGC,从论坛下了点参考例程,效果不是太好,有杂音啥的
回复

使用道具 举报

8

主题

104

回帖

138

积分

初级会员

积分
138
 楼主| 发表于 2023-12-6 09:06:45 | 显示全部楼层
lishang4650 发表于 2023-12-5 15:14
了解了,多谢!第一次用SigmaStudio编程,不太熟悉如何搭工程调参数。我们产品主要功能是AGC,从论坛下了 ...

不客气,是什么样的杂音呢,可以输入个正弦波,看看是不是 限幅器 值设置 小了。
回复

使用道具 举报

10

主题

43

回帖

73

积分

初级会员

积分
73
发表于 2023-12-6 11:55:12 | 显示全部楼层
由崎星空 发表于 2023-12-6 09:06
不客气,是什么样的杂音呢,可以输入个正弦波,看看是不是 限幅器 值设置 小了。

就是从论坛下载了个AGC工程自己跑了下,没有输入的时候就有底噪。还不了解哪里出来的
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-4-26 14:41 , Processed in 0.380766 second(s), 26 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表