AVC是android T 在cec部分添加的一个feature。以盒子为例,它会在playback device cec状态更新时尝试去确认TV是否是支持
和以前版本的区别,目前看就是在直接进行音量更新setStreamVolume时会发送
1.开机时就向AudioDeviceVolumeManager注册了OnDeviceVolumeBehaviorChangedListener
HdmiControlService.java
- @Override
- public void onBootPhase(int phase) {
- if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
- mDisplayManager = getContext().getSystemService(DisplayManager.class);
- mTvInputManager = (TvInputManager) getContext().getSystemService(
- Context.TV_INPUT_SERVICE);
- mPowerManager = new PowerManagerWrapper(getContext());
- mPowerManagerInternal = new PowerManagerInternalWrapper();
- mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
- mStreamMusicMaxVolume = getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC);
- if (mAudioDeviceVolumeManager == null) {
- mAudioDeviceVolumeManager =
- new AudioDeviceVolumeManagerWrapper(getContext());
- }
- getAudioDeviceVolumeManager().addOnDeviceVolumeBehaviorChangedListener(
- mServiceThreadExecutor, this::onDeviceVolumeBehaviorChanged);
- } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
- runOnServiceThread(this::bootCompleted);
- }
- }
- /**
- * @hide
- * Interface definition of a callback to be invoked when the volume behavior of an audio device
- * is updated.
- */
- public interface OnDeviceVolumeBehaviorChangedListener {
- /**
- * Called on the listener to indicate that the volume behavior of a device has changed.
- * @param device the audio device whose volume behavior changed
- * @param volumeBehavior the new volume behavior of the audio device
- */
- void onDeviceVolumeBehaviorChanged(
- @NonNull AudioDeviceAttributes device,
- @AudioManager.DeviceVolumeBehavior int volumeBehavior);
- }
这里后面再分析。
- /**
- * Listener for changes to the volume behavior of an audio output device. Caches the
- * volume behavior of devices used for Absolute Volume Control.
- */
- @VisibleForTesting
- @ServiceThreadOnly
- void onDeviceVolumeBehaviorChanged(AudioDeviceAttributes device, int volumeBehavior) {
- assertRunOnServiceThread();
- if (AVC_AUDIO_OUTPUT_DEVICES.contains(device)) {
- synchronized (mLock) {
- mAudioDeviceVolumeBehaviors.put(device, volumeBehavior);
- }
- checkAndUpdateAbsoluteVolumeControlState();
- }
- }
- // Audio output devices used for Absolute Volume Control
- private static final List
AVC_AUDIO_OUTPUT_DEVICES = - Collections.unmodifiableList(Arrays.asList(AUDIO_OUTPUT_DEVICE_HDMI,
- AUDIO_OUTPUT_DEVICE_HDMI_ARC, AUDIO_OUTPUT_DEVICE_HDMI_EARC));
PLAYBACK相关的AUDIO_OUTPUT_DEVICE_HDMI,TV类型的AUDIO_OUTPUT_DEVICE_HDMI_ARC, AUDIO_OUTPUT_DEVICE_HDMI_EARC都属于AVC设备。
当系统的AVC设备对应的volume behaviour发生变更时,就会通过checkAndUpdateAbsoluteVolumeControlState来检查和更新HdmiControlService内部的状态。
2. 进行volume control behavior更新的时间。
监听cec status的设计在T上这里的实现再次发生变更,直接通过DEVICE_OUT_HDMI的volume behaviour的方式来更新cec sink device的状态。
首先如果DEVICE_OUT_HDMI设备有过AudioService的setDeviceVolumeBehavior接口设置过behaviour的话,就不再更新了。
这里有一个问题,如果cec不可用了,然后也设置过FULL的behaviour的话,盒子的音量键将完全失去作用。
- //==========================================================================================
- // Hdmi CEC:
- // - System audio mode:
- // If Hdmi Cec's system audio mode is on, audio service should send the volume change
- // to HdmiControlService so that the audio receiver can handle it.
- // - CEC sink:
- // OUT_HDMI becomes a "full volume device", i.e. output is always at maximum level
- // and volume changes won't be taken into account on this device. Volume adjustments
- // are transformed into key events for the HDMI playback client.
- //==========================================================================================
-
- @GuardedBy("mHdmiClientLock")
- private void updateHdmiCecSinkLocked(boolean hdmiCecSink) {
- if (!hasDeviceVolumeBehavior(AudioSystem.DEVICE_OUT_HDMI)) {
- if (hdmiCecSink) {
- if (DEBUG_VOL) {
- Log.d(TAG, "CEC sink: setting HDMI as full vol device");
- }
- setDeviceVolumeBehaviorInternal(
- new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_HDMI, ""),
- AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL,
- "AudioService.updateHdmiCecSinkLocked()");
- } else {
- if (DEBUG_VOL) {
- Log.d(TAG, "TV, no CEC: setting HDMI as regular vol device");
- }
- // Android TV devices without CEC service apply software volume on
- // HDMI output
- setDeviceVolumeBehaviorInternal(
- new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_HDMI, ""),
- AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE,
- "AudioService.updateHdmiCecSinkLocked()");
- }
- postUpdateVolumeStatesForAudioDevice(AudioSystem.DEVICE_OUT_HDMI,
- "HdmiPlaybackClient.DisplayStatusCallback");
- }
- }
只有persistDeviceVolumeBehavior才会将volume behaviour记录到system settings里面,比如
_id:36 name:AudioService_DeviceVolumeBehavior_hdmi pkg:android value:1 default:1 defaultSystemSet:true
- public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device,
- @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior, @Nullable String pkgName) {
- // verify permissions
- enforceModifyAudioRoutingPermission();
- // verify arguments
- Objects.requireNonNull(device);
- AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior);
- sVolumeLogger.log(new AudioEventLogger.StringEvent("setDeviceVolumeBehavior: dev:"
- + AudioSystem.getOutputDeviceName(device.getInternalType()) + " addr:"
- + device.getAddress() + " behavior:"
- + AudioDeviceVolumeManager.volumeBehaviorName(deviceVolumeBehavior)
- + " pack:" + pkgName).printLog(TAG));
- if (pkgName == null) {
- pkgName = "";
- }
- if (device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP) {
- avrcpSupportsAbsoluteVolume(device.getAddress(),
- deviceVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
- return;
- }
-
- setDeviceVolumeBehaviorInternal(device, deviceVolumeBehavior, pkgName);
- persistDeviceVolumeBehavior(device.getInternalType(), deviceVolumeBehavior);
- }
AudioService在设置MyHdmiControlStatusChangeListenerCallback时必然会调用updateHdmiCecSinkLocked来更新一次volume behaviour。
- @VisibleForTesting
- void addHdmiControlStatusChangeListener(
- final IHdmiControlStatusChangeListener listener) {
- final HdmiControlStatusChangeListenerRecord record =
- new HdmiControlStatusChangeListenerRecord(listener);
- try {
- listener.asBinder().linkToDeath(record, 0);
- } catch (RemoteException e) {
- Slog.w(TAG, "Listener already died");
- return;
- }
- synchronized (mLock) {
- mHdmiControlStatusChangeListenerRecords.add(record);
- }
-
- // Inform the listener of the initial state of each HDMI port by generating
- // hotplug events.
- runOnServiceThread(new Runnable() {
- @Override
- public void run() {
- synchronized (mLock) {
- if (!mHdmiControlStatusChangeListenerRecords.contains(record)) return;
- }
-
- // Return the current status of mHdmiControlEnabled;
- synchronized (mLock) {
- invokeHdmiControlStatusChangeListenerLocked(listener, mHdmiControlEnabled);
- }
- }
- });
- }
private class MyHdmiControlStatusChangeList