手机输出设备有很多,如听筒、扬声器、蓝牙耳机等都是常用的输出通路;对于音频类的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层,这些内容还有待学习;