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 MyHdmiControlStatusChangeListenerCallback
implements HdmiControlManager.HdmiControlStatusChangeListener {
public void onStatusChange(@HdmiControlManager.HdmiCecControl int isCecEnabled,
boolean isCecAvailable) {
synchronized (mHdmiClientLock) {
if (mHdmiManager == null) return;
boolean cecEnabled = isCecEnabled == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED;
updateHdmiCecSinkLocked(cecEnabled ? isCecAvailable : false);
}
}
};
以Playback为例,当连接TV的CEC ready时,则设置behaviour为DEVICE_VOLUME_BEHAVIOUR_FULL,这里仅仅是将DEVICE_HDMI_OUT添加到full device列表里面,如
? mFixedVolumeDevices=0x800,0x200000 ? mFullVolumeDevices=0x400,0x40000,0x40001 ? mAbsoluteVolumeDevices.keySet()=
private void setDeviceVolumeBehaviorInternal(@NonNull AudioDeviceAttributes device,
@AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior, @NonNull String caller) {
int audioSystemDeviceOut = device.getInternalType();
boolean volumeBehaviorChanged = false;
// update device masks based on volume behavior
switch (deviceVolumeBehavior) {
case AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE:
volumeBehaviorChanged |=
removeAudioSystemDeviceOutFromFullVolumeDevices(audioSystemDeviceOut)
| removeAudioSystemDeviceOutFromFixedVolumeDevices(audioSystemDeviceOut)
| (removeAudioSystemDeviceOutFromAbsVolumeDevices(audioSystemDeviceOut)
!= null);
break;
case AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED:
volumeBehaviorChanged |=
removeAudioSystemDeviceOutFromFullVolumeDevices(audioSystemDeviceOut)
| addAudioSystemDeviceOutToFixedVolumeDevices(audioSystemDeviceOut)
| (removeAudioSystemDeviceOutFromAbsVolumeDevices(audioSystemDeviceOut)
!= null);
break;
case AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL:
volumeBehaviorChanged |=
addAudioSystemDeviceOutToFullVolumeDevices(audioSystemDeviceOut)
| removeAudioSystemDeviceOutFromFixedVolumeDevices(audioSystemDeviceOut)
| (removeAudioSystemDeviceOutFromAbsVolumeDevices(audioSystemDeviceOut)
!= null);
break;
case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE:
case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE:
throw new IllegalArgumentException("Absolute volume unsupported for now");
}
if (volumeBehaviorChanged) {
sendMsg(mAudioHandler, MSG_DISPATCH_DEVICE_VOLUME_BEHAVIOR, SENDMSG_QUEUE,
deviceVolumeBehavior, 0, device, /*delay*/ 0);
}
// log event and caller
sDeviceLogger.log(new AudioEventLogger.StringEvent(
"Volume behavior " + deviceVolumeBehavior + " for dev=0x"
+ Integer.toHexString(audioSystemDeviceOut) + " from:" + caller));
// make sure we have a volume entry for this device, and that volume is updated according
// to volume behavior
postUpdateVolumeStatesForAudioDevice(audioSystemDeviceOut,
"setDeviceVolumeBehavior:" + caller);
}
所以每次cec control状态的变更,都会更新mFullVolumeDevices,并且通知IDeviceVolumeBehaviorDispatcher的监听器们进行更新。
private void dispatchDeviceVolumeBehavior(AudioDeviceAttributes device, int volumeBehavior) {
final int dispatchers = mDeviceVolumeBehaviorDispatchers.beginBroadcast();
for (int i = 0; i < dispatchers; i++) {
try {
mDeviceVolumeBehaviorDispatchers.getBroadcastItem(i)
.dispatchDeviceVolumeBehaviorChanged(device, volumeBehavior);
} catch (RemoteException e) {
}
}
mDeviceVolumeBehaviorDispatchers.finishBroadcast();
}
AudioDeviceVolumeManager.java
前面已经有分析过,HdmiControlService在onBootPhase初始化阶段就注册过该监听器。
private final class DeviceVolumeBehaviorDispatcherStub
extends IDeviceVolumeBehaviorDispatcher.Stub implements CallbackUtil.DispatcherStub {
public void register(boolean register) {
try {
getService().registerDeviceVolumeBehaviorDispatcher(register, this);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
@Override
public void dispatchDeviceVolumeBehaviorChanged(@NonNull AudioDeviceAttributes device,
@AudioManager.DeviceVolumeBehavior int volumeBehavior) {
mDeviceVolumeBehaviorChangedListenerMgr.callListeners((listener) ->
listener.onDeviceVolumeBehaviorChanged(device, volumeBehavior));
}
}
包括开机、hotplug、cec switch change等场景,最终都会进行avc状态的检查。满足进行avc状态检查的一个条件就是AVC_AUDIO_OUT_DEVICE的behaviour必须是FULL或者ABSOLUTE的,而默认情况下只要TV支持CEC这个条件就满足了,所以必然最后会发送<Set Device Volume Level>消息来进行检查。换言之如果cec状态不满足,比如cec关闭,tv不支持cec等场景,那么AVC也就无须考虑了。
/**
* 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();
}
}
2.checkAndUpdateAbsoluteVolumeControlState
Absolute Volume Control is AVC. 当使用外部音频设备时,并且该音频设备支持<Set Audio Volume Level>时,则可以配置该音频设备支持AVC,并启动相关设置。
目前这里判断外部音频设备是否支持AVC是通过判断是否回复<Feature Abort>来决定的,这值得商榷。
而判断设备自身是否enable avc,则需要满足以下条件。
①TV需要建立system audio control,Playback无此要求。
②CEC使用的AVC audio device相关的volume behaviour必须是DEVICE_VOLUME_BEHAVIOR_FULL? or DEVICE_VOLUME_BEHAVIOR_ABSOLUTE。TV对应的audio device是HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC,而playback对应的则是AUDIO_OUTPUT_DEVICE_HDMI,这俩都是AudioDeviceAttributes。
除了三方应用或者CTS测试可以在通过AudioManager::setDeviceVolumeBehavior接口来设置他们的behaviour之外,一般都是通过AudioService updateCecSink设置的DEVICE_OUT_HDMI behaviour为FULL,这个条件默认也是满足的。
③volume control开关必须是打开的。 T上修改开关是通过HdmiControlManager::setHdmiCecVolumeControlEnabled来实现的,不是之前的版本通过Settings Global了。
④外部的TV/AVR是支持AVC的。目前判断是否支持AVC是通过发送给<Set Volume Level>以后看设备是否回复<Feature Abort>实现的。在CTS测试里面,由于TV的CEC是关闭的,所以这个条件是满足的。
/**
* Checks the conditions for Absolute Volume Control (AVC), and enables or disables the feature
* if necessary. AVC is enabled precisely when a specific audio output device
* (HDMI for playback devices, and HDMI_ARC or HDMI_EARC for TVs) is using absolute volume
* behavior.
*
* AVC must be enabled on a Playback device or TV precisely when it is playing
* audio on an external device (the System Audio device) that supports the feature.
* This reduces to these conditions:
*
* 1. If the System Audio Device is an Audio System: System Audio Mode is active
* 2. Our HDMI audio output device is using full volume behavior
* 3. CEC volume is enabled
* 4. The System Audio device supports AVC (i.e. it supports <Set Audio Volume Level>)
*
* If not all of these conditions are met, this method disables AVC if necessary.
*
* If all of these conditions are met, this method starts an action to query the System Audio
* device's audio status, which enables AVC upon obtaining the audio status.
*/
void checkAndUpdateAbsoluteVolumeControlState() {
assertRunOnServiceThread();
// Can't enable or disable AVC before we have access to system services
if (getAudioManager() == null) {
return;
}
HdmiCecLocalDevice localCecDevice;
if (isTvDevice() && tv() != null) {
localCecDevice = tv();
// Condition 1: TVs need System Audio Mode to be active
// (Doesn't apply to Playback Devices, where if SAM isn't active, we assume the
// TV is the System Audio Device instead.)
if (!isSystemAudioActivated()) {
disableAbsoluteVolumeControl();
return;
}
} else if (isPlaybackDevice() && playback() != null) {
localCecDevice = playback();
} else {
// Either this device type doesn't support AVC, or it hasn't fully initialized yet
return;
}
HdmiDeviceInfo systemAudioDeviceInfo = getHdmiCecNetwork().getSafeCecDeviceInfo(
localCecDevice.findAudioReceiverAddress());
@AudioManager.DeviceVolumeBehavior int currentVolumeBehavior =
getDeviceVolumeBehavior(getAvcAudioOutputDevice());
// Condition 2: Already using full or absolute volume behavior
boolean alreadyUsingFullOrAbsoluteVolume =
currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL
|| currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE;
// Condition 3: CEC volume is enabled
boolean cecVolumeEnabled =
getHdmiCecVolumeControl() == HdmiControlManager.VOLUME_CONTROL_ENABLED;
if (!cecVolumeEnabled || !alreadyUsingFullOrAbsoluteVolume) {
disableAbsoluteVolumeControl();
return;
}
// Check for safety: if the System Audio device is a candidate for AVC, we should already
// have received messages from it to trigger the other conditions.
if (systemAudioDeviceInfo == null) {
disableAbsoluteVolumeControl();
return;
}
// Condition 4: The System Audio device supports AVC (i.e. <Set Audio Volume Level>).
switch (systemAudioDeviceInfo.getDeviceFeatures().getSetAudioVolumeLevelSupport()) {
case DeviceFeatures.FEATURE_SUPPORTED:
if (!isAbsoluteVolumeControlEnabled()) {
// Start an action that will call {@link #enableAbsoluteVolumeControl}
// once the System Audio device sends <Report Audio Status>
localCecDevice.addAvcAudioStatusAction(
systemAudioDeviceInfo.getLogicalAddress());
}
return;
case DeviceFeatures.FEATURE_NOT_SUPPORTED:
disableAbsoluteVolumeControl();
return;
case DeviceFeatures.FEATURE_SUPPORT_UNKNOWN:
disableAbsoluteVolumeControl();
localCecDevice.queryAvcSupport(systemAudioDeviceInfo.getLogicalAddress());
return;
default:
return;
}
}
AudioDeviceAttributes
比如??? AudioAttributes: usage=USAGE_MEDIA content=CONTENT_TYPE_UNKNOWN flags=0x800 tags= bundle=null forVolume: true stream: STREAM_MUSIC(3) ?? ??? ?AudioDeviceAttributes: role:output type:hdmi addr: name: profiles:[] descriptors:[]
@VisibleForTesting
static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI = new AudioDeviceAttributes(
AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI, "");
@VisibleForTesting
static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI_ARC =
new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
AudioDeviceInfo.TYPE_HDMI_ARC, "");
disableAbsoluteVolumeControl 上面的4个条件不满足的时候,会取消设置avc,如果做过avc的设置,即将系统的avc设备的behaviour设置过DEVICE_VOLUME_BEHAVIOR_ABSOLUTE,那么会将behaviour重置为DEVICE_VOLUME_BEHAVIOR_FULL。对于盒子来说,这会让它始终通过CEC把音量键发给TV处理。
private void disableAbsoluteVolumeControl() {
if (isPlaybackDevice()) {
playback().removeAvcAudioStatusAction();
} else if (isTvDevice()) {
tv().removeAvcAudioStatusAction();
}
AudioDeviceAttributes device = getAvcAudioOutputDevice();
if (getDeviceVolumeBehavior(device) == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
getAudioManager().setDeviceVolumeBehavior(device,
AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
}
}
一开始,Network里面记录的外部设备的avc状态必然是DeviceFeatures.FEATURE_SUPPORT_UNKNOWN, 则会启动SetAudioVolumeLevelDiscoveryAction来确认该设备是否支持。
/**
* Determines whether {@code targetAddress} supports <Set Audio Volume Level>. Does two things
* in parallel: send <Give Features> (to get <Report Features> in response),
* and send <Set Audio Volume Level> (to see if it gets a <Feature Abort> in response).
*/
@ServiceThreadOnly
void queryAvcSupport(int targetAddress) {
assertRunOnServiceThread();
// Send <Give Features> if using CEC 2.0 or above.
if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
synchronized (mLock) {
mService.sendCecCommand(HdmiCecMessageBuilder.buildGiveFeatures(
getDeviceInfo().getLogicalAddress(), targetAddress));
}
}
// If we don't already have a {@link SetAudioVolumeLevelDiscoveryAction} for the target
// device, start one.
List<SetAudioVolumeLevelDiscoveryAction> savlDiscoveryActions =
getActions(SetAudioVolumeLevelDiscoveryAction.class);
if (savlDiscoveryActions.stream().noneMatch(a -> a.getTargetAddress() == targetAddress)) {
addAndStartAction(new SetAudioVolumeLevelDiscoveryAction(this, targetAddress,
new IHdmiControlCallback.Stub() {
@Override
public void onComplete(int result) {
if (result == HdmiControlManager.RESULT_SUCCESS) {
getService().checkAndUpdateAbsoluteVolumeControlState();
}
}
}));
}
}
boolean start() {
sendCommand(SetAudioVolumeLevelMessage.build(
getSourceAddress(), mTargetAddress, Constants.AUDIO_VOLUME_STATUS_UNKNOWN),
result -> {
if (result == SendMessageResult.SUCCESS) {
// Message sent successfully; wait for <Feature Abort> in response
mState = STATE_WAITING_FOR_FEATURE_ABORT;
addTimer(mState, HdmiConfig.TIMEOUT_MS);
} else {
finishWithCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED);
}
});
return true;
}
只有在target超时不回复时才算支持,否则就是不支持。
其中关于feature abort消息的处理,也不是直接更新状态的,而是在HdmiCecNetwork中有单独的处理。比如
??? CEC: logical_address: 0x04 device_type: 4 cec_version: 5 vendor_id: 1877008 display_name: GoogleTV8014 power_status: 0 physical_address: 0x3000 port_id: 0 ????? Device features: record_tv_screen: N set_osd_string: N deck_control: N set_audio_rate: N arc_tx: N arc_rx: N set_audio_volume_level: N
??? [S] time=2022-08-15 19:09:56 message=<Report Physical Address> 4F:84:30:00:04 ??? [S] time=2022-08-15 19:09:56 message=<Device Vendor Id> 4F:87:1C:A4:10 ??? [S] time=2022-08-15 19:09:56 message=<Set Osd Name> 40:47 <Redacted len=12> ??? [S] time=2022-08-15 19:09:56 message=<Give System Audio Mode Status> 45:7D ??? [R] time=2022-08-15 19:09:57 message=<Give Osd Name> 04:46 ??? [S] time=2022-08-15 19:09:57 message=<Set Audio Volume Level> 40:73:7F ??? [S] time=2022-08-15 19:09:57 message=<Set Osd Name> 40:47 <Redacted len=12> ??? [R] time=2022-08-15 19:09:57 message=<Give Device Vendor Id> 04:8C ??? [S] time=2022-08-15 19:09:57 message=<Device Vendor Id> 4F:87:1C:A4:10 ??? [R] time=2022-08-15 19:09:57 message=<Feature Abort> 04:00:73:00
/**
* Updates the System Audio device's support for <Set Audio Volume Level> in the
* {@link HdmiCecNetwork}. Can fail if the System Audio device is not in our
* {@link HdmiCecNetwork}.
*
* @return Whether support was successfully updated in the network.
*/
private boolean updateAvcSupport(
@DeviceFeatures.FeatureSupportStatus int setAudioVolumeLevelSupport) {
HdmiCecNetwork network = localDevice().mService.getHdmiCecNetwork();
HdmiDeviceInfo currentDeviceInfo = network.getCecDeviceInfo(mTargetAddress);
if (currentDeviceInfo == null) {
return false;
} else {
network.updateCecDevice(
currentDeviceInfo.toBuilder()
.setDeviceFeatures(currentDeviceInfo.getDeviceFeatures().toBuilder()
.setSetAudioVolumeLevelSupport(setAudioVolumeLevelSupport)
.build())
.build()
);
return true;
}
}
private void handleFeatureAbort(HdmiCecMessage message) {
assertRunOnServiceThread();
if (message.getParams().length < 2) {
return;
}
int originalOpcode = message.getParams()[0] & 0xFF;
int reason = message.getParams()[1] & 0xFF;
// Check if we received <Feature Abort> in response to <Set Audio Volume Level>.
// This provides information on whether the source supports the message.
if (originalOpcode == Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL) {
@DeviceFeatures.FeatureSupportStatus int featureSupport =
reason == Constants.ABORT_UNRECOGNIZED_OPCODE
? DeviceFeatures.FEATURE_NOT_SUPPORTED
: DeviceFeatures.FEATURE_SUPPORT_UNKNOWN;
HdmiDeviceInfo currentDeviceInfo = getCecDeviceInfo(message.getSource());
HdmiDeviceInfo newDeviceInfo = currentDeviceInfo.toBuilder()
.updateDeviceFeatures(
currentDeviceInfo.getDeviceFeatures().toBuilder()
.setSetAudioVolumeLevelSupport(featureSupport)
.build()
)
.build();
updateCecDevice(newDeviceInfo);
mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState();
}
}
当完成查询之后,又会再调用一次checkAndUpdateAbsoluteVolumeControlState来更新当前avc设备的状态。这个方法除了volume behaviour变更会执行之外,在在network进行新的设备addCecDevice和removeCecDevice会执行,,volume control开关变更会执行,system audio control变更也会执行。。。。
当连接的音频设备确认支持AVC以后,则会启动AbsoluteVolumeAudioStatusAction来完成从音频source一侧到reception的音量更新。这里涉及的消息和以前一样,是<Give Audio Status>和<Report Audio Status>。 ?
void addAvcAudioStatusAction(int targetAddress) {
if (!hasAction(AbsoluteVolumeAudioStatusAction.class)) {
addAndStartAction(new AbsoluteVolumeAudioStatusAction(this, targetAddress));
}
}
private boolean handleReportAudioStatus(HdmiCecMessage cmd) {
if (mTargetAddress != cmd.getSource() || cmd.getParams().length == 0) {
return false;
}
boolean mute = HdmiUtils.isAudioStatusMute(cmd);
int volume = HdmiUtils.getAudioStatusVolume(cmd);
AudioStatus audioStatus = new AudioStatus(volume, mute);
if (mState == STATE_WAIT_FOR_INITIAL_AUDIO_STATUS) {
localDevice().getService().enableAbsoluteVolumeControl(audioStatus);
mState = STATE_MONITOR_AUDIO_STATUS;
} else if (mState == STATE_MONITOR_AUDIO_STATUS) {
if (audioStatus.getVolume() != mLastAudioStatus.getVolume()) {
localDevice().getService().notifyAvcVolumeChange(audioStatus.getVolume());
}
if (audioStatus.getMute() != mLastAudioStatus.getMute()) {
localDevice().getService().notifyAvcMuteChange(audioStatus.getMute());
}
}
mLastAudioStatus = audioStatus;
return true;
}
enableAbsoluteVolumeControl,主要是设置AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener到AudioDeviceVolumeManager。再将其封装到DeviceVolumeDispatcherStub里面调用AudioService::registerDeviceVolumeDispatcherForAbsoluteVolume注册到AudioService,主要是将device配置为ABSOLUTE behaviour,并添加监听器。
/**
* Enables Absolute Volume Control. Should only be called when all the conditions for
* AVC are met (see {@link #checkAndUpdateAbsoluteVolumeControlState}).
* @param audioStatus The initial audio status to set the audio output device to
*/
void enableAbsoluteVolumeControl(AudioStatus audioStatus) {
HdmiCecLocalDevice localDevice = isPlaybackDevice() ? playback() : tv();
HdmiDeviceInfo systemAudioDevice = getHdmiCecNetwork().getDeviceInfo(
localDevice.findAudioReceiverAddress());
VolumeInfo volumeInfo = new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
.setMuted(audioStatus.getMute())
.setVolumeIndex(audioStatus.getVolume())
.setMaxVolumeIndex(AudioStatus.MAX_VOLUME)
.setMinVolumeIndex(AudioStatus.MIN_VOLUME)
.build();
mAbsoluteVolumeChangedListener = new AbsoluteVolumeChangedListener(
localDevice, systemAudioDevice);
// AudioService sets the volume of the stream and device based on the input VolumeInfo
// when enabling absolute volume behavior, but not the mute state
notifyAvcMuteChange(audioStatus.getMute());
getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeBehavior(
getAvcAudioOutputDevice(), volumeInfo, mServiceThreadExecutor,
mAbsoluteVolumeChangedListener, true);
}
AudioDeviceVolumeManager.java
/**
* @hide
* Configures a device to use absolute volume model applied to different volume types, and
* registers a listener for receiving volume updates to apply on that device
* @param device the audio device set to absolute multi-volume mode
* @param volumes the list of volumes the given device responds to
* @param executor the Executor used for receiving volume updates through the listener
* @param vclistener the callback for volume updates
* @param handlesVolumeAdjustment whether the controller handles volume adjustments separately
* from volume changes. If true, adjustments from {@link AudioManager#adjustStreamVolume}
* will be sent via {@link OnAudioDeviceVolumeChangedListener#onAudioDeviceVolumeAdjusted}.
*/
@RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING,
android.Manifest.permission.BLUETOOTH_PRIVILEGED })
public void setDeviceAbsoluteMultiVolumeBehavior(
@NonNull AudioDeviceAttributes device,
@NonNull List<VolumeInfo> volumes,
@NonNull @CallbackExecutor Executor executor,
@NonNull OnAudioDeviceVolumeChangedListener vclistener,
boolean handlesVolumeAdjustment) {
Objects.requireNonNull(device);
Objects.requireNonNull(volumes);
Objects.requireNonNull(executor);
Objects.requireNonNull(vclistener);
final ListenerInfo listenerInfo = new ListenerInfo(
vclistener, executor, device, handlesVolumeAdjustment);
synchronized (mDeviceVolumeListenerLock) {
if (mDeviceVolumeListeners == null) {
mDeviceVolumeListeners = new ArrayList<>();
}
if (mDeviceVolumeListeners.size() == 0) {
if (mDeviceVolumeDispatcherStub == null) {
mDeviceVolumeDispatcherStub = new DeviceVolumeDispatcherStub();
}
} else {
mDeviceVolumeListeners.removeIf(info -> info.mDevice.equalTypeAddress(device));
}
mDeviceVolumeListeners.add(listenerInfo);
mDeviceVolumeDispatcherStub.register(true, device, volumes, handlesVolumeAdjustment);
}
}
DeviceVolumeDispatcherStub继承IAudioDeviceVolumeDispatcher.Stub,是aidl实现跨进程进行AudioService音量变更监听的监听器。cec在统满足avc的条件时会通过register方法,通过AudioService的 registerDeviceVolumeDispatcherForAbsoluteVolume来实现最终的监听器设置。
其他的dispatchDeviceVolumeChanged和dispatchDeviceVolumeAdjusted 回调方法实现AudioService在音量调节时针对avc设备实现对cec service的通知。
final class DeviceVolumeDispatcherStub extends IAudioDeviceVolumeDispatcher.Stub {
/**
* Register / unregister the stub
* @param register true for registering, false for unregistering
* @param device device for which volume is monitored
*/
public void register(boolean register, @NonNull AudioDeviceAttributes device,
@NonNull List<VolumeInfo> volumes, boolean handlesVolumeAdjustment) {
try {
getService().registerDeviceVolumeDispatcherForAbsoluteVolume(register,
this, mPackageName,
Objects.requireNonNull(device), Objects.requireNonNull(volumes),
handlesVolumeAdjustment);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
@Override
public void dispatchDeviceVolumeChanged(
@NonNull AudioDeviceAttributes device, @NonNull VolumeInfo vol) {
final ArrayList<ListenerInfo> volumeListeners;
synchronized (mDeviceVolumeListenerLock) {
volumeListeners = (ArrayList<ListenerInfo>) mDeviceVolumeListeners.clone();
}
for (ListenerInfo listenerInfo : volumeListeners) {
if (listenerInfo.mDevice.equalTypeAddress(device)) {
listenerInfo.mExecutor.execute(
() -> listenerInfo.mListener.onAudioDeviceVolumeChanged(device, vol));
}
}
}
@Override
public void dispatchDeviceVolumeAdjusted(
@NonNull AudioDeviceAttributes device, @NonNull VolumeInfo vol, int direction,
int mode) {
final ArrayList<ListenerInfo> volumeListeners;
synchronized (mDeviceVolumeListenerLock) {
volumeListeners = (ArrayList<ListenerInfo>) mDeviceVolumeListeners.clone();
}
for (ListenerInfo listenerInfo : volumeListeners) {
if (listenerInfo.mDevice.equalTypeAddress(device)) {
listenerInfo.mExecutor.execute(
() -> listenerInfo.mListener.onAudioDeviceVolumeAdjusted(device, vol,
direction, mode));
}
}
}
}
AudioService.java
在注册avc监听器的时候,主要是是做了4件事。
①更新avc device的volume behaviour,从full,fix列表中移除,在abs列表中添加。
②addAudioSystemDeviceOutToAbsVolumeDevices在添加列表之余,将AbsoluteVolumeDeviceInfo中的callback DeviceVolumeDispatcherStub也保存下来。
③通知volume behaviour变更,dispatchDeviceVolumeBehavior-->HdimControlService::onDeviceVolumeBehaviorChanged。也就是说在这个监听器注册的时候,就已经会被回调一次了。
④更新由cec 拿到的外部音频设备的音量。
此时在HdimControlService的记录就应该是这样的:
mIsAbsoluteVolumeControlEnabled: true
? mDeviceInfos: ??? CEC: logical_address: 0x00 device_type: 0 cec_version: 5 vendor_id: 1877008 display_name: xx power_status: 0 physical_address: 0x0000 port_id: -1 ????? Device features: record_tv_screen: ? set_osd_string: ? deck_control: ? set_audio_rate: ? arc_tx: ? arc_rx: ? set_audio_volume_level: Y
AudioService
? mUseFixedVolume=false ? mFixedVolumeDevices=0x800,0x200000 ? mFullVolumeDevices=0x40000,0x40001 ? mAbsoluteVolumeDevices.keySet()=0x400
@RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING,
android.Manifest.permission.BLUETOOTH_PRIVILEGED })
public void registerDeviceVolumeDispatcherForAbsoluteVolume(boolean register,
IAudioDeviceVolumeDispatcher cb, String packageName,
AudioDeviceAttributes device, List<VolumeInfo> volumes,
boolean handlesVolumeAdjustment) {
// verify permissions
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
!= PackageManager.PERMISSION_GRANTED
&& mContext.checkCallingOrSelfPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException(
"Missing MODIFY_AUDIO_ROUTING or BLUETOOTH_PRIVILEGED permissions");
}
// verify arguments
Objects.requireNonNull(device);
Objects.requireNonNull(volumes);
int deviceOut = device.getInternalType();
if (register) {
AbsoluteVolumeDeviceInfo info = new AbsoluteVolumeDeviceInfo(
device, volumes, cb, handlesVolumeAdjustment);
boolean volumeBehaviorChanged =
removeAudioSystemDeviceOutFromFullVolumeDevices(deviceOut)
| removeAudioSystemDeviceOutFromFixedVolumeDevices(deviceOut)
| (addAudioSystemDeviceOutToAbsVolumeDevices(deviceOut, info) == null);
if (volumeBehaviorChanged) {
dispatchDeviceVolumeBehavior(device, AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
}
// Update stream volumes to the given device, if specified in a VolumeInfo.
// Mute state is not updated because it is stream-wide - the only way to mute a
// stream's output to a particular device is to set the volume index to zero.
for (VolumeInfo volumeInfo : volumes) {
if (volumeInfo.getVolumeIndex() != VolumeInfo.INDEX_NOT_SET
&& volumeInfo.getMinVolumeIndex() != VolumeInfo.INDEX_NOT_SET
&& volumeInfo.getMaxVolumeIndex() != VolumeInfo.INDEX_NOT_SET) {
if (volumeInfo.hasStreamType()) {
setStreamVolumeInt(volumeInfo.getStreamType(),
rescaleIndex(volumeInfo, volumeInfo.getStreamType()),
deviceOut, false /*force*/, packageName,
true /*hasModifyAudioSettings*/);
} else {
for (int streamType : volumeInfo.getVolumeGroup().getLegacyStreamTypes()) {
setStreamVolumeInt(streamType, rescaleIndex(volumeInfo, streamType),
deviceOut, false /*force*/, packageName,
true /*hasModifyAudioSettings*/);
}
}
}
}
} else {
boolean wasAbsVol = removeAudioSystemDeviceOutFromAbsVolumeDevices(deviceOut) != null;
if (wasAbsVol) {
dispatchDeviceVolumeBehavior(device, AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE);
}
}
}
3.avc volume change
AudioService.java
adjust
adjustVolumeStream() {
...
if (isAbsoluteVolumeDevice(device)
&& (flags & AudioManager.FLAG_ABSOLUTE_VOLUME) == 0) {
AbsoluteVolumeDeviceInfo info = mAbsoluteVolumeDeviceInfoMap.get(device);
dispatchAbsoluteVolumeChanged(streamType, info, newIndex);
}
...
}
private void dispatchAbsoluteVolumeChanged(int streamType, AbsoluteVolumeDeviceInfo deviceInfo,
int index) {
VolumeInfo volumeInfo = deviceInfo.getMatchingVolumeInfoForStream(streamType);
if (volumeInfo != null) {
try {
deviceInfo.mCallback.dispatchDeviceVolumeChanged(deviceInfo.mDevice,
new VolumeInfo.Builder(volumeInfo)
.setVolumeIndex(rescaleIndex(index, streamType, volumeInfo))
.build());
} catch (RemoteException e) {
Log.w(TAG, "Couldn't dispatch absolute volume behavior volume change");
}
}
}
AudioServiceVolumeManager.java
@Override
public void dispatchDeviceVolumeAdjusted(
@NonNull AudioDeviceAttributes device, @NonNull VolumeInfo vol, int direction,
int mode) {
final ArrayList<ListenerInfo> volumeListeners;
synchronized (mDeviceVolumeListenerLock) {
volumeListeners = (ArrayList<ListenerInfo>) mDeviceVolumeListeners.clone();
}
for (ListenerInfo listenerInfo : volumeListeners) {
if (listenerInfo.mDevice.equalTypeAddress(device)) {
listenerInfo.mExecutor.execute(
() -> listenerInfo.mListener.onAudioDeviceVolumeAdjusted(device, vol,
direction, mode));
}
}
}
?adjust到最后也是发音量键,这个设计并不好,也重复了?
/**
* Called when AudioService adjusts the volume or mute state of an absolute volume
* audio output device
*/
@Override
public void onAudioDeviceVolumeAdjusted(
@NonNull AudioDeviceAttributes audioDevice,
@NonNull VolumeInfo volumeInfo,
@AudioManager.VolumeAdjustment int direction,
@AudioDeviceVolumeManager.VolumeAdjustmentMode int mode
) {
int keyCode;
switch (direction) {
case AudioManager.ADJUST_RAISE:
keyCode = KeyEvent.KEYCODE_VOLUME_UP;
break;
case AudioManager.ADJUST_LOWER:
keyCode = KeyEvent.KEYCODE_VOLUME_DOWN;
break;
case AudioManager.ADJUST_TOGGLE_MUTE:
case AudioManager.ADJUST_MUTE:
case AudioManager.ADJUST_UNMUTE:
// Many CEC devices only support toggle mute. Therefore, we send the
// same keycode for all three mute options.
keyCode = KeyEvent.KEYCODE_VOLUME_MUTE;
break;
default:
return;
}
switch (mode) {
case AudioDeviceVolumeManager.ADJUST_MODE_NORMAL:
mLocalDevice.sendVolumeKeyEvent(keyCode, true);
mLocalDevice.sendVolumeKeyEvent(keyCode, false);
break;
case AudioDeviceVolumeManager.ADJUST_MODE_START:
mLocalDevice.sendVolumeKeyEvent(keyCode, true);
break;
case AudioDeviceVolumeManager.ADJUST_MODE_END:
mLocalDevice.sendVolumeKeyEvent(keyCode, false);
break;
default:
return;
}
}
}
set
AudioService.java
setStreamVolume
if (isAbsoluteVolumeDevice(device)
&& ((flags & AudioManager.FLAG_ABSOLUTE_VOLUME) == 0)) {
AbsoluteVolumeDeviceInfo info = mAbsoluteVolumeDeviceInfoMap.get(device);
dispatchAbsoluteVolumeChanged(streamType, info, index);
}
HdmiControlService.java
对于android device而言,由用户针对设备发起的音量调节,有遥控器/语音助手,其中遥控器更常规。如果连接的avr/tv支持avc,发送一个set device volume level message,比连续发几个十几个甚至几十个按键消息有效准确得多。
/**
* Called when AudioService sets the volume level of an absolute volume audio output device
* to a numeric value.
*/
@Override
public void onAudioDeviceVolumeChanged(
@NonNull AudioDeviceAttributes audioDevice,
@NonNull VolumeInfo volumeInfo) {
int localDeviceAddress;
synchronized (mLocalDevice.mLock) {
localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress();
}
sendCecCommand(SetAudioVolumeLevelMessage.build(
localDeviceAddress,
mSystemAudioDevice.getLogicalAddress(),
volumeInfo.getVolumeIndex()),
// If sending the message fails, ask the System Audio device for its
// audio status so that we can update AudioService
(int errorCode) -> {
if (errorCode == SendMessageResult.SUCCESS) {
// Update the volume tracked in our AbsoluteVolumeAudioStatusAction
// so it correctly processes incoming <Report Audio Status> messages
HdmiCecLocalDevice avcDevice = isTvDevice() ? tv() : playback();
avcDevice.updateAvcVolume(volumeInfo.getVolumeIndex());
} else {
sendCecCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(
localDeviceAddress,
mSystemAudioDevice.getLogicalAddress()
));
}
});
}
|