AVC是android T 在cec部分添加的一个feature。以盒子为例,它会在playback device cec状态更新时尝试去确认TV是否是支持<SET AUDIO VOLUME LEVEL>的,如果支持就更新该设备的avc flag,并在音量调节时通知TV进行音量更新。
和以前版本的区别,目前看就是在直接进行音量更新setStreamVolume时会发送<SET AUDIO VOLUME LEVEL> message进行直接更新,但是adjustStreamVolume仍然还是依赖<User Control Pressed>,这样的话作用就大打折扣。鉴于目前支持这个消息的机器应该也是几乎没有,暂时看意义比较有限。此外,android为了支持这个feature,在playback内部也增加了DeviceDiscovery的设计,也增加了cec bus的负担。
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<AudioDeviceAttributes> 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