数字音频Mixer算法

1.1      问题提出

Mix的意思是混音,无论在自然界,还是在音频处理领域这都是非常普遍的现象。自然界里你能同时听到鸟鸣和水声,这是因为鸟鸣和水声的波形在空气中形成了叠加,耳朵听到后能区分鸟鸣和水声这两种波形。

在数字音频领域也是一样,比如你也可以一边打CS一边听歌,这是因为计算机把两个声音波形做了叠加。但是不同的是,计算机中的叠加,很容易造成越界。

比如

int plus1(int num0, int num1){

return num0+num1;

}

如果赋值int num0=0x70000000和int num1=0x70000000,运行后的result是0xE0000000,变换为十进制为-536870912。两个正数相加得到了负数,结果自然是错的。

我们知道,一个char的补码所能表示的数值范围是[-128, 127],写成16进制是[0x80,0x7F]。而一个int的补码的范围是[0x80000000,0x7FFFFFFF]。超出这个范围就是溢出。

如何防止溢出呢?最简单的做法是拓宽存储数据的容器,比如:

long long plus1(int num0, int num1){

return (long long)num0+(long long)num1;

}

赋值int num0=0x70000000和int num1=0x70000000,运行后的result是0xE0000000,变换为十进制为3758096384。这次没有溢出。

1.2         公式

怎么能做到不溢出呢?考虑这个公式

Z=A+B?AB,

如果A和B都在[0,1]范围内,那么:

0<=(1-A)(1-B)=1-A-B+AB<=1,那么

0<=Z<=1

这样,如果我们把A,B看做是两个输入波形,Z看做是一个输出波形的话,Z的上界和下界也在A和B的上界和下界内。也就是说,Z是不会溢出的。

对于3个输入信号来说,按照(1-A)(1-B)(1-C)运算,易得

Z=A+B+C?AB?AC?BC+ABC.

而对于取值范围不在[0,1]的信号,可以先转化为[0,1]来做。

比如A,B均在[0,255]范围内,则A/255在[0,1]内,则

Z/255=A/255+B/255-(A/255)*(B/255),那么

Z=A+B-AB/255

对于有符号的数,取值范围在[-128,127],则A’=(A+128)/255取值在[0,1]内,则

Z’=A’+B’?A’*B’,代入可得

(Z+128)/255=(A+128)/255+(B+128)/255-(A+128)/255*(B+128)/255,则

Z=A+B-(A+128)(B+128)/255+128

这种算法可以认为是简单的对输入信号进行了相加,并为了避免溢出,压缩了两个信号的和的波形。但是这种算法有个致命的缺点,那就是当两个信号相加没有溢出时,这种算法仍然压缩了波形,导致音质受损。而且过多的加减乘除的运算,会提升整个系统的功耗和复杂性,也会在四舍五入中降低数据的精度。

说句题外话,为了避免运算中声音信号精度的丢失,目前业界高端音频处理系统里都是用32位float采样来进行运算的,而输出的时候转化为16bit。

1.3         Android做法

我们看看成熟的软件是怎么做的。Android的Mixer在AudioMixer.cpp这个文件里,它针对不同的情况,有各种执行混音操作的函数,下面这个函数是处理无需重采样的立体声音频的。

voidAudioMixer::process__genericNoResampling(state_t* state, int64_t pts)

我们来看看它的处理方式:它是把各个track的声音数据相加。所谓声音数据,可以认为是一个个的采样点,Android默认支持的采样精度是16bit的,格式为signedPCM,所以每个采样点用有符号的16位数int16_t表示。如果直接加16bit的数据,肯定会造成16bit的值溢出,Android的做法是强转成int32_t,相加,并把和赋值给了32bit的数。注意,相加前乘上了音量,而表达音量的数据类型也是int32_t。这样,就能保证在这个过程中是不会溢出的。

voidAudioMixer::track__16BitsStereo(track_t* t, int32_t* out, size_t frameCount,

int32_t* temp __unused, int32_t* aux){

int32_t vl =t->prevVolume[0];

nt32_t vr =t->prevVolume[1];

const int16_t*in = static_cast<const int16_t *>(t->in);

*out++ += (vl>> 16) * (int32_t) *in++;

*out++ += (vr>> 16) * (int32_t) *in++;

}

此时,混音后的数据已经存在out指向的buffer里了,然后再调用

convertMixerFormat(out, t1.mMixerFormat,outTemp, t1.mMixerInFormat, BLOCKSIZE * t1.mMixerChannelCount);

其中有函数ditherAndClamp,这个是把int32_t格式的源数据sums消减成int16_t,并把左右声道一起放入int32_t格式的out中。

void ditherAndClamp(int32_t* out, constint32_t *sums, size_t c)

{

size_t i;

for (i=0 ; i<c ; i++) {

int32_t l = *sums++;

int32_t r = *sums++;

int32_t nl = l >> 12;

int32_t nr = r >> 12;

l = clamp16(nl);

r = clamp16(nr);

*out++ = (r<<16) | (l & 0xFFFF);

}

}

看它的做法,一个声道的32bit的输入,先右移12位,也就是保留前20位,然后clamp16(clamp是“夹”的意思)成16位,此时左右声道都是16位的了。然后再把右声道放高位,左声道放低位这么组成一个32bit的数。

下面看看clamp16到底做了什么:

static inline int16_t clamp16(int32_tsample)

{

if ((sample>>15) ^ (sample>>31))

sample = 0x7FFF ^ (sample>>31);

return sample;

}

这个函数仅仅是把溢出部分粗暴的去掉了。下面的测试程序可以很直观的看出来:

int test()

{

for(int i=32766; i<=32776; i++){

int temp = clamp16(i);

cout << "clamp16 tempInt = " << temp <<endl;

}

return 0;

}

输出是:

我们知道,16位的有符号数的上界是0x7FFF,也就是32767。通过测试结果发现,小于它的数得到了保留,如32766;而大于它的数都被夹(clamp)到了32767。

那么,为什么Android要这么做呢?为什么不去优雅的保留信号的波形,而是选择让它直接消减掉呢(尽管这样势必会造成听感上的Distortion)?

可能就是因为

1.    混音的情况比较少见

2.    混音后溢出的情况也比较少见

3.    如果努力去保留信号的波形,势必会造成上一节提出的问题

时间: 2024-10-11 21:33:30

数字音频Mixer算法的相关文章

音频降噪算法 附完整C代码

降噪是音频图像算法中的必不可少的. 目的肯定是让图片或语音 更加自然平滑,简而言之,美化. 图像算法和音频算法 都有其共通点. 图像是偏向 空间 处理,例如图片中的某个区域. 图像很多时候是以二维数据为主,矩形数据分布. 音频更偏向 时间 处理,例如语音中的某短时长. 音频一般是一维数据为主,单声道波长. 处理方式也是差不多,要不单通道处理,然后合并,或者直接多通道处理. 只是处理时候数据参考系维度不一而已. 一般而言, 图像偏向于多通道处理,音频偏向于单通道处理. 而从数字信号的角度来看,也可

一阶数字低通滤波器-软件算法模拟RC低通滤波器

将普通硬件RC低通滤波器的微分方程用差分方程来表求,变可以采用软件算法来模拟硬件滤波的功能,经推导,低通滤波算法如下: 式中 :本次采样值 :本次滤波的输出值 :上次的滤波输出值 a :滤波系数,其值通常远小于1 由上式可以看出,本次滤波的输出值主要取决于上次滤波的输出值 (注意不是上次的采样值),本次采样值对滤波输出的贡献是比较小的,但多少有些修正作用,这种算法便模拟了具体有教大惯性的低通滤波器功能. 滤波算法的截止频率可用以下式计算: 式中 a :滤波系数: t :采样间隔时间: 例如:当t

Python 手写数字识别-knn算法应用

在上一篇博文中,我们对KNN算法思想及流程有了初步的了解,KNN是采用测量不同特征值之间的距离方法进行分类,也就是说对于每个样本数据,需要和训练集中的所有数据进行欧氏距离计算.这里简述KNN算法的特点: 优点:精度高,对异常值不敏感,无数据输入假定 缺点:计算复杂度高,空间复杂度高 适用数据范围:数值型和标称型(具有有穷多个不同值,值之间无序)    knn算法代码: #-*- coding: utf-8 -*- from numpy import * import operatorimport

声音文件格式、常见的数字音频格式

数字声音在计算机中存储和处理时,其数据必须以文件的形式进行组织,所选用 的文件格式必须得到操作系统和应用软件的支持.不同计算机一级应用软件中使用的声音文件格式也互不相同. 常见的数字音频格式   音频格式 说明 1 Wave(.WAV) 是微软公司开 发的一种声音文件格式,也叫波形声音文件,是最早的数字音频格式,被Windows平台及其应用程序广泛支持.WAV格式支持许多压缩算法,支持多种音频 位数.采样频率和声道,采用44.1kHz的采样频率,16位量化位数,跟CD一样,对存储空间需求太大不便

OLA音频变速算法的仿真与剖析

前段时间,在尝试音乐节拍数的提取时,终于有了突破性的进展,效果基本上比市面上的许多商业软件还要好,在作节拍数检测时,高频信息作用不大, 通过重采样减小运算量.重采样让我想起了在学校里面做的变速变调算法,在这里顺便回顾一下. OLA(Overlap-and-Add, OLA)重叠叠加算法是音频变速算法中最简单的时域方法,它是后续时域算法(SOLA, SOLA-FS, TD-PSOLA, WSOLA)的基础. OLA分为分解与合成两个部分,公式看起来很复杂,所以不贴出了,基本思路从图中更能清晰的表现

基于傅里叶变换的音频重采样算法 (附完整c代码)

前面有提到音频采样算法: WebRTC 音频采样算法 附完整C++示例代码 简洁明了的插值音频重采样算法例子 (附完整C代码) 近段时间有不少朋友给我写过邮件,说了一些他们使用的情况和问题. 坦白讲,我精力有限,但一般都会抽空回复一下. 大多数情况,阅读一下代码就能解决的问题, 也是要尝试一下的. 没准,你就解决了呢? WebRtc的采样算法本身就考虑到它的自身应用场景, 所以它会有一些局限性,例如不支持任意采样率等等. 而简洁插值的这个算法, 我个人也一直在使用,因为简洁明了,简单粗暴. 我自

数字音频接口

数字音频接口DAI,即Digital Audio Interfaces,顾名思义,DAI表示在板级或板间传输数字音频信号的方式.相比于模拟接口,数字音频接口抗干扰能力更强,硬件设计简单,DAI在音频电路设计中得到越来越广泛的应用.图1和图2对比传统的音频信号和数字音频信号链的区别. 在传统的音频电路(图1)中有麦克风.前置放大器.模/数转换器ADC.数/模转换器DAC.输出放大器,以及扬声器,它们之间使用模拟信号连接.随着技术的发展和对性能考虑,模拟电路逐渐被推到链路的两端(集成到设备内部),信

一种简单高效的音频降噪算法示例(附完整C代码)

近期比较忙, 抽空出来5.1开源献礼. 但凡学习音频降噪算法的朋友,肯定看过一个算法. <<语音增强-理论与实践>> 中提及到基于对数的最小均方误差的降噪算法,也就是LogMMSE. 资料见: <<Speech enhancement using a minimum  mean-square error log-spectral amplitude estimator.>> -----Ephraim, Y. and Malah, D. (1985) 之前也是

记一道数字旋转排列算法题

记一道数字旋转排列算法题 面试的时候遇到一道算法题,当时没做出来,也没有什么思路.睡觉前突然想到解法,记录一下. 题的大意如下,数字以1开始,并围绕1做逆时针旋转,其中1的坐标为(0, 0),如下图所示: 要求给一个坐标,求其未知的数是多少?例:给出(1, 0),该坐标的数为2:给出(-1, -2),该坐标上的数为22. 说下解题思路,由点的坐标可以得出目标值所在的圈数p,比如5在第2圈,p的大小为坐标x或y较大绝对值n再+1,比如18为(-2,1),绝对值n为2,则18在第3圈(n+1),然后