热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

Android中一种效果奇好的混音方法详解

这篇文章主要给大家介绍了关于在Android中一种效果奇好的混音方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。

初识音频

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

采样频率(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 > 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 > 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 

结果比较

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

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

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

下面是我录的两路音轨:

音轨一:

音轨二:

线性叠加平均法:

自适应混音法:

多通道混音:

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

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

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

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

参考资料

多媒体会议中的快速实时自适应混音方案研究

总结

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


推荐阅读
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 提升Python编程效率的十点建议
    本文介绍了提升Python编程效率的十点建议,包括不使用分号、选择合适的代码编辑器、遵循Python代码规范等。这些建议可以帮助开发者节省时间,提高编程效率。同时,还提供了相关参考链接供读者深入学习。 ... [详细]
  • Monkey《大话移动——Android与iOS应用测试指南》的预购信息发布啦!
    Monkey《大话移动——Android与iOS应用测试指南》的预购信息已经发布,可以在京东和当当网进行预购。感谢几位大牛给出的书评,并呼吁大家的支持。明天京东的链接也将发布。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 本文讲述了作者通过点火测试男友的性格和承受能力,以考验婚姻问题。作者故意不安慰男友并再次点火,观察他的反应。这个行为是善意的玩人,旨在了解男友的性格和避免婚姻问题。 ... [详细]
  • 安卓select模态框样式改变_微软Office风格的多端(Web、安卓、iOS)组件库——Fabric UI...
    介绍FabricUI是微软开源的一套Office风格的多端组件库,共有三套针对性的组件,分别适用于web、android以及iOS,Fab ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 解决Cydia数据库错误:could not open file /var/lib/dpkg/status 的方法
    本文介绍了解决iOS系统中Cydia数据库错误的方法。通过使用苹果电脑上的Impactor工具和NewTerm软件,以及ifunbox工具和终端命令,可以解决该问题。具体步骤包括下载所需工具、连接手机到电脑、安装NewTerm、下载ifunbox并注册Dropbox账号、下载并解压lib.zip文件、将lib文件夹拖入Books文件夹中,并将lib文件夹拷贝到/var/目录下。以上方法适用于已经越狱且出现Cydia数据库错误的iPhone手机。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
author-avatar
小周颖子
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有