手机输出设备有很多,如听筒、扬声器、蓝牙耳机等都是常用的输出通路;对于音频类的apk开发,Android提供了诸如MediaPlayer、AudioTrack、AudioManager等接口;本篇主要讲解手机如何选择设备的;
直接从AudioTrack.cpp的createTrack_l说起,这个方法中会调用getOutputForAttr方法来获取输出的output通路。output可以理解为hal层的音频通路的代表,有primary out、lowlatency out、offload、direct_pcm、a2dp output、usb_device output、dp output等,这个可以在dumpsys出media.audio_policy服务查看到(HW Modules dump项下的);
下面来看getOutputForAttr方法,其实现在AudioPolicyManager中,作用主要是根据输出音频的attribute信息来选择输出该音频数据的output;查看代码后会发现其实里面又调用getDeviceForStrategy / getOutputForDevice来返回output。getDeviceForStrategy 方法就是我们本篇的主题 --- 如何选择音频输出设备;
首先通过getStrategyForAttr获取音频策略strategy,然后根据strategy选择device;首先来看getStrategyForAttr,实现在Engine.cpp中,该方法同Engine::getStrategyForStream一样都是为了选择音频策略,得到音频策略后,就可以调用getDeviceForStrategy来选择设备了;为了简便来看getDeviceForStrategy其中的STRATEGY_MEDIA case:
case STRATEGY_REROUTING:
case STRATEGY_MEDIA: {
uint32_t device2 = AUDIO_DEVICE_NONE;
if (isInCall() && (strategy == STRATEGY_MEDIA)) {
device = getDeviceForStrategyInt(
STRATEGY_PHONE, availableOutputDevices, availableInputDevices, outputs);
break;
}
if ((device2 == AUDIO_DEVICE_NONE) &&
(mForceUse[AUDIO_POLICY_FORCE_FOR_MEDIA] != AUDIO_POLICY_FORCE_NO_BT_A2DP) &&
(outputs.getA2dpOutput() != 0)) {
device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
if (device2 == AUDIO_DEVICE_NONE) {
device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;
}
if (device2 == AUDIO_DEVICE_NONE) {
device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER;
}
}
if ((device2 == AUDIO_DEVICE_NONE) &&
(mForceUse[AUDIO_POLICY_FORCE_FOR_MEDIA] == AUDIO_POLICY_FORCE_SPEAKER)) {
device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_SPEAKER;
}
if (device2 == AUDIO_DEVICE_NONE) {
device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_WIRED_HEADPHONE;
}
......
if ((device2 == AUDIO_DEVICE_NONE) &&
(mForceUse[AUDIO_POLICY_FORCE_FOR_DOCK] == AUDIO_POLICY_FORCE_ANALOG_DOCK)) {
device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET;
}
if (device2 == AUDIO_DEVICE_NONE) {
device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_SPEAKER;
}
int device3 = AUDIO_DEVICE_NONE;
if (strategy == STRATEGY_MEDIA) {
// ARC, SPDIF and AUX_LINE can co-exist with others.
device3 = availableOutputDevicesType & AUDIO_DEVICE_OUT_HDMI_ARC;
device3 |= (availableOutputDevicesType & AUDIO_DEVICE_OUT_SPDIF);
device3 |= (availableOutputDevicesType & AUDIO_DEVICE_OUT_AUX_LINE);
}
device2 |= device3;
// device is DEVICE_OUT_SPEAKER if we come from case STRATEGY_SONIFICATION or
// STRATEGY_ENFORCED_AUDIBLE, AUDIO_DEVICE_NONE otherwise
device |= device2;
// If hdmi system audio mode is on, remove speaker out of output list.
if ((strategy == STRATEGY_MEDIA) &&
(mForceUse[AUDIO_POLICY_FORCE_FOR_HDMI_SYSTEM_AUDIO] ==
AUDIO_POLICY_FORCE_HDMI_SYSTEM_AUDIO_ENFORCED)) {
device &= ~AUDIO_DEVICE_OUT_SPEAKER;
}
} break;
.........
return device;
方法最终返回了满足条件、被选择好的device;其实,决定选择哪个设备的,除了strategy外,还有isInCall()、mForceUse[AUDIO_POLICY_FORCE_FOR_MEDIA] == AUDIO_POLICY_FORCE_SPEAKER等条件,这些都影响了设备的选择;来看下这2种影响选择设备因素;
a. isInCall很好理解,看实现就知道inCall指的是AUDIO_MODE_IN_CALL / AUDIO_MODE_IN_COMMUNICATION两种情况,而state的值来是从AudioManager的setMode来设置的;
static inline bool is_state_in_call(int state)
{
return (state == AUDIO_MODE_IN_CALL) || (state == AUDIO_MODE_IN_COMMUNICATION);
}
b. mForceUse[AUDIO_POLICY_FORCE_FOR_MEDIA] == AUDIO_POLICY_FORCE_SPEAKER,这类条件其实是在判断上层应用有无强制要求输出设备的,例如我们可以使用AudioManager.java 的setSpeakerphoneOn / setBluetoothScoOn /setBluetoothA2dpOn等接口设置外放或蓝牙输出;下面来跟读代码详细看下实现吧:
从 setBluetoothScoOn接口开始,AudioManager.setBluetoothScoOn -> AudioService.setBluetoothScoOnInt .... sendmsg
public void setBluetoothScoOnInt(boolean on) {
if (on) {
mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
} else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
mForcedUseForComm = AudioSystem.FORCE_NONE;
}
sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, null, 0);
sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
AudioSystem.FOR_RECORD, mForcedUseForComm, null, 0);
}
on为true,mForcedUseForComm = AudioSystem.FORCE_BT_SCO;有两个sendMsg,来看FOR_COMMUNICATION的那个吧;搜MSG_SET_FORCE_USE这个msg.what,发现调用了setForceUse(msg.arg1, msg.arg2),将AudioSystem.FOR_COMMUNICATION, mForcedUseForComm作为参数输入;setForceUse的实现在native的Engine.cpp中:
status_t Engine::setForceUse(audio_policy_force_use_t usage, audio_policy_forced_cfg_t config)
{
switch(usage) {
case AUDIO_POLICY_FORCE_FOR_COMMUNICATION:
if (config != AUDIO_POLICY_FORCE_SPEAKER && config != AUDIO_POLICY_FORCE_BT_SCO &&
config != AUDIO_POLICY_FORCE_NONE) {
ALOGW("setForceUse() invalid config %d for FOR_COMMUNICATION", config);
return BAD_VALUE;
}
mForceUse[usage] = config;
break;
case AUDIO_POLICY_FORCE_FOR_MEDIA:
if (config != AUDIO_POLICY_FORCE_HEADPHONES && config != AUDIO_POLICY_FORCE_BT_A2DP &&
config != AUDIO_POLICY_FORCE_WIRED_ACCESSORY &&
config != AUDIO_POLICY_FORCE_ANALOG_DOCK &&
config != AUDIO_POLICY_FORCE_DIGITAL_DOCK && config != AUDIO_POLICY_FORCE_NONE &&
config != AUDIO_POLICY_FORCE_NO_BT_A2DP && config != AUDIO_POLICY_FORCE_SPEAKER ) {
ALOGW("setForceUse() invalid config %d for FOR_MEDIA", config);
return BAD_VALUE;
}
mForceUse[usage] = config;
break;
......
default:
ALOGW("setForceUse() invalid usage %d", usage);
break; // TODO return BAD_VALUE?
}
return NO_ERROR;
}
第一个case就是COMMUNICATION,case中将config(AudioSystem.FORCE_BT_SCO)设置在了mForceUse[usage]中,这样当调用getDeviceForStrategyInt时,会作用到设备选择中:
case STRATEGY_ENFORCED_AUDIBLE:
if (((availableOutputDevicesType & AUDIO_DEVICE_OUT_ALL_SCO) != 0) &&
(mForceUse[AUDIO_POLICY_FORCE_FOR_COMMUNICATION] == AUDIO_POLICY_FORCE_BT_SCO))
uint32_t device2 = AUDIO_DEVICE_NONE;
device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
if (device2 == AUDIO_DEVICE_NONE) {
device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
}
if (device2 == AUDIO_DEVICE_NONE) {
device2 = availableOutputDevicesType & AUDIO_DEVICE_OUT_BLUETOOTH_SCO;
}
if (device2 != AUDIO_DEVICE_NONE) {
device |= device2;
break;
}
}
availableOutputDevicesType & AUDIO_DEVICE_OUT_ALL_SCO,如果可用的设备中有蓝牙sco设备,且mForceUse[AUDIO_POLICY_FORCE_FOR_COMMUNICATION] == AUDIO_POLICY_FORCE_BT_SCO,那么就可以向下按优先级选择蓝牙sco设备了;好了,影响设备选择的两类接口介绍完毕;
遗留问题:这只是Android设备选择的策略这一小块内容,像设备选择后什么时候更新(getNewOutputDevice)以及设备选择如何作用到hal层,这些内容还有待学习;