Connect an input device in AAOS

You can use these mechanisms to play audio in Android:

Each mechanism allows for audio playback to be performed in Android. For radio playback or playback from input devices, these options might not suffice, although each could be coupled with audio capture or the MediaRecorder class to first capture the audio and then play it back from Android. For system apps in particular, the following information can used to connect an input device to an output mixer in AAOS.

HwAudioSource player

HwAudioSource connects the audio source device directly to an Android mixer.

Motivations

Several limitations may arise when using a device-to-device or hardware audio patch with Android. Each option is unable to receive media key events such as PLAY, PAUSE, and STOP and, because they circumvent Android's audio stack, each require hardwares to mix the patch into other audio from Android.

Use HwAudioSource

HwAudioSource is a new type of player designed as a software patch. This enables apps that use this player to receive media key events and the output stream to be mixed and routed by Android.

mHwAudioSource = new HwAudioSource.Builder()
                .setAudioDeviceInfo(AudioDeviceInfo: info)
                .setAudioAttributes(new AudioAttributes.Builder()
                        .setUsage(AudioAttributes.USAGE_MEDIA)
                        .build())
                .build();
mHwAudioSource.play();
mHwAudioSource.stop();

Changes to the audio HAL

With this new player, consider these expectations for the audio HAL. For example, device/generic/car/emulator/audio/driver/audio_hw.c.

  • adev_create_audio_patch expects the request to establish an audio patch from a device to a mixer.

  • adev_open_input_stream expects the audio_source to be AUDIO_SOURCE_FM_TUNER.

  • in_read fills the audio buffer with broadcast radio audio data.

We recommend you configure a tuner device with type AUDIO_DEVICE_IN_FM_TUNER in audio_policy_configuration.xml:

<devicePort
    tagName="Tuner_source"
    type="AUDIO_DEVICE_IN_FM_TUNER"
    role="source"
    address="tuner0">
    <profile
        name=""
        format="AUDIO_FORMAT_PCM_16_BIT"
        samplingRates="48000"
        channelMasks="AUDIO_CHANNEL_IN_STEREO"/>
</devicePort>

With this device configuration, you can facilitate finding the FM radio input device by using the AudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS in conjunction with AudioDeviceInfo.TYPE_FM_TUNER.

Create 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 the 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 can switch between Android and FM_TUNER audio through a simple app switch (no explicit media source choice is needed).

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 then associate mixports with the device ports.

Sample configuration

See this sample configuration, 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);
...
}

Radio tuner

When building a radio app, we recommend you use the HwAudioSource as it handles both creating the patch as well as a media session to handle media key events. Multiple audio sources can be created for the same source and audio attributes. It's possible to have one for regular radio usage as well as a second one for traffic announcements.

If recording the FM_TUNER, in Android 11 the permission for doing was changed to android.permission.CAPTURE_AUDIO_OUTPUT. It no longer goes through the OP_RECORD_AUDIO permission check, which applies to microphones only. This shouldn't impact apps since FM_TUNER already requires SYSTEM_API permission for access.

See Implement radio for details about building a radio app.