深圳网站建设V芯ee8888e/简述如何优化网站的方法
核心 Android 系统提供的调节音量的方法
核心 Android 系统提供了多种调节音量的方法,这些方法主要包括如下这些。
- 如在 Android Automotive 调节音量的过程 中我们看到的,
CarAudioService
最终在CarAudioDeviceInfo
中 (packages/services/Car/service/src/com/android/car/audio/CarAudioDeviceInfo.java
) 通过AudioManager
直接为设备设置音量:
// Input is in millibelsvoid setCurrentGain(int gainInMillibels) {// Clamp the incoming value to our valid range. Out of range values ARE legal inputif (gainInMillibels < mMinGain) {gainInMillibels = mMinGain;} else if (gainInMillibels > mMaxGain) {gainInMillibels = mMaxGain;}// Push the new gain value down to our underlying port which will cause it to show up// at the HAL.AudioGain audioGain = getAudioGain();if (audioGain == null) {Slog.e(CarLog.TAG_AUDIO, "getAudioGain() returned null.");return;}// size of gain values is 1 in MODE_JOINTAudioGainConfig audioGainConfig = audioGain.buildConfig(AudioGain.MODE_JOINT,audioGain.channelMask(),new int[] { gainInMillibels },0);if (audioGainConfig == null) {Slog.e(CarLog.TAG_AUDIO, "Failed to construct AudioGainConfig");return;}int r = AudioManager.setAudioPortGain(getAudioDevicePort(), audioGainConfig);if (r == AudioManager.SUCCESS) {// Since we can't query for the gain on a device port later,// we have to remember what we asked formCurrentGain = gainInMillibels;} else {Slog.e(CarLog.TAG_AUDIO, "Failed to setAudioPortGain: " + r);}}
这里看到的 AudioManager
的 setAudioPortGain(getAudioDevicePort(), audioGainConfig)
方法可以用于调节特定设备的音量,在 AudioManager
中,这个方法的实现 (frameworks/base/media/java/android/media/AudioManager.java) 为:
/*** Set the gain on the specified AudioPort. The AudioGainConfig config is build by* AudioGain.buildConfig()* @hide*/public static int setAudioPortGain(AudioPort port, AudioGainConfig gain) {if (port == null || gain == null) {return ERROR_BAD_VALUE;}AudioPortConfig activeConfig = port.activeConfig();AudioPortConfig config = new AudioPortConfig(port, activeConfig.samplingRate(),activeConfig.channelMask(), activeConfig.format(), gain);config.mConfigMask = AudioPortConfig.GAIN;return AudioSystem.setAudioPortConfig(config);}
AudioManager
的 setAudioPortGain(getAudioDevicePort(), audioGainConfig)
方法通过 AudioSystem
的静态方法 setAudioPortConfig(config)
来给设备设置音量,AudioSystem.setAudioPortConfig(config)
是一个在 frameworks/base/media/java/android/media/AudioSystem.java 中声明,并最终在 frameworks/base/core/jni/android_media_AudioSystem.cpp 中定义的 JNI 本地层方法:
static jint
android_media_AudioSystem_setAudioPortConfig(JNIEnv *env, jobject clazz,jobject jAudioPortConfig)
{ALOGV("setAudioPortConfig");if (jAudioPortConfig == NULL) {return AUDIO_JAVA_BAD_VALUE;}if (!env->IsInstanceOf(jAudioPortConfig, gAudioPortConfigClass)) {return AUDIO_JAVA_BAD_VALUE;}struct audio_port_config nAudioPortConfig = {};jint jStatus = convertAudioPortConfigToNative(env, &nAudioPortConfig, jAudioPortConfig, true);if (jStatus != AUDIO_JAVA_SUCCESS) {return jStatus;}status_t status = AudioSystem::setAudioPortConfig(&nAudioPortConfig);ALOGV("AudioSystem::setAudioPortConfig() returned %d", status);jStatus = nativeToJavaStatus(status);return jStatus;
}
- App 可以通过
AudioManager
调节某个STREAM_TYPE
的音量,如:
private boolean setStreamVolume(int volume) {Logging.d(TAG, "setStreamVolume(" + volume + ")");assertTrue(audioManager != null);if (isVolumeFixed()) {Logging.e(TAG, "The device implements a fixed volume policy.");return false;}audioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL, volume, 0);return true;}
这里看到的 setStreamVolume()
方法在 AudioManager
中的实现如下:
/*** Sets the volume index for a particular stream.* <p>This method has no effect if the device implements a fixed volume policy* as indicated by {@link #isVolumeFixed()}.* <p>From N onward, volume adjustments that would toggle Do Not Disturb are not allowed unless* the app has been granted Do Not Disturb Access.* See {@link NotificationManager#isNotificationPolicyAccessGranted()}.* @param streamType The stream whose volume index should be set.* @param index The volume index to set. See* {@link #getStreamMaxVolume(int)} for the largest valid value.* @param flags One or more flags.* @see #getStreamMaxVolume(int)* @see #getStreamVolume(int)* @see #isVolumeFixed()* @throws SecurityException if the volume change triggers a Do Not Disturb change* and the caller is not granted notification policy access.*/public void setStreamVolume(int streamType, int index, int flags) {final IAudioService service = getService();try {service.setStreamVolume(streamType, index, flags, getContext().getOpPackageName());} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}
AudioManager
中单步地调大或者调小特定流类型的adjustStreamVolume()
:
/*** Adjusts the volume of a particular stream by one step in a direction.* <p>* This method should only be used by applications that replace the platform-wide* management of audio settings or the main telephony application.* <p>This method has no effect if the device implements a fixed volume policy* as indicated by {@link #isVolumeFixed()}.* <p>From N onward, ringer mode adjustments that would toggle Do Not Disturb are not allowed* unless the app has been granted Do Not Disturb Access.* See {@link NotificationManager#isNotificationPolicyAccessGranted()}.** @param streamType The stream type to adjust. One of {@link #STREAM_VOICE_CALL},* {@link #STREAM_SYSTEM}, {@link #STREAM_RING}, {@link #STREAM_MUSIC},* {@link #STREAM_ALARM} or {@link #STREAM_ACCESSIBILITY}.* @param direction The direction to adjust the volume. One of* {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or* {@link #ADJUST_SAME}.* @param flags One or more flags.* @see #adjustVolume(int, int)* @see #setStreamVolume(int, int, int)* @throws SecurityException if the adjustment triggers a Do Not Disturb change* and the caller is not granted notification policy access.*/public void adjustStreamVolume(int streamType, int direction, int flags) {final IAudioService service = getService();try {service.adjustStreamVolume(streamType, direction, flags,getContext().getOpPackageName());} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}
AudioManager
中调节最相关的流的音量的adjustVolume()
和adjustSuggestedStreamVolume()
:
/*** Adjusts the volume of the most relevant stream. For example, if a call is* active, it will have the highest priority regardless of if the in-call* screen is showing. Another example, if music is playing in the background* and a call is not active, the music stream will be adjusted.* <p>* This method should only be used by applications that replace the* platform-wide management of audio settings or the main telephony* application.* <p>* This method has no effect if the device implements a fixed volume policy* as indicated by {@link #isVolumeFixed()}.** @param direction The direction to adjust the volume. One of* {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE},* {@link #ADJUST_SAME}, {@link #ADJUST_MUTE},* {@link #ADJUST_UNMUTE}, or {@link #ADJUST_TOGGLE_MUTE}.* @param flags One or more flags.* @see #adjustSuggestedStreamVolume(int, int, int)* @see #adjustStreamVolume(int, int, int)* @see #setStreamVolume(int, int, int)* @see #isVolumeFixed()*/public void adjustVolume(int direction, int flags) {MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags);}/*** Adjusts the volume of the most relevant stream, or the given fallback* stream.* <p>* This method should only be used by applications that replace the* platform-wide management of audio settings or the main telephony* application.* <p>* This method has no effect if the device implements a fixed volume policy* as indicated by {@link #isVolumeFixed()}.** @param direction The direction to adjust the volume. One of* {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE},* {@link #ADJUST_SAME}, {@link #ADJUST_MUTE},* {@link #ADJUST_UNMUTE}, or {@link #ADJUST_TOGGLE_MUTE}.* @param suggestedStreamType The stream type that will be used if there* isn't a relevant stream. {@link #USE_DEFAULT_STREAM_TYPE} is* valid here.* @param flags One or more flags.* @see #adjustVolume(int, int)* @see #adjustStreamVolume(int, int, int)* @see #setStreamVolume(int, int, int)* @see #isVolumeFixed()*/public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) {MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags);}
这两个方法最终通过 media session 服务调节音量 (frameworks/base/media/java/android/media/session/MediaSessionLegacyHelper.java) :
private MediaSessionLegacyHelper(Context context) {mContext = context;mSessionManager = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);}public void sendAdjustVolumeBy(int suggestedStream, int delta, int flags) {mSessionManager.dispatchAdjustVolume(suggestedStream, delta, flags);if (DEBUG) {Log.d(TAG, "dispatched volume adjustment");}}
AudioManager
中为特定AudioAttributes
设置音量的setVolumeIndexForAttributes()
。
/*** Sets the volume index for a particular {@link AudioAttributes}.* @param attr The {@link AudioAttributes} whose volume index should be set.* @param index The volume index to set. See* {@link #getMaxVolumeIndexForAttributes(AudioAttributes)} for the largest valid value* {@link #getMinVolumeIndexForAttributes(AudioAttributes)} for the lowest valid value.* @param flags One or more flags.* @see #getMaxVolumeIndexForAttributes(AudioAttributes)* @see #getMinVolumeIndexForAttributes(AudioAttributes)* @see #isVolumeFixed()* @hide*/@SystemApi@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index, int flags) {Preconditions.checkNotNull(attr, "attr must not be null");final IAudioService service = getService();try {service.setVolumeIndexForAttributes(attr, index, flags,getContext().getOpPackageName());} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}
AudioManager
中调节最相关流的音量,且会基于所提供的组件名、uid 和 pid 值检查调用者是否有必要权限的adjustSuggestedStreamVolumeForUid()
。
/*** Adjusts the volume of the most relevant stream, or the given fallback* stream.* <p>* This method should only be used by applications that replace the* platform-wide management of audio settings or the main telephony* application.* <p>* This method has no effect if the device implements a fixed volume policy* as indicated by {@link #isVolumeFixed()}.* <p>This API checks if the caller has the necessary permissions based on the provided* component name, uid, and pid values.* See {@link #adjustSuggestedStreamVolume(int, int, int)}.** @param suggestedStreamType The stream type that will be used if there* isn't a relevant stream. {@link #USE_DEFAULT_STREAM_TYPE} is* valid here.* @param direction The direction to adjust the volume. One of* {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE},* {@link #ADJUST_SAME}, {@link #ADJUST_MUTE},* {@link #ADJUST_UNMUTE}, or {@link #ADJUST_TOGGLE_MUTE}.* @param flags One or more flags.* @param packageName the package name of client application* @param uid the uid of client application* @param pid the pid of client application* @param targetSdkVersion the target sdk version of client application* @see #adjustVolume(int, int)* @see #adjustStreamVolume(int, int, int)* @see #setStreamVolume(int, int, int)* @see #isVolumeFixed()** @hide*/@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)public void adjustSuggestedStreamVolumeForUid(int suggestedStreamType, int direction, int flags,@NonNull String packageName, int uid, int pid, int targetSdkVersion) {try {getService().adjustSuggestedStreamVolumeForUid(suggestedStreamType, direction, flags,packageName, uid, pid, UserHandle.getUserHandleForUid(uid), targetSdkVersion);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}
AudioManager
中单步地调大或者调小特定流类型的音量,且会基于所提供的组件名、uid 和 pid 值检查调用者是否有必要权限的adjustStreamVolumeForUid()
:
/*** Adjusts the volume of a particular stream by one step in a direction.* <p>* This method should only be used by applications that replace the platform-wide* management of audio settings or the main telephony application.* <p>This method has no effect if the device implements a fixed volume policy* as indicated by {@link #isVolumeFixed()}.* <p>From N onward, ringer mode adjustments that would toggle Do Not Disturb are not allowed* unless the app has been granted Do Not Disturb Access.* See {@link NotificationManager#isNotificationPolicyAccessGranted()}.* <p>This API checks if the caller has the necessary permissions based on the provided* component name, uid, and pid values.* See {@link #adjustStreamVolume(int, int, int)}.** @param streamType The stream type to adjust. One of {@link #STREAM_VOICE_CALL},* {@link #STREAM_SYSTEM}, {@link #STREAM_RING}, {@link #STREAM_MUSIC},* {@link #STREAM_ALARM} or {@link #STREAM_ACCESSIBILITY}.* @param direction The direction to adjust the volume. One of* {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or* {@link #ADJUST_SAME}.* @param flags One or more flags.* @param packageName the package name of client application* @param uid the uid of client application* @param pid the pid of client application* @param targetSdkVersion the target sdk version of client application* @see #adjustVolume(int, int)* @see #setStreamVolume(int, int, int)* @throws SecurityException if the adjustment triggers a Do Not Disturb change* and the caller is not granted notification policy access.** @hide*/@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)public void adjustStreamVolumeForUid(int streamType, int direction, int flags,@NonNull String packageName, int uid, int pid, int targetSdkVersion) {try {getService().adjustStreamVolumeForUid(streamType, direction, flags, packageName, uid,pid, UserHandle.getUserHandleForUid(uid), targetSdkVersion);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}
AudioManager
中调节某个STREAM_TYPE
的音量,且会基于所提供的组件名、uid 和 pid 值检查调用者是否有必要权限的setStreamVolumeForUid()
:
/*** Sets the volume index for a particular stream.* <p>This method has no effect if the device implements a fixed volume policy* as indicated by {@link #isVolumeFixed()}.* <p>From N onward, volume adjustments that would toggle Do Not Disturb are not allowed unless* the app has been granted Do Not Disturb Access.* See {@link NotificationManager#isNotificationPolicyAccessGranted()}.* <p>This API checks if the caller has the necessary permissions based on the provided* component name, uid, and pid values.* See {@link #setStreamVolume(int, int, int)}.** @param streamType The stream whose volume index should be set.* @param index The volume index to set. See* {@link #getStreamMaxVolume(int)} for the largest valid value.* @param flags One or more flags.* @param packageName the package name of client application* @param uid the uid of client application* @param pid the pid of client application* @param targetSdkVersion the target sdk version of client application* @see #getStreamMaxVolume(int)* @see #getStreamVolume(int)* @see #isVolumeFixed()* @throws SecurityException if the volume change triggers a Do Not Disturb change* and the caller is not granted notification policy access.** @hide*/@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)public void setStreamVolumeForUid(int streamType, int index, int flags,@NonNull String packageName, int uid, int pid, int targetSdkVersion) {try {getService().setStreamVolumeForUid(streamType, index, flags, packageName, uid, pid,UserHandle.getUserHandleForUid(uid), targetSdkVersion);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}
总结一下,上面这些方法,从功能上来看主要包括这样一些:
- 直接给设备设置音量;
- 调节某个
STREAM_TYPE
的音量; - 单步地调大或者调小特定流类型的音量;
- 调节最相关的流的音量;
- 为特定
AudioAttributes
设置音量; - 调节最相关的流的音量,且会基于所提供的组件名、uid 和 pid 值检查调用者是否有必要权限;
- 单步地调大或者调小特定流类型的音量,且会基于所提供的组件名、uid 和 pid 值检查调用者是否有必要权限;
- 调节某个
STREAM_TYPE
的音量,且会基于所提供的组件名、uid 和 pid 值检查调用者是否有必要权限。
AudioManager
主要是运行于 system_server 中的 audio 服务的客户端代理,但其中封装的一些操作也会直接通过 media session 服务或者 audio policy 服务等完成。上面除了第 1 和 第 4 项功能外,其它功能基本上都通过调用 audio 服务的同名方法完成。
接下来,逐个看下上面调用的 AudioService
的方法的实现。
- 在
AudioService
中 (frameworks/base/services/core/java/com/android/server/audio/AudioService.java
),与AudioManager
中的方法对应的setStreamVolume()
方法实现如下:
/** @see AudioManager#setStreamVolume(int, int, int)* Part of service interface, check permissions here */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;}if ((streamType == AudioManager.STREAM_VOICE_CALL) && (index == 0)&& (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)!= PackageManager.PERMISSION_GRANTED)) {Log.w(TAG, "Trying to call setStreamVolume() for STREAM_VOICE_CALL and index 0 without"+ " MODIFY_PHONE_STATE callingPackage=" + callingPackage);return;}if ((streamType == AudioManager.STREAM_ASSISTANT)&& (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)!= PackageManager.PERMISSION_GRANTED)) {Log.w(TAG, "Trying to call setStreamVolume() for STREAM_ASSISTANT without"+ " MODIFY_AUDIO_ROUTING callingPackage=" + callingPackage);return;}sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,index/*val1*/, flags/*val2*/, callingPackage));setStreamVolume(streamType, index, flags, callingPackage, callingPackage,Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission());}
这个操作执行的过程大体为:
(1). 确保调用方具有足够的修改 stream type 的音量的权限;
(2). 为 stream type 设置音量。
这个操作最终回到了为 stream type 设置音量。
- 单步地调大或者调小特定流类型的音量
/** @see AudioManager#adjustStreamVolume(int, int, int)* Part of service interface, check permissions here */public void adjustStreamVolume(int streamType, int direction, int flags,String callingPackage) {if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) {Log.w(TAG, "Trying to call adjustStreamVolume() for a11y without"+ "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage);return;}sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,direction/*val1*/, flags/*val2*/, callingPackage));adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,Binder.getCallingUid(), Binder.getCallingPid(),callingHasAudioSettingsPermission(), VOL_ADJUST_NORMAL);}
这个操作执行的过程大体为:
(1). 确保调用方具有足够的修改 stream type 的音量的权限;
(2). 调整 stream type 的音量。
这个操作最终回到了调整 stream type 的音量。
-
调节最相关的流的音量的
adjustVolume()
和adjustSuggestedStreamVolume()
将经 media session 服务绕一道,最终回到调整 stream type 的音量或设置 stream type 的音量。 -
为特定
AudioAttributes
设置音量
/** @see AudioManager#setVolumeIndexForAttributes(attr, int, int) */public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index, int flags,String callingPackage) {enforceModifyAudioRoutingPermission();Objects.requireNonNull(attr, "attr must not be null");final int volumeGroup = getVolumeGroupIdForAttributes(attr);if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {Log.e(TAG, ": no volume group found for attributes " + attr.toString());return;}final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(),index/*val1*/, flags/*val2*/, callingPackage));vgs.setVolumeIndex(index, flags);// For legacy reason, propagate to all streams associated to this volume groupfor (final int groupedStream : vgs.getLegacyStreamTypes()) {try {ensureValidStreamType(groupedStream);} catch (IllegalArgumentException e) {Log.d(TAG, "volume group " + volumeGroup + " has internal streams (" + groupedStream+ "), do not change associated stream volume");continue;}setStreamVolume(groupedStream, index, flags, callingPackage, callingPackage,Binder.getCallingUid(), true /*hasModifyAudioSettings*/);}}
这个过程大体为:
(1). 确保调用方具有足够的修改 audio routing 的权限;
(2). 根据传入的 AudioAttributes 获得音量组;
(3). 为音量组本身设置音量;
(4). 为音量组中的每个 stream type 设置音量。
这个操作最终回到了为 stream type 设置音量。
- 调节最相关的流的音量,且会基于所提供的组件名、uid 和 pid 值检查调用者是否有必要权限
private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,String callingPackage, String caller, int uid, int pid, boolean hasModifyAudioSettings,int keyEventMode) {if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream=" + suggestedStreamType+ ", flags=" + flags + ", caller=" + caller+ ", volControlStream=" + mVolumeControlStream+ ", userSelect=" + mUserSelectedVolumeControlStream);if (direction != AudioManager.ADJUST_SAME) {sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType,direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage).append("/").append(caller).append(" uid:").append(uid).toString()));}boolean hasExternalVolumeController = notifyExternalVolumeController(direction);new MediaMetrics.Item(mMetricsId + "adjustSuggestedStreamVolume").setUid(Binder.getCallingUid()).set(MediaMetrics.Property.CALLING_PACKAGE, callingPackage).set(MediaMetrics.Property.CLIENT_NAME, caller).set(MediaMetrics.Property.DIRECTION, direction > 0? MediaMetrics.Value.UP : MediaMetrics.Value.DOWN).set(MediaMetrics.Property.EXTERNAL, hasExternalVolumeController? MediaMetrics.Value.YES : MediaMetrics.Value.NO).set(MediaMetrics.Property.FLAGS, flags).record();if (hasExternalVolumeController) {return;}final int streamType;synchronized (mForceControlStreamLock) {// Request lock in case mVolumeControlStream is changed by other thread.if (mUserSelectedVolumeControlStream) { // implies mVolumeControlStream != -1streamType = mVolumeControlStream;} else {final int maybeActiveStreamType = getActiveStreamType(suggestedStreamType);final boolean activeForReal;if (maybeActiveStreamType == AudioSystem.STREAM_RING|| maybeActiveStreamType == AudioSystem.STREAM_NOTIFICATION) {activeForReal = wasStreamActiveRecently(maybeActiveStreamType, 0);} else {activeForReal = mAudioSystem.isStreamActive(maybeActiveStreamType, 0);}if (activeForReal || mVolumeControlStream == -1) {streamType = maybeActiveStreamType;} else {streamType = mVolumeControlStream;}}}final boolean isMute = isMuteAdjust(direction);ensureValidStreamType(streamType);final int resolvedStream = mStreamVolumeAlias[streamType];// Play sounds on STREAM_RING only.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 requests// Don't suppress adjustments for single volume deviceif (mVolumeController.suppressAdjustment(resolvedStream, flags, isMute)&& !mIsSingleVolume) {direction = 0;flags &= ~AudioManager.FLAG_PLAY_SOUND;flags &= ~AudioManager.FLAG_VIBRATE;if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment");}adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid, pid,hasModifyAudioSettings, keyEventMode);}. . . . . ./** @see AudioManager#adjustSuggestedStreamVolumeForUid(int, int, int, String, int, int, int) */@Overridepublic void adjustSuggestedStreamVolumeForUid(int streamType, int direction, int flags,@NonNull String packageName, int uid, int pid, UserHandle userHandle,int targetSdkVersion) {if (Binder.getCallingUid() != Process.SYSTEM_UID) {throw new SecurityException("Should only be called from system process");}// direction and stream type swap here because the public// adjustSuggested has a different order than the other methods.adjustSuggestedStreamVolume(direction, streamType, flags, packageName, packageName,uid, pid, hasAudioSettingsPermission(uid, pid), VOL_ADJUST_NORMAL);}
这个操作执行的过程大体为:
(1). 确保调用方的 UID 为 Process.SYSTEM_UID;
(2). 找到合适的 stream type;
(3). 调整 stream type 的音量。
这个操作最终回到了调整 stream type 的音量。
- 单步地调大或者调小特定流类型的音量,且会基于所提供的组件名、uid 和 pid 值检查调用者是否有必要权限;
/** @see AudioManager#adjustStreamVolumeForUid(int, int, int, String, int, int, int) */@Overridepublic void adjustStreamVolumeForUid(int streamType, int direction, int flags,@NonNull String packageName, int uid, int pid, UserHandle userHandle,int targetSdkVersion) {if (Binder.getCallingUid() != Process.SYSTEM_UID) {throw new SecurityException("Should only be called from system process");}if (direction != AudioManager.ADJUST_SAME) {sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_VOL_UID, streamType,direction/*val1*/, flags/*val2*/,new StringBuilder(packageName).append(" uid:").append(uid).toString()));}adjustStreamVolume(streamType, direction, flags, packageName, packageName, uid, pid,hasAudioSettingsPermission(uid, pid), VOL_ADJUST_NORMAL);}
这个操作执行的过程大体为:
(1). 确保调用方的 UID 为 Process.SYSTEM_UID;
(2). 调整 stream type 的音量。
这个操作最终回到了调整 stream type 的音量。
- 调节某个
STREAM_TYPE
的音量,且会基于所提供的组件名、uid 和 pid 值检查调用者是否有必要权限
/** @see AudioManager#setStreamVolumeForUid(int, int, int, String, int, int, int) */@Overridepublic void setStreamVolumeForUid(int streamType, int index, int flags,@NonNull String packageName, int uid, int pid, UserHandle userHandle,int targetSdkVersion) {if (Binder.getCallingUid() != Process.SYSTEM_UID) {throw new SecurityException("Should only be called from system process");}setStreamVolume(streamType, index, flags, packageName, packageName, uid,hasAudioSettingsPermission(uid, pid));}
这个操作执行的过程大体为:
(1). 确保调用方的 UID 为 Process.SYSTEM_UID;
(2). 为 stream type 设置音量,传入的 uid 和 pid 用于检查权限。
这个操作最终回到了为 stream type 设置音量。
总体来看,AudioManager
除了直接调节设备音量的接口之外的其它接口,最终都会回到为 stream type 设置音量或调整 stream type 的音量。这里看一下为 stream type 设置音量的过程。
由上面这些调节音量的方法可以看到,Android Automotive 调节音量的动作有意在 Car 服务中直接对设备调节了音量,而没有继续经过核心 Android 系统的 audio 服务。
这里不再继续深入跟踪调整 stream type 的音量的过程,但可以看一下为 stream type 设置音量的过程。
为 stream type 设置音量
为 stream type 设置音量的 setStreamVolume()
方法定义如下:
private void setStreamVolume(int streamType, int index, int flags, String callingPackage,String caller, int uid, boolean hasModifyAudioSettings) {if (DEBUG_VOL) {Log.d(TAG, "setStreamVolume(stream=" + streamType+", index=" + index+ ", calling=" + callingPackage + ")");}if (mUseFixedVolume) {return;}ensureValidStreamType(streamType);int streamTypeAlias = mStreamVolumeAlias[streamType];VolumeStreamState streamState = mStreamStates[streamTypeAlias];final int device = getDeviceForStream(streamType);int oldIndex;// skip a2dp absolute volume control request when the device// is not an a2dp deviceif (!AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)&& (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 (!checkNoteAppOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)) {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 (mSafeMediaVolumeStateLock) {// reset any pending volume commandmPendingVolumeCommand = null;oldIndex = streamState.getIndex(device);index = rescaleIndex(index * 10, streamType, streamTypeAlias);if (streamTypeAlias == AudioSystem.STREAM_MUSIC&& AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)&& (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {if (DEBUG_VOL) {Log.d(TAG, "setStreamVolume postSetAvrcpAbsoluteVolumeIndex index=" + index+ "stream=" + streamType);}mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index / 10);}if (device == AudioSystem.DEVICE_OUT_HEARING_AID&& streamType == getHearingAidStreamType()) {Log.i(TAG, "setStreamVolume postSetHearingAidVolumeIndex index=" + index+ " stream=" + streamType);mDeviceBroker.postSetHearingAidVolumeIndex(index, streamType);}if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {setSystemAudioVolume(oldIndex, index, getStreamMaxVolume(streamType), flags);}flags &= ~AudioManager.FLAG_FIXED_VOLUME;if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) {flags |= AudioManager.FLAG_FIXED_VOLUME;// volume is either 0 or max allowed for fixed volume devicesif (index != 0) {if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE &&mSafeMediaVolumeDevices.contains(device)) {index = safeMediaVolumeIndex(device);} else {index = streamState.getMaxIndex();}}}if (!checkSafeMediaVolume(streamTypeAlias, index, device)) {mVolumeController.postDisplaySafeVolumeWarning(flags);mPendingVolumeCommand = new StreamVolumeCommand(streamType, index, flags, device);} else {onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings);index = mStreamStates[streamType].getIndex(device);}}synchronized (mHdmiClientLock) {if (streamTypeAlias == AudioSystem.STREAM_MUSIC&& (oldIndex != index)) {maybeSendSystemAudioStatusCommand(false);}}sendVolumeUpdate(streamType, oldIndex, index, flags, device);}
这个方法首先检查了是否配置使用固定音频策略 mUseFixedVolume
,这个值来自于配置文件:frameworks/base/core/res/res/values/config.xml:
mUseFixedVolume = mContext.getResources().getBoolean(com.android.internal.R.bool.config_useFixedVolume);
这个值可以被专门为设备定义的值覆盖掉。如对于车载版的模拟器,device/generic/car/emulator/audio/overlay/frameworks/base/core/res/res/values/config.xml 文件中定义的值覆盖了 framework 中定义的值。
这个是音频策略执行的主要的地方。
之后根据 stream type 通过 AudioSystem
获得 device 号 (frameworks/base/services/core/java/com/android/server/audio/AudioSystemAdapter.java) :
public int getDevicesForStream(int stream) {if (!ENABLE_GETDEVICES_STATS) {return getDevicesForStreamImpl(stream);}mMethodCallCounter[METHOD_GETDEVICESFORSTREAM]++;final long startTime = SystemClock.uptimeNanos();final int res = getDevicesForStreamImpl(stream);mMethodTimeNs[METHOD_GETDEVICESFORSTREAM] += SystemClock.uptimeNanos() - startTime;return res;}private int getDevicesForStreamImpl(int stream) {if (USE_CACHE_FOR_GETDEVICES) {Integer res;synchronized (mDevicesForStreamCache) {res = mDevicesForStreamCache.get(stream);if (res == null) {res = AudioSystem.getDevicesForStream(stream);mDevicesForStreamCache.put(stream, res);if (DEBUG_CACHE) {Log.d(TAG, mMethodNames[METHOD_GETDEVICESFORSTREAM]+ streamDeviceToDebugString(stream, res));}return res;}// cache hitmMethodCacheHit[METHOD_GETDEVICESFORSTREAM]++;if (DEBUG_CACHE) {final int real = AudioSystem.getDevicesForStream(stream);if (res == real) {Log.d(TAG, mMethodNames[METHOD_GETDEVICESFORSTREAM]+ streamDeviceToDebugString(stream, res) + " CACHE");} else {Log.e(TAG, mMethodNames[METHOD_GETDEVICESFORSTREAM]+ streamDeviceToDebugString(stream, res)+ " CACHE ERROR real dev=0x" + Integer.toHexString(real));}}}return res;}// not using cachereturn AudioSystem.getDevicesForStream(stream);}
这个调用堆栈如下:
getDevicesForStreamImpl:164, AudioSystemAdapter (com.android.server.audio)
getDevicesForStream:151, AudioSystemAdapter (com.android.server.audio)
observeDevicesForStream_syncVSS:6838, AudioService$VolumeStreamState (com.android.server.audio)
getDevicesForStreamInt:6111, AudioService (com.android.server.audio)
getDeviceForStream:6063, AudioService (com.android.server.audio)
setStreamVolume:3576, AudioService (com.android.server.audio)
setStreamVolume:3327, AudioService (com.android.server.audio)
onTransact:1529, IAudioService$Stub (android.media)
execTransactInternal:1179, Binder (android.os)
execTransact:1143, Binder (android.os)
通过 AudioSystem
获得的 device 号可能包含多个设备,每个具体的设备在这里的设备号中用一个二进制位表示,此时需要从这些设备中按优先级对具体的设备进行选择,这个过程 (frameworks/base/services/core/java/com/android/server/audio/AudioService.java) 如下:
/** only public for mocking/spying, do not call outside of AudioService */@VisibleForTestingpublic int getDeviceForStream(int stream) {int device = getDevicesForStreamInt(stream);if ((device & (device - 1)) != 0) {// Multiple device selection is either:// - speaker + one other device: give priority to speaker in this case.// - one A2DP device + another device: happens with duplicated output. In this case// retain the device on the A2DP output as the other must not correspond to an active// selection if not the speaker.// - HDMI-CEC system audio mode only output: give priority to available item in order.// FIXME: Haven't applied audio device type refactor to this API// as it is going to be deprecated.if ((device & AudioSystem.DEVICE_OUT_SPEAKER) != 0) {device = AudioSystem.DEVICE_OUT_SPEAKER;} else if ((device & AudioSystem.DEVICE_OUT_HDMI_ARC) != 0) {// FIXME(b/184944421): DEVICE_OUT_HDMI_EARC has two bits set,// so it must be handled correctly as it aliases// with DEVICE_OUT_HDMI_ARC | DEVICE_OUT_EARPIECE.device = AudioSystem.DEVICE_OUT_HDMI_ARC;} else if ((device & AudioSystem.DEVICE_OUT_SPDIF) != 0) {device = AudioSystem.DEVICE_OUT_SPDIF;} else if ((device & AudioSystem.DEVICE_OUT_AUX_LINE) != 0) {device = AudioSystem.DEVICE_OUT_AUX_LINE;} else {for (int deviceType : AudioSystem.DEVICE_OUT_ALL_A2DP_SET) {if ((deviceType & device) == deviceType) {return deviceType;}}}}return device;}
之后对传入的音量值进行归一化 (frameworks/base/services/core/java/com/android/server/audio/AudioService.java):
private int rescaleIndex(int index, int srcStream, int dstStream) {int srcRange = getIndexRange(srcStream);int dstRange = getIndexRange(dstStream);if (srcRange == 0) {Log.e(TAG, "rescaleIndex : index range should not be zero");return mStreamStates[dstStream].getMinIndex();}return mStreamStates[dstStream].getMinIndex()+ ((index - mStreamStates[srcStream].getMinIndex()) * dstRange + srcRange / 2)/ srcRange;}
之后处理 stream type 为 AudioSystem.STREAM_MUSIC
,使用了蓝牙,且设置绝对音量时的情况,即发送消息来设置音量值。
之后处理设备为 AudioSystem.DEVICE_OUT_HEARING_AID
,且 stream type 为辅助听力时的情况,即发送消息给蓝牙设置音量。
之后,当 stream type 为 AudioSystem.STREAM_MUSIC
,还会设置系统音频音量,主要是根据需要设置 HDMI 音量。
之后,如果 stream type 为 AudioSystem.STREAM_MUSIC
,且设备为固定音量设备,则修正传入的音量值。
之后,检查修正后的音量值是否为安全音量,如果不是,则发送 stream 音量命令;如果是,则通过 onSetStreamVolume()
设置流音量。
onSetStreamVolume()
的实现如下:
private void onSetStreamVolume(int streamType, int index, int flags, int device,String caller, boolean hasModifyAudioSettings) {final int stream = mStreamVolumeAlias[streamType];setStreamVolumeInt(stream, index, device, false, caller, hasModifyAudioSettings);// 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*/);}// setting non-zero volume for a muted stream unmutes the stream and vice versa,// except for BT SCO stream where only explicit mute is allowed to comply to BT requirementsif (streamType != AudioSystem.STREAM_BLUETOOTH_SCO) {mStreamStates[stream].mute(index == 0);}}. . . . . ./*** Sets the stream state's index, and posts a message to set system volume.* This will not call out to the UI. Assumes a valid stream type.** @param streamType Type of the stream* @param index Desired volume index of the stream* @param device the device whose volume must be changed* @param force If true, set the volume even if the desired volume is same* @param caller* @param hasModifyAudioSettings true if the caller is granted MODIFY_AUDIO_SETTINGS or* MODIFY_AUDIO_ROUTING permission* as the current volume.*/private void setStreamVolumeInt(int streamType,int index,int device,boolean force,String caller, boolean hasModifyAudioSettings) {if (isFullVolumeDevice(device)) {return;}VolumeStreamState streamState = mStreamStates[streamType];if (streamState.setIndex(index, device, caller, hasModifyAudioSettings) || force) {// Post message to set system volume (it in turn will post a message// to persist).sendMsg(mAudioHandler,MSG_SET_DEVICE_VOLUME,SENDMSG_QUEUE,device,0,streamState,0);}}
这里首先会更新维护的关于 stream 的音量的信息,当音量真的发生改变时,还会发出 broadcast;然后发送消息去设置设备的音量。设置设备音量的方法如下:
/*package*/ void setDeviceVolume(VolumeStreamState streamState, int device) {synchronized (VolumeStreamState.class) {// Apply volumestreamState.applyDeviceVolume_syncVSS(device);// Apply change to all streams using this one as aliasint numStreamTypes = AudioSystem.getNumStreamTypes();for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {if (streamType != streamState.mStreamType &&mStreamVolumeAlias[streamType] == streamState.mStreamType) {// Make sure volume is also maxed out on A2DP device for aliased stream// that may have a different device selectedint streamDevice = getDeviceForStream(streamType);if ((device != streamDevice) && mAvrcpAbsVolSupported&& AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)) {mStreamStates[streamType].applyDeviceVolume_syncVSS(device);}mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice);}}}// Post a persist volume msgsendMsg(mAudioHandler,MSG_PERSIST_VOLUME,SENDMSG_QUEUE,device,0,streamState,PERSIST_DELAY);}
设置设备的音量时,先设置传入的设备的音量,然后设置别名设备的音量,之后发送消息将音量设置持久化。设置设备音量,最终还是要通过 AudioSystem
执行:
private void setStreamVolumeIndex(int index, int device) {// Only set audio policy BT SCO stream volume to 0 when the stream is actually muted.// This allows RX path muting by the audio HAL only when explicitly muted but not when// index is just set to 0 to repect BT requirementsif (mStreamType == AudioSystem.STREAM_BLUETOOTH_SCO && index == 0&& !isFullyMuted()) {index = 1;}AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device);}// must be called while synchronized VolumeStreamState.class/*package*/ void applyDeviceVolume_syncVSS(int device) {int index;if (isFullyMuted()) {index = 0;} else if (AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)&& mAvrcpAbsVolSupported) {index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);} else if (isFullVolumeDevice(device)) {index = (mIndexMax + 5)/10;} else if (device == AudioSystem.DEVICE_OUT_HEARING_AID) {index = (mIndexMax + 5)/10;} else {index = (getIndex(device) + 5)/10;}setStreamVolumeIndex(index, device);}
AudioSystem
的 AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device)
方法定义如下:
/** @hide Wrapper for native methods called from AudioService */public static int setStreamVolumeIndexAS(int stream, int index, int device) {if (DEBUG_VOLUME) {Log.i(TAG, "setStreamVolumeIndex: " + STREAM_NAMES[stream]+ " dev=" + Integer.toHexString(device) + " idx=" + index);}return setStreamVolumeIndex(stream, index, device);}. . . . . .@UnsupportedAppUsageprivate static native int setStreamVolumeIndex(int stream, int index, int device);
最终,通过在 frameworks/base/core/jni/android_media_AudioSystem.cpp 中定义的静态 JNI 本地层方法来完成:
static jint
android_media_AudioSystem_setStreamVolumeIndex(JNIEnv *env,jobject thiz,jint stream,jint index,jint device)
{return (jint) check_AudioSystem_Command(AudioSystem::setStreamVolumeIndex(static_cast <audio_stream_type_t>(stream),index,(audio_devices_t)device));
}
再回到 setStreamVolume()
,之后,如果 stream type 为 AudioSystem.STREAM_MUSIC
,且音量发生了改变,则向 HDMI service 发送报告。
最后,向 volume controller 发送音量更新报告。
我们看到了 AudioSystem
的 setAudioPortConfig(config)
和 AudioSystem.setStreamVolumeIndex(stream, index, device)
这两种给设备设置音量的方法,在这两个方法中,它们都通过 frameworks/av/media/libaudioclient/AudioSystem.cpp
定义的函数完成操作。在这个过程中,也可以看到 stream type 是本地层维护的概念。
Audio policy service 中的音量设置
接着上面的过程,继续来看 AudioSystem::setStreamVolumeIndex()
的实现。本地层的 AudioSystem
在 libaudioclient
中实现。AudioSystem::setStreamVolumeIndex()
函数的定义 (frameworks/av/media/libaudioclient/AudioSystem.cpp) 如下:
// establish binder interface to AudioPolicy service
const sp<IAudioPolicyService> AudioSystem::get_audio_policy_service() {sp<IAudioPolicyService> ap;sp<AudioPolicyServiceClient> apc;{Mutex::Autolock _l(gLockAPS);if (gAudioPolicyService == 0) {sp<IServiceManager> sm = defaultServiceManager();sp<IBinder> binder;do {binder = sm->getService(String16("media.audio_policy"));if (binder != 0)break;ALOGW("AudioPolicyService not published, waiting...");usleep(500000); // 0.5 s} while (true);if (gAudioPolicyServiceClient == NULL) {gAudioPolicyServiceClient = new AudioPolicyServiceClient();}binder->linkToDeath(gAudioPolicyServiceClient);gAudioPolicyService = interface_cast<IAudioPolicyService>(binder);LOG_ALWAYS_FATAL_IF(gAudioPolicyService == 0);apc = gAudioPolicyServiceClient;// Make sure callbacks can be received by gAudioPolicyServiceClientProcessState::self()->startThreadPool();}ap = gAudioPolicyService;}if (apc != 0) {int64_t token = IPCThreadState::self()->clearCallingIdentity();ap->registerClient(apc);ap->setAudioPortCallbacksEnabled(apc->isAudioPortCbEnabled());ap->setAudioVolumeGroupCallbacksEnabled(apc->isAudioVolumeGroupCbEnabled());IPCThreadState::self()->restoreCallingIdentity(token);}return ap;
}. . . . . .
status_t AudioSystem::setStreamVolumeIndex(audio_stream_type_t stream,int index,audio_devices_t device) {const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();if (aps == 0) return PERMISSION_DENIED;media::AudioStreamType streamAidl = VALUE_OR_RETURN_STATUS(legacy2aidl_audio_stream_type_t_AudioStreamType(stream));int32_t indexAidl = VALUE_OR_RETURN_STATUS(convertIntegral<int32_t>(index));int32_t deviceAidl = VALUE_OR_RETURN_STATUS(legacy2aidl_audio_devices_t_int32_t(device));return statusTFromBinderStatus(aps->setStreamVolumeIndex(streamAidl, deviceAidl, indexAidl));
}
AudioSystem
通过 binder 机制,访问 audio policy service 来设置 stream 的音量。
Audio policy service 是一个运行于 audioserver 中 (frameworks/av/media/audioserver) 的系统服务,其实现由多个组件及动态链接库组成,整体的结构如下:
AudioPolicyService
类是 audio policy service 的 IPC 接口层,是服务端的代理,它直接接收客户端发过来的请求,同时也封装其它一些组件,协助完成一些具体操作。AudioPolicyService
类成员函数的具体实现主要分布在两个文件中,一是 frameworks/av/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp,如其文件名所指示的那样,主要是接口函数的实现;二是 frameworks/av/services/audiopolicy/service/AudioPolicyService.cpp,包含其它成员函数的实现。
AudioPolicyService
类封装的 AudioPolicyInterface
,也就是 AudioPolicyManager
协助其完成主要的音频策略逻辑。AudioPolicyManager
的具体定制版实现可以被动态加载加载起来,具体定制版实现的动态链接库文件名需要是 libaudiopolicymanagercustom.so,android 系统提供了默认的实现,在定制版实现不存在,会采用这个默认实现。相关逻辑实现如下:
static AudioPolicyInterface* createAudioPolicyManager(AudioPolicyClientInterface *clientInterface)
{AudioPolicyManager *apm = new AudioPolicyManager(clientInterface);status_t status = apm->initialize();if (status != NO_ERROR) {delete apm;apm = nullptr;}return apm;
}static void destroyAudioPolicyManager(AudioPolicyInterface *interface)
{delete interface;
}
// ----------------------------------------------------------------------------AudioPolicyService::AudioPolicyService(): BnAudioPolicyService(),mAudioPolicyManager(NULL),mAudioPolicyClient(NULL),mPhoneState(AUDIO_MODE_INVALID),mCaptureStateNotifier(false),mCreateAudioPolicyManager(createAudioPolicyManager),mDestroyAudioPolicyManager(destroyAudioPolicyManager) {
}void AudioPolicyService::loadAudioPolicyManager()
{mLibraryHandle = dlopen(kAudioPolicyManagerCustomPath, RTLD_NOW);if (mLibraryHandle != nullptr) {ALOGI("%s loading %s", __func__, kAudioPolicyManagerCustomPath);mCreateAudioPolicyManager = reinterpret_cast<CreateAudioPolicyManagerInstance>(dlsym(mLibraryHandle, "createAudioPolicyManager"));const char *lastError = dlerror();ALOGW_IF(mCreateAudioPolicyManager == nullptr, "%s createAudioPolicyManager is null %s",__func__, lastError != nullptr ? lastError : "no error");mDestroyAudioPolicyManager = reinterpret_cast<DestroyAudioPolicyManagerInstance>(dlsym(mLibraryHandle, "destroyAudioPolicyManager"));lastError = dlerror();ALOGW_IF(mDestroyAudioPolicyManager == nullptr, "%s destroyAudioPolicyManager is null %s",__func__, lastError != nullptr ? lastError : "no error");if (mCreateAudioPolicyManager == nullptr || mDestroyAudioPolicyManager == nullptr){unloadAudioPolicyManager();LOG_ALWAYS_FATAL("could not find audiopolicymanager interface methods");}}
}void AudioPolicyService::onFirstRef()
{{Mutex::Autolock _l(mLock);// start audio commands threadmAudioCommandThread = new AudioCommandThread(String8("ApmAudio"), this);// start output activity command threadmOutputCommandThread = new AudioCommandThread(String8("ApmOutput"), this);mAudioPolicyClient = new AudioPolicyClient(this);loadAudioPolicyManager();mAudioPolicyManager = mCreateAudioPolicyManager(mAudioPolicyClient);}// load audio processing modulessp<AudioPolicyEffects> audioPolicyEffects = new AudioPolicyEffects();sp<UidPolicy> uidPolicy = new UidPolicy(this);sp<SensorPrivacyPolicy> sensorPrivacyPolicy = new SensorPrivacyPolicy(this);{Mutex::Autolock _l(mLock);mAudioPolicyEffects = audioPolicyEffects;mUidPolicy = uidPolicy;mSensorPrivacyPolicy = sensorPrivacyPolicy;}uidPolicy->registerSelf();sensorPrivacyPolicy->registerSelf();// Create spatializer if supportedif (mAudioPolicyManager != nullptr) {Mutex::Autolock _l(mLock);const audio_attributes_t attr = attributes_initializer(AUDIO_USAGE_MEDIA);AudioDeviceTypeAddrVector devices;bool hasSpatializer = mAudioPolicyManager->canBeSpatialized(&attr, nullptr, devices);if (hasSpatializer) {mSpatializer = Spatializer::create(this);}}AudioSystem::audioPolicyReady();
}void AudioPolicyService::unloadAudioPolicyManager()
{ALOGV("%s ", __func__);if (mLibraryHandle != nullptr) {dlclose(mLibraryHandle);}mLibraryHandle = nullptr;mCreateAudioPolicyManager = nullptr;mDestroyAudioPolicyManager = nullptr;
}
AudioPolicyManager
会从 audio_policy_configuration.xml 等文件中加载配置信息,它借助于 EngineInterface
维护 stream type 和设备之间的关系之类的信息,同样 EngineInterface
也是动态加载获得的:
status_t AudioPolicyManager::initialize() {{std::string path = "libaudiopolicyengine" + getConfig().getEngineLibraryNameSuffix() + ".so";ALOGD("%s: libaudiopolicyengine file path %s", __FUNCTION__, path.c_str());auto engLib = EngineLibrary::load(path);if (!engLib) {ALOGE("%s: Failed to load the engine library", __FUNCTION__);return NO_INIT;}mEngine = engLib->createEngine();if (mEngine == nullptr) {ALOGE("%s: Failed to instantiate the APM engine", __FUNCTION__);return NO_INIT;}}mEngine->setObserver(this);status_t status = mEngine->initCheck();if (status != NO_ERROR) {LOG_FATAL("Policy engine not initialized(err=%d)", status);return status;}mCommunnicationStrategy = mEngine->getProductStrategyForAttributes(mEngine->getAttributesForStreamType(AUDIO_STREAM_VOICE_CALL));// after parsing the config, mOutputDevicesAll and mInputDevicesAll contain all known devices;// open all output streams needed to access attached devicesonNewAudioModulesAvailableInt(nullptr /*newDevices*/);// make sure default device is reachableif (mDefaultOutputDevice == 0 || !mAvailableOutputDevices.contains(mDefaultOutputDevice)) {ALOGE_IF(mDefaultOutputDevice != 0, "Default device %s is unreachable",mDefaultOutputDevice->toString().c_str());status = NO_INIT;}// If microphones address is empty, set it according to device typefor (size_t i = 0; i < mAvailableInputDevices.size(); i++) {if (mAvailableInputDevices[i]->address().empty()) {if (mAvailableInputDevices[i]->type() == AUDIO_DEVICE_IN_BUILTIN_MIC) {mAvailableInputDevices[i]->setAddress(AUDIO_BOTTOM_MICROPHONE_ADDRESS);} else if (mAvailableInputDevices[i]->type() == AUDIO_DEVICE_IN_BACK_MIC) {mAvailableInputDevices[i]->setAddress(AUDIO_BACK_MICROPHONE_ADDRESS);}}}ALOGW_IF(mPrimaryOutput == nullptr, "The policy configuration does not declare a primary output");// Silence ALOGV statementsproperty_set("log.tag." LOG_TAG, "D");updateDevicesAndOutputs();return status;
}
android 提供了两个 EngineInterface
的实现,分别是位于 frameworks/av/services/audiopolicy/enginedefault 的 libaudiopolicyenginedefault.so 和位于 frameworks/av/services/audiopolicy/engineconfigurable 的 libaudiopolicyengineconfigurable.so,具体使用哪个由 audio_policy_configuration.xml 等配置文件中的 engine_library
配置项决定,默认使用 libaudiopolicyenginedefault.so。
libaudiopolicyenginedefault.so 等从配置文件 /vendor/etc/audio_policy_engine_configuration.xml 加载 audio policy engine 配置信息,其中主要包括产品策略,音量组等信息及其内容类型、usage 等信息。EngineInterface
的具体实现所需的设备信息从 AudioPolicyManager
获取。
我们回到设置音量的操作上来。在 audio policy service 中,这个操作请求首先来到 AudioPolicyService::setStreamVolumeIndex()
:
Status AudioPolicyService::setStreamVolumeIndex(media::AudioStreamType streamAidl,int32_t deviceAidl, int32_t indexAidl) {audio_stream_type_t stream = VALUE_OR_RETURN_BINDER_STATUS(aidl2legacy_AudioStreamType_audio_stream_type_t(streamAidl));int index = VALUE_OR_RETURN_BINDER_STATUS(convertIntegral<int>(indexAidl));audio_devices_t device = VALUE_OR_RETURN_BINDER_STATUS(aidl2legacy_int32_t_audio_devices_t(deviceAidl));if (mAudioPolicyManager == NULL) {return binderStatusFromStatusT(NO_INIT);}if (!settingsAllowed()) {return binderStatusFromStatusT(PERMISSION_DENIED);}if (uint32_t(stream) >= AUDIO_STREAM_PUBLIC_CNT) {return binderStatusFromStatusT(BAD_VALUE);}Mutex::Autolock _l(mLock);AutoCallerClear acc;return binderStatusFromStatusT(mAudioPolicyManager->setStreamVolumeIndex(stream,index,device));
}
在这里,首先转换传入的参数,随后操作被传给 AudioPolicyManager::setStreamVolumeIndex()
:
status_t AudioPolicyManager::setStreamVolumeIndex(audio_stream_type_t stream,int index,audio_devices_t device)
{auto attributes = mEngine->getAttributesForStreamType(stream);if (attributes == AUDIO_ATTRIBUTES_INITIALIZER) {ALOGW("%s: no group for stream %s, bailing out", __func__, toString(stream).c_str());return NO_ERROR;}ALOGD("%s: stream %s attributes=%s", __func__,toString(stream).c_str(), toString(attributes).c_str());return setVolumeIndexForAttributes(attributes, index, device);
}status_t AudioPolicyManager::getStreamVolumeIndex(audio_stream_type_t stream,int *index,audio_devices_t device)
{// if device is AUDIO_DEVICE_OUT_DEFAULT_FOR_VOLUME, return volume for device selected for this// stream by the engine.DeviceTypeSet deviceTypes = {device};if (device == AUDIO_DEVICE_OUT_DEFAULT_FOR_VOLUME) {deviceTypes = mEngine->getOutputDevicesForStream(stream, true /*fromCache*/).types();}return getVolumeIndex(getVolumeCurves(stream), *index, deviceTypes);
}status_t AudioPolicyManager::setVolumeIndexForAttributes(const audio_attributes_t &attributes,int index,audio_devices_t device)
{// Get Volume group matching the Audio Attributesauto group = mEngine->getVolumeGroupForAttributes(attributes);if (group == VOLUME_GROUP_NONE) {ALOGD("%s: no group matching with %s", __FUNCTION__, toString(attributes).c_str());return BAD_VALUE;}ALOGD("%s: group %d matching with %s", __FUNCTION__, group, toString(attributes).c_str());status_t status = NO_ERROR;IVolumeCurves &curves = getVolumeCurves(attributes);VolumeSource vs = toVolumeSource(group);product_strategy_t strategy = mEngine->getProductStrategyForAttributes(attributes);status = setVolumeCurveIndex(index, device, curves);if (status != NO_ERROR) {ALOGE("%s failed to set curve index for group %d device 0x%X", __func__, group, device);return status;}DeviceTypeSet curSrcDevices;auto curCurvAttrs = curves.getAttributes();if (!curCurvAttrs.empty() && curCurvAttrs.front() != defaultAttr) {auto attr = curCurvAttrs.front();curSrcDevices = mEngine->getOutputDevicesForAttributes(attr, nullptr, false).types();} else if (!curves.getStreamTypes().empty()) {auto stream = curves.getStreamTypes().front();curSrcDevices = mEngine->getOutputDevicesForStream(stream, false).types();} else {ALOGE("%s: Invalid src %d: no valid attributes nor stream",__func__, vs);return BAD_VALUE;}audio_devices_t curSrcDevice = Volume::getDeviceForVolume(curSrcDevices);resetDeviceTypes(curSrcDevices, curSrcDevice);// update volume on all outputs and streams matching the following:// - The requested stream (or a stream matching for volume control) is active on the output// - The device (or devices) selected by the engine for this stream includes// the requested device// - For non default requested device, currently selected device on the output is either the// requested device or one of the devices selected by the engine for this stream// - For default requested device (AUDIO_DEVICE_OUT_DEFAULT_FOR_VOLUME), apply volume only if// no specific device volume value exists for currently selected device.for (size_t i = 0; i < mOutputs.size(); i++) {sp<SwAudioOutputDescriptor> desc = mOutputs.valueAt(i);DeviceTypeSet curDevices = desc->devices().types();if (curDevices.erase(AUDIO_DEVICE_OUT_SPEAKER_SAFE)) {curDevices.insert(AUDIO_DEVICE_OUT_SPEAKER);}if (!(desc->isActive(vs) || isInCall())) {continue;}if (device != AUDIO_DEVICE_OUT_DEFAULT_FOR_VOLUME &&curDevices.find(device) == curDevices.end()) {continue;}bool applyVolume = false;if (device != AUDIO_DEVICE_OUT_DEFAULT_FOR_VOLUME) {curSrcDevices.insert(device);applyVolume = (curSrcDevices.find(Volume::getDeviceForVolume(curDevices)) != curSrcDevices.end());} else {applyVolume = !curves.hasVolumeIndexForDevice(curSrcDevice);}if (!applyVolume) {continue; // next output}// Inter / intra volume group priority management: Loop on strategies arranged by priority// If a higher priority strategy is active, and the output is routed to a device with a// HW Gain management, do not change the volumeif (desc->useHwGain()) {applyVolume = false;for (const auto &productStrategy : mEngine->getOrderedProductStrategies()) {auto activeClients = desc->clientsList(true /*activeOnly*/, productStrategy,false /*preferredDevice*/);if (activeClients.empty()) {continue;}bool isPreempted = false;bool isHigherPriority = productStrategy < strategy;for (const auto &client : activeClients) {if (isHigherPriority && (client->volumeSource() != vs)) {ALOGV("%s: Strategy=%d (\nrequester:\n"" group %d, volumeGroup=%d attributes=%s)\n"" higher priority source active:\n"" volumeGroup=%d attributes=%s) \n"" on output %zu, bailing out", __func__, productStrategy,group, group, toString(attributes).c_str(),client->volumeSource(), toString(client->attributes()).c_str(), i);applyVolume = false;isPreempted = true;break;}// However, continue for loop to ensure no higher prio clients running on outputif (client->volumeSource() == vs) {applyVolume = true;}}if (isPreempted || applyVolume) {break;}}if (!applyVolume) {continue; // next output}}//FIXME: workaround for truncated touch sounds// delayed volume change for system stream to be removed when the problem is// handled by system UIstatus_t volStatus = checkAndSetVolume(curves, vs, index, desc, curDevices,((vs == toVolumeSource(AUDIO_STREAM_SYSTEM))?TOUCH_SOUND_FIXED_DELAY_MS : 0));if (volStatus != NO_ERROR) {status = volStatus;}}mpClientInterface->onAudioVolumeGroupChanged(group, 0 /*flags*/);return status;
}status_t AudioPolicyManager::setVolumeCurveIndex(int index,audio_devices_t device,IVolumeCurves &volumeCurves)
{// VOICE_CALL stream has minVolumeIndex > 0 but can be muted directly by an// app that has MODIFY_PHONE_STATE permission.bool hasVoice = hasVoiceStream(volumeCurves.getStreamTypes());if (((index < volumeCurves.getVolumeIndexMin()) && !(hasVoice && index == 0)) ||(index > volumeCurves.getVolumeIndexMax())) {ALOGD("%s: wrong index %d min=%d max=%d", __FUNCTION__, index,volumeCurves.getVolumeIndexMin(), volumeCurves.getVolumeIndexMax());return BAD_VALUE;}if (!audio_is_output_device(device)) {return BAD_VALUE;}// Force max volume if stream cannot be mutedif (!volumeCurves.canBeMuted()) index = volumeCurves.getVolumeIndexMax();ALOGD("%s device %08x, index %d", __FUNCTION__ , device, index);volumeCurves.addCurrentVolumeIndex(device, index);return NO_ERROR;
}. . . . . .
status_t AudioPolicyManager::checkAndSetVolume(IVolumeCurves &curves,VolumeSource volumeSource,int index,const sp<AudioOutputDescriptor>& outputDesc,DeviceTypeSet deviceTypes,int delayMs,bool force)
{// do not change actual attributes volume if the attributes is mutedif (outputDesc->isMuted(volumeSource)) {ALOGVV("%s: volume source %d muted count %d active=%d", __func__, volumeSource,outputDesc->getMuteCount(volumeSource), outputDesc->isActive(volumeSource));return NO_ERROR;}VolumeSource callVolSrc = toVolumeSource(AUDIO_STREAM_VOICE_CALL);VolumeSource btScoVolSrc = toVolumeSource(AUDIO_STREAM_BLUETOOTH_SCO);bool isVoiceVolSrc = callVolSrc == volumeSource;bool isBtScoVolSrc = btScoVolSrc == volumeSource;bool isScoRequested = isScoRequestedForComm();// do not change in call volume if bluetooth is connected and vice versa// if sco and call follow same curves, bypass forceUseForCommif ((callVolSrc != btScoVolSrc) &&((isVoiceVolSrc && isScoRequested) ||(isBtScoVolSrc && !isScoRequested))) {ALOGV("%s cannot set volume group %d volume when is%srequested for comm", __func__,volumeSource, isScoRequested ? " " : "n ot ");// Do not return an error here as AudioService will always set both voice call// and bluetooth SCO volumes due to stream aliasing.return NO_ERROR;}if (deviceTypes.empty()) {deviceTypes = outputDesc->devices().types();}float volumeDb = computeVolume(curves, volumeSource, index, deviceTypes);if (outputDesc->isFixedVolume(deviceTypes) ||// Force VoIP volume to max for bluetooth SCO device except if muted(index != 0 && (isVoiceVolSrc || isBtScoVolSrc) &&isSingleDeviceType(deviceTypes, audio_is_bluetooth_out_sco_device))) {volumeDb = 0.0f;}outputDesc->setVolume(volumeDb, volumeSource, curves.getStreamTypes(), deviceTypes, delayMs, force);if (outputDesc == mPrimaryOutput && (isVoiceVolSrc || isBtScoVolSrc)) {float voiceVolume;// Force voice volume to max or mute for Bluetooth SCO as other attenuations are managed by the headsetif (isVoiceVolSrc) {voiceVolume = (float)index/(float)curves.getVolumeIndexMax();} else {voiceVolume = index == 0 ? 0.0 : 1.0;}if (voiceVolume != mLastVoiceVolume) {mpClientInterface->setVoiceVolume(voiceVolume, delayMs);mLastVoiceVolume = voiceVolume;}}return NO_ERROR;
}
AudioPolicyManager
根据音量的 index 值,计算 db 值,随后通过 AudioOutputDescriptor
设置音量。AudioOutputDescriptor
在 android 中有两个实现,分别是 SwAudioOutputDescriptor
和 HwAudioOutputDescriptor
,音量设置的执行来到 SwAudioOutputDescriptor::setVolume()
:
bool SwAudioOutputDescriptor::setVolume(float volumeDb,VolumeSource vs, const StreamTypeVector &streamTypes,const DeviceTypeSet& deviceTypes,uint32_t delayMs,bool force)
{ALOGD("SwAudioOutputDescriptor::%s: volumeDb %f", __FUNCTION__, volumeDb);StreamTypeVector streams = streamTypes;if (!AudioOutputDescriptor::setVolume(volumeDb, vs, streamTypes, deviceTypes, delayMs, force)) {return false;}if (streams.empty()) {streams.push_back(AUDIO_STREAM_MUSIC);}for (const auto& devicePort : devices()) {// APM loops on all group, so filter on active group to set the port gain,// let the other groups set the stream volume as per legacy// TODO: Pass in the device address and check against it.if (isSingleDeviceType(deviceTypes, devicePort->type()) &&devicePort->hasGainController(true) && isActive(vs)) {ALOGV("%s: device %s has gain controller", __func__, devicePort->toString().c_str());// @todo: here we might be in trouble if the SwOutput has several active clients with// different Volume Source (or if we allow several curves within same volume group)//// @todo: default stream volume to max (0) when using HW Port gain?float volumeAmpl = Volume::DbToAmpl(0);for (const auto &stream : streams) {mClientInterface->setStreamVolume(stream, volumeAmpl, mIoHandle, delayMs);}AudioGains gains = devicePort->getGains();int gainMinValueInMb = gains[0]->getMinValueInMb();int gainMaxValueInMb = gains[0]->getMaxValueInMb();int gainStepValueInMb = gains[0]->getStepValueInMb();int gainValueMb = ((volumeDb * 100)/ gainStepValueInMb) * gainStepValueInMb;gainValueMb = std::max(gainMinValueInMb, std::min(gainValueMb, gainMaxValueInMb));audio_port_config config = {};devicePort->toAudioPortConfig(&config);config.config_mask = AUDIO_PORT_CONFIG_GAIN;config.gain.values[0] = gainValueMb;return mClientInterface->setAudioPortConfig(&config, 0) == NO_ERROR;}}// Force VOICE_CALL to track BLUETOOTH_SCO stream volume when bluetooth audio is enabledfloat volumeAmpl = Volume::DbToAmpl(getCurVolume(vs));if (hasStream(streams, AUDIO_STREAM_BLUETOOTH_SCO)) {mClientInterface->setStreamVolume(AUDIO_STREAM_VOICE_CALL, volumeAmpl, mIoHandle, delayMs);}for (const auto &stream : streams) {ALOGD("%s output %d for volumeSource %d, volume %f, delay %d stream=%s", __func__,mIoHandle, vs, volumeDb, delayMs, toString(stream).c_str());mClientInterface->setStreamVolume(stream, volumeAmpl, mIoHandle, delayMs);}return true;
}
SwAudioOutputDescriptor
又通过 AudioPolicyClientInterface
设置音量;AudioPolicyClientInterface
的实现为 AudioPolicyService::AudioPolicyClient
,设置音量的动作经 AudioPolicyService::AudioPolicyClient
转回 AudioPolicyService
,只是这次调用的是 AudioPolicyService::setStreamVolume()
;在 AudioPolicyService::setStreamVolume()
中, 会构造音频命令,并发送命令给音频命令线程;音频命令线程在其命令执行循环中,通过 AudioSystem::setStreamVolume()
设置音量:
case SET_VOLUME: {VolumeData *data = (VolumeData *)command->mParam.get();ALOGD("AudioCommandThread() processing set volume stream %d, \volume %f, output %d", data->mStream, data->mVolume, data->mIO);mLock.unlock();command->mStatus = AudioSystem::setStreamVolume(data->mStream,data->mVolume,data->mIO);mLock.lock();}break;
AudioSystem::setStreamVolume()
将请求 AudioFlinger
设置音量:
status_t AudioSystem::setStreamVolume(audio_stream_type_t stream, float value,audio_io_handle_t output) {if (uint32_t(stream) >= AUDIO_STREAM_CNT) return BAD_VALUE;const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger();if (af == 0) return PERMISSION_DENIED;af->setStreamVolume(stream, value, output);return NO_ERROR;
}
这样,调节音量的动作在 audio policy service 中流转的过程如下图:
Audio policy service 接收 index 形式的音量值,将音量的 index 值转为db 值,又转为增益值,最后将音量增益设置给 audio flinger。
Audio flinger 中的音量设置
先来看一下 AudioFlinger 服务 IPC 接口层的实现结构:
如上图所示,AudioFlinger 服务 IPC 接口层实现为 AudioFlingerServerAdapter
,它的代码其实在动态链接库 libaudioclient.so 中,它接收请求,并在收到请求之后,将请求传给服务的真正实现者,也就是位于 frameworks/av/services/audioflinger/AudioFlinger.cpp 的 AudioFlinger
。
接着上面的过程,AudioFlinger::setStreamVolume()
的实现如下:
status_t AudioFlinger::setStreamVolume(audio_stream_type_t stream, float value,audio_io_handle_t output)
{ALOGD("AudioFlinger::%s, stream %d, volume %f, output %d", __func__,static_cast<int>(stream), value, static_cast<int>(output));// check calling permissionsif (!settingsAllowed()) {return PERMISSION_DENIED;}status_t status = checkStreamType(stream);if (status != NO_ERROR) {return status;}if (output == AUDIO_IO_HANDLE_NONE) {return BAD_VALUE;}LOG_ALWAYS_FATAL_IF(stream == AUDIO_STREAM_PATCH && value != 1.0f,"AUDIO_STREAM_PATCH must have full scale volume");AutoMutex lock(mLock);VolumeInterface *volumeInterface = getVolumeInterface_l(output);if (volumeInterface == NULL) {return BAD_VALUE;}volumeInterface->setStreamVolume(stream, value);return NO_ERROR;
}. . . . . .
// checkPlaybackThread_l() must be called with AudioFlinger::mLock held
AudioFlinger::VolumeInterface *AudioFlinger::getVolumeInterface_l(audio_io_handle_t output) const
{VolumeInterface *volumeInterface = mPlaybackThreads.valueFor(output).get();if (volumeInterface == nullptr) {MmapThread *mmapThread = mMmapThreads.valueFor(output).get();if (mmapThread != nullptr) {if (mmapThread->isOutput()) {MmapPlaybackThread *mmapPlaybackThread =static_cast<MmapPlaybackThread *>(mmapThread);volumeInterface = mmapPlaybackThread;}}}return volumeInterface;
}
AudioFlinger
根据 audio_io_handle_t
获得 AudioFlinger::VolumeInterface
,随后将 stream 的音量设置给它。AudioFlinger
中的这些 Thread
的结构如下:
frameworks/av/services/audioflinger/Threads.cpp 中 AudioFlinger::PlaybackThread
的 setStreamVolume()
的定义如下:
void AudioFlinger::PlaybackThread::setStreamVolume(audio_stream_type_t stream, float value)
{Mutex::Autolock _l(mLock);mStreamTypes[stream].volume = value;broadcast_l();
}
AudioFlinger::MmapPlaybackThread
的 setStreamVolume()
的定义如下:
void AudioFlinger::MmapPlaybackThread::setStreamVolume(audio_stream_type_t stream, float value)
{Mutex::Autolock _l(mLock);if (stream == mStreamType) {mStreamVolume = value;broadcast_l();}
}
即流的音量增益值被保存起来,后续在处理数据时,用来对音频数据做增益。
核心 Android 系统调节音量的过程到 Audio flinger 这一层大体如此。
参考文档:
Android音量控制曲线
Done.
相关文章:

核心 Android 调节音量的过程
核心 Android 系统提供的调节音量的方法 核心 Android 系统提供了多种调节音量的方法,这些方法主要包括如下这些。 如在 Android Automotive 调节音量的过程 中我们看到的,CarAudioService 最终在 CarAudioDeviceInfo 中 (packages/services/Car/servi…...

用C/C++制作一个简单的俄罗斯方块小游戏
用C/C制作一个简单的俄罗斯方块小游戏 用C/C制作一个简单的俄罗斯方块小游戏 0 准备1 游戏界面设计 1.1 界面布局1.2 用 EasyX 显示界面1.3 音乐播放 2 方块设计 2.1 方块显示2.2 随机生成一个方块2.3 方块记录 3 方块移动和旋转 3.1 方块的移动3.2 方块的旋转3.3 方块的碰撞和…...

使用免费负载生成器swingbench对oracle数据库进行压力测试(测试Oracle的功能或评估性能)
1.Swingbench 简介 Swingbench 是一个免费负载生成器(和基准测试),旨在对 Oracle 数据库 进行压力测试。目前最新版本 Swingbench 2.6。 SwingBench 由负载生成器,协调器和集群概述组成。该软件可以生成负载 并绘制交易/响应时间…...
【预告】ORACLE Primavera P6 v22.12 虚拟机发布
引言 离ORACLE Primavera P6 EPPM最新系统 v22.12已过去了3个多月,应盆友需要,也为方便大家体验,我近日将构建最新的P6的虚拟环境,届时将分享给大家,最终可通过VMWare vsphere (esxi) / workstation 或Oracle virtua…...

机器学习100天(四十):040 线性支持向量机-公式推导
《机器学习100天》完整目录:目录 机器学习 100 天,今天讲的是:线性支持向量机-公式推导! 首先来看这样一个问题,在二维平面上需要找到一条直线划分正类和负类。 我们找到了 A、B、C 三条直线。这三条直线都能正确分类所有训练样本。但是,哪条直线最好呢?直观上来看,我…...

失败经验之震荡玩家往往死于趋势市场
亏损,是从去年开始的吧。 尤其是去年,仅仅一年,就亏掉了自从交易以来的所有盈利。 现在,我甚至不敢去计算具体的亏损金额。 保守估计,已经亏损100万左右。 现在回想,似乎也是必然。 交易本来就是一个走…...

应用层与传输层~
文章目录应用层自定义应用层协议什么是自定义应用层协议自定义方式运输层运输层概述运输层特点运输层协议UDP协议UDP的特点UDP首部格式校验规则TCP协议TCP的特点TCP协议段格式TCP的性质确认序号超时重传连接管理三次握手四次挥手TCP的状态滑动窗口流量控制拥塞控制延迟应答捎带…...

IO文件操作
认识文件 狭义的文件 存储在硬盘上的数据,以“文件"为单位,进行组织 常见的就是普通的文件 (文本文件,图片, office系列,视频,音频可执行程序…)文件夹也叫做"目录" 也是一种特殊的文件。 广义的文件 操作系统,是要负责管理软硬件资源,操作系统(…...

【构建工具】webpack 3、4 升级指南,摆脱低版本的困扰
一、依赖处理 1.升级通用依赖 借用 ncu 库实现,帮你改写需要升级的package.json 然后再 npm install ncu -u <packages> # 可以指定依赖 ncu # 升级全部依赖大概列了下升级的效果 add-asset-html-webpack-plugin ^2.1.3 → ^5.0.2 clean-webpack-…...

Javaweb第一个项目——实现简单的登陆功能
第一步:打开idea-->文件-->新建 第二步: 在Demo文件夹 点击右键-->添加框架支持-->找到Web应用程序 勾选 第三步:配置Tomcat 第四步:新建一个lib(建在web-INF文件夹下)文件夹 用于存放jar包…...

OpenKruise 开发者不容错过的带薪实习机会!马上加入 LFX Mentorship 计划
LFX Mentorship 计划由 Linux Foundation 组织发起,为像 OpenKruise 这样的 CNCF 托管项目提供了激励开源贡献、扶植社区发展的优秀土壤。参与其中的开发者不仅有机会在经验丰富的社区 Mentor 指导下贡献开源项目、为职业生涯加分,完成工作后还能获得 $3…...

《c++ primer笔记》第八章 IO库
前言 简单看一下就行 文章目录一、IO类1.1基本概念1.2管理输出缓冲二、文件输入输出2.1文件模式三、string流3.1istringstream3.2ostringstream一、IO类 1.1基本概念 我们常见的流有istream和ostream,这两个流都是有关输入和输出的,此外,…...

web开发 用idea创建一个新项目
这个写着就是给自己当备忘录用的QAQ 这个老师上课一通操作啥也没看清…卑微搞了半天看样子是成功了 记录一下省的以后忘了怎么创建(? zufe lxy 2023.3 先行条件是已经自己装好了Tomcat和idea!!(我的idea是申请了教育…...

【FMCW 03】测速
从上一讲 测距 末尾的frame讲起。我们知道一个chirp对应了一个采样后的IF信号,我们将这些采样后的IF信号按chirp的次序排列成一个帧(frame),这就得到了我们实际中接收后处理的FMCW信号。 由于chirp的发射返回时间很短,…...

ERP(企业资源管理)概述
🌟所属专栏:ERP企业资源管理🐔作者简介:rchjr——五带信管菜只因一枚😮前言:该系列将持续更新ERP的相关学习笔记,欢迎和我一样的小白订阅,一起学习共同进步~👉文章简介&a…...

深入理解java虚拟机精华总结:性能监控和故障处理工具、类加载机制
深入理解java虚拟机精华总结:性能监控和故障处理工具、类加载机制性能监控和故障处理工具、类加载机制jpsjstatjinfojmapjhatjstackVisualVM类加载机制类加载的时机类加载的过程加载验证准备解析初始化类加载器类与类加载器双亲委派模型破坏双亲委派模型往期内容&am…...

推荐系统与推荐算法
文章目录第一章1.1推荐系统意义与价值1.2推荐系统历史与框架1.3推荐算法分类第二章2.1协同过滤的基本思想与分类2.2基于用户的协同过滤2.3基于项目的协同过滤2.4基于邻域的评分预测2.5基于二部图的协同过滤第三章3.1基于关联规则的推荐3.2基于矩阵分解的评分预测3.3概率矩阵分解…...

socket 编程实战(编写客户端程序 )
编写客户端程序 接着上一篇:实战服务端程序 接下来我们再编写一个简单地客户端应用程序,客户端的功能是连接上小节所实现的服务器,连接成功之后向服务器发送数据,发送的数据由用户输入。示例代码如下所示: #include…...

“巨亏成名”的魔鬼交易员,你知道几个?
谁说在期货市场上只有赚大钱才能出名?殊不知还有这样一群特殊的交易员靠着巨额亏损而“一战成名”,亏得是老东家元气大伤,外号“魔鬼交易员”——“不亏不成魔”!接下来火象就给大家盘点几位代表性魔鬼交易员,看看他们…...

1380:分糖果(candy)
1380:分糖果(candy) 时间限制: 1000 ms 内存限制: 65536 KB 【题目描述】 童年的我们,将和朋友分享美好的事物作为自己的快乐。这天,C小朋友得到了Plenty of candies,将要把这些糖果分给要好的朋友们。已知糖果从一个人传…...

数据挖掘(2.1)--数据预处理
一、基础知识 1.数据的基本概念 1.1基础知识 数据是数据对象(Data Objects)及其属性(Attributes)的集合。 数据对象(一条记录、一个实体、一个案例、一个样本等)是对一个事物或者物理对象的描述。 数据对象的属性则是这个对象的性质或特征,例如一个人的肤色、眼球…...

PMP考前冲刺3.06 | 2023新征程,一举拿证
题目1-2:1.一名团队成员表示,他们的用户故事要等到迭代结束后才能完成,因为他们的职能经理要求他们协助解决高优先级的生产问题。项目经理应该做什么?A.将问题上报给项目发起人以解决和调整项目燃尽图B.与产品负责人讨论用户故事不…...

buuctf-pwn write-ups (11)
文章目录buu083-x_ctf_b0verfl0wbuu084-picoctf_2018_leak_mebuu085-inndy_echobuu086-hitcontraining_unlinkbuu087-ciscn_2019_final_3buu088-axb_2019_fmt64buu089-wustctf2020_name_your_catbuu090-pwnme1buu091-axb_2019_brop64buu092-[极客大挑战 2019]Not Badbuu083-x_c…...

【VTK】VTK隐藏vtkOutputWindow窗口的正确方法
VTK隐藏vtkOutputWindow窗口 要求隐藏vtkOutputWindow窗口,但是不能把Warning警告和Error错误的信息都给屏蔽了 网上常见的错误方法: 现在百度搜索出来的方法几乎都是在这样做:在main文件中使用vtkOutputWindow::SetGlobalWarningDisplay(0…...

顺序表以及链表的应用及区别(包含OJ讲解)
前面我已经发过怎么实现链表以及顺序表,今天大概的总结一下。 顺序表: 1.能够随时的存取,比较方便。 2.插入删除时,需要挪动数据,比较麻烦,因为是连续存储。 3.存储密度相对于链表来说是比较高的&#…...

JVM简介
一、什么是JVM JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。 Java语言的一个非常重要的特点就是与平…...

Leetcode.1653 使字符串平衡的最少删除次数
题目链接 Leetcode.1653 使字符串平衡的最少删除次数 Rating : 1794 题目描述 给你一个字符串 s,它仅包含字符 a和 b 。 你可以删除 s中任意数目的字符,使得 s平衡 。当不存在下标对 (i,j)满足 i < j,且 s[i] b的同…...

leetcode 71~80 学习经历
leetcode 71~80 学习经历71. 简化路径72. 编辑距离73. 矩阵置零74. 搜索二维矩阵75. 颜色分类76. 最小覆盖子串77. 组合78. 子集79. 单词搜索80. 删除有序数组中的重复项 II小结71. 简化路径 给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 &am…...

使用metrics-server监控k8s的资源指标
首先,欢迎使用DHorse部署k8s应用。 k8s可以通过top命令来查询pod和node的资源使用情况,如果直接运行该命令,如下所示。 [rootcentos05 deployment]# kubectl top pod W0306 15:23:24.990550 8247 top_pod.go:140] Using json format to …...

【Copula】考虑风光联合出力和相关性的Copula场景生成(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...