音频控制 HAL

Android 9 中引入了音频控制 HAL,可支持与汽车相关的音频用例。从 Android 14 开始,音频控制 HAL 支持:

  • 淡变和平衡
  • HAL 音频焦点请求
  • 设备静音和闪避
  • 音频设备增益变化
  • 音频端口配置更改

图 1 简要概述了车载音频服务架构,其中车载音频服务会与音频控制 HAL 进行通信。

配置多区音频

图 1. 配置多区音频。

音频淡出和平衡

HIDL 音频控制 HAL 版本 1 是在 Android 9 中引入的,用于在汽车用例中支持音频淡出和平衡。与 Android 中已提供的通用音效不同,此机制允许系统应用通过 CarAudioManager API 设置音频平衡和淡出:

class CarAudioManager {
       /**
       *   Adjust the relative volume in the front vs back of the vehicle cabin.
       *
       *   @param value in the range -1.0 to 1.0 for fully toward the back through
       *   fully toward the front. 0.0 means evenly balanced.
       */
       @SystemApi
       @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
       public void setFadeTowardFront(float value);

       /**
       *   Adjust the relative volume on the left vs right side of the vehicle cabin.
       *
       *   @param value in the range -1.0 to 1.0 for fully toward the left through
       *   fully toward the right. 0.0 means evenly balanced.
       */
       @SystemApi
       @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
       public void setBalanceTowardRight(float value);
}

调用这些 API 后,即会从车载音频服务中调用相应的音频控制 HAL API:

interface IAudioControl {
       /**
       *   Control the right/left balance setting of the car speakers.
       */
       oneway setBalanceTowardRight(float value);

       /**
       *   Control the fore/aft fade setting of the car speakers.
       */
       oneway setFadeTowardFront(float value);
}

该 API 适用于所有版本的音频控制 HAL,包括新的 AIDL HAL 接口。

来自 HAL 的音频焦点请求

与 Android 类似,AAOS 依靠应用对音频焦点的积极参与来管理车载音频播放。焦点信息用于管理要进行音量和闪避控制的音频流。因此,为了进一步扩展音频焦点并将汽车特有声音更好地集成到 Android 体验中,Android 11 中引入了以下音频属性:

  • EMERGENCY
  • SAFETY
  • VEHICLE_STATUS
  • ANNOUNCEMENT

除这一变化外,还针对来自 Android 外部的声音添加了一种参与音频焦点请求的机制。因此,我们引入了 HIDL 音频控制 HAL 版本 2,以允许源自 Android 外部的焦点请求:

interface IAudioControl {
       /**
       *   Registers focus listener to be used by HAL for requesting and
       *   abandoning audio focus.
       *   @param listener the listener interface
       *   @return closeHandle A handle to unregister observer.
       */
       registerFocusListener(IFocusListener listener)
       generates (ICloseHandle closeHandle);

       /**
       *   Notifies HAL of changes in audio focus status for focuses requested
       *   or abandoned by the HAL.
       *
       *   @param usage The audio usage associated with the focus change
       *   @param zoneId The identifier for the audio zone that the HAL is
       *   playing the stream in
       *   @param focusChange the AudioFocusChange that has occurred
       */
       oneway onAudioFocusChange(bitfield<AudioUsage> usage, int32_t zoneId,
       bitfield<AudioFocusChange> focusChange);
}

其中,IFocusListener 的定义如下:

interface IFocusListener {
       /**
       *   Called whenever HAL is requesting focus as it is starting to play
       *   audio of a given usage in a specified zone.
       *
       *   @param usage The audio usage associated with the focus request
       *    {@code AttributeUsage}
       *   @param zoneId The identifier for the audio zone where the HAL is
       *    requesting focus
       *   @param focusGain The AudioFocusChange associated with this request.
       */
       oneway requestAudioFocus(bitfield<AudioUsage> usage,
       int32_t zoneId, bitfield<AudioFocusChange> focusGain);
       /**
       *   Called whenever HAL is abandoning focus as it is finished playing audio
       *   of a given usage in a specific zone.
       *
       *   @param usage The audio usage for which the HAL is abandoning focus
       *    {@code AttributeUsage}
       *   @param zoneId The identifier for the audio zone that the HAL
       *    abandoning focus
       */
       oneway abandonAudioFocus(bitfield<AudioUsage> usage, int32_t zoneId);
}

上述 API 可分别用于从 HAL 请求和放弃音频焦点。作为响应,车载音频服务会考虑音频焦点请求,并将结果异步转发给 IAudioControl#onAudioFocusChange 方法。

此 API 还可用于监控源自音频控制 HAL 的音频焦点请求的变化。一般来说,系统会将来自 HAL 的任何持续音频焦点请求视为处于活跃状态,这与来自 Android 的音频焦点请求不同,后者只有处于活跃状态的相应音轨播放才被视为处于活跃状态。

将 HIDL 迁移到 AIDL 音频控制 HAL

随着 AIDL 的出现以及 Android 12 中要求的迁移(如需了解详情,请参阅适用于 HAL 的 AIDL),音频控制 HAL 已迁移到 AIDL。对于现有的 HIDL 音频控制版本 2 API,迁移过程需要对现有方法进行少量更新:

interface IAudioControl {
       /**
       *   Notifies HAL of changes in audio focus status for focuses requested
       *   or abandoned by the HAL.
       *
       *   @param usage The audio usage associated with the focus change
       *        {@code AttributeUsage}. See {@code audioUsage} in
       *        audio_policy_configuration.xsd for the list of allowed values.
       *   @param zoneId The identifier for the audio zone that the HAL is
       *        playing the stream in
       *   @param focusChange the AudioFocusChange that has occurred.
       */
       oneway void onAudioFocusChange(in String usage, in int zoneId,
              in AudioFocusChange focusChange);
       /**
       *   Registers focus listener to be used by HAL for requesting and
       *   abandoning audio focus.
       *   @param listener the listener interface.
       */
       oneway void registerFocusListener(in IFocusListener listener);
       /**
       *   Control the right/left balance setting of the car speakers.
       */
       oneway void setBalanceTowardRight(in float value);
       /**
       *   Control the fore/aft fade setting of the car speakers.
       */
       oneway void setFadeTowardFront(in float value);
}

以及相应的 IFocusListener

       interface IFocusListener {
       /**
       *   Called whenever HAL is abandoning focus as it is finished playing audio
       *   of a given usage in a specific zone.
       *
       *   @param usage The audio usage for which the HAL is abandoning focus
       *        {@code AttributeUsage}. See {@code audioUsage} in
       *        audio_policy_configuration.xsd for the list of allowed values.
       *   @param zoneId The identifier for the audio zone that the HAL
       *        abandoning focus
       */
       oneway void abandonAudioFocus(in String usage, in int zoneId);
       /**
       *   Called whenever HAL is requesting focus as it is starting to play audio
       *        of a given usage in a specified zone.
       *
       *   @param usage The audio usage associated with the focus request
       *        {@code AttributeUsage}. See {@code audioUsage} in
       *        audio_policy_configuration.xsd for the list of allowed values.
       *   @param zoneId The identifier for the audio zone where the HAL is
       *        requesting focus
       *   @param focusGain The AudioFocusChange associated with this request.
       */
       oneway void requestAudioFocus(in String usage, in int zoneId,
              in AudioFocusChange focusGain);
}

音量组静音

Android 12 引入了音量组静音功能,以便在用户的音频互动期间实现更全面的静音控制。这样一来,音频控制 HAL 便可以接收车载音频服务截获的静音事件。

如需启用此功能,OEM 必须在汽车服务 config.xml 中将 audioUseCarVolumeGroupMuting 配置设为 true

<!-- Configuration to enable muting of individual volume groups.
If this is set to false, muting of individual volume groups is disabled,
instead muting will toggle master mute. If this is set to true, car volume
group muting is enabled and each individual volume group can be muted separately. -->
<bool name="audioUseCarVolumeGroupMuting">true</bool>

在 Android 13 之前,必须使用 packages/services/Car/service/res/values/config.xml 的运行时资源叠加层来覆盖配置(要了解详情,请参阅利用资源叠加层自定义 build)。从 Android 13 开始,您可以使用运行时资源叠加层来更改配置值。如需了解详情,请参阅在运行时更改应用资源的值

系统应用可以使用 CarAudioManager#isAudioFeatureEnabled API 确定是否已启用此功能。传入的参数必须是 CarAudioManager.AUDIO_FEATURE_VOLUME_GROUP_MUTING 常量。如果在设备上启用了相应功能,该方法会返回 true,否则返回 false

除了启用 audioUseCarVolumeGroupMuting 功能之外,AIDL 音频控制 HAL 必须实现音量组静音机制:

interface IAudioControl {
       /**
       *   Notifies HAL of changes in output devices that the HAL should apply
       *   muting to.
       *
       *   This will be called in response to changes in audio mute state for each
       *   volume group and will include a {@link MutingInfo} object per audio
       *   zone that experienced a mute state event.
       *
       *   @param mutingInfos an array of {@link MutingInfo} objects for the audio
       *   zones where audio mute state has changed.
       */
       oneway void onDevicesToMuteChange(in MutingInfo[] mutingInfos);
}

其中,静音信息包含音频系统的相关静音信息:

parcelable MutingInfo {
       /**
       *   ID of the associated audio zone
       */
       int zoneId;
       /**
       *   List of addresses for audio output devices that should be muted.
       */
       String[] deviceAddressesToMute;
       /**
       *   List of addresses for audio output devices that were previously be
       *   muted and should now be unmuted.
       */
       String[] deviceAddressesToUnmute;
}

AAOS 有两种不同的静音机制,采用的依据分别如下:

  • 使用音频 KEYCODE_VOLUME_MUTE 的按键事件。

  • 使用车载音频管理器静音 API CarAudioManager#setVolumeGroupMute 直接调用车载音频服务。

启用后,这两种机制都会触发对音频控制 HAL 的通话静音。

车载音频闪避

Android 12 引入了车载音频闪避,以优化对音频流并发播放的控制。这样一来,OEM 可以根据汽车的物理音频配置和当前播放状态(由车载音频服务确定)实现自己的闪避行为。

闪避机制是以音频焦点堆栈变化为基础。每当焦点发生变化时(无论是请求焦点还是放弃焦点),音频控制 HAL 都会收到通知。与汽车音量组静音支持类似,车载音频闪避功能可以通过 audioUseHalDuckingSignals 配置标志启用:

<!-- Configuration to enable IAudioControl#onDevicesToDuckChange API to
inform HAL when to duck. If this is set to true, the API will receive signals
indicating which output devices to duck as well as what usages are currently
holding focus. If set to false, the API will not be called. -->
<bool name="audioUseHalDuckingSignals">true</bool>

如需启用此功能,AIDL 音频控制 HAL 必须使用从车载音频服务接收到的信号实现相关逻辑:

interface IAudioControl {
       /**
       *   Notifies HAL of changes in output devices that the HAL should apply
       *   ducking to.
       *
       *   This will be called in response to changes in audio focus, and will
       *   include a {@link DuckingInfo} object per audio zone that experienced
       *   a change in audo focus.
       *
       *   @param duckingInfos an array of {@link DuckingInfo} objects for the
       *   audio zones where audio focus has changed.
       */
       oneway void onDevicesToDuckChange(in DuckingInfo[] duckingInfos);
}

相关音频系统信息包含在音频闪避信息中:

parcelable DuckingInfo {
       /**
       *   ID of the associated audio zone
       */
       int zoneId;
       /**
       *   List of addresses for audio output devices that should be ducked.
       */
       String[] deviceAddressesToDuck;
       /**
       *   List of addresses for audio output devices that were previously be
       *   ducked and should now be unducked.
       */
       String[] deviceAddressesToUnduck;
       /**
       *   List of usages currently holding focus for this audio zone.
       */
       String[] usagesHoldingFocus;
}

除了要(取消)闪避的设备地址中包含的车载音频配置信息之外,闪避信息还包含有关哪些音频属性用法保持焦点的信息。这些数据旨在告知音频系统哪些音频属性用法处于活跃状态。

之所以需要这样做,因为在车载音频配置中,可以将多个音频属性分配给单个设备,如果没有额外的信息,就不清楚哪些用法处于活跃状态。

AIDL 音频控制 HAL 2.0

为了更新 API 并促进新功能的实现,AIDL 音频控制 HAL 在 Android 13 中已更新至 2.0 版:

  • 使用 PlaybackTrackMetadata 实现音频焦点
  • 音频增益回调

播放元数据在 android.hardware.audio.common 中定义,如下所示:

parcelable PlaybackTrackMetadata {
       AudioUsage usage = INVALID;
       AudioContentType contentType = UNKNOWN;
       float gain;
       AudioChannelLayout channelMask;
       AudioDevice sourceDevice;
       String[] tags;
}

AIDL 音频控制版本 1.0 中的所有其他功能均已保留且可以使用;不过,音频焦点更改方法除外,如关于音频焦点更改方法中所述。

包含播放曲目元数据的音频控制焦点

为了向 HAL 下方的音频系统公开更多信息,更新现在会公开 PlaybackTrackMetadata。具体而言,音频控制 HAL 使用新方法进行了扩展:

interface IAudioControl {
       /**
       *   Notifies HAL of changes in audio focus status for focuses requested
       *   or abandoned by the HAL.
       *
       *   The HAL is not required to wait for a callback of AUDIOFOCUS_GAIN
       *   before playing audio, nor is it required to stop playing audio in the
       *   event of a AUDIOFOCUS_LOSS callback is received.
       *
       *   @param playbackMetaData The output stream metadata associated with
       *    the focus request
       *   @param zoneId The identifier for the audio zone that the HAL is
       *    playing the stream in
       *   @param focusChange the AudioFocusChange that has occurred.
       */
       oneway void onAudioFocusChangeWithMetaData(
       in PlaybackTrackMetadata playbackMetaData, in int zoneId,
       in AudioFocusChange focusChange);
}

IFocusListener 进行了类似的相应更改:

       /**
       *   Called to indicate that the audio output stream associated with
       *   {@link android.hardware.audio.common.PlaybackTrackMetadata} is
       *   abandoning focus as playback has stopped.
       *
       *   @param playbackMetaData The output stream metadata associated with
       *    the focus request
       *   @param zoneId The identifier for the audio zone that the HAL
       *    abandoning focus
       */
       oneway void abandonAudioFocusWithMetaData(
       in PlaybackTrackMetadata playbackMetaData, in int zoneId);
       /**
       *   Called to indicate that the audio output stream associated with
       *   {@link android.hardware.audio.common.PlaybackTrackMetadata} has taken
       *   the focus as playback is starting for the corresponding stream.
       *
       *   @param playbackMetaData The output stream metadata associated with
       *    the focus request
       *   @param zoneId The identifier for the audio zone that the HAL
       *    abandoning focus
       *   @param focusGain The focus type requested.
       */
       oneway void requestAudioFocusWithMetaData(
       in PlaybackTrackMetadata playbackMetaData, in int zoneId,
       in AudioFocusChange focusGain);
}

关于音频焦点更改方法

上述焦点操作的执行方式与 HAL 音频焦点请求中描述的方式相同。只有播放曲目元数据包含更多信息以及音频属性用法。一般来说,除非需要播放曲目元数据提供的额外信息,否则更新后的 Android 控制 HAL 可以继续使用以前的方法。

如果 HAL 开发者决定不支持 IAudioControl#onAudioFocusChangeWithMetaData,该方法应返回包含 UNKNOWN_TRANSACTION 错误的结果,如使用版本化接口方法中所述。

音频服务会先调用 onAudioFocusChangeWithMetaData,如果 UNKNOWN_TRANSACTION 失败,则会使用 onAudioFocusChange 方法重试。

包含播放曲目元数据的车载音频闪避

2.0 版的 AIDL 音频控制 HAL 在音频闪避信息中添加了播放曲目元数据:

parcelable DuckingInfo {
       /**
       *   ID of the associated audio zone
       */
       int zoneId;
       /**
       *   List of addresses for audio output devices that should be ducked.
       */
       String[] deviceAddressesToDuck;
       /**
       *   List of addresses for audio output devices that were previously be
       *   ducked and should now be unducked.
       */
       String[] deviceAddressesToUnduck;
       /**
       *   List of usages currently holding focus for this audio zone.
       */
       String[] usagesHoldingFocus;
       /**
       *   List of output stream metadata associated with the current focus
       *   holder for this audio zone
       */
       @nullable PlaybackTrackMetadata[] playbackMetaDataHoldingFocus;
}

废弃了 usagesHoldingFocus。开发者现在应使用 playbackMetaDataHoldingFocus 来确定音频属性用法和其他音频信息。也就是说,usagesHoldingFocus 参数仍然包含所需的信息,直到该选项被正式移除。

音频增益回调

在 Android 13 中,为了让 AAOS 更清楚地了解 HAL 下方的音频变化,我们添加了一种机制,您可以使用这种机制将车载音频系统的音频增益变化传达给车载音频服务。该机制会暴露音频增益音量指数的变化,并提供增益发生变化的对应原因:

  • 阻塞或静音限制
  • 功能限制
  • 衰减限制

这些更改将 HAL 以下的这些限制暴露给车载音频服务,最终暴露给系统界面应用以通知用户。后一部分(即暴露给可能的系统界面)在 Android 14 中得到了进一步扩展,使系统界面应用能够通过音量组信息回调机制更轻松地获取此信息。

音频控制 HAL API 注册增益回调的方法如下:

interface IAudioControl {
       /**
       *   Registers callback to be used by HAL for reporting unexpected gain(s)
       *    changed and the reason(s) why.
       *
       *   @param callback The {@link IAudioGainCallback}.
       */
       oneway void registerGainCallback(in IAudioGainCallback callback);
}

IAudioGainCallback 的定义如下:

interface IAudioGainCallback {
       /**
       *   Used to indicate that one or more audio device port gains have changed,
       *   i.e. initiated by HAL, not by CarAudioService.
       *   This is the counter part of the
       *   {@link onDevicesToDuckChange}, {@link onDevicesToMuteChange} and,
       *   {@link setAudioDeviceGainsChanged} APIs.
       *
       *   @param reasons List of reasons that triggered the given gains changed.
       *   @param gains List of gains affected by the change.
       */
       void onAudioDeviceGainsChanged(in Reasons[] reasons,
       in AudioGainConfigInfo[] gains);
}

正如 API 文档中强调的那样,增益回调由车载音频服务注册到音频控制 HAL。当音频控制 HAL 调用 API 时,车载音频服务会响应相应的操作(如阻塞、限制或衰减增益指数)。

HAL 会确定何时调用 API,主要是为了报告增益指数状态的变化。根据法规要求,车载音频系统应执行必要的操作,并使用回调向车载音频服务报告信息以供用户使用。例如,向用户显示界面。

AIDL 音频控制 HAL 3.0

Android 14 AIDL 音频控制 HAL 版本已更新至 3.0 版,以更新 API 来提供更强大的音频增益指数功能。音频控制 HAL API 允许音频服务设置和取消设置 IModuleChangeCallback

interface IAudioControl {
       /**
       *   Sets callback with HAL for notifying changes to hardware module
       *   (that is: {@link android.hardware.audio.core.IModule}) configurations.
       *
       *   @param callback The {@link IModuleChangeCallback} interface to use
       *    use when new updates are available for
       */
       void setModuleChangeCallback(in IModuleChangeCallback callback);
       /**
       *   Clears module change callback
       */
       void clearModuleChangeCallback();
}

setModuleChangeCallback 会在车载音频服务启动时注册或从错误中恢复时注册。例如,车载音频服务收到的音频控制 HAL binder 终止通知。调用该 API 时,音频控制 HAL 实现应替换任何现有的模块更改回调。

对于 clearModuleChangeCallback API,该实现应清除现有的回调;如果不存在回调,则不执行任何操作。音频控制实现最好为回调注册终止观察器,然后在触发 binder 终止时清除回调。

IModuleChangeCallback 的定义如下:

oneway interface IModuleChangeCallback {
       /**
       *   Used to indicate that one or more {@link AudioPort} configs have
       *   changed. Implementations MUST return at least one AudioPort.
       *
       *   @param audioPorts list of {@link AudioPort} that are updated
       */
       void onAudioPortsChanged(in AudioPort[] audioPorts);
}

当车载音频服务注册模块更改回调后,它就可以通过 onAudioPortChanged API 接收音频端口更改了。该 API 可用于在注册回调后立即初始化音频系统的音量增益。对于其他动态增益变化,可以随时调用该 API。系统会应用相应的更改,车载音频服务也会随之更新。