| 导语 透过本文,全面了解 Android 系统音频录制技能,深入理解王者时刻为什么没有把环境音或者人声录制下来
一、音频量化
音频基础的文章很多,想要了解更多,请自行百度。这里重点关注 PCM 和采样率,因为目前遇到的音频问题都跟这两个有关。 接下来看一张经典的音频采样流程图:
以上就是计算机系统中的音频文件的生成过程:采样、量化、编码。 人耳所能听到的声音,最低的频率是 20Hz ~ 20KHZ,因此音频文件格式的最大带宽是 20KHZ。
根据奈奎斯特的理论,只有采样频率高于声音信号最高频率的两倍时,才能把数字信号表示的声音还原成为原来的声音,所以音频文件的采样率一般在 40~50KHZ,比如最常见的 CD 音质采样率 44.1KHZ。
对声音进行采样、量化过程被称为脉冲编码调制(Pulse Code Modulation),简称PCM。PCM 数据是最原始的音频数据完全无损,所以 PCM 数据虽然音质优秀但体积庞大,为了解决这个问题先后诞生了一系列的音频格式,这些音频格式运用不同的方法对音频数据进行压缩,其中有无损压缩(ALAC、APE、FLAC)和有损压缩(MP3、AAC、OGG、WMA)两种。
二、录音方式
1、系统内录
直接上 Google 官方的文档,大意就通过该接口可以进行实时字幕和游戏内录音。这里不准备深入,因为硬性要求 Android Q 及以上系统版本(文末有 AudioPlaybackCapture 使用实例可以参考):
2、麦克风录制
麦克风录制要特别注意的两个情况:
- 同时只能有一个实例存在,比如有 GVoice 在录音,那么其他都要歇菜了。官方文档 “共享音频输入” 有详细的说明。
- 另一种情况就是不可控(可能会录制到 ”黄、爆” 音频)。
2.1、基本概念
Android 系统提供的录制麦克风方式就两种:MediaRecorder 和 AudioRecord:
- MediaRecorder:简易模式,调用简单,只有开始、结束,录音之后的文件也是指定编码格式,系统播放器可以直接播放。
- AudioRecord:原始模式,可以暂停、继续,可以实时获取到 PCM 数据然后进行音视频的混合,也是录屏软件经常使用的接口。
2.2、麦克风内录的另类实现
通过上面的 AudioRecord 的代码可以发现,输入源是可以配置的,并且通过接口文档很快就发现 REMOTE_SUBMIX 这个输入源也可以实现内录功能,但是有两个必要条件:
- 需要系统权限,就是需要在 Android 源码中进行编译的工程才可以获取系统签名权限
- 会截走扬声器和耳机的声音,也就是说在录音时本地无法播放声音
3、Hook 内录
通过以下 Android 的音频系统架构图可以看出,Android 提供的唯二接口:MediaPlayer 和 AudioTrack,最终都是经过 AudioFlinger 的 AudioStreamOut::write(const void *buffer, size_t numBytes) 把数据传递到硬件层:
通过 inline hook 的形式可以导出音频数据,目前 KM 上已经有同事实践成功,具体可以参考链接: http://km.oa.com/group/22117/articles/show/223181
但是,inline hook 需要操作寄存器并且方案通用性欠佳,所以不建议使用 inline hook。不过,在音频数据流转的过程中截取数据的形式是通用做法,引擎内录就是借鉴的这种做法。在更高的层级进行的获取行为,并且由于层级比较高,所以无法做到一个方案覆盖全的情况。几乎都是跟引擎密切相关,但是也有好的方面,就是稳定性得到了保证并且可以统一多端的获取逻辑。
4、引擎内录
引擎内录可以统一 Android、iOS、PC 多端音频获取逻辑
从上图可以看出,整个获取内音的逻辑很简单,但是,引擎这么多,引擎插件怎么写就成了一个难题
我们先从最简单的 Unity FMOD 开始。
4.1、Unity FMOD 内录
Unity 內置的 Audio 內部使用的是 FMOD,但是没有导出接口,所以无法使用 FMOD 插件的方式。
通过查找 ,Unity 给出类似的接口,简单实现如下,具体的请查看附件 ExportAudioRes.cs 和 FmodSupport.cs。
集成的话,只需要把 ExportAudioRes.cs 挂载到 AudioListener 所在的主 Camera 就可以(保存成 PCM 文件是常用的调试技巧):
代码语言:javascript复制public class ExportAudioRes : MonoBehaviour
{
...
void OnAudioFilterRead(float[] data, int channels)
{
FmodSupport.FmodWritePCM(data, data.Length, (uint)channels);
if (DebugInGame)
{
// 把音频数据直接保存成 PCM 文件,这样就可以定位是否是音频本身的问题
this.Write(data);
}
}
}
4.2、 FMOD Studio 内录
感觉这是废话:安装 FMOD Studio ,打开工程导入 FMOD for Unity
如果只是测试,可以直接使用 FMOD Studio 自带的样例的 bank 文件,FMOD 在 Unity 里面的配置也比较简单,如下动图所示,设置 bank 路径然后添加事件响应就可以:
接下来是 FMOD 插件实现的关键代码:
4.3、 Wwise 内录
安装 wwise Launcher 之后可以在本地路径下找到 AkDelay 的源码,通过改造源码添加转发器就可以实现自定义插件:
代码语言:javascript复制D:Program Files (x86)AudiokineticWwise 2017.1.9.6501SDKsamplesPluginsAkDelay
//通过修改上面的工程名,就可以得到一个自定义插件,有需要可以找我要工程源码
这里的关键就变成,如何在自定义插件里面把 “音频转发器” 这个库给拉起来? 为了减少工程之间的依赖,Unity 里面可以通过 PInvoke 的形式动态拉起,但是在 wwise 插件里面就需要另辟蹊径,但是肯定还是想使用动态打开库的形式。直接上代码,大体如下:
另外需要注意的就是 wwise 插件需要挂载到 Master Audio Bus 上,并且不能同时挂载多个,否则录制出来的声音会叠加在一起:
前面只是抛砖引玉。 想要实现一个插件远比上面说的复杂。因为没有考虑版本兼容问题、插件的编译环境、动态获取库代码健壮性、不同平台差异兼容、插件集成等等问题。
5、思考扩展
既然引擎层面都有插件特效,那么 Android 原生应该也有对应的功能才对?是否可以利用呢?
代码语言:javascript复制//https://developer.android.com/reference/android/media/audiofx/Visualizer
//下面是摘抄于 Google 的文档,Google 早就料到会有人像我这样思考, 还特别提醒:这不是音频录制接口,别瞎用
三、联调案例
两个音频相关的实例:CFM 上出现的音频加速问题和 AOV 上出现的音频叠加问题
如果出现类似的情况,你会如何思考定位问题呢?答案就在上面的内容里,如果有好的思路请留言回复
无处不在的辛普森悖论
走近鹅厂专家 | Ta们靠什么成为专家?
如何通过画像洞察用户价值点