涉及硬件的音视频能力,比如采集、渲染、硬件编码、硬件解码,通常是与客户端操作系统强相关的,就算是跨平台的多媒体框架也必须使用平台原生语言的模块来支持这些功能。
本系列文章将详细讲述移动端音视频的采集、渲染、硬件编码、硬件解码这些涉及硬件的能力该如何实现。本文为该系列文章的第 4 篇,将详细讲述在 Android 平台下影响音频路由相关的知识点。
一、前言
咋回事,怎么听不到对方的声音了?
我这明明播放了音乐啊,怎么什么声音都没有?
相信做过音视频业务的同学都遇到过类似的问题,当然出现此类问题的原因比较多,例如:音频设备故障,网络、音频路由等,其他的我们先暂时搁置一旁,今天着重讲讲音频路由相关的知识点。
音频路由所产生的音频采集、播放异常(故障) 对业务产生的影响持续时间比较差,且难于排查。主要原因是开发者不仅需要对 Android 平台音频路由相关的知识点非常熟悉,还需要很多一些特殊机型、特殊场景下的经验值。本文会详细介绍下影响 Android 音频路由相关的应用层知识点。
二、影响音频路由的参数
在 Android 平台中有三个非常重要的参数,audioSource、streamType、audioMode 这三种参数分别表示 采集音频源、播放音频流类型、音频场景,他们很大程度上决定着系统路由(指的是输入设备和输出设备)的选择和优先级。除了在 commuication 的模式下,可以允许开发者控制扬声器和听筒的切换,大部分场景音频路由都是由系统根据这三个参数来决定的。
2.1 audioSource
PS: 如果您阅读过之前有关音频采集的文章,应该对这个参数很熟悉。
表示采集源类型,通常会影响到系统选择采集设备的优先级,常见的类型值:
- Default(0): 默认的 audio source,Android 系统来决定,一般取值为 MIC(1)。
- MIC(1): 麦克风采集,适用于对音质效果比较好的场景。
- VOICE_UPLINK(2) VOICE_DOWN_LINK(3) VOICE_CALL(4) 系统应用才可用,需要申请 CAPTURE_AUDIO_OUTPUT 权限,用于实现通话录音功能。分别录制上行、下行、上下行。
- CAMCORDER(5) 采集的音频会跟随着视频的方向来调整音频的空间方向;还会根据视频的画面做一些同步和匹配。以提供更具沉浸感和真实感的视听体验,如拍摄 vlog、微电影、纪录片等。
- VOICE_RECOGNITION(6) 语音识别相关的应用场景,会对采集的音频做一些优化处理,通常过滤掉环境的底噪。
- VOICE_COMMUNICATION(7) 语音通信场景像VoIP,采集的音频源会经过回声消除、自动增益等算法,来提升语音通信的质量。
从上面的描述来看,audioSource 不仅会影响优先级,还会根据音频源的用途做一些额外的音频处理,但这些通常和路由没什么关系,普通开发者这里只需要知道就行。
以下是 audiosource 为 VOICE_COMMUNICATION 的情况下,Android 音频路由选择的代码逻辑。
case AUDIO_SOURCE_VOICE_COMMUNICATION:
//如果是 in_call 状态,指定可用设备列表 (primary)。
if ((getPhoneState() == AUDIO_MODE_IN_CALL) &&
(availableOutputDevices.getDevice(AUDIO_DEVICE_OUT_TELEPHONY_TX,
String8(""), AUDIO_FORMAT_DEFAULT)) == nullptr) {
LOG_ALWAYS_FATAL_IF(availablePrimaryDevices.isEmpty(), "Primary devices not found");
availableDevices = availablePrimaryDevices;
}
//优先选择带有语音通信的蓝牙
if (audio_is_bluetooth_out_sco_device(commDeviceType)) {
// if SCO device is requested but no SCO device is available, fall back to default case
device = availableDevices.getDevice(
AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET, String8(""), AUDIO_FORMAT_DEFAULT);
if (device != nullptr) {
break;
}
}
switch (commDeviceType) {
//支持低功耗协议的蓝牙设备
case AUDIO_DEVICE_OUT_BLE_HEADSET:
device = availableDevices.getDevice(
AUDIO_DEVICE_IN_BLE_HEADSET, String8(""), AUDIO_FORMAT_DEFAULT);
break;
//外放(喇叭)
case AUDIO_DEVICE_OUT_SPEAKER:
device = availableDevices.getFirstExistingDevice({
AUDIO_DEVICE_IN_BACK_MIC, AUDIO_DEVICE_IN_BUILTIN_MIC,
AUDIO_DEVICE_IN_USB_DEVICE