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.    如果努力去保留信号的波形,势必会造成上一节提出的问题

数字音频Mixer算法相关推荐

  1. java数字音频最强教程之lowpass、highpass的应用(保留人声)

    数字音频滤波器的简单使用. 音视频处理都是比较复杂的,本章主要讲解javacv转码视频的时候,如果使得音频仅保留人声段,去除不必要的声音,其实很简单,不需要其他peak滤波器,只需要一组低通.高通滤波 ...

  2. python音频颤音算法_数字音效算法的研究与实现

    摘要: 随着科技的发展,数字信号处理器DSP(Digital Signal Processor)被广泛的应用在各种电子产品中,从便携的个人数字助手PDA(Personal Digital Assist ...

  3. 数字音频接口(二) 高通平台MI2S总线调试

    硬件平台:高通SM6125 基线代码:LA.UM.8.11.1.c1 git clone https://git.codelinaro.org/clo/la/platform/vendor/opens ...

  4. 数字音频特效的软件实现项目

    最近主持数字音频特效的软件实现项目,主要工作有相关的理论学习研究,算法的仿真,软件的实现,最后要移植到相应的嵌入式平台上,并做相关的算法优化工作.这是一很有挑战性的工作,希望做出性能与SRS, Pla ...

  5. 数字音频总线A2B开发详解十一(A2B一Slave板做音效处理-31段EQ,高中低音分频,延时,3D音效等)

    作者的话 A2B系统的一个优点是方便的系统链接,一根双绞线,就能完成供电.音频传输.I2C控制,还有另外一个非常大的优点就是,我做的Master板和Slave板,都用的是ADAU系列的DSP,这一类的 ...

  6. EN 50332手机Type-C数字音频输出测试

    1.前言 本文主要介绍:EN 50332标准.EN 50332标准测试分类.EN 50332标准测试应用.EN 50332-2.1的Type-C数字音频输出测试. 2.EN 50332概述 随着消费类 ...

  7. java 对音频文件降噪_(转)音频降噪算法 附完整C代码

    转:https://www.cnblogs.com/cpuimage/p/8905965.html 降噪是音频图像算法中的必不可少的. 目的肯定是让图片或语音 更加自然平滑,简而言之,美化. 图像算法 ...

  8. 内置DSP的数字音频功放芯片优势?

    传统功放主要功能是放大功率:而内置DSP功放主要功能是处理音频信号和放大功率:是指采用DSP芯片,通过数字信号处理算法优化和管理音频参数的功放:它是一种将双声道立体声信号变成多声道环绕声信号的技术. ...

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

    本文转载自博客:https://cloud.tencent.com/developer/article/1117226 降噪是音频图像算法中的必不可少的. 目的肯定是让图片或语音 更加自然平滑,简而言 ...

最新文章

  1. 「每周CV论文推荐」 初学深度学习活体与伪造人脸检测必读的文章
  2. jQuery中ajax的4种常用请求方式
  3. 鸟哥的Linux私房菜(基础篇)- Red Hat 6.x旧文件
  4. 2021-03-20 包含生成树的性质
  5. vue3中ref、reactive、shallowRef、 shallowReactive、toRaw、unref、toRef、toRefs、customRef使用与区别
  6. http和https和ssl和tcp/ip之间的关系和区别
  7. servlet的应用------request对象和bean实体的反射关系
  8. Softmax, DNN, WideDeep Model
  9. BUPT复试专题—最小距离查询(2013)
  10. Spring WebFlux 要革了谁的命?
  11. 数据科学和人工智能技术笔记 十九、数据整理(下)
  12. finereport java无符合资料_Java报表工具FineReport常见的数据集报错错误代码和解释...
  13. 直播一小时营收破百万!虚拟主播说英文在B站疯狂吸金,背后企划公司IPO作价23亿...
  14. 基于JAVA高速公路收费管理计算机毕业设计源码+数据库+lw文档+系统+部署
  15. linux Centos的ftp搭建-配置-上传下载文件--全面版
  16. android开发播放声音文件
  17. 你还用60款下架侵害用户权益APP吗!!!!!
  18. Redis Manager 2020.7 安装问题(RDM 2020.7)
  19. ccf z字形 java,Java具有简单、 __________ 、稳定、与平台无关、解释型、多线程、动态等特点。...
  20. 后台友好的前端框架LayUI

热门文章

  1. 【操作系统】面向真题学操作系统 —— 操作系统概述客观题
  2. Asp.Net中几种相似的标记符号: 解释及用法
  3. R语言——星图和脸谱图画图及函数使用笔记
  4. 初探JAVA代码在虚拟机中的运行机制
  5. 【python】 pypinyin 拼音库
  6. dynet的一个基本介绍(1):动态神经网络工具包Dynet
  7. Android手机摄像头编程入门
  8. sql语句交换男性和女性的性别——将所有f值更改为m,反之亦然
  9. Python2也不错
  10. 2019年1+X 证书 Web 前端开发中级理论考试——易错题、陌生但又会考到的题目原题+答案(文章含五套题的内容)