开发平台,安卓4.4。
最近一项目中客户有一需求:提供一接口,设置音频输出设备:听筒或者扬声器。设置后所有的音频都从同一输出设备中输出。
而通话默许走的是听筒,播放音乐等走的是扬声器,这该怎么办呢?
通过设置AudioManager.setMode和AudioManager.setSpeakerphoneOn两个方法所说也可以实现,但是其中逻辑有些复杂,于是放弃了。
下面用的方法非常简单:
我们知道AudioPolicyManagerBase.cpp是管理音频策略的,所以就直接修改它的策略就行了。
先讲几个概念
安卓系统的音频设置比较复杂,不同的音频流走的哪个输出设备之间对应着几种策略
安卓系统音频流分为如下几种:
AudioManager.java中就是引用的AudioSystem.java中定义的音频流类型
//frameworks/base/media/java/android/media/AudioSystem.java
/*
* If these are modified, please also update Settings.System.VOLUME_SETTINGS
* and attrs.xml and AudioManager.java.
*/
/* The audio stream for phone calls */
public static final int STREAM_VOICE_CALL = 0;
/* The audio stream for system sounds */
public static final int STREAM_SYSTEM = 1;
/* The audio stream for the phone ring and message alerts */
public static final int STREAM_RING = 2;
/* The audio stream for music playback */
public static final int STREAM_MUSIC = 3;
/* The audio stream for alarms */
public static final int STREAM_ALARM = 4;
/* The audio stream for notifications */
public static final int STREAM_NOTIFICATION = 5;
/* @hide The audio stream for phone calls when connected on bluetooth */
public static final int STREAM_BLUETOOTH_SCO = 6;
/* @hide The audio stream for enforced system sounds in certain countries (e.g camera in Japan) */
public static final int STREAM_SYSTEM_ENFORCED = 7;
/* @hide The audio stream for DTMF tones */
public static final int STREAM_DTMF = 8;
/* @hide The audio stream for text to speech (TTS) */
public static final int STREAM_TTS = 9;
// modified for FM start
// add STREAM_FM
/* @hide The audio stream for FM */
public static final int STREAM_FM = 10;
// modified for FM end
/**
音频流的作用:
a. 设备选择,比如MUSIC类型不插耳机只从扬声器出来,手上耳机从耳机出来。来电时RING类型会从扬声器和耳机同时出来
b. 音量控制,不同流类型的音量级不同,例如MUSIC有15个音量级,而STREAM_VOICE_CALL 只有7个音量级,且最低为1
声音模式
也就是AudioManager.setMode(mode)的这个mode值。看下它的定义:
//frameworks/base/media/java/android/media/AudioSystem.java
/* modes for setPhoneState, must match AudioSystem.h audio_mode */
//这些定义同样在AudioSystem.h中有,且必须是对应的
public static final int MODE_INVALID = -2;
public static final int MODE_CURRENT = -1;
public static final int MODE_NORMAL = 0;
public static final int MODE_RINGTONE = 1;
public static final int MODE_IN_CALL = 2;
public static final int MODE_IN_COMMUNICATION = 3;
public static final int NUM_MODES = 4;
AudioSystem区分Phone的状态,与Phone的硬件结构是有关系的,看一张图:
- 系统有一个音频DSP,声音的输入输出都由它来处理(蓝牙除外)。它处理完的数字信号,需要通过D/A转换后送到输出设备上,如扬声器,耳机,听筒等。
- 系统有一个应用处理核心AP(Application Processor),可以理解为我们的CPU,还有一个基带处理核心BP(Baseband Processor),与通信相关。
- AP和BP都能向DSP发送数据,硬件链路上互不干扰,但是如果AP和BP同时往DSP送数据,这时就会出现通话声和音乐声音混杂。所以在打电话时将由AP上的Phone程序,主动设置Audio系统的mode,比如设置为MODE_IN_CALL,在这种模式下把MUSIC的音量减小。
强制使用及配置
通话时可以设置强制使用扬声器,就是这个意思。
可以强制使用的设备有:
//frameworks/base/media/java/android/media/AudioSystem.java
// device categories config for setForceUse, must match audio_policy_forced_cfg_t
public static final int FORCE_NONE = 0;
public static final int FORCE_SPEAKER = 1;
public static final int FORCE_HEADPHONES = 2;
public static final int FORCE_BT_SCO = 3;
public static final int FORCE_BT_A2DP = 4;
public static final int FORCE_WIRED_ACCESSORY = 5;
public static final int FORCE_BT_CAR_DOCK = 6;
public static final int FORCE_BT_DESK_DOCK = 7;
public static final int FORCE_ANALOG_DOCK = 8;
public static final int FORCE_DIGITAL_DOCK = 9;
public static final int FORCE_NO_BT_A2DP = 10;
public static final int FORCE_SYSTEM_ENFORCED = 11;
public static final int FORCE_HDMI_SYSTEM_AUDIO_ENFORCED = 12;
public static final int FORCE_ENCODED_SURROUND_NEVER = 13;
public static final int FORCE_ENCODED_SURROUND_ALWAYS = 14;
public static final int NUM_FORCE_CONFIG = 15;
public static final int FORCE_DEFAULT = FORCE_NONE;
那么使用的时机是什么呢?所以还有定义了在什么情况下强制使用:
//frameworks/base/media/java/android/media/AudioSystem.java
// usage for setForceUse, must match audio_policy_force_use_t
public static final int FOR_COMMUNICATION = 0;
public static final int FOR_MEDIA = 1;
public static final int FOR_RECORD = 2;
public static final int FOR_DOCK = 3;
public static final int FOR_SYSTEM = 4;
public static final int FOR_HDMI_SYSTEM_AUDIO = 5;
public static final int FOR_ENCODED_SURROUND = 6;
private static final int NUM_FORCE_USE = 7;
输出设备的定义
//frameworks/base/media/java/android/media/AudioSystem.java
// output devices, be sure to update AudioManager.java also
public static final int DEVICE_OUT_EARPIECE = 0x1;
public static final int DEVICE_OUT_SPEAKER = 0x2;
public static final int DEVICE_OUT_WIRED_HEADSET = 0x4;
public static final int DEVICE_OUT_WIRED_HEADPHONE = 0x8;
public static final int DEVICE_OUT_BLUETOOTH_SCO = 0x10;
public static final int DEVICE_OUT_BLUETOOTH_SCO_HEADSET = 0x20;
public static final int DEVICE_OUT_BLUETOOTH_SCO_CARKIT = 0x40;
public static final int DEVICE_OUT_BLUETOOTH_A2DP = 0x80;
public static final int DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES = 0x100;
public static final int DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER = 0x200;
public static final int DEVICE_OUT_AUX_DIGITAL = 0x400;
public static final int DEVICE_OUT_HDMI = DEVICE_OUT_AUX_DIGITAL;
public static final int DEVICE_OUT_ANLG_DOCK_HEADSET = 0x800;
public static final int DEVICE_OUT_DGTL_DOCK_HEADSET = 0x1000;
public static final int DEVICE_OUT_USB_ACCESSORY = 0x2000;
public static final int DEVICE_OUT_USB_DEVICE = 0x4000;
public static final int DEVICE_OUT_REMOTE_SUBMIX = 0x8000;
public static final int DEVICE_OUT_TELEPHONY_TX = 0x10000;
public static final int DEVICE_OUT_LINE = 0x20000;
public static final int DEVICE_OUT_HDMI_ARC = 0x40000;
public static final int DEVICE_OUT_SPDIF = 0x80000;
public static final int DEVICE_OUT_FM = 0x100000;
public static final int DEVICE_OUT_AUX_LINE = 0x200000;
public static final int DEVICE_OUT_SPEAKER_SAFE = 0x400000;
public static final int DEVICE_OUT_IP = 0x800000;
public static final int DEVICE_OUT_BUS = 0x1000000;
public static final int DEVICE_OUT_PROXY = 0x2000000;
public static final int DEVICE_OUT_USB_HEADSET = 0x4000000;
安卓系统的音频路由策略有如下几种
那么音频流和Phone的模式以及输出设备之间是如何协调的呢,就需要为不同的流和输出设备之间制定一定的规则
//AudioPolicyManagerBase.h中定义了如下几种路由策略
enum routing_strategy {
STRATEGY_MEDIA,
STRATEGY_PHONE,
STRATEGY_SONIFICATION,
STRATEGY_SONIFICATION_RESPECTFUL,
STRATEGY_DTMF,
STRATEGY_ENFORCED_AUDIBLE,
STRATEGY_FM, //modified for FM
NUM_STRATEGIES
};
再放一张图说明一下它们之间的关系:
分析上图中策略的代码实现
获取音频策略的方法,为了方便起见,我只贴出了STRATEGY_PHONE和STRATEGY_MEDIA,其它的是类似的:
audio_devices_t AudioPolicyManagerBase::getDeviceForStrategy(routing_strategy strategy, bool fromCache){
uint32_t device = AUDIO_DEVICE_NONE;
switch (strategy) {
case STRATEGY_PHONE:
//对于phone的策略,首先考虑强制使用的设备,如果强制使用的设备不可用再考虑其它可用设备
switch (mForceUse[AudioSystem::FOR_COMMUNICATION]) {
case AudioSystem::FORCE_BT_SCO:
if (!isInCall() || strategy != STRATEGY_DTMF) {
device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT;
if (device) break;
}
device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
if (device) break;
device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_SCO;
if (device) break;
// if SCO device is requested but no SCO device is available, fall back to default case
// FALL THROUGH
case AudioSystem::FORCE_SPEAKER:
// when not in a phone call, phone strategy should route STREAM_VOICE_CALL to
// A2DP speaker when forcing to speaker output
if (mHasA2dp && !isInCall() &&
(mForceUse[AudioSystem::FOR_MEDIA] != AudioSystem::FORCE_NO_BT_A2DP) &&
(getA2dpOutput() != 0) && !mA2dpSuspended) {
device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER;
if (device) break;
}
if (mPhoneState != AudioSystem::MODE_IN_CALL) {
device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_USB_ACCESSORY;
if (device) break;
device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_USB_DEVICE;
if (device) break;
device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET;
if (device) break;
device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_AUX_DIGITAL;
if (device) break;
device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET;
if (device) break;
}
device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_SPEAKER;
if (device) break;
device = mDefaultOutputDevice;
if (device == AUDIO_DEVICE_NONE) {
ALOGE("getDeviceForStrategy() no device found for STRATEGY_PHONE, FORCE_SPEAKER");
}
break;
default: // FORCE_NONE,没有定义强制使用
// when not in a phone call, phone strategy should route STREAM_VOICE_CALL to A2DP
if (mHasA2dp && !isInCall() &&
(mForceUse[AudioSystem::FOR_MEDIA] != AudioSystem::FORCE_NO_BT_A2DP) &&
(getA2dpOutput() != 0) && !mA2dpSuspended) {
device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
if (device) break;
device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;
if (device) break;
}
device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_WIRED_HEADPHONE;
if (device) break;
device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_WIRED_HEADSET;
if (device) break;
if (mPhoneState != AudioSystem::MODE_IN_CALL) {
device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_USB_ACCESSORY;
if (device) break;
device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_USB_DEVICE;
if (device) break;
device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET;
if (device) break;
device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_AUX_DIGITAL;
if (device) break;
device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET;
if (device) break;
}
//如果没有定义强制使用,默认为EARPIECE
device = mAvailableOutputDevices & AUDIO_DEVICE_OUT_EARPIECE;
if (device) break;
device = mDefaultOutputDevice;
if (device == AUDIO_DEVICE_NONE) {
ALOGE("getDeviceForStrategy() no device found for STRATEGY_PHONE");
}
break;
}
break;
case STRATEGY_MEDIA: {
uint32_t device2 = AUDIO_DEVICE_NONE;
if (strategy != STRATEGY_SONIFICATION) {
// no sonification on remote submix (e.g. WFD)
device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_REMOTE_SUBMIX;
}
if (strategy == STRATEGY_MEDIA || strategy == STRATEGY_ENFORCED_AUDIBLE) {
switch (mForceUse[AudioSystem::FOR_MEDIA]) {
case AudioSystem::FORCE_SPEAKER:
ALOGD("geting device of force_speaker");
if (device2 == AUDIO_DEVICE_NONE) {
device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_SPEAKER;
}
//强制使用SPEAKER
device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_SPEAKER;
default://如果没有定义,默认改为听筒
ALOGI("bianjb--getDeviceForStrategy() STRATEGY_MEDIA-aaa");
device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_EARPIECE;
break;
}
}
if ((device2 == AUDIO_DEVICE_NONE) &&
mHasA2dp && (mForceUse[AudioSystem::FOR_MEDIA] != AudioSystem::FORCE_NO_BT_A2DP) &&
(getA2dpOutput() != 0) && !mA2dpSuspended) {
device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
if (device2 == AUDIO_DEVICE_NONE) {
device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;
}
if (device2 == AUDIO_DEVICE_NONE) {
device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER;
}
}
switch (mForceUse[AudioSystem::FOR_FM]) {
case AudioSystem::FORCE_SPEAKER:
ALOGI("geting device of force_speaker :STRATEGY_FM");
if (device2 == AUDIO_DEVICE_NONE) {
device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_SPEAKER;
}
}
if (device2 == AUDIO_DEVICE_NONE) {
device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_WIRED_HEADPHONE;
}
if (device2 == AUDIO_DEVICE_NONE) {
device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_WIRED_HEADSET;
}
if (device2 == AUDIO_DEVICE_NONE) {
device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_USB_ACCESSORY;
}
if (device2 == AUDIO_DEVICE_NONE) {
device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_USB_DEVICE;
}
if (device2 == AUDIO_DEVICE_NONE) {
device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET;
}
if ((device2 == AUDIO_DEVICE_NONE) && (strategy != STRATEGY_SONIFICATION)) {
// no sonification on aux digital (e.g. HDMI)
device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_AUX_DIGITAL;
}
if ((device2 == AUDIO_DEVICE_NONE) &&
(mForceUse[AudioSystem::FOR_DOCK] == AudioSystem::FORCE_ANALOG_DOCK)) {
device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET;
}
if (device2 == AUDIO_DEVICE_NONE) {
device2 = mAvailableOutputDevices & AUDIO_DEVICE_OUT_SPEAKER;
}
// device is DEVICE_OUT_SPEAKER if we come from case STRATEGY_SONIFICATION or
// STRATEGY_ENFORCED_AUDIBLE, AUDIO_DEVICE_NONE otherwise
device |= device2;
if (device) break;
device = mDefaultOutputDevice;
if (device == AUDIO_DEVICE_NONE) {
ALOGE("getDeviceForStrategy() no device found for STRATEGY_MEDIA");
}
} break;
default:
ALOGW("getDeviceForStrategy() unknown strategy: %d", strategy);
break;
}
接下来就是设置强制用了代码了
在自己的应用中做如下设置,当然必须为系统应用,否则还是不能调用的:
if(TextUtils.equals("1", ssamValue)){
AudioSystem.setForceUse(AudioSystem.FOR_COMMUNICATION, AudioSystem.FORCE_SPEAKER);
AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, AudioSystem.FORCE_SPEAKER);
Log.d(TAG, "set force use speaker");
replyMsg(getResString(R.string.ok));
}else if(TextUtils.equals("0", ssamValue)){
AudioSystem.setForceUse(AudioSystem.FOR_COMMUNICATION, AudioSystem.FORCE_NONE);
AudioSystem.setForceUse(AudioSystem.FOR_MEDIA, AudioSystem.FORCE_NONE);
Log.d(TAG, "set force use headphone");
replyMsg(getResString(R.string.ok));
}
写的不是很详细,有什么问题欢迎交流!