Implementing the AudioControl HAL

Android 9 deprecates the AUDIO_* properties in previous iterations of the Vehicle HAL and replaces them with a dedicated Audio Control HAL that includes explicit function calls and typed parameter lists.

This new HAL exposes IAudioControl as the primary interface object that provides entry points to interact with the vehicle's audio engine for configuration and volume control. The system can contain exactly one instance of this object, which is created by CarAudioService when it starts up. This object is an automotive extension of the traditional Android Audio HAL; in most implementations, the same process that publishes the Audio HAL interfaces should also publish the IAudioControl interfaces.

Supported interfaces

The AudioControl HAL supports the following interfaces:

  • getBusforContext. Called at startup once per context to get the mapping from ContextNumber to busAddress. Example usage:
    getBusForContext(ContextNumber contextNumber)
        generates (uint32_t busNumber);
    
    Enables the vehicle to tell the framework where to route the physical output stream for each context. For every context, a valid bus number (0 - num busses-1) must be returned. If an unrecognized contextNumber is encountered, -1 shall be returned. Any context for which an invalid busNumber is returned will be routed to bus 0.

    Any concurrent sounds associated with the same busNumber via this mechanism will be mixed by the Android AudioFlinger before being delivered as a single stream to the Audio HAL. This supersedes the Vehicle HAL properties AUDIO_HW_VARIANT and AUDIO_ROUTING_POLICY.
  • setBalanceTowardRight. Control the right/left balance setting of vehicle speakers. Example usage:
    setBalanceTowardRight(float value);
    
    Shifts the speaker volume toward the right (+) or left (-) side of the car. 0.0 is centered, +1.0 is fully right, -1.0 is fully left, and a value outside the range -1 to 1 is an error.
  • setFadeTowardFront. Control the fore/aft fade setting of vehicle speakers. Example usage:
    setFadeTowardFront(float value);
    
    Shifts the speaker volume toward the front (+) or back (-) of the car. 0.0 is centered, +1.0 is fully forward, -1.0 is fully rearward, and a value outside the range -1 to 1 is an error.

Configuring volume

Android automotive implementations should control volume using a hardware amplifier instead of a software mixer. To avoid side effects, in device/generic/car/emulator/audio/overlay/frameworks/base/core/res/res/values/config.xml, set the config_useFixedVolume flag to true (overlay as necessary):

<resources>
    <!-- Car uses hardware amplifier for volume. -->
    <bool name="config_useFixedVolume">true</bool>
</resources>

When the config_useFixedVolume flag is not set (or set to false), applications can call AudioManager.setStreamVolume() and change the volume by stream type in the software mixer. This may be undesirable because of the potential effect on other applications and the fact that volume attenuation in the software mixer results in fewer significant bits available in the signal when received at the hardware amplifier.

Configuring volume groups

CarAudioService uses volume groups defined in packages/services/Car/service/res/xml/car_volume_group.xml. You can override this file to redefine volume groups as necessary. Groups are identified at runtime by their order of definition in the XML file. IDs range from 0 to N-1, where N is the number of volume groups. Example:

<volumeGroups xmlns:car="http://schemas.android.com/apk/res-auto">
    <group>
        <context car:context="music"/>
        <context car:context="call_ring"/>
        <context car:context="notification"/>
        <context car:context="system_sound"/>
    </group>
    <group>
        <context car:context="navigation"/>
        <context car:context="voice_command"/>
    </group>
    <group>
        <context car:context="call"/>
    </group>
    <group>
        <context car:context="alarm"/>
    </group>
</volumeGroups>

The attributes used in this configuration are defined in packages/services/Car/service/res/values/attrs.xml.

Handling volume key events

Android defines several keycodes for volume control, including KEYCODE_VOLUME_UP, KEYCODE_VOLUME_DOWN, and KEYCODE_VOLUME_MUTE. By default, Android routes the volume key events to applications. Automotive implementations should force these key events to CarAudioService, which can then call setGroupVolume or setMasterMute as appropriate.

To force this behavior, in device/generic/car/emulator/car/overlay/frameworks/base/core/res/res/values/config.xml, set the config_handleVolumeKeysInWindowManager flag to true:

<resources>
    <bool name="config_handleVolumeKeysInWindowManager">true</bool>
</resources>

CarAudioManager API

The CarAudioManager uses CarAudioService to configure and control vehicle audio systems. The manager is invisible to most apps in the system, but vehicle-specific components, such as a volume controller, can use the CarAudioManager API to interact with the system.

The following sections describe Android 9 changes to the CarAudioManager API.

Deprecated APIs

Android 9 handles device enumeration through the existing AudioManager getDeviceList API, so the following vehicle-specific functions have been deprecated and removed:

  • String[] getSupportedExternalSourceTypes()
  • String[] getSupportedRadioTypes()

Android 9 handles volume using AudioAttributes.AttributeUsage or volume group-based entry points, so the following APIs that rely on streamType have been removed:

  • void setStreamVolume(int streamType, int index, int flags)
  • int getStreamMaxVolume(int streamType)
  • int getStreamMinVolume(int streamType)
  • void setVolumeController(IVolumeController controller)

New APIs

Android 9 adds the following new APIs for controlling amplifier hardware (explicitly based on volume groups):

  • int getVolumeGroupIdForUsage(@AudioAttributes.AttributeUsage int usage)
  • int getVolumeGroupCount()
  • int getGroupVolume(int groupId)
  • int getGroupMaxVolume(int groupId)
  • int getGroupMinVolume(int groupId)

In addition, Android 9 provides the following new system APIs for use by System GUI:

  • void setGroupVolume(int groupId, int index, int flags)
  • void registerVolumeChangeObserver(@NonNull ContentObserver observer)
  • void unregisterVolumeChangeObserver(@NonNull ContentObserver observer)
  • void registerVolumeCallback(@NonNull IBinder binder)
  • void unregisterVolumeCallback(@NonNull IBinder binder)
  • void setFadeToFront(float value)
  • Void setBalanceToRight(float value)

Finally, Android 9 adds new APIs for external source management. These are intended primarily to support audio routing from external sources to the output buses based on media type. They can also potentially enable third-party application access to external devices.

  • String[] getExternalSources(). Returns an array of addresses identifying the available audio ports in the system of type AUX_LINE, FM_TUNER, TV_TUNER, and BUS_INPUT.
  • CarPatchHandle createAudioPatch(String sourceAddress, int carUsage). Routes the source addresses to the output BUS associated with the provided carUsage.
  • int releaseAudioPatch(CarPatchHandle patch). Removes the provided patch. If the creator of the CarPatchHandle dies unexpectedly, this is handled automatically by AudioPolicyService::removeNotificationClient().

Creating audio patches

You can create an audio patch between two audio ports, either a mix port or a device port. Typically, an audio patch from mix port to device port is for playback while the reverse direction is for capture.

For example, an audio patch that routes audio samples from FM_TUNER source directly to media sink bypasses the software mixer. You must then use a hardware mixer to mix the audio samples from Android and FM_TUNER for the sink. When creating an audio patch directly from FM_TUNER source to the media sink:

  • Volume control applies to the media sink and should affect both the Android and FM_TUNER audio.
  • Users should be able to switch between Android and FM_TUNER audio via a simple app switch (no explicit media source choice should be necessary).

Automotive implementations might also need to create an audio patch between two device ports. To do so, you must first declare the device ports and possible routes in audio_policy_configuration.xml and associate mixports with these device ports.

Example configuration

See also device/generic/car/emulator/audio/audio_policy_configuration.xml.

<audioPolicyConfiguration>
    <modules>
        <module name="primary" halVersion="3.0">
            <attachedDevices>
                <item>bus0_media_out</item>
                <item>bus1_audio_patch_test_in</item>
            </attachedDevices>
            <mixPorts>
                <mixPort name="mixport_bus0_media_out" role="source"
                        flags="AUDIO_OUTPUT_FLAG_PRIMARY">
                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                            samplingRates="48000"
                            channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
                </mixPort>
                <mixPort name="mixport_audio_patch_in" role="sink">
                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                           samplingRates="48000"
                           channelMasks="AUDIO_CHANNEL_IN_STEREO"/>
                </mixPort>
            </mixPorts>
            <devicePorts>
                <devicePort tagName="bus0_media_out" role="sink" type="AUDIO_DEVICE_OUT_BUS"
                        address="bus0_media_out">
                    <profile balance="" format="AUDIO_FORMAT_PCM_16_BIT"
                            samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
                    <gains>
                        <gain name="" mode="AUDIO_GAIN_MODE_JOINT"
                                minValueMB="-8400" maxValueMB="4000" defaultValueMB="0" stepValueMB="100"/>
                    </gains>
                </devicePort>
                <devicePort tagName="bus1_audio_patch_test_in" type="AUDIO_DEVICE_IN_BUS" role="source"
                        address="bus1_audio_patch_test_in">
                    <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                            samplingRates="48000" channelMasks="AUDIO_CHANNEL_IN_STEREO"/>
                    <gains>
                        <gain name="" mode="AUDIO_GAIN_MODE_JOINT"
                                minValueMB="-8400" maxValueMB="4000" defaultValueMB="0" stepValueMB="100"/>
                    </gains>
                </devicePort>
            </devicePorts>
            <routes>
                <route type="mix" sink="bus0_media_out" sources="mixport_bus0_media_out,bus1_audio_patch_test_in"/>
                <route type="mix" sink="mixport_audio_patch_in" sources="bus1_audio_patch_test_in"/>
            </routes>
        </module>
    </modules>
</audioPolicyConfiguration>

Audio driver API

You can use getExternalSources() to retrieve a list of available sources (identified by address), then create audio patches between these sources and the sink ports by audio usages. The corresponding entry points on the Audio HAL appear in IDevice.hal:

Interface IDevice {
...
/**
* Creates an audio patch between several source and sink ports.  The handle
* is allocated by the HAL and must be unique for this audio HAL module.
*
* @param sources patch sources.
* @param sinks patch sinks.
* @return retval operation completion status.
* @return patch created patch handle.
*/
createAudioPatch(vec<AudioPortConfig> sources, vec<AudioPortConfig> sinks)
       generates (Result retval, AudioPatchHandle patch);

/**
* Release an audio patch.
*
* @param patch patch handle.
* @return retval operation completion status.
*/
releaseAudioPatch(AudioPatchHandle patch) generates (Result retval);
...
}

Configuring the volume settings UI

Android 9 decouples the volume settings UI from volume group configuration (which can be overlaid as described in Configuring volume groups). This separation ensures that no changes are required if the volume groups configuration changes in the future.

In car settings UI, the packages/apps/Car/Settings/res/xml/car_volume_items.xml file contains UI elements (title and icon resources) associated with each defined AudioAttributes.USAGE. This file provides for a reasonable rendering of the defined VolumeGroups by using resources associated with the first recognized usage contained in each VolumeGroup.

For example, the following example defines a VolumeGroup as including both voice_communication and voice_communication_signalling. The default implementation of the car settings UI renders the VolumeGroup using the resources associated with voice_communication as that is the first match in the file.

<carVolumeItems xmlns:car="http://schemas.android.com/apk/res-auto">
    <item car:usage="voice_communication"
          car:title="@*android:string/volume_call"
          car:icon="@*android:drawable/ic_audio_ring_notif"/>
    <item car:usage="voice_communication_signalling"
          car:title="@*android:string/volume_call"
          car:icon="@*android:drawable/ic_audio_ring_notif"/>
    <item car:usage="media"
          car:title="@*android:string/volume_music"
          car:icon="@*android:drawable/ic_audio_media"/>
    <item car:usage="game"
          car:title="@*android:string/volume_music"
          car:icon="@*android:drawable/ic_audio_media"/>
    <item car:usage="alarm"
          car:title="@*android:string/volume_alarm"
          car:icon="@*android:drawable/ic_audio_alarm"/>
    <item car:usage="assistance_navigation_guidance"
          car:title="@string/navi_volume_title"
          car:icon="@drawable/ic_audio_navi"/>
    <item car:usage="notification_ringtone"
          car:title="@*android:string/volume_ringtone"
          car:icon="@*android:drawable/ic_audio_ring_notif"/>
    <item car:usage="assistant"
          car:title="@*android:string/volume_unknown"
          car:icon="@*android:drawable/ic_audio_vol"/>
    <item car:usage="notification"
          car:title="@*android:string/volume_notification"
          car:icon="@*android:drawable/ic_audio_ring_notif"/>
    <item car:usage="notification_communication_request"
          car:title="@*android:string/volume_notification"
          car:icon="@*android:drawable/ic_audio_ring_notif"/>
    <item car:usage="notification_communication_instant"
          car:title="@*android:string/volume_notification"
          car:icon="@*android:drawable/ic_audio_ring_notif"/>
    <item car:usage="notification_communication_delayed"
          car:title="@*android:string/volume_notification"
          car:icon="@*android:drawable/ic_audio_ring_notif"/>
    <item car:usage="notification_event"
          car:title="@*android:string/volume_notification"
          car:icon="@*android:drawable/ic_audio_ring_notif"/>
    <item car:usage="assistance_accessibility"
          car:title="@*android:string/volume_notification"
          car:icon="@*android:drawable/ic_audio_ring_notif"/>
    <item car:usage="assistance_sonification"
          car:title="@*android:string/volume_unknown"
          car:icon="@*android:drawable/ic_audio_vol"/>
    <item car:usage="unknown"
          car:title="@*android:string/volume_unknown"
          car:icon="@*android:drawable/ic_audio_vol"/>
</carVolumeItems>

The attributes and values used in the above configuration are declared in packages/apps/Car/Settings/res/values/attrs.xml. The volume settings UI uses the following VolumeGroup-based CarAudioManager APIs:

  • getVolumeGroupCount() to know how many controls should be drawn.
  • getGroupMinVolume() and getGroupMaxVolume() to get lower and upper bounds.
  • getGroupVolume() to get the current volume.
  • registerVolumeChangeObserver() to get notified on volume changes.