前言:android系统中,音频输出的方式有很多种,外放即扬声器(Speaker)、听筒(Telephone Receiver)、有线耳机(WiredHeadset)、蓝牙音箱(Bluetooth A2DP)等,
android 系统默认有自己的音频输出优先级,那我们可以按照需求定制自己的音频切换方式么?答案是可以的。即可以在应用层修改,也可以在Framework修改,今天讲的就是Framework层的修改,也就是修改AudioService。接下来我们先了解下基本的使用
一音频输出通道
//base/media/java/android/media/AudioSystem.java下 @UnsupportedAppUsage 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;//有线设备通道,如有线耳机 @UnsupportedAppUsage public static final int FORCE_BT_CAR_DOCK = 6; @UnsupportedAppUsage public static final int FORCE_BT_DESK_DOCK = 7; @UnsupportedAppUsage public static final int FORCE_ANALOG_DOCK = 8; @UnsupportedAppUsage 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 FORCE_ENCODED_SURROUND_MANUAL = 15; public static final int NUM_FORCE_CONFIG = 16; public static final int FORCE_DEFAULT = FORCE_NONE;
常用的也就是:扬声器,有线耳机,听筒,蓝牙耳机等;
二 音频模式的理解和使用
- 音频模式设置的使用:
public static void setSpeakerNormal(Context context, int mode) { Log.e(TAG, "setSpeakerNormal: " + on); AudioManager am=(AudioManager)context.getSystemService(Context.AUDIO_SERVICE); am.setMode(mode); }
- 音频模式的选项:
在使用音频输出通道时,需要指定播放模式,设置音频模式的方法audioManager.setMode()方法的值有如下几种:
可用的模式有:MODE_NORMAL,//默认(平时)状态//base/media/java/android/media/AudioManager.java下 /* modes for setMode/getMode/setRoute/getRoute */ /** * Audio harware modes. */ /** * Invalid audio mode. */ public static final int MODE_INVALID = AudioSystem.MODE_INVALID; /** * Current audio mode. Used to apply audio routing to current mode. */ public static final int MODE_CURRENT = AudioSystem.MODE_CURRENT; /** * Normal audio mode: not ringing and no call established. */ public static final int MODE_NORMAL = AudioSystem.MODE_NORMAL; /** * Ringing audio mode. An incoming is being signaled. */ public static final int MODE_RINGTONE = AudioSystem.MODE_RINGTONE; /** * In call audio mode. A telephony call is established. */ public static final int MODE_IN_CALL = AudioSystem.MODE_IN_CALL; /** * In communication audio mode. An audio/video chat or VoIP call is established. */ public static final int MODE_IN_COMMUNICATION = AudioSystem.MODE_IN_COMMUNICATION;
MODE_RINGTONE,//响玲模式
MODE_IN_CALL,//通话模式
MODE_IN_COMMUNICATION//(非通话)切换至听筒模式
其引用的AudioSystem,该类定义如下AudioSystem.java /* modes for setPhoneState, must match AudioSystem.h audio_mode */ public static final int MODE_INVALID = -2; public static final int MODE_CURRENT = -1; public static final int MODE_NORMAL = 0;//待机模式,既不是铃声模式也不是通话模式,如music public static final int MODE_RINGTONE = 1;//铃声模式 public static final int MODE_IN_CALL = 2;//音频通话模式 public static final int MODE_IN_COMMUNICATION = 3;//通信模式,包括音/视频,VoIP通话.(3.0加入的,与通话模式类似) public static final int NUM_MODES = 4;
设备默认模式:MODE_NORMAL:
public static void setSpeakerNormal(Context context) { AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); am.setMode(AudioManager.MODE_NORMAL); }
- 设置听筒模式(非通话):MODE_IN_COMMUNICATION
public static void setTingtong(Context context) { Log.e(TAG, "setTingtong start "); AudioManager am = (AudioManager)context. getSystemService(Context.AUDIO_SERVICE); am.setSpeakerphoneOn(false); am.setMode(AudioManager.MODE_IN_COMMUNICATION); }
- 设置通话模式
public static void setInCall(Context context) { Log.e(TAG, "setTingtong start "); AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); am.setSpeakerphoneOn(false); am.setMode(AudioManager.MODE_IN_CALL); }
三 流类型
设置播放模式的时候,需要考虑流类型,常用的流类型有:
//base/media/java/android/media/AudioSystem.java下 /* These values must be kept in sync with system/audio.h */ /* * If these are modified, please also update Settings.System.VOLUME_SETTINGS * and attrs.xml and AudioManager.java. */ /** Used to identify the default audio stream volume */ public static final int STREAM_DEFAULT = -1; /** Used to identify the volume of audio streams for phone calls */ public static final int STREAM_VOICE_CALL = 0;//用于电话通话的音频流。 /** Used to identify the volume of audio streams for system sounds */ public static final int STREAM_SYSTEM = 1;//用于系统声音的音频流 /** Used to identify the volume of audio streams for the phone ring and message alerts */ public static final int STREAM_RING = 2;//用于电话铃声的音频流 /** Used to identify the volume of audio streams for music playback */ public static final int STREAM_MUSIC = 3;//用于音乐播放的音频流 /** Used to identify the volume of audio streams for alarms */ public static final int STREAM_ALARM = 4;//用于警报的音频流 /** Used to identify the volume of audio streams for notifications */ public static final int STREAM_NOTIFICATION = 5;//用于通知的音频流 /** Used to identify the volume of audio streams for phone calls when connected on bluetooth */ public static final int STREAM_BLUETOOTH_SCO = 6;//用于连接到蓝牙电话的手机音频流 /** Used to identify the volume of audio streams for enforced system sounds in certain * countries (e.g camera in Japan) */ @UnsupportedAppUsage public static final int STREAM_SYSTEM_ENFORCED = 7;//在某些国家实施的系统声音的音频流 /** Used to identify the volume of audio streams for DTMF tones */ public static final int STREAM_DTMF = 8;//DTMF音调的音频流。 /** Used to identify the volume of audio streams exclusively transmitted through the * speaker (TTS) of the device */ public static final int STREAM_TTS = 9;//文本到语音转换(TTS)的音频流。 /** Used to identify the volume of audio streams for accessibility prompts */ public static final int STREAM_ACCESSIBILITY = 10;//辅助功能提示音频流
四 音频输出通道,播放模式和流类型的关系
音频通道是与播放模式一起用的,而播放模式与音频流类型有关系;
(1)音频通道是指声音从哪里出来,这个容易理解;
(2)播放模式,也叫音频状态,手机有4种音频状态:待机状态,音视频通话状态,视频/VoIP通话状态与响铃状态。这4种状态对底层的音频输出设备的选择影响很大,相应的情景下就得使用相应的模式,如视频情景的播放模式就是MODE_IN_COMMUNICATION,或者,播放音乐情景的播放模式就是MODE_NORMAL,什么样的情形就得用什么样的播放模式,不能搞混,比如MODE_IN_CALL,就只能由通话时才能使用;
(3)音频流类型,我们操作手机的音频时需要指定操作的是哪一个流,虽然手机的中音频流类型有很多,但是一旦进入到属性里,android就会将其整理成几种类型,这才是实际的类型,与上面的播放模式对应;
五 audioManager.setMode(int mode)的源码实现
- 在AudioManager类中的实现
/** * Sets the audio mode. * <p> * The audio mode encompasses audio routing AND the behavior of * the telephony layer. Therefore this method should only be used by applications that * replace the platform-wide management of audio settings or the main telephony application. * In particular, the {@link #MODE_IN_CALL} mode should only be used by the telephony * application when it places a phone call, as it will cause signals from the radio layer * to feed the platform mixer. * * @param mode the requested audio mode ({@link #MODE_NORMAL}, {@link #MODE_RINGTONE}, * {@link #MODE_IN_CALL} or {@link #MODE_IN_COMMUNICATION}). * Informs the HAL about the current audio state so that * it can route the audio appropriately. */ public void setMode(int mode) { final IAudioService service = getService(); try { service.setMode(mode, mICallBack, mApplicationContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
- 接下来是AudioService中方法的实现
/** @see AudioManager#setMode(int) */ public void setMode(int mode, IBinder cb, String callingPackage) { if (DEBUG_MODE) { Log.v(TAG, "setMode(mode=" + mode + ", callingPackage=" + callingPackage + ")"); } //检测权限不合法,return if (!checkAudioSettingsPermission("setMode()")) { return; } //通话模式下,无权限return if ( (mode == AudioSystem.MODE_IN_CALL) && (mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE) != PackageManager.PERMISSION_GRANTED)) { Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: setMode(MODE_IN_CALL) from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); return; } //新的mode值不合法return if (mode < AudioSystem.MODE_CURRENT || mode >= AudioSystem.NUM_MODES) { return; } int oldModeOwnerPid = 0; int newModeOwnerPid = 0; synchronized (mDeviceBroker.mSetModeLock) { if (!mSetModeDeathHandlers.isEmpty()) { oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid(); } if (mode == AudioSystem.MODE_CURRENT) { mode = mMode; } //设置新的模式值,如果此次设置的音频播放模式和上一次的不同,返回这次使用新音频播放模式的进程的pid newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid(), callingPackage); } // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all // SCO connections not started by the application changing the mode when pid changes ///如果进入了RINGTONE, IN_CALL 或者IN_COMMUNICATION模式,清除掉当前更改音频模式的应用进程的蓝牙SCO连接 if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) { mDeviceBroker.postDisconnectBluetoothSco(newModeOwnerPid); } }
//设置新的模式值
newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid(), callingPackage);
3. setModeInt的实现
// setModeInt() returns a valid PID if the audio mode was successfully set to // any mode other than NORMAL. @GuardedBy("mDeviceBroker.mSetModeLock") private int setModeInt(int mode, IBinder cb, int pid, String caller) { if (DEBUG_MODE) { Log.v(TAG, "setModeInt(mode=" + mode + ", pid=" + pid + ", caller=" + caller + ")"); } int newModeOwnerPid = 0; if (cb == null) { Log.e(TAG, "setModeInt() called with null binder"); return newModeOwnerPid; } if(caller != null && caller.equals("com.android.soundrecorder") && mode == AudioSystem.MODE_IN_CALL) { mIsSoundRecorderPlayInEARPIECE = true; }else{ mIsSoundRecorderPlayInEARPIECE = false; } SetModeDeathHandler hdlr = null; Iterator iter = mSetModeDeathHandlers.iterator(); //循环遍历mSetModeDeathHandlers,找到与传递进来的相同pid的SetModeDeathHandler,并赋值给hdlr, //相同的pid也就是相同的应用,即找到相同的应用 while (iter.hasNext()) { SetModeDeathHandler h = (SetModeDeathHandler)iter.next(); if (h.getPid() == pid) { hdlr = h; // Remove from client list so that it is re-inserted at top of list iter.remove(); hdlr.getBinder().unlinkToDeath(hdlr, 0); break; } } final int oldMode = mMode; int status = AudioSystem.AUDIO_STATUS_OK; int actualMode; do { //将传递进来的mode赋值初始化实际的播放模式 actualMode = mode; //如果设置的模式是正常的播放模式,那就从mSetModeDeathHandlers列表的顶端获取一个模式给actualMode, //最近一次设置非正常音频模式的应用都会被放在mSetModeDeathHandlers的顶端 if (mode == AudioSystem.MODE_NORMAL) { // get new mode from client at top the list if any if (!mSetModeDeathHandlers.isEmpty()) { hdlr = mSetModeDeathHandlers.get(0); cb = hdlr.getBinder(); actualMode = hdlr.getMode(); if (DEBUG_MODE) { Log.w(TAG, " using mode=" + mode + " instead due to death hdlr at pid=" + hdlr.mPid); } } } else { if (hdlr == null) { hdlr = new SetModeDeathHandler(cb, pid); } // Register for client death notification try { cb.linkToDeath(hdlr, 0); } catch (RemoteException e) { // Client has died! Log.w(TAG, "setMode() could not link to "+cb+" binder death"); } // Last client to call setMode() is always at top of client list // as required by SetModeDeathHandler.binderDied() //将hdlr加到mSetModeDeathHandlers中,并放到首位,也就是最后一个调用setMode()的进程位于列表的顶部 mSetModeDeathHandlers.add(0, hdlr); //设置当前进程的音频播放模式,hdlr.setMode()会将mode设置给mMode,这个要注意,要不然很容易跟下面的"actualMode != mMode"混淆 hdlr.setMode(mode); } if (actualMode != mMode) { final long identity = Binder.clearCallingIdentity(); //通过AudioSystem将当前的音频模式设置到底层去,status返回设置的结果 status = AudioSystem.setPhoneState(actualMode); Binder.restoreCallingIdentity(identity); if (status == AudioSystem.AUDIO_STATUS_OK) { if (DEBUG_MODE) { Log.v(TAG, " mode successfully set to " + actualMode); } //如果设置成功,保存当前的音频播放模式 mMode = actualMode; } else { if (hdlr != null) { //如果设置不成功,从mSetModeDeathHandlers中删除该应用 mSetModeDeathHandlers.remove(hdlr); cb.unlinkToDeath(hdlr, 0); } // force reading new top of mSetModeDeathHandlers stack if (DEBUG_MODE) { Log.w(TAG, " mode set to MODE_NORMAL after phoneState pb"); } mode = AudioSystem.MODE_NORMAL; } } else { status = AudioSystem.AUDIO_STATUS_OK; } } while (status != AudioSystem.AUDIO_STATUS_OK && !mSetModeDeathHandlers.isEmpty()); if (status == AudioSystem.AUDIO_STATUS_OK) { if (actualMode != AudioSystem.MODE_NORMAL) { if (mSetModeDeathHandlers.isEmpty()) { Log.e(TAG, "setMode() different from MODE_NORMAL with empty mode client stack"); } else { //如果这个进程设置的音频模式为非正常模式,那就返回这个进程的pid newModeOwnerPid = mSetModeDeathHandlers.get(0).getPid(); } } // Note: newModeOwnerPid is always 0 when actualMode is MODE_NORMAL mModeLogger.log( new PhoneStateEvent(caller, pid, mode, newModeOwnerPid, actualMode)); //下面的代码用来设置当前音频流类型的音量 int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE); int device = getDeviceForStream(streamType); int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device); setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true, caller); updateStreamVolumeAlias(true /*updateVolumes*/, caller); // change of mode may require volume to be re-applied on some devices updateAbsVolumeMultiModeDevices(oldMode, actualMode); } return newModeOwnerPid; }
AudioService用mMode来保存当前的音频播放模式。
六 设置音频输出管道
设置音频输出管道的方法有两个分别是setSpeakerphoneOn()和setBluetoothScoOn(),我们来就看下最常用的设置扬声器播放;
/** * Sets the speakerphone on or off. * <p> * This method should only be used by applications that replace the platform-wide * management of audio settings or the main telephony application. * * @param on set <var>true</var> to turn on speakerphone; * <var>false</var> to turn it off *true 是开启扬声器,false关闭扬声器 */ public void setSpeakerphoneOn(boolean on){ final IAudioService service = getService(); try { service.setSpeakerphoneOn(on); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
以上方法是调用AudioService中的setSpeakerphoneOn(boolean on)
看下AudioSevice中该方法的实现
/** @see AudioManager#setSpeakerphoneOn(boolean) */ public void setSpeakerphoneOn(boolean on){ //检查android.Manifest.permission.MODIFY_AUDIO_SETTINGS是否允许 if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) { return; } //通话模式,检查权限是否允许 if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { synchronized (mSetModeDeathHandlers) { for (SetModeDeathHandler h : mSetModeDeathHandlers) { if (h.getMode() == AudioSystem.MODE_IN_CALL) { Log.w(TAG, "getMode is call, Permission Denial: setSpeakerphoneOn from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); return; } } } } // for logging only final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on) .append(") from u/pid:").append(Binder.getCallingUid()).append("/") .append(Binder.getCallingPid()).toString(); //设置扬声器开/关 final boolean stateChanged = mDeviceBroker.setSpeakerphoneOn(on, eventSource); if (stateChanged) { //设置成功 final long ident = Binder.clearCallingIdentity(); try { //发送成功后,发送扬声器状态改变的广播 mContext.sendBroadcastAsUser( new Intent(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED) .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), UserHandle.ALL); } finally { Binder.restoreCallingIdentity(ident); } } }
以上方法体中使用 final boolean stateChanged = mDeviceBroker.setSpeakerphoneOn(on, eventSource); 修改扬声器开关。返回true,则修改成功。该方法在AudioDeviceBroker中实现
/** * Turns speakerphone on/off * @param on * @param eventSource for logging purposes * @return true if speakerphone state changed */ /*package*/ boolean setSpeakerphoneOn(boolean on, String eventSource) { synchronized (mDeviceStateLock) { final boolean wasOn = isSpeakerphoneOn(); if (on) { //开启扬声器 if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) { setForceUse_Async(AudioSystem.FOR_RECORD, AudioSystem.FORCE_NONE, eventSource); } //进入扬声器播放的标志AudioSystem.FORCE_SPEAKER mForcedUseForComm = AudioSystem.FORCE_SPEAKER; } else if (mForcedUseForComm == AudioSystem.FORCE_SPEAKER) { //取消扬声器 mForcedUseForComm = AudioSystem.FORCE_NONE; } mForcedUseForCommExt = mForcedUseForComm; //此时是语音模式AudioSystem.FOR_COMMUNICATION,mForcedUseForComm表示当前是哪种音频通道 setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource); return (wasOn != isSpeakerphoneOn()); } }
以上方法中可以看到AudioService用mForcedUseForComm和mForcedUseForCommExt保存了当前的音频通道。以上方法根据状态设置对应的模式使用方法 setForceUse_Async(AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource);
/*package*/ void setForceUse_Async(int useCase, int config, String eventSource) { //传递进来的参数封装,交给mAudioHandler处理; //arg1是AudioSystem.FOR_COMMUNICATION, arg2是mForcedUseForComm, //obj是eventSource,eventSource就是调用扬声器的进程的信息 sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE, useCase, config, eventSource); }
我们看下sendIILMsgNoDelay方法的实现
private void sendIILMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj) { sendIILMsg(msg, existingMsgPolicy, arg1, arg2, obj, 0); } private void sendIILMsg(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) { if (existingMsgPolicy == SENDMSG_REPLACE) { mBrokerHandler.removeMessages(msg); } else if (existingMsgPolicy == SENDMSG_NOOP && mBrokerHandler.hasMessages(msg)) { return; } if (isMessageHandledUnderWakelock(msg)) { final long identity = Binder.clearCallingIdentity(); try { mBrokerEventWakeLock.acquire(BROKER_WAKELOCK_TIMEOUT_MS); } catch (Exception e) { Log.e(TAG, "Exception acquiring wakelock", e); } Binder.restoreCallingIdentity(identity); } synchronized (sLastDeviceConnectionMsgTimeLock) { long time = SystemClock.uptimeMillis() + delay; switch (msg) { case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE: case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE: case MSG_IL_SET_HEARING_AID_CONNECTION_STATE: case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: case MSG_IL_BTA2DP_DOCK_TIMEOUT: case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE: if (sLastDeviceConnectMsgTime >= time) { // add a little delay to make sure messages are ordered as expected time = sLastDeviceConnectMsgTime + 30; } sLastDeviceConnectMsgTime = time; break; default: break; } private void sendIILMsgNoDelay(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj) { sendIILMsg(msg, existingMsgPolicy, arg1, arg2, obj, 0); } private void sendIILMsg(int msg, int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) { if (existingMsgPolicy == SENDMSG_REPLACE) { mBrokerHandler.removeMessages(msg); } else if (existingMsgPolicy == SENDMSG_NOOP && mBrokerHandler.hasMessages(msg)) { return; } if (isMessageHandledUnderWakelock(msg)) { final long identity = Binder.clearCallingIdentity(); try { mBrokerEventWakeLock.acquire(BROKER_WAKELOCK_TIMEOUT_MS); } catch (Exception e) { Log.e(TAG, "Exception acquiring wakelock", e); } Binder.restoreCallingIdentity(identity); } synchronized (sLastDeviceConnectionMsgTimeLock) { long time = SystemClock.uptimeMillis() + delay; switch (msg) { case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE: case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE: case MSG_IL_SET_HEARING_AID_CONNECTION_STATE: case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: case MSG_IL_BTA2DP_DOCK_TIMEOUT: case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE: if (sLastDeviceConnectMsgTime >= time) { // add a little delay to make sure messages are ordered as expected time = sLastDeviceConnectMsgTime + 30; } sLastDeviceConnectMsgTime = time; break; default: break; } //传递进来的参数封装,交给mAudioHandler处理; //arg1是AudioSystem.FOR_COMMUNICATION, arg2是mForcedUseForComm, //obj是eventSource,eventSource就是调用扬声器的进程的信息 mBrokerHandler.sendMessageAtTime(mBrokerHandler.obtainMessage(msg, arg1, arg2, obj), time); } } mBrokerHandler.sendMessageAtTime(mBrokerHandler.obtainMessage(msg, arg1, arg2, obj), time); } }
根据代码逻辑看Message的参数arg1,arg2,obj 三个参数的意义
arg1 = AudioSystem.FOR_COMMUNICATION
arg2 = mForcedUseForComm,
obj = eventSource//eventSource就是调用扬声器的进程的信息
以上方法发送消息 消息接收
case MSG_IIL_SET_FORCE_USE: // intended fall-through case MSG_IIL_SET_FORCE_BT_A2DP_USE: onSetForceUse(msg.arg1, msg.arg2, (String) msg.obj);
该方法的实现
//--------------------------------------------------------------------- // Internal handling of messages // These methods are ALL synchronous, in response to message handling in BrokerHandler // Blocking in any of those will block the message queue private void onSetForceUse(int useCase, int config, String eventSource) { Log.d(TAG, "onSetForceUse usage= "+useCase+" config= "+config+" eventSource="+eventSource); if (useCase == AudioSystem.FOR_MEDIA) { postReportNewRoutes(); } AudioService.sForceUseLogger.log( new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource)); //将语音模式AudioSystem.FOR_COMMUNICATION,音频通道mForcedUseForComm交给AudioSystem, //AudioSystem会将其设置到HAL底层 AudioSystem.setForceUse(useCase, config); }
AudioService在调用AudioSystem.setForceUse(usage, config)方法时,会将相应的音频播放模式和音频通道设置到底层,从这里可以就看出,为什么在调用setSpeakerphoneOn()时要结合setMode()一起使用了;
我们看到调用了AudioSystem的setForceUse方法,该在AudioSystem中的实现 是一个native方法。
//base/media/java/android/media/AudioSystem.java @UnsupportedAppUsage public static native int setForceUse(int usage, int config);
总结:
疑惑:
前面介绍中就说了android手机中有很多的音频输出通道,为啥AudioService只提供了setSpeakerphoneOn()和setBluetoothScoOn这两个手动切换音频输出通道的方法呢?
之所以AudioService只提供这两个方法,是因为有些切换是系统自动完成的,比如有线耳机,蓝牙耳机的插入和拔出等,这些音频外设的切换在应用层是无法处理的;
听筒,扬声器,有线耳机这三个输出设备的切换
1)听筒通道
听筒模式一般只会在通话或者语音过程中才会用到,所以,要使用听筒模式,必须得指定播放模式为MODE_IN_CALL或者是MODE_IN_COMMUNICATION;MODE_IN_CALL只有在通话时才可以用到。非通话时使用MODE_IN_COMMUNICATION。
2)扬声器通道
在不插入音频外设如耳机的情况下,手机中的输出设备只有听筒和扬声器,要想在听筒和扬声器中切换是比较容易的,无非就是setSpeakerphoneOn(boolean)方法调用以及设置播放模式为MODE_IN_CALL或者MODE_IN_COMMUNICATION;
3)有线耳机
耳机是音频外设,此时手机中的音频输出设备有3个,除了耳机还有听筒和扬声器;那底层是怎样选择一个设备进行音频输出的呢?这就和音频系统中的音频路由策略有关,底层在播放音频时会选择一个设备,这个逻辑跟设备的优先级有关,代码在AudoPolicyServcie中,有时间在剖析这个具体原理;
所以,当手机中的音频输出设备有耳机,听筒和扬声器时,会根据设备的优先级来进行选择;
从测试的结果来看,3个当中,耳机的优先级最高,其次是听筒;