初识音频

从初中物理上我们就学到,声音是一种波。计算机只能处理离散的信号,通过收集足够多的离散的信号,来不断逼近波形,这个过程我们叫做采样。怎么样才能更好的还原声音信息呢?这里很自然引出两个概念了。

采样频率(Sample Rate):每秒采集声音的数量,它用赫兹(Hz)来表示。

采样率越高越靠近原声音的波形,常见的采样率有以下几种:

8khz:电话等使用,对于记录人声已经足够使用。

22.05khz:广播使用频率。

44.1kb:音频CD。

48khz:DVD、数字电视中使用。

96khz-192khz:DVD-Audio、蓝光高清等使用。

采样精度(Bit Depth): 它表示每次采样的精度,位数越多,能记录的范围就越大。

采样精度常用范围为8bit-32bit,而CD中一般都使用16bit。

把声音记录下来之后,通过喇叭的震动把波再还给空气传到你的耳朵就完成了这个完美的循环了。但是富有创造力的人类不会限制于此就结束了,很快人们发现,当把不同的声音传递到不同的喇叭的时候,竟然会惊奇地让声音变得有空间感了,即时是同一个声音,也比单个通道能获得更好的体验,于是就出现了什么立体声,5.1 环绕等看起来很高大上的东西。所以,音频又多了一个东西:

声音通道(Channel): 你知道每个通道存储的声音会从其中的一个喇叭出来就好了,不过可以通过算法的模拟来让没有那么多喇叭也能出来类似的效果。

有了声音通道,乐队在录音的时候就可以每个人插一条音轨了,然后每一个声音可以写到不同的通道里面,当然,实际录音当然都是后期混音而成的。下面介绍的其中一个混音算法会用到声音通道这个特性。

最后再介绍一个大家经常看到的概念:

比特率(bps [bits per second]): 其实看单位就很容易知道它要表达的意思了,就是每秒钟要播放多少 bit 的数据。公式一目了然:

比特率 = 采样率 × 采样深度 × 通道。

比如 采样率 = 44100,采样深度 = 16,通道 = 2 的音频的的比特率就是 44100 16 2 = 1411200 bps。

一般来说,比特率越高,音频质量越好。要注意一些比特率的换算不是 1024 作为一个级别换算的哈。

1,000 bps = 【1 kbps】 = 1,000 bit/s

1,000,000 bps = 【1 Mbps】 = 1,000,000 bit/s

1,000,000,000 bps = 【1 Gbps】 = 1,000,000,000 bit/s

音频在计算机中的表示

我们来看一下真实音频在计算机中究竟是怎样的表示状态,这里指的是原始的数据表示,而非编码(Mp3,Acc等)后的表示,平时我们看到的.wav后缀的音频,把前面 44 个字节用于记录采样率、通道等的头部信息去掉后就是就是原始的音频数据了。

在理解了上面的概念之后,我们再来看这张图。对于文件头部信息我们就不详细介绍了,不影响我们理解介绍的混音处理方式,需要了解的可以点击这里。

我们抽取其中的一个采样来看,这里我加多了一个通道,便于大家理解通道的存储位置。

不难理解,这个采样中有三个通道,每通道采样精度是 16 比特。每个采样值的排序是 Little-Endian 低位在前的方式,比如通道 1 的采样值就是 AB03, 每个采样值的大小表示的是幅度信息。

混音的原理

音频混音的原理: 空气中声波的叠加等价于量化的语音信号的叠加。

这句话可能有点拗口,我们从程序员的角度去观察就不难理解了。下图是两条音轨的数据,将每个通道的值做线性叠加后的值就是混音的结果了。比如音轨A和音轨B的叠加,A.1 表示 A 音轨的 1 通道的值 AB03 , B.1 表示 B 音轨的 1 通道的值 1122 , 结果是 bc25,然后按照低位在前的方式排列,在合成音轨中就是 25bc,这里的表示都是 16 进制的。

直接加起来就可以了?事情如果这么简单就好了。音频设备支持的采样精度肯定都是有限的,一般为 8 位或者 16 位,大一些的为 32 位。在音轨数据叠加的过程中,肯定会导致溢出的问题。为了解决这个问题,人们找了不少的办法。这里我主要介绍几种我用过的,并给出相关代码实现和最终的混音效果对比结果。

线性叠加平均

这种办法的原理非常简单粗暴,也不会引入噪音。原理就是把不同音轨的通道值叠加之后取平均值,这样就不会有溢出的问题了。但是会带来的后果就是某一路或几路音量特别小那么整个混音结果的音量会被拉低。

以下的的单路音轨的音频参数我们假定为采样频率一致,通道数一致,通道采样精度统一为 16 位。

其中参数 bMulRoadAudios 的一维表示的是音轨数,二维表示该音轨的音频数据。

Java 代码实现:

@Override

public byte[] mixRawAudioBytes(byte[][] bMulRoadAudios) {

if (bMulRoadAudios == null || bMulRoadAudios.length == 0)

return null;

byte[] realMixAudio = bMulRoadAudios[0];

if(realMixAudio == null){

return null;

}

final int row = bMulRoadAudios.length;

//单路音轨

if (bMulRoadAudios.length == 1)

return realMixAudio;

//不同轨道长度要一致,不够要补齐

for (int rw = 0; rw < bMulRoadAudios.length; ++rw) {

if (bMulRoadAudios[rw] == null || bMulRoadAudios[rw].length != realMixAudio.length) {

return null;

}

}

/**

* 精度为 16位

*/

int col = realMixAudio.length / 2;

short[][] sMulRoadAudios = new short[row][col];

for (int r = 0; r < row; ++r) {

for (int c = 0; c < col; ++c) {

sMulRoadAudios[r][c] = (short) ((bMulRoadAudios[r][c * 2] & 0xff) | (bMulRoadAudios[r][c * 2 + 1] & 0xff) << 8);

}

}

short[] sMixAudio = new short[col];

int mixVal;

int sr = 0;

for (int sc = 0; sc < col; ++sc) {

mixVal = 0;

sr = 0;

for (; sr < row; ++sr) {

mixVal += sMulRoadAudios[sr][sc];

}

sMixAudio[sc] = (short) (mixVal / row);

}

for (sr = 0; sr < col; ++sr) {

realMixAudio[sr * 2] = (byte) (sMixAudio[sr] & 0x00FF);

realMixAudio[sr * 2 + 1] = (byte) ((sMixAudio[sr] & 0xFF00) >> 8);

}

return realMixAudio;

}

自适应混音

参与混音的多路音频信号自身的特点,以它们自身的比例作为权重,从而决定它们在合成后的输出中所占的比重。具体的原理可以参考这篇论文:快速实时自适应混音方案研究。这种方法对于音轨路数比较多的情况应该会比上面的平均法要好,但是可能会引入噪音。

Java 代码实现:

@Override

public byte[] mixRawAudioBytes(byte[][] bMulRoadAudios) {

//简化检查代码

/**

* 精度为 16位

*/

int col = realMixAudio.length / 2;

short[][] sMulRoadAudios = new short[row][col];

for (int r = 0; r < row; ++r) {

for (int c = 0; c < col; ++c) {

sMulRoadAudios[r][c] = (short) ((bMulRoadAudios[r][c * 2] & 0xff) | (bMulRoadAudios[r][c * 2 + 1] & 0xff) << 8);

}

}

short[] sMixAudio = new short[col];

int sr = 0;

double wValue;

double absSumVal;

for (int sc = 0; sc < col; ++sc) {

sr = 0;

wValue = 0;

absSumVal = 0;

for (; sr < row; ++sr) {

wValue += Math.pow(sMulRoadAudios[sr][sc], 2) * Math.signum(sMulRoadAudios[sr][sc]);

absSumVal += Math.abs(sMulRoadAudios[sr][sc]);

}

sMixAudio[sc] = absSumVal == 0 ? 0 : (short) (wValue / absSumVal);

}

for (sr = 0; sr < col; ++sr) {

realMixAudio[sr * 2] = (byte) (sMixAudio[sr] & 0x00FF);

realMixAudio[sr * 2 + 1] = (byte) ((sMixAudio[sr] & 0xFF00) >> 8);

}

return realMixAudio;

}

多通道混音

在实际开发中,我发现上面的两种方法都不能达到满意的效果。一方面是和音乐相关,对音频质量要求比较高;另外一方面是通过手机录音,效果肯定不会太好。不知道从哪里冒出来的灵感,为什么不试着把不同的音轨数据塞到不同的通道上,让声音从不同的喇叭上同时发出,这样也可以达到混音的效果啊!而且不会有音频数据损失的问题,能很完美地呈现原来的声音。

于是我开始查了一下 Android 对多通道的支持情况,对应代码可以在android.media.AudioFormat中查看,结果如下:

public static final int CHANNEL_OUT_FRONT_LEFT = 0x4;

public static final int CHANNEL_OUT_FRONT_RIGHT = 0x8;

public static final int CHANNEL_OUT_FRONT_CENTER = 0x10;

public static final int CHANNEL_OUT_LOW_FREQUENCY = 0x20;

public static final int CHANNEL_OUT_BACK_LEFT = 0x40;

public static final int CHANNEL_OUT_BACK_RIGHT = 0x80;

public static final int CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x100;

public static final int CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x200;

public static final int CHANNEL_OUT_BACK_CENTER = 0x400;

public static final int CHANNEL_OUT_SIDE_LEFT = 0x800;

public static final int CHANNEL_OUT_SIDE_RIGHT = 0x1000;

一共支持 10 个通道,对于我的情况来说是完全够用了。我们的耳机一般只有左右声道,那些更多通道的支持是 Android 系统内部通过软件算法模拟实现的,至于具体如何实现的,我也没有深入了解,在这里我们知道这回事就行了。我们平时所熟知的立体声,5.1 环绕等就是上面那些通道的组合。

int CHANNEL_OUT_MONO = CHANNEL_OUT_FRONT_LEFT;

int CHANNEL_OUT_STEREO = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT);

int CHANNEL_OUT_5POINT1 = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT |

CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT);

知道原理之后,实现起来非常简单,下面是具体的代码:

@Override

public byte[] mixRawAudioBytes(byte[][] bMulRoadAudios) {

int roadLen = bMulRoadAudios.length;

//单路音轨

if (roadLen == 1)

return bMulRoadAudios[0];

int maxRoadByteLen = 0;

for(byte[] audioData : bMulRoadAudios){

if(maxRoadByteLen < audioData.length){

maxRoadByteLen = audioData.length;

}

}

byte[] resultMixData = new byte[maxRoadByteLen * roadLen];

for(int i = 0; i != maxRoadByteLen; i = i + 2){

for(int r = 0; r != roadLen; r++){

resultMixData[i * roadLen + 2 * r] = bMulRoadAudios[r][i];

resultMixData[i * roadLen + 2 * r + 1] = bMulRoadAudios[r][i+1];

}

}

return resultMixData;

}

结果比较

线性叠加平均法虽然看起来很简单,但是在音轨数量比较少的时候取得的效果可能会比复杂的自适应混音法要出色。

自适应混音法比较合适音轨数量比较多的情况,但是可能会引入一些噪音。

多通道混音虽然看起来很完美,但是产生的文件大小是数倍于其他的处理方法。

没有银弹,还是要根据自己的应用场景来选择,多试一下。

下面是我录的两路音轨:

音轨一:

音轨二:

线性叠加平均法:

自适应混音法:

多通道混音:

采样频率、采样精度和通道数不同的情况如何处理?

不同采样频率需要算法进行重新采样处理,让所有音轨在同一采样率下进行混音,这个比较复杂,等有机会再写篇文章介绍。

采样精度不同比较好处理,向上取精度较高的作为基准即可,高位补0;如果是需要取向下精度作为基准的,那么就要把最大通道值和基准最大值取个倍数,把数值都降到最大基准数以下,然后把低位移除。

通道数不同的情况也和精度不同的情况相似处理。

参考资料

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

java 混音_Android中一种效果奇好的混音方法详解相关推荐

  1. java android消息推送_Android中使用socket通信实现消息推送的方法详解

    原理最近用socket写了一个消息推送的demo,在这里和大家分享一下. 主要实现了:一台手机向另外一台手机发送消息,这两台手机可以随时自由发送文本消息进行通信,类似我们常用的QQ. 效果图: 原理: ...

  2. 3种Javascript图片预加载的方法详解

    3种Javascript图片预加载的方法详解 预加载图片是提高用户体验的一个很好方法.图片预先加载到浏览器中,访问者便可顺利地在你的网站上冲浪,并享受到极快的加载速度. 这对图片画廊及图片占据很大比例 ...

  3. [转] java - 过滤ASCII码中的不可见字符, ASCII三部分, 各控制字符详解

    标签 PostgreSQL , java , Greenplum , 0x00 , 空字符 , NUL , 数据清洗 , 规则 , 过滤 , 非法字符 背景 原文 http://www.codeweb ...

  4. php中in array函数_in_array函数介绍与使用方法详解

    PHP的性能一直在提高.然而,若是用的不恰当,或是一个不留神,还是可能会踩到PHP内部实现方面的坑的.我在前几天的一个性能问题上就碰到了PHP的性能一直在提高.然而,若是用的不恰当,或是一个不留神,还 ...

  5. Android上一种效果奇好的混音方法介绍

    本文将对几种音频混音的方法进行详细的介绍和比较,读完之后你应该可以对混音有个基本的认识,针对不同情形知道应该采用哪种具体的处理方法了. 如果对音频的一些基础知识还不是很了解的建议先去阅读一下上一篇文章 ...

  6. html盒子浮动效果,有关CSS盒子浮动的方法详解

    在标准流中,一个块级元素在水平方向会自动伸展,直到包含它的元素的边界:而在竖直方向和兄弟元素依次排列,不能并排.使用"浮动"方式后,块级元素的表现就会有所不同. CSS中有一个fl ...

  7. Java基础(二):迭代器(Iterator)(含使用方法详解)

    Java Iterator(迭代器) Java Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法,可用于迭代 ArrayList 和 HashSet 等集合. Iterator 是 ...

  8. java工程引用其他工程,Android工程:引用另一个Android工程的方法详解

    现在已经有了一个Android工程A.我们想扩展A的功能,但是不想在A的基础上做开发,于是新建了另外一个Android工程B,想在B中引用A. 一个思路是把工程A做成纯Jar包,这样其他的工程就可以直 ...

  9. c语言字母输出什么意思,C语言中字符的输入输出以及计算字符个数的方法详解...

    C语言字符输入与输出 标准库提供的输入/输出模型非常简单.无论文本从何处输入,输出到何处,其输入/输出都是按照字符流的方式处理.文本流是由多行字符构成的字符序列,而每行字符则由 0 个或多个字符组成, ...

最新文章

  1. Win64 驱动内核编程-12.回调监控进线程创建和退出
  2. 使用ldirectord实现后端RS健康状态监测及LVS调度功能
  3. 计算机领域中随处可见的抽象
  4. 基于事件驱动架构构建微服务第10部分:在docker容器内运行单元测试
  5. java 李刚 pdf_Java数据库技术详解(李刚) PDF_源雷技术空间
  6. Portal-Basic Java Web 应用开发框架:应用篇(十四) —— 异步 Action
  7. WindowManager.LayoutParams类
  8. Typecho 支持黑暗模式的后台主题插件
  9. C#算法设计查找篇之02-二分查找
  10. mysql注入内置函数_PHP面试之mysql内置函数,xss漏洞,sql注入
  11. 大数据之-Hadoop本地模式_执行Grep官方案例---大数据之hadoop工作笔记0021
  12. Sublime Text : 创建工程
  13. 如何手动卸载SQLServer 2005
  14. 激光雷达点云数据处理
  15. 多测师杭州拱墅校区__肖sir__软件测试生命周期(4)
  16. (新零售)商户网格化运营 - 阿里云RDS PostgreSQL最佳实践
  17. 用Python读红楼梦之——一、词云绘制
  18. 无向图、深度优先搜索(无向图)、广度优先搜索(无向图)、无向图路径查找(基于深度优先搜索)
  19. iOS开发——网络请求案例汇总(AFNetworking)
  20. 数据分析3之几种常用图

热门文章

  1. purify的API和提示信息代号
  2. 利用形态学检测边缘和角点
  3. Arm和Unity联合推出:适用于移动应用程序的3D美术优化-[2]几何体
  4. sql查询部门下的所有子部门(mybatis用法)
  5. 【AWS入门】AWS对等连接之实现两个VPC间私网通信
  6. 查看linux版本号
  7. 我公司研发的无线IC卡燃气表
  8. watching memory
  9. 甜甜花酿鸡(自学研究代码实现)
  10. python:冒泡排序(Bubble Sort)超详细教程!