前言:AudioService这个系统服务包含或者使用了几乎所有与音频有关的内容,AudioService是音频系统在java层的大本营;

android音频系统,分为两个部分:数据流和策略;

数据流:描述了音频数据从数据源流向目的地的流程,之前我们分析的AudioTrack,AudioFlinger就是数据流;

策略:管理及控制数据流的路径与呈现方式,之前我们分析的AudioPolicyService,以及等会我们要分析的AudioService,它们都是策略得范畴;

AudioService:

①音频系统在java层中基本上不参与数据流的,AudioService这个系统服务包含或者使用了几乎所有与音频有关的内容,所以说AudioService是音频系统在java层的大本营;

②AudioManager拥有AudioService的Bp端,是AudioService在客户端的一个代理,几乎所有客户端对AudioManager进行的请求,最终都会交由AudioService实现;

③AudioService的功能实现依赖于AudioSystem类,AudioSystem无法实例化,它是java层到native层的代理,AudioService通过它与AudioPolicyService以及AudioFlinger进行通信;

今天我们分析的是,AudioService中的音量管理;

调整音量有好几种方式,我们慢慢看常见的两种:

1.音量管理:按下音量键

音量键被按下后,按键事件会一路派发给Acitivity,如果无人拦截并处理,承载当前Activity的显示PhoneWindow类的onKeyDown()以及onKeyUp()函数将会被处理,从而开始通过音量键调整音量的处理流程;

按照输入事件的派发策略,Window对象在事件的派发队列中位于Acitivity的后面,所以应用程序可以重写自己的Activity.onKeyDown()函数以截获音量键的消息,并将其用作其他的功能。比如说,在一个相机应用中,按下音量键所执行的动作是拍照而不是调节音量;

PhoneWindow.java
protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) {final KeyEvent.DispatcherState dispatcher =mDecor != null ? mDecor.getKeyDispatcherState() : null;switch (keyCode) {case KeyEvent.KEYCODE_VOLUME_UP:case KeyEvent.KEYCODE_VOLUME_DOWN:case KeyEvent.KEYCODE_VOLUME_MUTE: {//MediaController与视频播放器有关,使用VideoView+MediaController可实现视频播放器if (mMediaController != null) {int direction = 0;switch (keyCode) {case KeyEvent.KEYCODE_VOLUME_UP:direction = AudioManager.ADJUST_RAISE;break;case KeyEvent.KEYCODE_VOLUME_DOWN:direction = AudioManager.ADJUST_LOWER;break;case KeyEvent.KEYCODE_VOLUME_MUTE:direction = AudioManager.ADJUST_TOGGLE_MUTE;break;}mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);} else {//我们分析的是这句代码,后面有关于mVolumeControlStreamType的解析MediaSessionLegacyHelper.getHelper(getContext()).sendVolumeKeyEvent(event, mVolumeControlStreamType, false);}return true;}
}

点评:将音量键输入按键事件传递给视频播放器或者MediaSessionLegacyHelper,我们这里分析的是MediaSessionLegacyHelper;

继续看代码:

public void sendVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly) {if (keyEvent == null) {return;}mSessionManager.dispatchVolumeKeyEvent(keyEvent, stream, musicOnly);
}

关于这个mSessionManager:mSessionManager = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);

我们来到MediaSessionManager.java,继续看:

MediaSessionManager.java
public void dispatchVolumeKeyEvent(@NonNull KeyEvent keyEvent, int stream, boolean musicOnly) {try {mService.dispatchVolumeKeyEvent(keyEvent, stream, musicOnly);} catch (RemoteException e) {Log.e(TAG, "Failed to send volume key event.", e);}
}

这个mService是啥?

ISessionManager mService;
IBinder b = ServiceManager.getService(Context.MEDIA_SESSION_SERVICE);
mService = ISessionManager.Stub.asInterface(b);

通过分析我们知道mService是MediaSessionService;

继续看看MediaSessionService.dispatchVolumeKeyEvent()方法:

MediaSessionService.java
public void dispatchVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly) {/*这个方法是用来分析并处理音量键的长按和短按操作,如果有音量键的长按监听器,那么在音量键长按后,那么当前操作就不是调整音量,而是将事件派发给音量键的长按监听器*/if (keyEvent == null ||(keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_UP&& keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_DOWN&& keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_MUTE)) {Log.w(TAG, "Attempted to dispatch null or non-volume key event.");return;}final int pid = Binder.getCallingPid();final int uid = Binder.getCallingUid();final long token = Binder.clearCallingIdentity();if (DEBUG_KEY_EVENT) {Log.d(TAG, "dispatchVolumeKeyEvent, pid=" + pid + ", uid=" + uid + ", event="+ keyEvent);}try {synchronized (mLock) {if (isGlobalPriorityActiveLocked()|| mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) {//有活动的全局优先级会话或者没有音量键的长按监听,那就处理音量键的短按事件dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly);} else {// TODO: Consider the case when both volume up and down keys are pressed//       at the same time.if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {//第一次短按,在一段时间的延迟之后如果手指没有抬起,那就触发长按事件if (keyEvent.getRepeatCount() == 0) {//保存传递进来的参数副本mCurrentFullUserRecord.mInitialDownVolumeKeyEvent =KeyEvent.obtain(keyEvent);mCurrentFullUserRecord.mInitialDownVolumeStream = stream;mCurrentFullUserRecord.mInitialDownMusicOnly = musicOnly;//一段延迟后触发长按事件,也就是触发onVolumeKeyLongPress方法,我们这里只分析短按事件,这个就不仔细剖析了mHandler.sendMessageDelayed(mHandler.obtainMessage(MessageHandler.MSG_VOLUME_INITIAL_DOWN,mCurrentFullUserRecord.mFullUserId, 0),mLongPressTimeout);}//多次短按,或者本身就是一个长按事件时,直接触发长按事件if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) {mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null) {dispatchVolumeKeyLongPressLocked(mCurrentFullUserRecord.mInitialDownVolumeKeyEvent);// Mark that the key is already handled.mCurrentFullUserRecord.mInitialDownVolumeKeyEvent = null;}dispatchVolumeKeyLongPressLocked(keyEvent);}} else { // if upmHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null&& mCurrentFullUserRecord.mInitialDownVolumeKeyEvent.getDownTime() == keyEvent.getDownTime()) {// Short-press. Should change volume.dispatchVolumeKeyEventLocked(mCurrentFullUserRecord.mInitialDownVolumeKeyEvent,mCurrentFullUserRecord.mInitialDownVolumeStream,mCurrentFullUserRecord.mInitialDownMusicOnly);dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly);} else {dispatchVolumeKeyLongPressLocked(keyEvent);}}}}} finally {Binder.restoreCallingIdentity(token);}
}

点评:这段代码是用来处理音量键的长按与短按的,我们这里只分析短按事件,继续看:

MediaSessionService.java
dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly);
private void dispatchVolumeKeyEventLocked(KeyEvent keyEvent, int stream, boolean musicOnly) {//注意我们传进来的参数,keyEvent为down事件,stream是音频类型,musicOnly为falseboolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN;boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP;int direction = 0;boolean isMute = false;//音量调整的方向switch (keyEvent.getKeyCode()) {case KeyEvent.KEYCODE_VOLUME_UP:direction = AudioManager.ADJUST_RAISE;break;case KeyEvent.KEYCODE_VOLUME_DOWN://direction为音量调整的方向,1为增大,-1为减小direction = AudioManager.ADJUST_LOWER;break;case KeyEvent.KEYCODE_VOLUME_MUTE:isMute = true;break;}if (down || up) {//设置标签int flags = AudioManager.FLAG_FROM_KEY;if (musicOnly) {// This flag is used when the screen is off to only affect active media.flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY;} else {// These flags are consistent with the home screenif (up) {//按下音量键后手指抬起,PLAY_SOUND,即播放按键音并且震动flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;} else {//手指刚按下音量键,SHOW_UI,即显示UI并且震动,这个UI指的是音量调节框,后面来看到flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;}}if (direction != 0) {//因为down事件已经处理了音量的调整,up事件不需要再做什么,播放个按键音声音就好if (up) {direction = 0;}//要开始调节音量了dispatchAdjustVolumeLocked(stream, direction, flags);} else if (isMute) {//按下了静音键if (down && keyEvent.getRepeatCount() == 0) {dispatchAdjustVolumeLocked(stream, AudioManager.ADJUST_TOGGLE_MUTE, flags);}}}
}

点评:这段代码的主要工作是,确定音量调整的方向以及设置按下音量键时所携带的动作;

重点来了:dispatchAdjustVolumeLocked();

MediaSessionService.java
private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags) {MediaSessionRecord session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession: mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession();boolean preferSuggestedStream = false;if (isValidLocalStreamType(suggestedStream)&& AudioSystem.isStreamActive(suggestedStream, 0)) {preferSuggestedStream = true;}if (DEBUG_KEY_EVENT) {Log.d(TAG, "Adjusting " + session + " by " + direction + ". flags="+ flags + ", suggestedStream=" + suggestedStream+ ", preferSuggestedStream=" + preferSuggestedStream);}if (session == null || preferSuggestedStream) {if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0&& !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) {if (DEBUG) {Log.d(TAG, "No active session to adjust, skipping media only volume event");}return;}// Execute mAudioService.adjustSuggestedStreamVolume() on// handler thread of MediaSessionService.// This will release the MediaSessionService.mLock sooner and avoid// a potential deadlock between MediaSessionService.mLock and// ActivityManagerService lock.mHandler.post(new Runnable() {@Overridepublic void run() {try {String packageName = getContext().getOpPackageName();//重点来了,调用AudioService的adjustSuggestedStreamVolume()方法mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,flags, packageName, TAG);} catch (RemoteException e) {Log.e(TAG, "Error adjusting default volume.", e);} catch (IllegalArgumentException e) {Log.e(TAG, "Cannot adjust volume: direction=" + direction+ ", suggestedStream=" + suggestedStream + ", flags=" + flags,e);}}});} else {session.adjustVolume(direction, flags, getContext().getPackageName(),Process.SYSTEM_UID, true);}
}

点评:上面的代码只有一个核心:

mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,flags, packageName, TAG);

分析下方法中参数的含义:

direction:表示了音量的调整方向,1位增大,-1为减小,0就是保持原音量;

suggestedStream:操作的音频类型,后面会分析到;

flags:音量键动作所携带的消息,比如播放按键音,显示音量调节框等;

packageName:应用包名;

mAudioService就是AudioService,来到AudioService.java,继续往下看:

AudioService.java
private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,String callingPackage, String caller, int uid) {final int streamType;//从这段代码可以看出,在AudioService中还有地方可以强行改变音量键控制的流类型,//后面有关于mUserSelectedVolumeControlStream的解析if (mUserSelectedVolumeControlStream) { // implies mVolumeControlStream != -1streamType = mVolumeControlStream;} else {//通过getActiveStreamType函数获取要控制的流类型,这里根据建议的流类型与AudioService的实际情况,返回一个值final int maybeActiveStreamType = getActiveStreamType(suggestedStreamType);final boolean activeForReal;if (maybeActiveStreamType == AudioSystem.STREAM_MUSIC) {activeForReal = isAfMusicActiveRecently(0);} else {activeForReal = AudioSystem.isStreamActive(maybeActiveStreamType, 0);}if (activeForReal || mVolumeControlStream == -1) {streamType = maybeActiveStreamType;} else {streamType = mVolumeControlStream;}}final boolean isMute = isMuteAdjust(direction);ensureValidStreamType(streamType);//这句代码等会过来分析final int resolvedStream = mStreamVolumeAlias[streamType];//只能在STREAM_RING音频类型下才能播放声音if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 &&resolvedStream != AudioSystem.STREAM_RING) {flags &= ~AudioManager.FLAG_PLAY_SOUND;}// For notifications/ring, show the ui before making any adjustments// Don't suppress mute/unmute requestsif (mVolumeController.suppressAdjustment(resolvedStream, flags, isMute)) {direction = 0;flags &= ~AudioManager.FLAG_PLAY_SOUND;flags &= ~AudioManager.FLAG_VIBRATE;if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment");}//调用adjustStreamVolume()adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid);
}

点评:我们先把流程走完,有些细节后面一起分析;

继续看adjustStreamVolume()方法:

AudioService.java
private void adjustStreamVolume(int streamType, int direction, int flags,String callingPackage, String caller, int uid) {if (mUseFixedVolume) {return;}if (DEBUG_VOL) Log.d(TAG, "adjustStreamVolume() stream=" + streamType + ", dir=" + direction+ ", flags=" + flags + ", caller=" + caller);ensureValidDirection(direction);ensureValidStreamType(streamType);//是否是静音调节boolean isMuteAdjust = isMuteAdjust(direction);if (isMuteAdjust && !isStreamAffectedByMute(streamType)) {return;}// use stream type alias here so that streams with same alias have the same behavior,// including with regard to silent mode control (e.g the use of STREAM_RING below and in// checkForRingerModeChange() in place of STREAM_RING or STREAM_NOTIFICATION)//获取streamType映射到的流类型int streamTypeAlias = mStreamVolumeAlias[streamType];//注意VolumeStreamState类VolumeStreamState streamState = mStreamStates[streamTypeAlias];final int device = getDeviceForStream(streamTypeAlias);//获取当前音量int aliasIndex = streamState.getIndex(device);boolean adjustVolume = true;int step;// skip a2dp absolute volume control request when the device// is not an a2dp deviceif ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) == 0 &&(flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) {return;}// If we are being called by the system (e.g. hardware keys) check for current user// so we handle user restrictions correctly.if (uid == android.os.Process.SYSTEM_UID) {uid = UserHandle.getUid(getCurrentUserId(), UserHandle.getAppId(uid));}if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)!= AppOpsManager.MODE_ALLOWED) {return;}// reset any pending volume commandsynchronized (mSafeMediaVolumeState) {mPendingVolumeCommand = null;}flags &= ~AudioManager.FLAG_FIXED_VOLUME;if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) &&((device & mFixedVolumeDevices) != 0)) {flags |= AudioManager.FLAG_FIXED_VOLUME;// Always toggle between max safe volume and 0 for fixed volume devices where safe// volume is enforced, and max and 0 for the others.// This is simulated by stepping by the full allowed volume rangeif (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE &&(device & mSafeMediaVolumeDevices) != 0) {step = safeMediaVolumeIndex(device);} else {step = streamState.getMaxIndex();}if (aliasIndex != 0) {aliasIndex = step;}} else {// convert one UI step (+/-1) into a number of internal units on the stream alias//由于不同的流类型的音量调节范围不同,rescaleIndex用于将音量值的变化量从源流类型变换到目标流类型下step = rescaleIndex(10, streamType, streamTypeAlias);}// If either the client forces allowing ringer modes for this adjustment,// or the stream type is one that is affected by ringer modes//接下来就要开始有用的操作了if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||(streamTypeAlias == getUiSoundsStreamType())) {int ringerMode = getRingerModeInternal();// do not vibrate if already in vibrate modeif (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {flags &= ~AudioManager.FLAG_VIBRATE;}// Check if the ringer mode handles this adjustment. If it does we don't// need to adjust the volume further.//变更情景模式,final int result = checkForRingerModeChange(aliasIndex, direction, step,streamState.mIsMuted, callingPackage, flags);//adjustVolume表示是否有必要继续设置音量值,因为在某些情况下,音量键是改变情景模式,而不是设置音量的,比如音量为0时,继续减小音量,此时就是改变情景模式为静音adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0;// If suppressing a volume adjustment in silent mode, display the UI hintif ((result & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) {flags |= AudioManager.FLAG_SHOW_SILENT_HINT;}// If suppressing a volume down adjustment in vibrate mode, display the UI hintif ((result & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0) {flags |= AudioManager.FLAG_SHOW_VIBRATE_HINT;}}// If the ringermode is suppressing media, prevent changesif (!volumeAdjustmentAllowedByDnd(streamTypeAlias, flags)) {adjustVolume = false;}//取出调整前的音量值,这个值稍后被用在sendVolumeUpdate()方法中int oldIndex = mStreamStates[streamType].getIndex(device);if (adjustVolume && (direction != AudioManager.ADJUST_SAME)) {mAudioHandler.removeMessages(MSG_UNMUTE_STREAM);// Check if volume update should be send to AVRCPif (streamTypeAlias == AudioSystem.STREAM_MUSIC &&(device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&(flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {synchronized (mA2dpAvrcpLock) {if (mA2dp != null && mAvrcpAbsVolSupported) {mA2dp.adjustAvrcpAbsoluteVolume(direction);}}}if (isMuteAdjust) {//这是静音情况,暂时忽略boolean state;if (direction == AudioManager.ADJUST_TOGGLE_MUTE) {state = !streamState.mIsMuted;} else {state = direction == AudioManager.ADJUST_MUTE;}if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {setSystemAudioMute(state);}for (int stream = 0; stream < mStreamStates.length; stream++) {if (streamTypeAlias == mStreamVolumeAlias[stream]) {if (!(readCameraSoundForced()&& (mStreamStates[stream].getStreamType()== AudioSystem.STREAM_SYSTEM_ENFORCED))) {mStreamStates[stream].mute(state);}}}} else if ((direction == AudioManager.ADJUST_RAISE) &&!checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex);mVolumeController.postDisplaySafeVolumeWarning(flags);} else if (streamState.adjustIndex(direction * step, device, caller)|| streamState.mIsMuted) {//上面的判断中调用了streamState.adjustIndex()方法,表示如果音量值在调整之后没有发生变化,比如说达到了最大值,就不需要继续后面的操作了// Post message to set system volume (it in turn will post a// message to persist).//静音情况暂时忽略if (streamState.mIsMuted) {// Unmute the stream if it was previously mutedif (direction == AudioManager.ADJUST_RAISE) {// unmute immediately for volume upstreamState.mute(false);} else if (direction == AudioManager.ADJUST_LOWER) {if (mIsSingleVolume) {sendMsg(mAudioHandler, MSG_UNMUTE_STREAM, SENDMSG_QUEUE,streamTypeAlias, flags, null, UNMUTE_STREAM_DELAY);}}}//发送信息给mAudioHandler,这个消息将把音量设置到底层去,并将其存储到SettingsProvider中,后面的分析会看到sendMsg(mAudioHandler,MSG_SET_DEVICE_VOLUME,SENDMSG_QUEUE,device,0,streamState,0);}// Check if volume update should be sent to Hdmi system audio.int newIndex = mStreamStates[streamType].getIndex(device);if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {setSystemAudioVolume(oldIndex, newIndex, getStreamMaxVolume(streamType), flags);}if (mHdmiManager != null) {synchronized (mHdmiManager) {// mHdmiCecSink true => mHdmiPlaybackClient != nullif (mHdmiCecSink &&streamTypeAlias == AudioSystem.STREAM_MUSIC &&oldIndex != newIndex) {synchronized (mHdmiPlaybackClient) {int keyCode = (direction == -1) ? KeyEvent.KEYCODE_VOLUME_DOWN :KeyEvent.KEYCODE_VOLUME_UP;final long ident = Binder.clearCallingIdentity();try {mHdmiPlaybackClient.sendKeyEvent(keyCode, true);mHdmiPlaybackClient.sendKeyEvent(keyCode, false);} finally {Binder.restoreCallingIdentity(ident);}}}}}}int index = mStreamStates[streamType].getIndex(device);//最后调用sendVolumeUpdate,通知外界音量值发生了变化sendVolumeUpdate(streamType, oldIndex, index, flags);
}

点评:上述代码的逻辑很多,我们逐一分析:

①在这个函数中,我们首先看到了这两句代码:

int streamTypeAlias = mStreamVolumeAlias[streamType];

VolumeStreamState streamState = mStreamStates[streamTypeAlias];

VolumeStreamState是什么?

我们知道,android的音量是依赖于某种流类型的,如果Android定义了N个流类型,AudioService就需要维护N个音量值与之相对应,所以AudioService提供了VolumeStreamState,为每一种流类型都分配了一个VolumeStreamState对象,VolumeStreamState保存与一个流类型所有音量相关的信息。并且以流类型的的值为索引,将它保存在一个名为mStreamStates的数组中;

②streamState.adjustIndex

streamState也就是VolumeStreamState,VolumeStreamState.adjustIndex()就是改变这个对象存储的音量值,不过它仅仅是改变了它的存储值,并没有把这个变化设置到底层;

我们来看下这个方法:

public boolean adjustIndex(int deltaIndex, int device, String caller) {//将现有的音量值加上变化量,然后调用setIndex进行设置return setIndex(getIndex(device) + deltaIndex, device, caller);
}public boolean setIndex(int index, int device, String caller) {boolean changed = false;int oldIndex;synchronized (VolumeStreamState.class) {oldIndex = getIndex(device);index = getValidIndex(index);synchronized (mCameraSoundForced) {if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) {index = mIndexMax;}}//保存设置的音量值,使用了MapmIndexMap.put(device, index);changed = oldIndex != index;//同时设置所有映射到当前流类型的其他流的音量final boolean currentDevice = (device == getDeviceForStream(mStreamType));final int numStreamTypes = AudioSystem.getNumStreamTypes();for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {final VolumeStreamState aliasStreamState = mStreamStates[streamType];if (streamType != mStreamType &&mStreamVolumeAlias[streamType] == mStreamType &&(changed || !aliasStreamState.hasIndexForDevice(device))) {final int scaledIndex = rescaleIndex(index, mStreamType, streamType);aliasStreamState.setIndex(scaledIndex, device, caller);if (currentDevice) {aliasStreamState.setIndex(scaledIndex,getDeviceForStream(streamType), caller);}}}}if (changed) {//加5是为了四舍五入,除10,因为10的整数容易操作oldIndex = (oldIndex + 5) / 10;index = (index + 5) / 10;//发送广播mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index);mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex);mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,mStreamVolumeAlias[mStreamType]);sendBroadcastToAll(mVolumeChanged);}return changed;
}

点评:这个函数有如下工作:

(1)首先是保存设置的音量值,音量值与设备相关联,对同一种流类型来说,在不同的音频设备下将会有不同的音量值;

(2)再就是对流映射的处理。既然A→B,那么在设置B的音量的同时要改变A的音量;

可以看出,VolumeStreamState.adjustIndex()除了更新自己所保存的音量值外,没有做其他的事情;

再来看sendMsg(),发送MSG_SET_DEVICE_VOLUME消息;

这个消息是发送给mAudioHandler,mAudioHandler是运行在AudioService主线程上的Handler,我们看下mAudioHandler处理这个消息的setDeviceVolume函数:

private void setDeviceVolume(VolumeStreamState streamState, int device) {synchronized (VolumeStreamState.class) {//这个函数就是调用AudioSystem.setStreamVolumeIndex,将音量设置到底层的AudioFlingerstreamState.applyDeviceVolume_syncVSS(device);// Apply change to all streams using this one as alias//处理流映射的情况int numStreamTypes = AudioSystem.getNumStreamTypes();for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {if (streamType != streamState.mStreamType &&mStreamVolumeAlias[streamType] == streamState.mStreamType) {int streamDevice = getDeviceForStream(streamType);if ((device != streamDevice) && mAvrcpAbsVolSupported &&((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) {mStreamStates[streamType].applyDeviceVolume_syncVSS(device);}mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice);}}}/*发送消息给mAudioHandler,其处理函数将会调用persitVolume()函数,这将会把音量的设置信息存储到SettingsProvider中;AudioService在初始化时,将会从SettingsProvider中将音量设置读取出来并进行设置 */sendMsg(mAudioHandler,MSG_PERSIST_VOLUME,SENDMSG_QUEUE,device,0,streamState,PERSIST_DELAY);
}

再看下mAudioHandler处理MSG_PERSIST_VOLUME消息的persistVolume函数:

private void persistVolume(VolumeStreamState streamState, int device) {if (mUseFixedVolume) {return;}if (mIsSingleVolume && (streamState.mStreamType != AudioSystem.STREAM_MUSIC)) {return;}if (streamState.hasValidSettingsName()) {//将音量值写进SettingProviderSystem.putIntForUser(mContentResolver,streamState.getSettingNameForDevice(device),(streamState.getIndex(device) + 5)/ 10,UserHandle.USER_CURRENT);}
}

点评:sendMsg是个异步操作,消息是串行的,由于是先返回值会操作,就意味着执行结果可能会有延迟,即获取的音量值没问题,但是手机发出的声音音量值大小并没有变化;

③sendVolumeUpdate()分析

sendVolumeUpdate()的工作内容,就是通知外界音量值发生了变化,其中有一项就是通知音量调节通知框,因为6.0以后的代码逻辑改变比较大,我们后面再分析这个;

这样,音量调节的流程就结束了,接下来做个总结:

①音量键处理流程的发起者是PhoneWindow;

②MediaSessionService仅仅起到代理的作用;

③AudioService接受MediaSessionService的调用请求,操作VolumeStreamState的实例进行音量的设置;

④VolumeStreamState负责保存音量设置,并且提供了将音量设置到底层的方法;

⑤AudioService负责将设置结果以广播的形式通知外界;

OK!音量调节的流程说完了,接下来要说遗留的细节代码了!

①在PhoneWindow.java的onKeyDown方法中,有这么一句代码:

MediaSessionLegacyHelper.getHelper(getContext()).sendVolumeKeyEvent(event, mVolumeControlStreamType, false);

这里的mVolumeControlStreamType是什么?

mVolumeControlStreamType:它指的是要改变哪一种流类型的音量,adnroid的音量控制与流密不可分,每种流类型都独立的拥有自己的音量设置,如音乐音量,通话音量就是相互独立的。mVolumeControlStreamType它是从哪来的呢?

在Activity中有这么一个函数,通过它可以来指定显示这个Activity时音量键所控制的流类型;

Activity.java

Activity.java
public final void setVolumeControlStream(int streamType) {getWindow().setVolumeControlStream(streamType);
}

getWindow()就是用于显示当前Activity的PhoneWindow,再回到PhoneWindow.setVolumeControlStream()方法:

PhoneWindow.java
public void setVolumeControlStream(int streamType) {mVolumeControlStreamType = streamType;
}

这就是mVolumeControlStreamType的由来;

我们应该能看出,这个设置被绑定到Activity的Window上,在不同Activity之间切换时,接收按键事件的Window也会随之切换,所以应用不需要去考虑在其生命周期中音量键所控制的流类型的切换问题;

②在AudioService.adjustStreamVolume()中有这么两句代码:

int streamTypeAlias = mStreamVolumeAlias[streamType];
VolumeStreamState streamState = mStreamStates[streamTypeAlias];

我们先看mStreamVolumeAlias是什么?

找到mStreamVolumeAlias被赋值的其中一处:

mStreamVolumeAlias = STREAM_VOLUME_ALIAS_DEFAULT;
private final int[] STREAM_VOLUME_ALIAS_DEFAULT = new int[] {AudioSystem.STREAM_VOICE_CALL,      // STREAM_VOICE_CALLAudioSystem.STREAM_RING,            // STREAM_SYSTEMAudioSystem.STREAM_RING,            // STREAM_RINGAudioSystem.STREAM_MUSIC,           // STREAM_MUSICAudioSystem.STREAM_ALARM,           // STREAM_ALARMAudioSystem.STREAM_RING,            // STREAM_NOTIFICATIONAudioSystem.STREAM_BLUETOOTH_SCO,   // STREAM_BLUETOOTH_SCOAudioSystem.STREAM_RING,            // STREAM_SYSTEM_ENFORCEDAudioSystem.STREAM_RING,            // STREAM_DTMFAudioSystem.STREAM_MUSIC,           // STREAM_TTSAudioSystem.STREAM_MUSIC            // STREAM_ACCESSIBILITY
};

看到这里我们知道了,mStreamVolumeAlias是一个数组,数组里面依次保存着音频流类型;所以resolvedStream也只不过是从mStreamVolumeAlias数组中获取到相应的流类型而已;

mStreamStates也是用来保存VolumeStreamState的数组,streamState是从数组中获取到相应的VolumeStreamState;

初看:这种写法不是多次一举吗,直接VolumeStreamState streamState = mStreamStates[streamType]不就可以了吗?

其实不然:

这种设计是为了满足为了所谓的“将铃声音量用作音乐音量”这种需求,如果碰到这个需求,我们就可以这么处理:

mStreamVolumeAlias[AudioSystem.STREAM_MUSIC] = AudioSystem.STREAM_RING;

当我们在操作音乐类型的流时,实际上是在操作铃声类型的流;

这种设计是不是很棒,是不是很nice,其实HaspMap也可以做嘛,键是源流类型,值是目标流类型;

当真的有“将铃声音量用作音乐音量”这种奇葩需求时:

我们需要根据实际情况给mStreamVolumeAlias数组赋值,AudioService也给它提供了几种在不同情形下的不同赋值,它的赋值跟硬件平台有关;

③在AudioService.adjustSuggestedStreamVolume()中有这么一句:

if (mUserSelectedVolumeControlStream) { // implies mVolumeControlStream != -1streamType = mVolumeControlStream;
}

mUserSelectedVolumeControlStream是什么?只要它等于true,就强制调整音量的类型就是它!

看看它是在哪被赋值的?

public void forceVolumeControlStream(int streamType, IBinder cb) {if (DEBUG_VOL) { Log.d(TAG, String.format("forceVolumeControlStream(%d)", streamType)); }synchronized(mForceControlStreamLock) {//mVolumeControlStream != -1,便将mUserSelectedVolumeControlStream设为trueif (mVolumeControlStream != -1 && streamType != -1) {mUserSelectedVolumeControlStream = true;}//给mVolumeControlStream 赋值mVolumeControlStream = streamType;if (mVolumeControlStream == -1) {if (mForceControlStreamClient != null) {mForceControlStreamClient.release();mForceControlStreamClient = null;}mUserSelectedVolumeControlStream = false;} else {if (mForceControlStreamClient != null) {mForceControlStreamClient.release();}mForceControlStreamClient = new ForceControlStreamClient(cb);}}
}

我们可以看到,mUserSelectedVolumeControlStream以及mVolumeControlStream 是在forceVolumeControlStream()方法中被赋值的,而forceVolumeControlStream()方法是由VolumePanel调用的,VolumePanel就是我们按下音量键后的那个音量调节通知框,如果我们在VolumePanel显示时改变的是铃声类型的音量,那么它会调用forceVolumeControlStream强制后续的音量键操作固定为促使它显示的那个流类型,也就是铃声类型,并在它关闭时取消这个强制设置,即设置mVolumeControlStream为-1,这个在后面分析VolumePanel时会看到;

2.音量管理:setStreamVolume()

还有一种直接调整音量的接口:AudioManager.setStreamVolume();

AudioManager.java
public void setStreamVolume(int streamType, int index, int flags) {final IAudioService service = getService();Log.d(TAG, "setStreamVolume: StreamType = " + streamType + ", index = " + index);try {service.setStreamVolume(streamType, index, flags, getContext().getOpPackageName());} catch (RemoteException e) {throw e.rethrowFromSystemServer();}
}

我们知道,service就是AudioService,继续:;

AudioService.java
public void setStreamVolume(int streamType, int index, int flags, String callingPackage) {if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) {Log.w(TAG, "Trying to call setStreamVolume() for a11y without"+ " CHANGE_ACCESSIBILITY_VOLUME  callingPackage=" + callingPackage);return;}mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,index/*val1*/, flags/*val2*/, callingPackage));setStreamVolume(streamType, index, flags, callingPackage, callingPackage,Binder.getCallingUid());
}private void setStreamVolume(int streamType, int index, int flags, String callingPackage,String caller, int uid) {if (DEBUG_VOL) {Log.d(TAG, "setStreamVolume(stream=" + streamType+", index=" + index+ ", calling=" + callingPackage + ")");}if (mUseFixedVolume) {return;}//判读流类型的有效性ensureValidStreamType(streamType);int streamTypeAlias = mStreamVolumeAlias[streamType];//获取相应的VolumeStreamStateVolumeStreamState streamState = mStreamStates[streamTypeAlias];//获取当前流将使用哪一个音频设备进行播放final int device = getDeviceForStream(streamType);int oldIndex;// skip a2dp absolute volume control request when the device// is not an a2dp deviceif ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) == 0 &&(flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) {return;}// If we are being called by the system (e.g. hardware keys) check for current user// so we handle user restrictions correctly.if (uid == android.os.Process.SYSTEM_UID) {uid = UserHandle.getUid(getCurrentUserId(), UserHandle.getAppId(uid));}if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)!= AppOpsManager.MODE_ALLOWED) {return;}if (isAndroidNPlus(callingPackage)&& wouldToggleZenMode(getNewRingerMode(streamTypeAlias, index, flags))&& !mNm.isNotificationPolicyAccessGrantedForPackage(callingPackage)) {throw new SecurityException("Not allowed to change Do Not Disturb state");}if (!volumeAdjustmentAllowedByDnd(streamTypeAlias, flags)) {return;}synchronized (mSafeMediaVolumeState) {// reset any pending volume commandmPendingVolumeCommand = null;//获取流当前的音量oldIndex = streamState.getIndex(device);//将原流类型下的音量值映射到目标流类型下的音量值;//因为不同流类型的音量值刻度不一样,所以需要进行转换index = rescaleIndex(index * 10, streamType, streamTypeAlias);if (streamTypeAlias == AudioSystem.STREAM_MUSIC &&(device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&(flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {synchronized (mA2dpAvrcpLock) {if (mA2dp != null && mAvrcpAbsVolSupported) {mA2dp.setAvrcpAbsoluteVolume(index / 10);}}}if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {setSystemAudioVolume(oldIndex, index, getStreamMaxVolume(streamType), flags);}flags &= ~AudioManager.FLAG_FIXED_VOLUME;if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) &&((device & mFixedVolumeDevices) != 0)) {flags |= AudioManager.FLAG_FIXED_VOLUME;// volume is either 0 or max allowed for fixed volume devicesif (index != 0) {if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE &&(device & mSafeMediaVolumeDevices) != 0) {index = safeMediaVolumeIndex(device);} else {index = streamState.getMaxIndex();}}}if (!checkSafeMediaVolume(streamTypeAlias, index, device)) {mVolumeController.postDisplaySafeVolumeWarning(flags);mPendingVolumeCommand = new StreamVolumeCommand(streamType, index, flags, device);} else {//调用onSetStreamVolumeonSetStreamVolume(streamType, index, flags, device, caller);//获取设置的结果index = mStreamStates[streamType].getIndex(device);}}//广播通知sendVolumeUpdate(streamType, oldIndex, index, flags);
}

我们来看一下:onSetStreamVolume()

private void onSetStreamVolume(int streamType, int index, int flags, int device,String caller) {final int stream = mStreamVolumeAlias[streamType];//调用setStreamVolumeIntsetStreamVolumeInt(stream, index, device, false, caller);// setting volume on ui sounds stream type also controls silent modeif (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||(stream == getUiSoundsStreamType())) {setRingerMode(getNewRingerMode(stream, index, flags),TAG + ".onSetStreamVolume", false /*external*/);}//如果调节后的音量为0了,那就让某种流的音量静音mStreamStates[stream].mute(index == 0);
}private void setStreamVolumeInt(int streamType,int index,int device,boolean force,String caller) {VolumeStreamState streamState = mStreamStates[streamType];//调用streamState.setIndexif (streamState.setIndex(index, device, caller) || force) {//如果setIndex返回true,或者force为true,就在这里向mAudioHandler发送通知sendMsg(mAudioHandler,MSG_SET_DEVICE_VOLUME,SENDMSG_QUEUE,device,0,streamState,0);}
}

使用setStreamVolume()调整音量,跟点击音量键控制音质,其实也是差不多的,就不再过多的叙述了;

3.音量管理的总结

①AudioService音量管理的核心是VolumeStreamState,它保存了一个流类型所有的音量信息;

②VolumeStreamState保存了运行时的音量信息,而音量的生效则是在底层AudioFlinger完成的;

所以进行音量设置需要做两件事情:更新VolumeStreamState存储的音量值,设置音量到Audio底层系统;

android音频系统(4):AudioService之音量管理相关推荐

  1. Android音频系统之四AudioPolicy

    4.1 AudioPolicy的诞生 AudioPolicyService是Android音频系统的两大服务之一,另一个服务是AudioFlinger,这两大服务都在系统启动时有MediaSever加 ...

  2. android音频系统之AudioTrack的使用

    今天,简单讲讲  AudioTrack的使用. 1.Android AudioTrack简介 在android中播放声音可以用MediaPlayer和AudioTrack两种方案的,但是两种方案是 ...

  3. Android 音频系统:从 AudioTrack 到 AudioFlinger(全)

    Android 音频框架概述 Audio 是整个 Android 平台非常重要的一个组成部分,负责音频数据的采集和输出.音频流的控制.音频设备的管理.音量调节等,主要包括如下部分: Audio App ...

  4. Android音频系统的改进设想和展望 PulseAudio介绍

    http://www.soomal.com/doc/10100002871.htm 在这里先说明,本人并没有仔细地去看Android和PulseAudio的音频具体源代码和实现,欢迎指正. 从硬件用料 ...

  5. 深度剖析 Android音频系统解析与改进

    导读:Android是用了一个Google自己开发的中间层API来让APP和声音驱动(ALSA或者HAL封闭驱动)通信的.在早期,它是个ALSA的插件:现在则命名为AudioFlinger.但是安卓音 ...

  6. android音频系统(5):AudioService之音频焦点

    前言:上一节我们分析了AudioService对音量的管理,这一节来看下AudioService对音频焦点的处理,也就是音频系统中的AudioFocus机制,它用来处理多个音频不合理的同时播放的糟糕后 ...

  7. android 4.0 电话录音,ANDROID音频系统散记之四:4.0音频系统HAL初探

    昨天(2011-11-15)发布了Android4.0的源码,今天download下来,开始挺进4.0时代.简单看了一下,发现音频系统方面与2.3的有较多地方不同,下面逐一描述. 一.代码模块位置 1 ...

  8. Android音频系统之AudioPolicyService

    地址:http://blog.csdn.net/edmond999/article/details/18599327 1.1 AudioPolicy Service 在AudioFlinger小节,我 ...

  9. Android音频系统扫盲

    原址 0. 专用术语 1. 物理结构 2. 系统架构 本文基于Freescale IMX平台Codec ALC5625为例. 0. 专用术语 ASLA - Advanced Sound Linux A ...

最新文章

  1. mysql distinct多个字段_深入浅出Mysql索引的那些事儿
  2. Foundation HTML5 Canvas中的2处错误
  3. 回首2020,我们砥砺前行
  4. 二进制 |_元二进制搜索| 单边二元搜索
  5. Ansible-playbook 学习
  6. 面试14种神回复,HR妹子可能会爱上你!
  7. 随机函数_巧用随机函数,生成各种姓名组合。
  8. 【Unity3D插件】MiniMap插件分享《小地图插件》
  9. matlab 线性回归 参数显著性,matlab做多元线性回归后回归系数的显著性检验
  10. FreeBSD——艺术、科学、哲学概论
  11. Java和Android笔试题
  12. 【BZOJ4198】【NOI2015】荷马史诗(贪心,Huffman树)
  13. 很傻很天真的问题: 什么是语法糖!
  14. 自定义水晶报表的外观
  15. 100年量子计算风云史,“量子比特”何时统治世界?| 技术特稿
  16. 服务器原装的系统怎么格式化,怎么将云服务器系统格式化
  17. 蓝牙 bluetooth-之一
  18. SQL语句模糊查询 JavaWeb 项目 dao层 【常用来做搜索框】
  19. c++实现推箱子游戏(带链表)
  20. qt 之usb(hid)与单片机通信

热门文章

  1. 国务院发展研究中心发布《中国云计算产业发展与应用白皮书》| 附下载
  2. 程序员的真实工资是多少?
  3. 只可顺守不可逆取书法_关于如何练字,分享给想练好书法的人
  4. Java SE 第三讲(原生数据类型使用陷阱 Pitfall of Primitive Data Type)
  5. 【ORACLE】RAC 磁盘超时,导致数据库重启 WARNING: Waited 15 secs for write IO to PST disk 0 in group 1.
  6. Web开发过程流程图
  7. 如何知道qq号手机号后三位_知道位
  8. 太阳能光伏发电和路灯应用系统的详细计算
  9. 网站统计中访客标识码有什么作用
  10. 网页版怎么连接tcp服务器,请教怎么做一个tcp客户端访问网页