Volume management

AAOS has its own volume management within CarAudioService. It uses fixed volumes with the expectation that volumes should be applied below the HAL by a hardware amplifier rather than in software. It also organizes output devices into volume groups to apply the same gains to all devices associated with the volume group.

Using fixed volumes

AAOS implementations should control volume using a hardware amplifier instead of a software mixer. To avoid side effects, 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.

Volume groups

Volume groups manage the volumes for a collection of devices within an audio zone. For each volume group the volume can be controlled independently, and the resulting gains are configured on the associated devices to be applied by the vehicle's amplifier. Volume settings are persisted for the user, and are loaded when the user signs in.

Defining volume groups

CarAudioService uses volume groups defined in car_audio_configuration.xml:

<audioZoneConfiguration version="2.0">
    <zones>
        <zone name="primary zone" isPrimary="true">
            <volumeGroups>
                <group>
                    <device address="bus0_media_out">
                        <context context="music"/>
                    </device>
                </group>
                <group>
                    <device address="bus1_navigation_out">
                        <context context="navigation"/>
                    </device>
                    <device address="bus2_voice_command_out">
                        <context context="voice_command"/>
                    </device>
                </group>
                ...
            </volumeGroups>
        </zone>
     </zones>
</audioZoneConfiguration>

Example car_audio_configuration.xml implementation.

Each volume group should contain one or more output devices with associated addresses. These addresses should correspond to output devices defined in audio_policy_configuration.xml.

Configuring volume group gains

Each volume group has a minimum, maximum, and default gain values as well as a step size. These are determined based on the values configured in audio_policy_configuration.xml for the devices associated with the volume group.

<devicePort tagName="bus0_media_out" role="sink" type="AUDIO_DEVICE_OUT_BUS" address="bus0_media_out">
  <profile name="" format="AUDIO_FORMAT_PCM_16_BIT" samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
  <gains>
    <gain name="" mode="AUDIO_GAIN_MODE_JOINT"
      minValueMB="-3200" maxValueMB="600" defaultValueMB="0" stepValueMB="100"/>
  </gains>
</devicePort>

During initialization, the volume group will check the gain values of the associated devices and configure the group as follows:

  • Step size. Must be the same for all devices controlled by the volume group
  • Minimum gain. Smallest minimum gain amongst the devices in the group
  • Maximum gain. Highest maximum gain amongst the devices in the group
  • Default gain. Highest default gain amongst the devices in the group

Because of the way these values are configured, it's possible to set the gain of a volume group outside the range supported for a device associated with the volume group. In this case, for that device the gain will be set to the device's minimum or maximum gain value based on whether the volume group's value is below or above the range.

Volume group identifiers

Volume groups are identified at runtime by their order of definition in the XML file. IDs range from 0 to N-1 within an audio zone, where N is the number of volume groups in that zone. In this way, volume group IDs are not unique across zones. These identifiers are used for CarAudioManager APIs associated with volume groups. Any API that takes in a groupId without a zoneId will default to the primary audio zone.

Multi-zone volume management

Each audio zone is expected to have one or more volume groups, and each volume group is only associated with a single audio zone. This relationship is defined as part of car_audio_configuration.xml. See the example provided in Defining volume groups above.

The current volume levels for each zone are persisted for the user associated with that zone. These settings are zone specific, meaning if a user signs in on a display associated with the primary zone, and then later on signs into a zone associated with a secondary audio zone, the volume levels loaded and persisted for the first zone will be different than those for the secondary zone.

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, set the config_handleVolumeKeysInWindowManager flag to true:

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

Volume key events currently have no way of distinguishing which zone they're intended for, and as such are assumed to all be associated with the primary audio zone. When a volume key event is received, CarAudioService determines which volume group to adjust by fetching the audio contexts for the active players, and then adjusting the volume group that contains the output device associated with the highest priority audio context. The prioritization is determined based on a fixed ordering defined in CarVolume.AUDIO_CONTEXT_VOLUME_PRIORITY.

Fade and balance

Both versions of the AudioControl HAL include APIs for setting fade and balance in the vehicle. There are corresponding system APIs for CarAudioManager that pass values down to the AudioControl HAL. These APIs require android.car.permission.CAR_CONTROL_AUDIO_VOLUME.

The AudioControl APIs are:

  • 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(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.

It is up to OEMs to decide how these values should be applied and how they will be surfaced to users. They could be applied strictly to media or across the board to all Android sounds.

Android 11 also introduced support for applying audio effects to output devices. With this, it's possible to alternatively manage fade and balance via audio effects on the appropriate output devices rather than through these APIs.

Audio ducking

Audio ducking occurs when the vehicle reduces the gain for one stream so that another stream playing at the same time can be heard more clearly. In AAOS, audio ducking is left up to the HAL to implement as there are potentially many sounds outside of Android that the OS has no control over. In Android 11, the main information available to the HAL to make ducking decisions is whether two output devices both have active streams.

When to duck

While it's up to the individual OEM to determine how ducking will be handled by their HAL, there are some general guidelines we recommend. Multiple streams playing within Android will most commonly occur when two apps/services hold audio focus concurrently. With that in mind, see Interaction matrix to learn when Android may grant concurrent focus and, therefore, when it's possible for two different streams to play concurrently.

Keep in mind that any streams mixed together by Android will be done so prior to any gains being applied. As such, any stream that should be ducked when played concurrently with another should be routed to separate output devices so the HAL can apply ducking before mixing them together.

Recommended ducking behavior

The following are potential concurrent interactions where we recommend ducking to be applied:

  • EMERGENCY. Duck or mute everything except SAFETY to ensure the driver hears the sound
  • SAFETY. Duck everything except EMERGENCY to ensure the driver hears the sound
  • NAVIGATION. Duck everything except SAFETY and EMERGENCY
  • CALL. Duck everything except SAFETY, EMERGENCY, and NAVIGATION
  • VOICE. Duck CALL_RING
  • It's up to OEMs to determine the importance of the active VEHICLE_SOUNDS and whether or not they should duck other sounds to ensure the driver hears them.
  • MUSIC and ANNOUNCEMENT should be ducked by everything. The main exception to this are touch interaction tones which currently get played as SYSTEM_SOUND

Other considerations when ducking

Some apps/services such as navigation or assistant might use multiple players to complete their actions. OEMs should avoid unducking too aggressively based on when stream data stops coming through these output devices to ensure the user doesn't momentarily have media return to full volume before being ducked back down as the next playback from the navigation or assistant app begins.

For vehicles with multiple sound stages with good enough isolation, there's also the option to route audio to different areas of the car rather than ducking. For example, navigation instructions could be routed to the driver's headrest speakers while music continues to play throughout the cabin at a normal volume.

Safety critical sounds

While Android 11 introduced HAL audio focus APIs, it's still up to the HAL to ensure safety critical sounds are prioritized over others. Even if the HAL holds audio focus for USAGE_EMERGENCY, that does not guarantee apps and services within Android will not play sounds. It is up to the HAL to determine which streams from Android should be mixed or muted as safety critical sounds are played.

Configuring the volume settings UI

AAOS 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 the 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 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.