背景介紹
最近遇到了一個(gè)case飒房,發(fā)現(xiàn)Android上媒體音量下也可以支持聽(tīng)筒播放,這個(gè)行為比較奇怪媚值,因此專(zhuān)門(mén)在源碼中分析下狠毯。
分析流程
在系統(tǒng)中有這樣一個(gè)接口isSpeakerphoneOn,有時(shí)候會(huì)用來(lái)判斷是否是外放褥芒,可是這個(gè)接口真的可以判斷么嚼松?
我們可以直接在啟動(dòng)應(yīng)用的時(shí)候讀取下這個(gè)接口的值,媒體音量下默認(rèn)是外放锰扶,理論上就應(yīng)該是true献酗,那真的是么?
搞一個(gè)demo坷牛,打印下罕偎,結(jié)果如下:
speakerphone on is false
此時(shí)應(yīng)用的確是外放。這是系統(tǒng)bug么京闰?我們從代碼上看下:
/**
* Indicates if preferred route selection for communication is speakerphone.
* @return true if speakerphone is active, false otherwise.
*/
/*package*/ boolean isSpeakerphoneOn() {
return isDeviceOnForCommunication(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
}
從注釋上看颜及,表示優(yōu)先選擇的路由是否是speakerphone,這兒并沒(méi)有說(shuō)是當(dāng)前的路由是speakerphone忙干。
/**
* Indicates if the device which type is passed as argument is currently either resquested
* to be used for communication or selected for an other reason (e.g bluetooth SCO audio
* is active for SCO device).
* @param deviceType the device type the query applies to.
* @return true if this device type is requested for communication.
*/
private boolean isDeviceOnForCommunication(int deviceType) {
synchronized (mDeviceStateLock) {
AudioDeviceAttributes device = preferredCommunicationDevice();
return device != null && device.getType() == deviceType;
}
}
這兒從preferredCommunicationDevice中看是否優(yōu)先選擇的路由是TYPE_BUILTIN_SPEAKER器予。
從之前的信息上看浪藻,就是這兒返回的應(yīng)該不是TYPE_BUILTIN_SPEAKER捐迫,否則就應(yīng)該是true了。
/**
* Determines which preferred device for phone strategy should be sent to audio policy manager
* as a function of current SCO audio activation state and active communication route requests.
* SCO audio state has the highest priority as it can result from external activation by
* telephony service.
* @return selected forced usage for communication.
*/
@GuardedBy("mDeviceStateLock")
@Nullable private AudioDeviceAttributes preferredCommunicationDevice() {
boolean btSCoOn = mBtHelper.isBluetoothScoOn();
synchronized (mBluetoothAudioStateLock) {
btSCoOn = btSCoOn && mBluetoothScoOn;
}
if (btSCoOn) {
// Use the SCO device known to BtHelper so that it matches exactly
// what has been communicated to audio policy manager. The device
// returned by requestedCommunicationDevice() can be a placeholder SCO device if legacy
// APIs are used to start SCO audio.
AudioDeviceAttributes device = mBtHelper.getHeadsetAudioDevice();
if (device != null) {
return device;
}
}
AudioDeviceAttributes device = requestedCommunicationDevice();
if (device == null || device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
// Do not indicate BT SCO selection if SCO is requested but SCO is not ON
return null;
}
return device;
}
雖然這兒想表達(dá)Sco連接的設(shè)備優(yōu)先級(jí)比較高爱葵,不過(guò)現(xiàn)在不涉及sco施戴,只關(guān)心外放,聽(tīng)筒就好了萌丈。所以邏輯還在requestedCommunicationDevice中, 其實(shí)這兒可以看到一些端倪赞哗,從邏輯上看,應(yīng)該是查詢(xún)應(yīng)用請(qǐng)求的路由辆雾,如果應(yīng)用沒(méi)有請(qǐng)求肪笋,那就應(yīng)該是空,而媒體音量下應(yīng)用一般不會(huì)請(qǐng)求路由度迂,因此這時(shí)候查詢(xún)的設(shè)備就是空藤乙,按照上面的邏輯,那就是找不到speakerphone設(shè)備惭墓,所以返回false坛梁,不過(guò)這個(gè)僅僅是我們的猜想,如果要求證還需要繼續(xù)往下看:
/**
* Returns the device currently requested for communication use case.
* Use the device requested by the communication route client selected by
* {@link #topCommunicationRouteClient()} if any or none otherwise.
* @return AudioDeviceAttributes the requested device for communication.
*/
@GuardedBy("mDeviceStateLock")
private AudioDeviceAttributes requestedCommunicationDevice() {
CommunicationRouteClient crc = topCommunicationRouteClient();
AudioDeviceAttributes device = crc != null ? crc.getDevice() : null;
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "requestedCommunicationDevice: "
+ device + " mAudioModeOwner: " + mAudioModeOwner.toString());
}
return device;
}
答案就在topCommunicationRouteClient中了
/**
* Returns the communication client with the highest priority:
* - 1) the client which is currently also controlling the audio mode
* - 2) the first client in the stack if there is no audio mode owner
* - 3) no client otherwise
* @return CommunicationRouteClient the client driving the communication use case routing.
*/
@GuardedBy("mDeviceStateLock")
private CommunicationRouteClient topCommunicationRouteClient() {
for (CommunicationRouteClient crc : mCommunicationRouteClients) {
if (crc.getUid() == mAudioModeOwner.mUid) {
return crc;
}
}
if (!mCommunicationRouteClients.isEmpty() && mAudioModeOwner.mPid == 0
&& mCommunicationRouteClients.get(0).isActive()) {
return mCommunicationRouteClients.get(0);
}
return null;
}
這兒會(huì)按照2個(gè)策略來(lái)選擇設(shè)備:
- 優(yōu)先讓當(dāng)前mode owner請(qǐng)求的設(shè)備腊凶,因?yàn)槭敲襟w音量划咐,不涉及mode owner拴念,因此這個(gè)我們不滿足
- 在看中請(qǐng)求設(shè)備列表中的第一個(gè)設(shè)備,因?yàn)槲覀儧](méi)請(qǐng)求過(guò),那就應(yīng)該是空碗啄。注意這兒還判斷了Active蚤认,內(nèi)部就是看有沒(méi)有存活的采集或播放,也就是如果沒(méi)有存活的采集或播放缔俄,請(qǐng)求路由也會(huì)不生效,這個(gè)也是合理的器躏,既然都沒(méi)有采播俐载,空占著路由反而會(huì)影響其他有采播的應(yīng)用。這個(gè)就當(dāng)額外的一個(gè)小知識(shí)了登失。
目前看起來(lái)基本符合我們的猜想遏佣。那如何實(shí)錘呢? 我們可以dump下這個(gè)信息:
Communication route clients:
[CommunicationRouteClient: mUid: 10372 mDevice: AudioDeviceAttributes: role:output type:speaker addr: name: profiles:[] descriptors:[] mIsPrivileged: false mPlaybackActive: false mRecordingActive: false]
Computed Preferred communication device: null
Applied Preferred communication device: null
Active communication device: AudioDeviceAttributes: role:output type:earpiece addr: name:Pixel 4 profiles:[] descriptors:[]
第一個(gè)打印的就是mCommunicationRouteClients, 這個(gè)是有一個(gè)成員揽浙,不過(guò)對(duì)應(yīng)的uid并不是demo的状婶,而且采播狀態(tài)也是false,從前面的信息我們知道如果一個(gè)應(yīng)用沒(méi)有存活的采播馅巷,那么他請(qǐng)求的route是不會(huì)生效的膛虫。
第二個(gè)就是打印的preferredCommunicationDevice,果然是null钓猬,因?yàn)槭莕ull稍刀,所以返回的是false。
不過(guò)可以看到最后一行信息有route打印敞曹,告訴我們當(dāng)前的route是earpiece账月,這個(gè)顯然不是真實(shí)的,那這個(gè)信息是什么呢澳迫?
mActiveCommunicationDevice = AudioManager.getDeviceInfoFromTypeAndAddress(
device.getType(), device.getAddress());
是從native audiopolicymanager報(bào)上來(lái)的設(shè)備局齿,這個(gè)是默認(rèn)的device,并不是當(dāng)前使用的device橄登。
接下來(lái)我們看下SetSpeakerphoneOn(false):
/**
* Turns speakerphone on/off
* @param on true to enable speakerphone
* @param eventSource for logging purposes
*/
/*package*/ void setSpeakerphoneOn(
IBinder cb, int uid, boolean on, boolean isPrivileged, String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "setSpeakerphoneOn, on: " + on + " uid: " + uid);
}
postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
cb, uid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
on, BtHelper.SCO_MODE_UNDEFINED, eventSource, isPrivileged));
}
對(duì)應(yīng)這兒的on就是false抓歼,也就是關(guān)閉speaker,接下來(lái)會(huì)到如下邏輯中:
/**
* Sets or resets the communication device for matching client. If no client matches and the
* request is to reset for a given device (deviceInfo.mOn == false), the method is a noop.
* @param deviceInfo information on the device and requester {@link #CommunicationDeviceInfo}
*/
@GuardedBy("mDeviceStateLock")
/*package*/ void onSetCommunicationDeviceForClient(CommunicationDeviceInfo deviceInfo) {
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "onSetCommunicationDeviceForClient: " + deviceInfo);
}
if (!deviceInfo.mOn) {
CommunicationRouteClient client = getCommunicationRouteClientForUid(deviceInfo.mUid);
if (client == null || (deviceInfo.mDevice != null
&& !deviceInfo.mDevice.equals(client.getDevice()))) {
return;
}
}
AudioDeviceAttributes device = deviceInfo.mOn ? deviceInfo.mDevice : null;
setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mUid, device,
deviceInfo.mScoAudioMode, deviceInfo.mIsPrivileged, deviceInfo.mEventSource);
}
如果是請(qǐng)求關(guān)閉拢锹,那么就需要獲取以前請(qǐng)求的設(shè)備谣妻,如果是空或者設(shè)備不匹配,那么就直接return了面褐。
從這兒我們可以看出拌禾,如果是要使用聽(tīng)筒,那么需要先請(qǐng)求過(guò)外放展哭,如果沒(méi)有請(qǐng)求過(guò)湃窍,那么是走不了聽(tīng)筒的闻蛀。
再重復(fù)一下,如果要使用聽(tīng)筒您市,就需要先請(qǐng)求外放觉痛。
接下來(lái)繼續(xù)看setCommunicationRouteForClient:
@GuardedBy("mDeviceStateLock")
/*package*/ void setCommunicationRouteForClient(
IBinder cb, int uid, AudioDeviceAttributes device,
int scoAudioMode, boolean isPrivileged, String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "setCommunicationRouteForClient: device: " + device
+ ", eventSource: " + eventSource);
}
AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"setCommunicationRouteForClient for uid: " + uid
+ " device: " + device + " isPrivileged: " + isPrivileged
+ " from API: " + eventSource)).printLog(TAG));
final boolean wasBtScoRequested = isBluetoothScoRequested();
CommunicationRouteClient client;
// Save previous client route in case of failure to start BT SCO audio
AudioDeviceAttributes prevClientDevice = null;
boolean prevPrivileged = false;
client = getCommunicationRouteClientForUid(uid);
if (client != null) {
prevClientDevice = client.getDevice();
prevPrivileged = client.isPrivileged();
}
if (device != null) {
client = addCommunicationRouteClient(cb, uid, device, isPrivileged);
if (client == null) {
Log.w(TAG, "setCommunicationRouteForClient: could not add client for uid: "
+ uid + " and device: " + device);
}
} else {
client = removeCommunicationRouteClient(cb, true);
}
if (client == null) {
return;
}
if (!mScoManagedByAudio) {
boolean isBtScoRequested = isBluetoothScoRequested();
if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) {
if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) {
Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for uid: "
+ uid);
// clean up or restore previous client selection
if (prevClientDevice != null) {
addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged);
} else {
removeCommunicationRouteClient(cb, true);
}
postBroadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
}
} else if (!isBtScoRequested && wasBtScoRequested) {
mBtHelper.stopBluetoothSco(eventSource);
}
}
// In BT classic for communication, the device changes from a2dp to sco device, but for
// LE Audio it stays the same and we must trigger the proper stream volume alignment, if
// LE Audio communication device is activated after the audio system has already switched to
// MODE_IN_CALL mode.
if (isBluetoothLeAudioRequested() && device != null) {
final int streamType = mAudioService.getBluetoothContextualVolumeStream();
final int leAudioVolIndex = getVssVolumeForDevice(streamType, device.getInternalType());
final int leAudioMaxVolIndex = getMaxVssVolumeForStream(streamType);
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "setCommunicationRouteForClient restoring LE Audio device volume lvl.");
}
postSetLeAudioVolumeIndex(leAudioVolIndex, leAudioMaxVolIndex, streamType);
}
updateCommunicationRoute(eventSource);
}
這兒看著邏輯多,其實(shí)大部分是和sco相關(guān)的茵休,我們只關(guān)心和外放薪棒,聽(tīng)筒相關(guān)的就好¢泡海可以看到如下信息:
- 如果是聽(tīng)筒俐芯,那么會(huì)先移除之前請(qǐng)求的client,如果之前也沒(méi)請(qǐng)求過(guò)钉鸯,那就直接返回了
- 如果是外放吧史,那么會(huì)添加到mCommunicationRouteClients中。
接下來(lái)就是updateCommunicationRoute:
/**
* Configures audio policy manager and audio HAL according to active communication route.
* Always called from message Handler.
*/
// @GuardedBy("mSetModeLock")
@GuardedBy("mDeviceStateLock")
private void updateCommunicationRoute(String eventSource) {
AudioDeviceAttributes preferredCommunicationDevice = preferredCommunicationDevice();
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "updateCommunicationRoute, preferredCommunicationDevice: "
+ preferredCommunicationDevice + " eventSource: " + eventSource);
}
AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"updateCommunicationRoute, preferredCommunicationDevice: "
+ preferredCommunicationDevice + " eventSource: " + eventSource)));
if (preferredCommunicationDevice == null) {
AudioDeviceAttributes defaultDevice = getDefaultCommunicationDevice();
if (defaultDevice != null) {
mDeviceInventory.setPreferredDevicesForStrategyInt(
mCommunicationStrategyId, Arrays.asList(defaultDevice));
mDeviceInventory.setPreferredDevicesForStrategyInt(
mAccessibilityStrategyId, Arrays.asList(defaultDevice));
} else {
mDeviceInventory.removePreferredDevicesForStrategyInt(mCommunicationStrategyId);
mDeviceInventory.removePreferredDevicesForStrategyInt(mAccessibilityStrategyId);
}
mDeviceInventory.applyConnectedDevicesRoles();
mDeviceInventory.reapplyExternalDevicesRoles();
} else {
mDeviceInventory.setPreferredDevicesForStrategyInt(
mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice));
mDeviceInventory.setPreferredDevicesForStrategyInt(
mAccessibilityStrategyId, Arrays.asList(preferredCommunicationDevice));
}
onUpdatePhoneStrategyDevice(preferredCommunicationDevice);
}
這兒從preferredCommunicationDevice中拿到優(yōu)先級(jí)最高的路由設(shè)備唠雕,前面已經(jīng)看過(guò)內(nèi)部的邏輯了贸营,優(yōu)先選擇modeOwner請(qǐng)求的設(shè)備,接下來(lái)是請(qǐng)求隊(duì)列中首個(gè)請(qǐng)求對(duì)應(yīng)的設(shè)備岩睁,不過(guò)前提是需要有存活的采播钞脂。
從我們關(guān)心的外放和聽(tīng)筒邏輯上看,如果是外放捕儒,那么這兒獲取到的優(yōu)先設(shè)備就應(yīng)該是外放冰啃,而如果是聽(tīng)筒,那么就是null肋层。
帶著這個(gè)信息我們繼續(xù)看下onUpdatePhoneStrategyDevice:
// @GuardedBy("mSetModeLock")
@GuardedBy("mDeviceStateLock")
private void onUpdatePhoneStrategyDevice(AudioDeviceAttributes device) {
boolean wasSpeakerphoneActive = isSpeakerphoneActive();
mPreferredCommunicationDevice = device;
updateActiveCommunicationDevice();
if (wasSpeakerphoneActive != isSpeakerphoneActive()) {
try {
mContext.sendBroadcastAsUser(
new Intent(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED)
.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
UserHandle.ALL);
} catch (Exception e) {
Log.w(TAG, "failed to broadcast ACTION_SPEAKERPHONE_STATE_CHANGED: " + e);
}
}
mAudioService.postUpdateRingerModeServiceInt();
dispatchCommunicationDevice();
}
這兒會(huì)比較是否外放狀態(tài)發(fā)生變化亿笤,如果是,那么就會(huì)對(duì)外發(fā)送廣播栋猖。這兒只關(guān)注下postUpdateRingerModeServiceInt,看這個(gè)方法的原因是我們需要找到是音頻路由在哪兒設(shè)置到系統(tǒng)中生效汪榔。
/*package*/ void postUpdateRingerModeServiceInt() {
sendMsg(mAudioHandler, MSG_UPDATE_RINGER_MODE, SENDMSG_QUEUE, 0, 0, null, 0);
}
中間就是handler處理蒲拉,我們直接看最終的實(shí)現(xiàn):
private void setRingerModeInt(int ringerMode, boolean persist) {
final boolean change;
synchronized(mSettingsLock) {
change = mRingerMode != ringerMode;
mRingerMode = ringerMode;
muteRingerModeStreams();
}
// Post a persist ringer mode msg
if (persist) {
sendMsg(mAudioHandler, MSG_PERSIST_RINGER_MODE,
SENDMSG_REPLACE, 0, 0, null, PERSIST_DELAY);
}
if (change) {
// Send sticky broadcast
broadcastRingerMode(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION, ringerMode);
}
}
muteRingerModeStreams內(nèi)部就會(huì)觸發(fā)設(shè)置路由到native:
@GuardedBy("mSettingsLock")
private void muteRingerModeStreams() {
// Mute stream if not previously muted by ringer mode and (ringer mode
// is not RINGER_MODE_NORMAL OR stream is zen muted) and stream is affected by ringer mode.
// Unmute stream if previously muted by ringer/zen mode and ringer mode
// is RINGER_MODE_NORMAL or stream is not affected by ringer mode.
int numStreamTypes = AudioSystem.getNumStreamTypes();
if (mNm == null) {
mNm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
}
final int ringerMode = mRingerMode; // Read ringer mode as reading primitives is atomic
final boolean ringerModeMute = ringerMode == AudioManager.RINGER_MODE_VIBRATE
|| ringerMode == AudioManager.RINGER_MODE_SILENT;
final boolean shouldRingSco = ringerMode == AudioManager.RINGER_MODE_VIBRATE
&& mDeviceBroker.isBluetoothScoActive();
final boolean shouldRingBle = ringerMode == AudioManager.RINGER_MODE_VIBRATE
&& (mDeviceBroker.isBluetoothBleHeadsetActive()
|| mDeviceBroker.isBluetoothBleSpeakerActive());
// Ask audio policy engine to force use Bluetooth SCO/BLE channel if needed
final String eventSource = "muteRingerModeStreams() from u/pid:" + Binder.getCallingUid()
+ "/" + Binder.getCallingPid();
int forceUse = AudioSystem.FORCE_NONE;
if (shouldRingSco) {
forceUse = AudioSystem.FORCE_BT_SCO;
} else if (shouldRingBle) {
forceUse = AudioSystem.FORCE_BT_BLE;
}
sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE, AudioSystem.FOR_VIBRATE_RINGING,
forceUse, eventSource, 0);
for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
final boolean isMuted = isStreamMutedByRingerOrZenMode(streamType);
final boolean muteAllowedBySco =
!((shouldRingSco || shouldRingBle) && streamType == AudioSystem.STREAM_RING);
final boolean shouldZenMute = isStreamAffectedByCurrentZen(streamType);
final boolean shouldMute = shouldZenMute || (ringerModeMute
&& isStreamAffectedByRingerMode(streamType) && muteAllowedBySco);
if (isMuted == shouldMute) continue;
if (!shouldMute) {
// unmute
// ring and notifications volume should never be 0 when not silenced
if (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING
|| mStreamVolumeAlias[streamType] == AudioSystem.STREAM_NOTIFICATION) {
synchronized (VolumeStreamState.class) {
final VolumeStreamState vss = mStreamStates[streamType];
for (int i = 0; i < vss.mIndexMap.size(); i++) {
int device = vss.mIndexMap.keyAt(i);
int value = vss.mIndexMap.valueAt(i);
if (value == 0) {
vss.setIndex(10, device, TAG, true /*hasModifyAudioSettings*/);
}
}
// Persist volume for stream ring when it is changed here
final int device = getDeviceForStream(streamType);
sendMsg(mAudioHandler,
MSG_PERSIST_VOLUME,
SENDMSG_QUEUE,
device,
0,
mStreamStates[streamType],
PERSIST_DELAY);
}
}
sRingerAndZenModeMutedStreams &= ~(1 << streamType);
sMuteLogger.enqueue(new AudioServiceEvents.RingerZenMutedStreamsEvent(
sRingerAndZenModeMutedStreams, "muteRingerModeStreams"));
mStreamStates[streamType].mute(false, "muteRingerModeStreams");
} else {
// mute
sRingerAndZenModeMutedStreams |= (1 << streamType);
sMuteLogger.enqueue(new AudioServiceEvents.RingerZenMutedStreamsEvent(
sRingerAndZenModeMutedStreams, "muteRingerModeStreams"));
mStreamStates[streamType].mute(true, "muteRingerModeStreams");
}
}
}
音量類(lèi)型處理的可以直接忽略,看MSG_SET_FORCE_USE就可以:
case MSG_SET_FORCE_USE:
{
final String eventSource = (String) msg.obj;
final int useCase = msg.arg1;
final int config = msg.arg2;
if (useCase == AudioSystem.FOR_MEDIA) {
Log.wtf(TAG, "Invalid force use FOR_MEDIA in AudioService from "
+ eventSource);
break;
}
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_FORCE_USE
+ MediaMetrics.SEPARATOR + AudioSystem.forceUseUsageToString(useCase))
.set(MediaMetrics.Property.EVENT, "setForceUse")
.set(MediaMetrics.Property.FORCE_USE_DUE_TO, eventSource)
.set(MediaMetrics.Property.FORCE_USE_MODE,
AudioSystem.forceUseConfigToString(config))
.record();
sForceUseLogger.enqueue(
new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource));
mAudioSystem.setForceUse(useCase, config);
}
break;
就是這兒的mAudioSystem.setForceUse(useCase, config)了痴腌,現(xiàn)在對(duì)應(yīng)的參數(shù)可以在dumpsys中看到:
11-02 20:46:58:546 setCommunicationRouteForClient for uid: 10376 device: AudioDeviceAttributes: role:output type:speaker addr: name: profiles:[] descriptors:[] isPrivileged: false from API: setSpeakerphoneOn(true) from u/pid:10376/7775
11-02 20:46:58:546 updateCommunicationRoute, preferredCommunicationDevice: AudioDeviceAttributes: role:output type:speaker addr: name: profiles:[] descriptors:[] eventSource: setSpeakerphoneOn(true) from u/pid:10376/7775
11-02 20:46:59:413 setForceUse(FOR_VIBRATE_RINGING, FORCE_NONE) due to muteRingerModeStreams() from u/pid:1000/1788
11-02 20:46:59:484 setForceUse(FOR_VIBRATE_RINGING, FORCE_NONE) due to muteRingerModeStreams() from u/pid:1000/1788
11-02 20:46:59:503 setForceUse(FOR_VIBRATE_RINGING, FORCE_NONE) due to muteRingerModeStreams() from u/pid:1000/1788
差不多都可以和log對(duì)上雌团,接下來(lái)就可以簡(jiǎn)單看下native了:
Status AudioPolicyService::setForceUse(AudioPolicyForceUse usageAidl,
AudioPolicyForcedConfig configAidl)
{
audio_policy_force_use_t usage = VALUE_OR_RETURN_BINDER_STATUS(
aidl2legacy_AudioPolicyForceUse_audio_policy_force_use_t(usageAidl));
audio_policy_forced_cfg_t config = VALUE_OR_RETURN_BINDER_STATUS(
aidl2legacy_AudioPolicyForcedConfig_audio_policy_forced_cfg_t(configAidl));
if (mAudioPolicyManager == NULL) {
return binderStatusFromStatusT(NO_INIT);
}
if (!modifyAudioRoutingAllowed()) {
return binderStatusFromStatusT(PERMISSION_DENIED);
}
if (usage < 0 || usage >= AUDIO_POLICY_FORCE_USE_CNT) {
return binderStatusFromStatusT(BAD_VALUE);
}
if (config < 0 || config >= AUDIO_POLICY_FORCE_CFG_CNT) {
return binderStatusFromStatusT(BAD_VALUE);
}
ALOGV("setForceUse()");
audio_utils::lock_guard _l(mMutex);
AutoCallerClear acc;
mAudioPolicyManager->setForceUse(usage, config);
onCheckSpatializer_l();
return Status::ok();
}
這兒信息比較有限,其實(shí)再往下看也比較有限:
void AudioPolicyManager::setForceUse(audio_policy_force_use_t usage,
audio_policy_forced_cfg_t config)
{
ALOGV("setForceUse() usage %d, config %d, mPhoneState %d", usage, config, mEngine->getPhoneState());
if (config == mEngine->getForceUse(usage)) {
return;
}
if (mEngine->setForceUse(usage, config) != NO_ERROR) {
ALOGW("setForceUse() could not set force cfg %d for usage %d", config, usage);
return;
}
bool forceVolumeReeval = (usage == AUDIO_POLICY_FORCE_FOR_COMMUNICATION) ||
(usage == AUDIO_POLICY_FORCE_FOR_DOCK) ||
(usage == AUDIO_POLICY_FORCE_FOR_SYSTEM);
// check for device and output changes triggered by new force usage
checkForDeviceAndOutputChanges();
// force client reconnection to reevaluate flag AUDIO_FLAG_AUDIBILITY_ENFORCED
if (usage == AUDIO_POLICY_FORCE_FOR_SYSTEM) {
invalidateStreams({AUDIO_STREAM_SYSTEM, AUDIO_STREAM_ENFORCED_AUDIBLE});
}
//FIXME: workaround for truncated touch sounds
// to be removed when the problem is handled by system UI
uint32_t delayMs = 0;
if (usage == AUDIO_POLICY_FORCE_FOR_COMMUNICATION) {
delayMs = TOUCH_SOUND_FIXED_DELAY_MS;
}
updateCallAndOutputRouting(forceVolumeReeval, delayMs);
updateInputRouting();
}
這兒就是按照策略選擇路由士聪,具體這兒就先不看了锦援。
在設(shè)置音量類(lèi)型的時(shí)候也會(huì)刷新路由,這塊邏輯也可以在代碼中串起來(lái):
/** @see AudioManager#setMode(int) */
public void setMode(int mode, IBinder cb, String callingPackage) {
int pid = Binder.getCallingPid();
int uid = Binder.getCallingUid();
if (DEBUG_MODE) {
Log.v(TAG, "setMode(mode=" + mode + ", pid=" + pid
+ ", uid=" + uid + ", caller=" + callingPackage + ")");
}
if (!checkAudioSettingsPermission("setMode()")) {
return;
}
if (cb == null) {
Log.e(TAG, "setMode() called with null binder");
return;
}
if (mode < AudioSystem.MODE_CURRENT || mode >= AudioSystem.NUM_MODES) {
Log.w(TAG, "setMode() invalid mode: " + mode);
return;
}
if (mode == AudioSystem.MODE_CURRENT) {
mode = getMode();
}
if (mode == AudioSystem.MODE_CALL_SCREENING && !mIsCallScreeningModeSupported) {
Log.w(TAG, "setMode(MODE_CALL_SCREENING) not permitted "
+ "when call screening is not supported");
return;
}
final boolean hasModifyPhoneStatePermission = mContext.checkCallingOrSelfPermission(
MODIFY_PHONE_STATE)
== PackageManager.PERMISSION_GRANTED;
if ((mode == AudioSystem.MODE_IN_CALL
|| mode == AudioSystem.MODE_CALL_REDIRECT
|| mode == AudioSystem.MODE_COMMUNICATION_REDIRECT)
&& !hasModifyPhoneStatePermission) {
Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: setMode("
+ AudioSystem.modeToString(mode) + ") from pid=" + pid
+ ", uid=" + Binder.getCallingUid());
return;
}
SetModeDeathHandler currentModeHandler = null;
synchronized (mDeviceBroker.mSetModeLock) {
for (SetModeDeathHandler h : mSetModeDeathHandlers) {
if (h.getPid() == pid) {
currentModeHandler = h;
break;
}
}
if (mode == AudioSystem.MODE_NORMAL) {
if (currentModeHandler != null) {
if (!currentModeHandler.isPrivileged()
&& currentModeHandler.getMode() == AudioSystem.MODE_IN_COMMUNICATION) {
mAudioHandler.removeEqualMessages(
MSG_CHECK_MODE_FOR_UID, currentModeHandler);
}
mSetModeDeathHandlers.remove(currentModeHandler);
if (DEBUG_MODE) {
Log.v(TAG, "setMode(" + mode + ") removing hldr for pid: " + pid);
}
try {
currentModeHandler.getBinder().unlinkToDeath(currentModeHandler, 0);
} catch (NoSuchElementException e) {
Log.w(TAG, "setMode link does not exist ...");
}
}
} else {
if (currentModeHandler != null) {
currentModeHandler.setMode(mode);
if (DEBUG_MODE) {
Log.v(TAG, "setMode(" + mode + ") updating hldr for pid: " + pid);
}
} else {
currentModeHandler = new SetModeDeathHandler(cb, pid, uid,
hasModifyPhoneStatePermission, callingPackage, mode);
// Register for client death notification
try {
cb.linkToDeath(currentModeHandler, 0);
} catch (RemoteException e) {
// Client has died!
Log.w(TAG, "setMode() could not link to " + cb + " binder death");
return;
}
mSetModeDeathHandlers.add(currentModeHandler);
if (DEBUG_MODE) {
Log.v(TAG, "setMode(" + mode + ") adding handler for pid=" + pid);
}
}
if (mode == AudioSystem.MODE_IN_COMMUNICATION) {
// Force active state when entering/updating the stack to avoid glitches when
// an app starts playing/recording after settng the audio mode,
// and send a reminder to check activity after a grace period.
if (!currentModeHandler.isPrivileged()) {
currentModeHandler.setPlaybackActive(true);
currentModeHandler.setRecordingActive(true);
sendMsg(mAudioHandler,
MSG_CHECK_MODE_FOR_UID,
SENDMSG_QUEUE,
0,
0,
currentModeHandler,
CHECK_MODE_FOR_UID_PERIOD_MS);
}
}
}
sendMsg(mAudioHandler,
MSG_UPDATE_AUDIO_MODE,
SENDMSG_REPLACE,
mode,
pid,
callingPackage,
0);
}
}
這兒只需要關(guān)注2個(gè)邏輯:
- 設(shè)置通話音量會(huì)在audioserver中記錄剥悟,而設(shè)置媒體音量會(huì)清除記錄
- 設(shè)置通話音量需要有存活的采播灵寺,如果沒(méi)有曼库,那么設(shè)置的音量類(lèi)型會(huì)在閾值時(shí)間過(guò)后被修改為媒體音量
接下來(lái)就是handler里的執(zhí)行了:
@GuardedBy("mDeviceBroker.mSetModeLock")
void onUpdateAudioMode(int requestedMode, int requesterPid, String requesterPackage,
boolean force) {
if (requestedMode == AudioSystem.MODE_CURRENT) {
requestedMode = getMode();
}
int mode = AudioSystem.MODE_NORMAL;
int uid = 0;
int pid = 0;
SetModeDeathHandler currentModeHandler = getAudioModeOwnerHandler();
if (currentModeHandler != null) {
mode = currentModeHandler.getMode();
uid = currentModeHandler.getUid();
pid = currentModeHandler.getPid();
}
if (DEBUG_MODE) {
Log.v(TAG, "onUpdateAudioMode() new mode: " + mode + ", current mode: "
+ mMode.get() + " requested mode: " + requestedMode);
}
if (mode != mMode.get() || force) {
int status = AudioSystem.SUCCESS;
final long identity = Binder.clearCallingIdentity();
try {
status = mAudioSystem.setPhoneState(mode, uid);
} finally {
Binder.restoreCallingIdentity(identity);
}
if (status == AudioSystem.AUDIO_STATUS_OK) {
if (DEBUG_MODE) {
Log.v(TAG, "onUpdateAudioMode: mode successfully set to " + mode);
}
sendMsg(mAudioHandler, MSG_DISPATCH_AUDIO_MODE, SENDMSG_REPLACE, mode, 0,
/*obj*/ null, /*delay*/ 0);
int previousMode = mMode.getAndSet(mode);
// Note: newModeOwnerPid is always 0 when actualMode is MODE_NORMAL
mModeLogger.enqueue(new PhoneStateEvent(requesterPackage, requesterPid,
requestedMode, pid, mode));
final int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
final int device = getDeviceForStream(streamType);
final int streamAlias = mStreamVolumeAlias[streamType];
if (DEBUG_MODE) {
Log.v(TAG, "onUpdateAudioMode: streamType=" + streamType
+ ", streamAlias=" + streamAlias);
}
final int index = mStreamStates[streamAlias].getIndex(device);
final int maxIndex = mStreamStates[streamAlias].getMaxIndex();
setStreamVolumeInt(streamAlias, index, device, true,
requesterPackage, true /*hasModifyAudioSettings*/);
updateStreamVolumeAlias(true /*updateVolumes*/, requesterPackage);
// change of mode may require volume to be re-applied on some devices
updateAbsVolumeMultiModeDevices(previousMode, mode);
setLeAudioVolumeOnModeUpdate(mode, device, streamAlias, index, maxIndex);
synchronized (mCachedAbsVolDrivingStreamsLock) {
mCachedAbsVolDrivingStreams.replaceAll((absDev, stream) -> {
int streamToDriveAbs = getBluetoothContextualVolumeStream();
if (stream != streamToDriveAbs) {
mAudioSystem.setDeviceAbsoluteVolumeEnabled(absDev, /*address=*/
"", /*enabled*/true, streamToDriveAbs);
}
return streamToDriveAbs;
});
}
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
// connections not started by the application changing the mode when pid changes
mDeviceBroker.postSetModeOwner(mode, pid, uid);
} else {
Log.w(TAG, "onUpdateAudioMode: failed to set audio mode to: " + mode);
}
}
}
這兒只需要關(guān)注幾點(diǎn):
- 通話音量?jī)?yōu)先級(jí)比媒體音量高
- 音量類(lèi)型變更后,就會(huì)刷新硬件音量
- 最后會(huì)將音量類(lèi)型通知給DeviceBroker
而我們關(guān)心的就是最后一個(gè)略板,看看DeviceBroker收到該事件后做了哪些操作毁枯,我們預(yù)期的是會(huì)刷新路由設(shè)備:
case MSG_I_SET_MODE_OWNER:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
boolean wasBtScoRequested = isBluetoothScoRequested();
mAudioModeOwner = (AudioModeInfo) msg.obj;
if (mAudioModeOwner.mMode != AudioSystem.MODE_RINGTONE) {
onUpdateCommunicationRouteClient(
wasBtScoRequested, "setNewModeOwner");
}
}
}
break;
這兒會(huì)調(diào)用onUpdateCommunicationRouteClient:
/**
* Select new communication device from communication route client at the top of the stack
* and restore communication route including restarting SCO audio if needed.
*/
// @GuardedBy("mSetModeLock")
@GuardedBy("mDeviceStateLock")
private void onUpdateCommunicationRouteClient(boolean wasBtScoRequested, String eventSource) {
CommunicationRouteClient crc = topCommunicationRouteClient();
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "onUpdateCommunicationRouteClient, crc: " + crc
+ " wasBtScoRequested: " + wasBtScoRequested + " eventSource: " + eventSource);
}
if (crc != null) {
setCommunicationRouteForClient(crc.getBinder(), crc.getUid(), crc.getDevice(),
BtHelper.SCO_MODE_UNDEFINED, crc.isPrivileged(), eventSource);
} else {
if (!mScoManagedByAudio && !isBluetoothScoRequested() && wasBtScoRequested) {
mBtHelper.stopBluetoothSco(eventSource);
}
updateCommunicationRoute(eventSource);
}
}
這兒就是熟悉的邏輯了,選擇一個(gè)優(yōu)先的設(shè)備作為路由設(shè)備叮称。
這下關(guān)于設(shè)置外放和聽(tīng)筒的基礎(chǔ)邏輯都介紹完了种玛,接下來(lái)我們總結(jié)下信息:
- isSpeakerphoneOn 是無(wú)法用來(lái)判斷是外放還是聽(tīng)筒的
- 系統(tǒng)會(huì)記錄isSpeakerphoneOn是true時(shí)候的請(qǐng)求信息,而為false就會(huì)移除請(qǐng)求信息瓤檐,接下來(lái)就是由系統(tǒng)自己決策路由
- 設(shè)置音量類(lèi)型也會(huì)刷新音頻路由
- media.audio_policy 中包含了當(dāng)前的路由信息
- 通話音量默認(rèn)路由是聽(tīng)筒赂韵,媒體音量默認(rèn)路由是外放
接下來(lái)直接在媒體音量下設(shè)置isSpeakerphoneOn false,看看能否切換到聽(tīng)筒挠蛉, 按照我們已經(jīng)儲(chǔ)備的信息右锨,結(jié)論是不可以的,因?yàn)樵诟侣酚蓵r(shí)會(huì)發(fā)現(xiàn)之前沒(méi)調(diào)用過(guò) true碌秸,所以沒(méi)有client信息绍移,會(huì)直接返回。我們驗(yàn)證下:
先看metrics信息讥电,是有調(diào)用setSpeakerphoneOn false
1971: {audio.device.setSpeakerphoneOn, (11-03 17:52:06.777), (10376, 22449, 10376), (state=off)}
1972: {audio.device.setSpeakerphoneOn, (11-03 17:52:07.780), (10376, 22449, 10376), (state=off)}
1973: {audio.device.setSpeakerphoneOn, (11-03 17:52:08.784), (10376, 22449, 10376), (state=off)}
1974: {audio.device.setSpeakerphoneOn, (11-03 17:52:09.787), (10376, 22449, 10376), (state=off)}
1975: {audio.device.setSpeakerphoneOn, (11-03 17:52:10.789), (10376, 22449, 10376), (state=off)}
1976: {audio.device.setSpeakerphoneOn, (11-03 17:52:11.792), (10376, 22449, 10376), (state=off)}
1977: {audio.device.setSpeakerphoneOn, (11-03 17:52:12.795), (10376, 22449, 10376), (state=off)}
1978: {audio.device.setSpeakerphoneOn, (11-03 17:52:13.798), (10376, 22449, 10376), (state=off)}
1979: {audio.device.setSpeakerphoneOn, (11-03 17:52:14.802), (10376, 22449, 10376), (state=off)}
1980: {audio.device.setSpeakerphoneOn, (11-03 17:52:15.805), (10376, 22449, 10376), (state=off)}
1981: {audio.device.setSpeakerphoneOn, (11-03 17:52:16.807), (10376, 22449, 10376), (state=off)}
1982: {audio.device.setSpeakerphoneOn, (11-03 17:52:17.810), (10376, 22449, 10376), (state=off)}
再看audio信息:
Computed Preferred communication device: null
Applied Preferred communication device: null
11-03 17:41:18:485 updateCommunicationRoute, preferredCommunicationDevice: null eventSource: setNewModeOwner
11-03 17:44:48:352 updateCommunicationRoute, preferredCommunicationDevice: null eventSource: setNewModeOwner
11-03 17:48:08:757 updateCommunicationRoute, preferredCommunicationDevice: null eventSource: setNewModeOwner
對(duì)應(yīng)時(shí)間點(diǎn)就壓根沒(méi)走到updateCommunicationRoute流程中蹂窖, 而且對(duì)應(yīng)的優(yōu)先設(shè)備也是null,這個(gè)也符合我們預(yù)期恩敌, 因?yàn)閏lient是空瞬测,發(fā)現(xiàn)client為空時(shí)候就直接返回了。
再看下policy:
-STRATEGY_MEDIA (id: 19)
Selected Device: {AUDIO_DEVICE_OUT_SPEAKER, @:}
Group: 20 stream: AUDIO_STREAM_ASSISTANT
Attributes: { Content type: AUDIO_CONTENT_TYPE_SPEECH Usage: AUDIO_USAGE_ASSISTANT Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
Group: 6 stream: AUDIO_STREAM_MUSIC
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_MEDIA Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
Group: 6 stream: AUDIO_STREAM_MUSIC
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_GAME Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
Group: 6 stream: AUDIO_STREAM_MUSIC
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_ASSISTANT Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
Group: 6 stream: AUDIO_STREAM_MUSIC
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
Group: 6 stream: AUDIO_STREAM_MUSIC
Attributes: { Any }
Group: 11 stream: AUDIO_STREAM_SYSTEM
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_ASSISTANCE_SONIFICATION Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
的確是外放纠炮,因此準(zhǔn)確的路由信息可以看policy月趟, audio中不包含準(zhǔn)確的信息。
那接下來(lái)可能會(huì)想恢口,如果先調(diào)用isSpeakerphoneOn true孝宗,再調(diào)用false,是否可以切過(guò)去耕肩?畢竟這樣client肯定不是空了, 驗(yàn)證結(jié)果還是外放因妇,這樣也符合預(yù)期,雖然false可以設(shè)置下去了猿诸,可是由于把client移除了婚被,實(shí)際上就還是讓系統(tǒng)在媒體音量下自己決策,那走的就還是外放了:
11-03 18:05:34:581 setCommunicationRouteForClient for uid: 10376 device: AudioDeviceAttributes: role:output type:speaker addr: name: profiles:[] descriptors:[] isPrivileged: false from API: setSpeakerphoneOn(true) from u/pid:10376/22943
11-03 18:05:34:582 updateCommunicationRoute, preferredCommunicationDevice: AudioDeviceAttributes: role:output type:speaker addr: name: profiles:[] descriptors:[] eventSource: setSpeakerphoneOn(true) from u/pid:10376/22943
11-03 18:05:34:833 setCommunicationRouteForClient for uid: 10376 device: null isPrivileged: false from API: setSpeakerphoneOn(false) from u/pid:10376/22943
11-03 18:05:34:834 updateCommunicationRoute, preferredCommunicationDevice: null eventSource: setSpeakerphoneOn(false) from u/pid:10376/22943
從log上看梳虽,流程和預(yù)期基本一樣址芯。
那媒體音量真的就不能走聽(tīng)筒么?從API調(diào)用上看似乎的確是,如果僅僅是這個(gè)結(jié)論的話谷炸,那似乎完全不需要這么大一個(gè)分析流程北专,幾句話就可以帶過(guò)了,實(shí)際上還有個(gè)信息我們忽略了淑廊,那就是policy逗余,policy負(fù)責(zé)為播放決策路由,那決策路由的策略依據(jù)是什么呢季惩?顯然不是音量類(lèi)型录粱,如果是的話,那Android audiopolicyservice中那么多復(fù)雜邏輯就完全不需要了画拾,那是什么呢啥繁?答案就是stream type,Android是按照stream type來(lái)決策的青抛,這樣仔細(xì)一想就會(huì)發(fā)現(xiàn)很合理旗闽,因?yàn)槁酚勺罱K是為播放服務(wù)的,從播放器角度做決策顯然是最合理的蜜另,接下來(lái)我們看下Android的策略:
Policy Engine dump:
Product Strategies dump:
-STRATEGY_PHONE (id: 14)
Selected Device: {AUDIO_DEVICE_OUT_EARPIECE, @:}
Group: 13 stream: AUDIO_STREAM_VOICE_CALL
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_VOICE_COMMUNICATION Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
Group: 3 stream: AUDIO_STREAM_BLUETOOTH_SCO
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_UNKNOWN Source: AUDIO_SOURCE_INVALID Flags: 0x4 Tags: }
-STRATEGY_SONIFICATION (id: 15)
Selected Device: {AUDIO_DEVICE_OUT_EARPIECE, @:}
Group: 10 stream: AUDIO_STREAM_RING
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
Group: 2 stream: AUDIO_STREAM_ALARM
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_ALARM Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
-STRATEGY_ENFORCED_AUDIBLE (id: 16)
Selected Device: {AUDIO_DEVICE_OUT_SPEAKER, @:}
Group: 5 stream: AUDIO_STREAM_ENFORCED_AUDIBLE
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_UNKNOWN Source: AUDIO_SOURCE_INVALID Flags: 0x1 Tags: }
-STRATEGY_ACCESSIBILITY (id: 17)
Selected Device: {AUDIO_DEVICE_OUT_SPEAKER, @:}
Group: 1 stream: AUDIO_STREAM_ACCESSIBILITY
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_ASSISTANCE_ACCESSIBILITY Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
-STRATEGY_SONIFICATION_RESPECTFUL (id: 18)
Selected Device: {AUDIO_DEVICE_OUT_EARPIECE, @:}
Group: 7 stream: AUDIO_STREAM_NOTIFICATION
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_NOTIFICATION Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
Group: 7 stream: AUDIO_STREAM_NOTIFICATION
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_NOTIFICATION_EVENT Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
-STRATEGY_MEDIA (id: 19)
Selected Device: {AUDIO_DEVICE_OUT_SPEAKER, @:}
Group: 20 stream: AUDIO_STREAM_ASSISTANT
Attributes: { Content type: AUDIO_CONTENT_TYPE_SPEECH Usage: AUDIO_USAGE_ASSISTANT Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
Group: 6 stream: AUDIO_STREAM_MUSIC
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_MEDIA Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
Group: 6 stream: AUDIO_STREAM_MUSIC
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_GAME Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
Group: 6 stream: AUDIO_STREAM_MUSIC
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_ASSISTANT Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
Group: 6 stream: AUDIO_STREAM_MUSIC
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
Group: 6 stream: AUDIO_STREAM_MUSIC
Attributes: { Any }
Group: 11 stream: AUDIO_STREAM_SYSTEM
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_ASSISTANCE_SONIFICATION Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
-STRATEGY_DTMF (id: 21)
Selected Device: {AUDIO_DEVICE_OUT_SPEAKER, @:}
Group: 4 stream: AUDIO_STREAM_DTMF
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
-STRATEGY_CALL_ASSISTANT (id: 22)
Selected Device: {AUDIO_DEVICE_OUT_TELEPHONY_TX, @:}
Group: 23 stream: AUDIO_STREAM_CALL_ASSISTANT
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_CALL_ASSISTANT Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
-STRATEGY_TRANSMITTED_THROUGH_SPEAKER (id: 24)
Selected Device: {AUDIO_DEVICE_OUT_SPEAKER, @:}
Group: 12 stream: AUDIO_STREAM_TTS
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_UNKNOWN Source: AUDIO_SOURCE_INVALID Flags: 0x8 Tags: }
Group: 12 stream: AUDIO_STREAM_TTS
Attributes: { Content type: AUDIO_CONTENT_TYPE_ULTRASOUND Usage: AUDIO_USAGE_UNKNOWN Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
-STRATEGY_REROUTING (id: 25)
Selected Device: {AUDIO_DEVICE_OUT_SPEAKER, @:}
Group: 9 stream: AUDIO_STREAM_REROUTING
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_VIRTUAL_SOURCE Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: reserved_internal_strategy }
-STRATEGY_PATCH (id: 26)
Selected Device: {AUDIO_DEVICE_OUT_SPEAKER, @:}
Group: 8 stream: AUDIO_STREAM_PATCH
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_UNKNOWN Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: reserved_internal_strategy }
可以看到适室,如果選AUDIO_STREAM_VOICE_CALL的stream type,那策略就會(huì)選擇聽(tīng)筒举瑰。接下來(lái)驗(yàn)證下捣辆,發(fā)現(xiàn)的確媒體音量走了聽(tīng)筒。
Audio mode:
- Requested mode = MODE_NORMAL
- Actual mode = MODE_NORMAL
- Mode owner:
None
- Mode owner stack:
Empty
Computed Preferred communication device: null
Applied Preferred communication device: null
Active communication device: AudioDeviceAttributes: role:output type:earpiece addr: name:Pixel 4 profiles:[] descriptors:[]
mCommunicationStrategyId: 14
mAccessibilityStrategyId: 17
這兒的mCommunicationStrategyId: 14對(duì)應(yīng)policy中就是聽(tīng)筒此迅。
-STRATEGY_PHONE (id: 14)
Selected Device: {AUDIO_DEVICE_OUT_EARPIECE, @:}
Group: 13 stream: AUDIO_STREAM_VOICE_CALL
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_VOICE_COMMUNICATION Source: AUDIO_SOURCE_INVALID Flags: 0x0 Tags: }
Group: 3 stream: AUDIO_STREAM_BLUETOOTH_SCO
Attributes: { Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_UNKNOWN Source: AUDIO_SOURCE_INVALID Flags: 0x4 Tags: }
現(xiàn)在就可以對(duì)Android的路由再做一個(gè)總結(jié)了:
- android 音頻路由的選擇依據(jù)不是音量類(lèi)型汽畴,而是播放的stream type
- Android媒體音量也支持聽(tīng)筒,只需要修改stream type就可以做到
- 普通媒體音量下切不到聽(tīng)筒耸序,是因?yàn)槊襟w音量下的stream type 默認(rèn)就是外放