mirror of
https://github.com/Evolution-X/hardware_interfaces
synced 2026-02-01 16:09:42 +00:00
Merge "audio HAL: Support for external device connections"
This commit is contained in:
@@ -75,6 +75,7 @@ aidl_interface {
|
||||
"android/hardware/audio/core/IModule.aidl",
|
||||
"android/hardware/audio/core/IStreamIn.aidl",
|
||||
"android/hardware/audio/core/IStreamOut.aidl",
|
||||
"android/hardware/audio/core/ModuleDebug.aidl",
|
||||
],
|
||||
imports: [
|
||||
"android.hardware.audio.common-V1",
|
||||
@@ -86,6 +87,4 @@ aidl_interface {
|
||||
platform_apis: true,
|
||||
},
|
||||
},
|
||||
versions: [
|
||||
],
|
||||
}
|
||||
|
||||
@@ -34,11 +34,15 @@
|
||||
package android.hardware.audio.core;
|
||||
@VintfStability
|
||||
interface IModule {
|
||||
void setModuleDebug(in android.hardware.audio.core.ModuleDebug debug);
|
||||
android.media.audio.common.AudioPort connectExternalDevice(in android.media.audio.common.AudioPort templateIdAndAdditionalData);
|
||||
void disconnectExternalDevice(int portId);
|
||||
android.hardware.audio.core.AudioPatch[] getAudioPatches();
|
||||
android.media.audio.common.AudioPort getAudioPort(int portId);
|
||||
android.media.audio.common.AudioPortConfig[] getAudioPortConfigs();
|
||||
android.media.audio.common.AudioPort[] getAudioPorts();
|
||||
android.hardware.audio.core.AudioRoute[] getAudioRoutes();
|
||||
android.hardware.audio.core.AudioRoute[] getAudioRoutesForAudioPort(int portId);
|
||||
android.hardware.audio.core.IStreamIn openInputStream(int portConfigId, in android.hardware.audio.common.SinkMetadata sinkMetadata);
|
||||
android.hardware.audio.core.IStreamOut openOutputStream(int portConfigId, in android.hardware.audio.common.SourceMetadata sourceMetadata, in @nullable android.media.audio.common.AudioOffloadInfo offloadInfo);
|
||||
android.hardware.audio.core.AudioPatch setAudioPatch(in android.hardware.audio.core.AudioPatch requested);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
|
||||
// two cases:
|
||||
// 1). this is a frozen version file - do not edit this in any case.
|
||||
// 2). this is a 'current' file. If you make a backwards compatible change to
|
||||
// the interface (from the latest frozen version), the build system will
|
||||
// prompt you to update this file with `m <name>-update-api`.
|
||||
//
|
||||
// You must not make a backward incompatible change to any AIDL file built
|
||||
// with the aidl_interface module type with versions property set. The module
|
||||
// type is used to build AIDL files in a way that they can be used across
|
||||
// independently updatable components of the system. If a device is shipped
|
||||
// with such a backward incompatible change, it has a high risk of breaking
|
||||
// later when a module using the interface is updated, e.g., Mainline modules.
|
||||
|
||||
package android.hardware.audio.core;
|
||||
@JavaDerive(equals=true, toString=true) @VintfStability
|
||||
parcelable ModuleDebug {
|
||||
boolean simulateDeviceConnections;
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import android.hardware.audio.core.AudioPatch;
|
||||
import android.hardware.audio.core.AudioRoute;
|
||||
import android.hardware.audio.core.IStreamIn;
|
||||
import android.hardware.audio.core.IStreamOut;
|
||||
import android.hardware.audio.core.ModuleDebug;
|
||||
import android.media.audio.common.AudioOffloadInfo;
|
||||
import android.media.audio.common.AudioPort;
|
||||
import android.media.audio.common.AudioPortConfig;
|
||||
@@ -43,6 +44,116 @@ import android.media.audio.common.AudioPortConfig;
|
||||
*/
|
||||
@VintfStability
|
||||
interface IModule {
|
||||
/**
|
||||
* Sets debugging configuration for the HAL module. This method is only
|
||||
* called during xTS testing and is intended for validating the aspects of
|
||||
* the HAL module behavior that would otherwise require human intervention.
|
||||
*
|
||||
* The HAL module must throw an error if there is an attempt to change
|
||||
* the debug behavior for the aspect which is currently in use.
|
||||
*
|
||||
* @param debug The debug options.
|
||||
* @throws EX_ILLEGAL_STATE If the flag(s) being changed affect functionality
|
||||
* which is currently in use.
|
||||
*/
|
||||
void setModuleDebug(in ModuleDebug debug);
|
||||
|
||||
/**
|
||||
* Set a device port of an external device into connected state.
|
||||
*
|
||||
* This method is used to inform the HAL module that an external device has
|
||||
* been connected to a device port selected using the 'id' field of the
|
||||
* input AudioPort parameter. This device port must have dynamic profiles
|
||||
* (an empty list of profiles). This port is further referenced to as "port
|
||||
* template" because it acts as a template for creating a new instance of a
|
||||
* "connected" device port which gets returned from this method.
|
||||
*
|
||||
* The input AudioPort parameter may contain any additional data obtained by
|
||||
* the system side from other subsystems. The nature of data depends on the
|
||||
* type of the connection. For example, for point-to-multipoint external
|
||||
* device connections, the input parameter may contain the address of the
|
||||
* connected external device. Another example is EDID information for HDMI
|
||||
* connections (ExtraAudioDescriptor), which can be provided by the HDMI-CEC
|
||||
* HAL module.
|
||||
*
|
||||
* It is the responsibility of the HAL module to query audio profiles
|
||||
* supported by the connected device and return them as part of the returned
|
||||
* AudioPort instance. In the case when the HAL is unable to query the
|
||||
* external device, an error must be thrown.
|
||||
*
|
||||
* Thus, the returned audio port instance is the result of combining the
|
||||
* following information:
|
||||
* - a unique port ID generated by the HAL module;
|
||||
* - static information from the port template;
|
||||
* - list of audio profiles supported by the connected device;
|
||||
* - additional data from the input AudioPort parameter.
|
||||
*
|
||||
* The HAL module must also update the list of audio routes to include the
|
||||
* ID of the instantiated connected device port. Normally, the connected
|
||||
* port allows the same routing as the port template.
|
||||
*
|
||||
* Also see notes on 'ModuleDebug.simulateDeviceConnections'.
|
||||
*
|
||||
* The following protocol is used by HAL module client for handling
|
||||
* connection of an external device:
|
||||
* 1. Obtain the list of device ports and their IDs via 'getAudioPorts'
|
||||
* method. Select the appropriate port template using
|
||||
* AudioDeviceDescription ('ext.device' field of AudioPort).
|
||||
* 2. Combine the ID of the port template with any additional data and call
|
||||
* 'connectExternalDevice'. The HAL module returns a new instance of
|
||||
* AudioPort created using the rules explained above. Both
|
||||
* 'getAudioPort' and 'getAudioPorts' methods will be returning the same
|
||||
* information for this port until disconnection.
|
||||
* 3. Configure the connected port with one of supported profiles using
|
||||
* 'setAudioPortConfig'.
|
||||
* 4. Query the list of AudioRoutes for the new AudioPort using
|
||||
* 'getAudioRoutesForAudioPort' or 'getAudioRoutes' methods.
|
||||
*
|
||||
* External devices are distinguished by the connection type and device
|
||||
* address. Calling this method multiple times to inform about connection of
|
||||
* the same external device without disconnecting it first is an error.
|
||||
*
|
||||
* The HAL module must perform validation of the input parameter and throw
|
||||
* an error if it is lacking required information, for example, when no
|
||||
* device address is specified for a point-to-multipoint external device
|
||||
* connection.
|
||||
*
|
||||
* Handling of a disconnect is done in a reverse order:
|
||||
* 1. Reset port configuration using the 'resetAudioPortConfig' method.
|
||||
* 2. Release the connected device port by calling the 'disconnectExternalDevice'
|
||||
* method. This also removes the audio routes associated with this
|
||||
* device port.
|
||||
*
|
||||
* @return New instance of an audio port for the connected external device.
|
||||
* @param templateIdAndAdditionalData Specifies port template ID and any
|
||||
* additional data.
|
||||
* @throws EX_ILLEGAL_ARGUMENT In the following cases:
|
||||
* - If the template port can not be found by the ID.
|
||||
* - If the template is not a device port, or
|
||||
* it does not have dynamic profiles.
|
||||
* - If the input parameter is lacking required
|
||||
* information.
|
||||
* @throws EX_ILLEGAL_STATE In the following cases:
|
||||
* - If the HAL module is unable to query audio profiles.
|
||||
* - If the external device has already been connected.
|
||||
*/
|
||||
AudioPort connectExternalDevice(in AudioPort templateIdAndAdditionalData);
|
||||
|
||||
/**
|
||||
* Set a device port of a an external device into disconnected state.
|
||||
*
|
||||
* This method is used to inform the HAL module that an external device has
|
||||
* been disconnected. The 'portId' must be of a connected device port
|
||||
* instance previously instantiated using the 'connectExternalDevice'
|
||||
* method.
|
||||
*
|
||||
* @throws EX_ILLEGAL_ARGUMENT In the following cases:
|
||||
* - If the port can not be found by the ID.
|
||||
* - If this is not a connected device port.
|
||||
* @throws EX_ILLEGAL_STATE If the port has active configurations.
|
||||
*/
|
||||
void disconnectExternalDevice(int portId);
|
||||
|
||||
/**
|
||||
* Return all audio patches of this module.
|
||||
*
|
||||
@@ -57,12 +168,10 @@ interface IModule {
|
||||
* Return the current state of the audio port.
|
||||
*
|
||||
* Using the port ID provided on input, returns the current state of the
|
||||
* audio port. For device port representing a connection to some external
|
||||
* device, e.g. over HDMI or USB, currently supported audio profiles and
|
||||
* extra audio descriptors may change.
|
||||
*
|
||||
* For all other audio ports it must be the same configuration as returned
|
||||
* for this port ID by 'getAudioPorts'.
|
||||
* audio port. The values of the AudioPort structure must be the same as
|
||||
* currently returned by the 'getAudioPorts' method. The 'getAudioPort'
|
||||
* method is provided to reduce overhead in the case when the client needs
|
||||
* to check the state of one port only.
|
||||
*
|
||||
* @return The current state of an audio port.
|
||||
* @param portId The ID of the audio port.
|
||||
@@ -87,32 +196,44 @@ interface IModule {
|
||||
AudioPortConfig[] getAudioPortConfigs();
|
||||
|
||||
/**
|
||||
* Return all audio ports provided by this module.
|
||||
* Return the current state of all audio ports provided by this module.
|
||||
*
|
||||
* Returns a list of all mix ports and device ports provided by this
|
||||
* module. Each returned port must have a unique ID within this module
|
||||
* ('AudioPort.id' field). The returned list must not change during
|
||||
* the lifetime of the IModule instance. For audio ports with dynamic
|
||||
* profiles (changing depending on external devices being connected
|
||||
* to the system) an empty list of profiles must be returned. The list
|
||||
* of currently supported audio profiles is obtained from 'getAudioPort'
|
||||
* method.
|
||||
* Returns a list of all mix ports and device ports provided by this HAL
|
||||
* module, reflecting their current states. Each returned port must have a
|
||||
* unique ID within this module ('AudioPort.id' field). The list also
|
||||
* includes "connected" ports created using 'connectExternalDevice' method.
|
||||
*
|
||||
* @return The list of audio ports.
|
||||
*/
|
||||
AudioPort[] getAudioPorts();
|
||||
|
||||
/**
|
||||
* Return all audio routes of this module.
|
||||
* Return all current audio routes of this module.
|
||||
*
|
||||
* Returns a list of audio routes, that is, allowed connections between
|
||||
* audio ports. The returned list must not change during the lifetime of the
|
||||
* IModule instance.
|
||||
* Returns the current list of audio routes, that is, allowed connections
|
||||
* between audio ports. The list can change when new device audio ports
|
||||
* get created as a result of connecting or disconnecting of external
|
||||
* devices.
|
||||
*
|
||||
* @return The list of audio routes.
|
||||
*/
|
||||
AudioRoute[] getAudioRoutes();
|
||||
|
||||
/**
|
||||
* Return audio routes related to the specified audio port.
|
||||
*
|
||||
* Returns the list of audio routes that include the specified port ID
|
||||
* as a source or as a sink. The returned list is a subset of the result
|
||||
* returned by the 'getAudioRoutes' method, filtered by the port ID.
|
||||
* An empty list can be returned, indicating that the audio port can not
|
||||
* be used for creating audio patches.
|
||||
*
|
||||
* @return The list of audio routes.
|
||||
* @param portId The ID of the audio port.
|
||||
* @throws EX_ILLEGAL_ARGUMENT If the port can not be found by the ID.
|
||||
*/
|
||||
AudioRoute[] getAudioRoutesForAudioPort(int portId);
|
||||
|
||||
/**
|
||||
* Open an input stream using an existing audio mix port configuration.
|
||||
*
|
||||
@@ -230,6 +351,10 @@ interface IModule {
|
||||
* parameter. The framework can then set the suggested configuration on a
|
||||
* subsequent retry call to this method.
|
||||
*
|
||||
* Device ports with dynamic audio profiles (an empty list of profiles)
|
||||
* can not be used with this method. The list of profiles must be filled in
|
||||
* as a result of calling 'connectExternalDevice' method.
|
||||
*
|
||||
* @return Whether the requested configuration has been applied.
|
||||
* @param requested Requested audio port configuration.
|
||||
* @param suggested Same as requested configuration, if it was applied.
|
||||
@@ -241,7 +366,7 @@ interface IModule {
|
||||
* - If the port can not be found by the port ID.
|
||||
* - If it is not possible to generate a suggested port
|
||||
* configuration, for example, if the port only has dynamic
|
||||
* profiles and they are currently empty.
|
||||
* profiles.
|
||||
*/
|
||||
boolean setAudioPortConfig(in AudioPortConfig requested, out AudioPortConfig suggested);
|
||||
|
||||
|
||||
38
audio/aidl/android/hardware/audio/core/ModuleDebug.aidl
Normal file
38
audio/aidl/android/hardware/audio/core/ModuleDebug.aidl
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.hardware.audio.core;
|
||||
|
||||
/**
|
||||
* This structure contains flags used for enabling various debugging aspects
|
||||
* in a HAL module. By default, all debugging aspects are turned off. They
|
||||
* can be enabled during xTS tests for functionality that, for example, would
|
||||
* otherwise require human intervention (e.g. connection of external devices).
|
||||
*/
|
||||
@JavaDerive(equals=true, toString=true)
|
||||
@VintfStability
|
||||
parcelable ModuleDebug {
|
||||
/**
|
||||
* When set to 'true', HAL module must simulate connection of external
|
||||
* devices. An external device becomes 'connected' after a call to
|
||||
* IModule.connectExternalDevice, simulation of connection requires:
|
||||
* - provision of at least one non-dynamic device port profile on
|
||||
* connection (as if it was retrieved from a connected device);
|
||||
* - simulating successful application of port configurations for reported
|
||||
* profiles.
|
||||
*/
|
||||
boolean simulateDeviceConnections;
|
||||
}
|
||||
@@ -13,6 +13,7 @@ cc_library_static {
|
||||
shared_libs: [
|
||||
"libbase",
|
||||
"libbinder_ndk",
|
||||
"android.media.audio.common.types-V1-ndk",
|
||||
"android.hardware.audio.core-V1-ndk",
|
||||
],
|
||||
export_include_dirs: ["include"],
|
||||
@@ -36,6 +37,7 @@ cc_binary {
|
||||
shared_libs: [
|
||||
"libbase",
|
||||
"libbinder_ndk",
|
||||
"android.media.audio.common.types-V1-ndk",
|
||||
"android.hardware.audio.core-V1-ndk",
|
||||
],
|
||||
static_libs: [
|
||||
|
||||
@@ -16,14 +16,15 @@
|
||||
|
||||
#include <aidl/android/media/audio/common/AudioChannelLayout.h>
|
||||
#include <aidl/android/media/audio/common/AudioDeviceType.h>
|
||||
#include <aidl/android/media/audio/common/AudioFormatDescription.h>
|
||||
#include <aidl/android/media/audio/common/AudioFormatType.h>
|
||||
#include <aidl/android/media/audio/common/AudioIoFlags.h>
|
||||
#include <aidl/android/media/audio/common/AudioOutputFlags.h>
|
||||
|
||||
#include "aidl/android/media/audio/common/AudioFormatDescription.h"
|
||||
#include "core-impl/Configuration.h"
|
||||
|
||||
using aidl::android::media::audio::common::AudioChannelLayout;
|
||||
using aidl::android::media::audio::common::AudioDeviceDescription;
|
||||
using aidl::android::media::audio::common::AudioDeviceType;
|
||||
using aidl::android::media::audio::common::AudioFormatDescription;
|
||||
using aidl::android::media::audio::common::AudioFormatType;
|
||||
@@ -54,9 +55,11 @@ static AudioProfile createProfile(PcmType pcmType, const std::vector<int32_t>& c
|
||||
return profile;
|
||||
}
|
||||
|
||||
static AudioPortExt createDeviceExt(AudioDeviceType devType, int32_t flags) {
|
||||
static AudioPortExt createDeviceExt(AudioDeviceType devType, int32_t flags,
|
||||
std::string connection = "") {
|
||||
AudioPortDeviceExt deviceExt;
|
||||
deviceExt.device.type.type = devType;
|
||||
deviceExt.device.type.connection = std::move(connection);
|
||||
deviceExt.flags = flags;
|
||||
return AudioPortExt::make<AudioPortExt::Tag::device>(deviceExt);
|
||||
}
|
||||
@@ -102,8 +105,64 @@ static AudioRoute createRoute(const std::vector<int32_t>& sources, int32_t sink)
|
||||
return route;
|
||||
}
|
||||
|
||||
// Configuration:
|
||||
//
|
||||
// Device ports:
|
||||
// * "Null", OUT_SPEAKER, default
|
||||
// - no profiles specified
|
||||
// * "Loopback Out", OUT_SUBMIX
|
||||
// - profile PCM 24-bit; STEREO; 48000
|
||||
// * "USB Out", OUT_DEVICE, CONNECTION_USB
|
||||
// - no profiles specified
|
||||
// * "Zero", IN_MICROPHONE, default
|
||||
// - no profiles specified
|
||||
// * "Loopback In", IN_SUBMIX
|
||||
// - profile PCM 24-bit; STEREO; 48000
|
||||
// * "USB In", IN_DEVICE, CONNECTION_USB
|
||||
// - no profiles specified
|
||||
//
|
||||
// Mix ports:
|
||||
// * "primary output", PRIMARY, 1 max open, 1 max active stream
|
||||
// - profile PCM 16-bit; MONO, STEREO; 44100, 48000
|
||||
// - profile PCM 24-bit; MONO, STEREO; 44100, 48000
|
||||
// * "loopback output", stream count unlimited
|
||||
// - profile PCM 24-bit; STEREO; 48000
|
||||
// * "primary input", 2 max open, 2 max active streams
|
||||
// - profile PCM 16-bit; MONO, STEREO, FRONT_BACK;
|
||||
// 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000
|
||||
// - profile PCM 24-bit; MONO, STEREO, FRONT_BACK;
|
||||
// 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000
|
||||
// * "loopback input", stream count unlimited
|
||||
// - profile PCM 24-bit; STEREO; 48000
|
||||
//
|
||||
// Routes:
|
||||
// "primary out" -> "Null"
|
||||
// "primary out" -> "USB Out"
|
||||
// "loopback out" -> "Loopback Out"
|
||||
// "Zero", "USB In" -> "primary input"
|
||||
// "Loopback In" -> "loopback input"
|
||||
//
|
||||
// Initial port configs:
|
||||
// * "Null" device port: PCM 24-bit; STEREO; 48000
|
||||
// * "Zero" device port: PCM 24-bit; MONO; 48000
|
||||
//
|
||||
// Profiles for device port connected state:
|
||||
// * USB Out":
|
||||
// - profile PCM 16-bit; MONO, STEREO; 44100, 48000
|
||||
// - profile PCM 24-bit; MONO, STEREO; 44100, 48000
|
||||
// * USB In":
|
||||
// - profile PCM 16-bit; MONO, STEREO; 44100, 48000
|
||||
// - profile PCM 24-bit; MONO, STEREO; 44100, 48000
|
||||
//
|
||||
Configuration& getNullPrimaryConfiguration() {
|
||||
static Configuration configuration = []() {
|
||||
const std::vector<AudioProfile> standardPcmAudioProfiles = {
|
||||
createProfile(PcmType::INT_16_BIT,
|
||||
{AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO},
|
||||
{44100, 48000}),
|
||||
createProfile(PcmType::INT_24_BIT,
|
||||
{AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO},
|
||||
{44100, 48000})};
|
||||
Configuration c;
|
||||
|
||||
AudioPort nullOutDevice =
|
||||
@@ -111,27 +170,19 @@ Configuration& getNullPrimaryConfiguration() {
|
||||
createDeviceExt(AudioDeviceType::OUT_SPEAKER,
|
||||
1 << AudioPortDeviceExt::FLAG_INDEX_DEFAULT_DEVICE));
|
||||
c.ports.push_back(nullOutDevice);
|
||||
|
||||
AudioPort primaryOutMix = createPort(c.nextPortId++, "primary output",
|
||||
1 << static_cast<int32_t>(AudioOutputFlags::PRIMARY),
|
||||
false, createPortMixExt(1, 1));
|
||||
primaryOutMix.profiles.push_back(
|
||||
createProfile(PcmType::INT_16_BIT,
|
||||
{AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO},
|
||||
{44100, 48000}));
|
||||
primaryOutMix.profiles.push_back(
|
||||
createProfile(PcmType::INT_24_BIT,
|
||||
{AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO},
|
||||
{44100, 48000}));
|
||||
c.ports.push_back(primaryOutMix);
|
||||
|
||||
c.routes.push_back(createRoute({primaryOutMix.id}, nullOutDevice.id));
|
||||
|
||||
c.initialConfigs.push_back(
|
||||
createPortConfig(nullOutDevice.id, nullOutDevice.id, PcmType::INT_24_BIT,
|
||||
AudioChannelLayout::LAYOUT_STEREO, 48000, 0, false,
|
||||
createDeviceExt(AudioDeviceType::OUT_SPEAKER, 0)));
|
||||
|
||||
AudioPort primaryOutMix = createPort(c.nextPortId++, "primary output",
|
||||
1 << static_cast<int32_t>(AudioOutputFlags::PRIMARY),
|
||||
false, createPortMixExt(1, 1));
|
||||
primaryOutMix.profiles.insert(primaryOutMix.profiles.begin(),
|
||||
standardPcmAudioProfiles.begin(),
|
||||
standardPcmAudioProfiles.end());
|
||||
c.ports.push_back(primaryOutMix);
|
||||
|
||||
AudioPort loopOutDevice = createPort(c.nextPortId++, "Loopback Out", 0, false,
|
||||
createDeviceExt(AudioDeviceType::OUT_SUBMIX, 0));
|
||||
loopOutDevice.profiles.push_back(
|
||||
@@ -144,13 +195,22 @@ Configuration& getNullPrimaryConfiguration() {
|
||||
createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000}));
|
||||
c.ports.push_back(loopOutMix);
|
||||
|
||||
c.routes.push_back(createRoute({loopOutMix.id}, loopOutDevice.id));
|
||||
AudioPort usbOutDevice =
|
||||
createPort(c.nextPortId++, "USB Out", 0, false,
|
||||
createDeviceExt(AudioDeviceType::OUT_DEVICE, 0,
|
||||
AudioDeviceDescription::CONNECTION_USB));
|
||||
c.ports.push_back(usbOutDevice);
|
||||
c.connectedProfiles[usbOutDevice.id] = standardPcmAudioProfiles;
|
||||
|
||||
AudioPort zeroInDevice =
|
||||
createPort(c.nextPortId++, "Zero", 0, true,
|
||||
createDeviceExt(AudioDeviceType::IN_MICROPHONE,
|
||||
1 << AudioPortDeviceExt::FLAG_INDEX_DEFAULT_DEVICE));
|
||||
c.ports.push_back(zeroInDevice);
|
||||
c.initialConfigs.push_back(
|
||||
createPortConfig(zeroInDevice.id, zeroInDevice.id, PcmType::INT_24_BIT,
|
||||
AudioChannelLayout::LAYOUT_MONO, 48000, 0, true,
|
||||
createDeviceExt(AudioDeviceType::IN_MICROPHONE, 0)));
|
||||
|
||||
AudioPort primaryInMix =
|
||||
createPort(c.nextPortId++, "primary input", 0, true, createPortMixExt(2, 2));
|
||||
@@ -166,13 +226,6 @@ Configuration& getNullPrimaryConfiguration() {
|
||||
{8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000}));
|
||||
c.ports.push_back(primaryInMix);
|
||||
|
||||
c.routes.push_back(createRoute({zeroInDevice.id}, primaryInMix.id));
|
||||
|
||||
c.initialConfigs.push_back(
|
||||
createPortConfig(zeroInDevice.id, zeroInDevice.id, PcmType::INT_24_BIT,
|
||||
AudioChannelLayout::LAYOUT_MONO, 48000, 0, true,
|
||||
createDeviceExt(AudioDeviceType::IN_MICROPHONE, 0)));
|
||||
|
||||
AudioPort loopInDevice = createPort(c.nextPortId++, "Loopback In", 0, true,
|
||||
createDeviceExt(AudioDeviceType::IN_SUBMIX, 0));
|
||||
loopInDevice.profiles.push_back(
|
||||
@@ -185,6 +238,16 @@ Configuration& getNullPrimaryConfiguration() {
|
||||
createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000}));
|
||||
c.ports.push_back(loopInMix);
|
||||
|
||||
AudioPort usbInDevice = createPort(c.nextPortId++, "USB In", 0, true,
|
||||
createDeviceExt(AudioDeviceType::IN_DEVICE, 0,
|
||||
AudioDeviceDescription::CONNECTION_USB));
|
||||
c.ports.push_back(usbInDevice);
|
||||
c.connectedProfiles[usbInDevice.id] = standardPcmAudioProfiles;
|
||||
|
||||
c.routes.push_back(createRoute({primaryOutMix.id}, nullOutDevice.id));
|
||||
c.routes.push_back(createRoute({primaryOutMix.id}, usbOutDevice.id));
|
||||
c.routes.push_back(createRoute({loopOutMix.id}, loopOutDevice.id));
|
||||
c.routes.push_back(createRoute({zeroInDevice.id, usbInDevice.id}, primaryInMix.id));
|
||||
c.routes.push_back(createRoute({loopInDevice.id}, loopInMix.id));
|
||||
|
||||
c.portConfigs.insert(c.portConfigs.end(), c.initialConfigs.begin(), c.initialConfigs.end());
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include <set>
|
||||
|
||||
#define LOG_TAG "AHAL_Module"
|
||||
#define LOG_NDEBUG 0
|
||||
#include <android-base/logging.h>
|
||||
|
||||
#include <aidl/android/media/audio/common/AudioOutputFlags.h>
|
||||
@@ -81,6 +80,7 @@ bool findAudioProfile(const AudioPort& port, const AudioFormatDescription& forma
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Module::cleanUpPatch(int32_t patchId) {
|
||||
@@ -135,6 +135,154 @@ void Module::registerPatch(const AudioPatch& patch) {
|
||||
do_insert(patch.sinkPortConfigIds);
|
||||
}
|
||||
|
||||
ndk::ScopedAStatus Module::setModuleDebug(
|
||||
const ::aidl::android::hardware::audio::core::ModuleDebug& in_debug) {
|
||||
LOG(DEBUG) << __func__ << ": old flags:" << mDebug.toString()
|
||||
<< ", new flags: " << in_debug.toString();
|
||||
if (mDebug.simulateDeviceConnections != in_debug.simulateDeviceConnections &&
|
||||
!mConnectedDevicePorts.empty()) {
|
||||
LOG(ERROR) << __func__ << ": attempting to change device connections simulation "
|
||||
<< "while having external devices connected";
|
||||
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
|
||||
}
|
||||
mDebug = in_debug;
|
||||
return ndk::ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
ndk::ScopedAStatus Module::connectExternalDevice(const AudioPort& in_templateIdAndAdditionalData,
|
||||
AudioPort* _aidl_return) {
|
||||
const int32_t templateId = in_templateIdAndAdditionalData.id;
|
||||
auto& ports = getConfig().ports;
|
||||
AudioPort connectedPort;
|
||||
{ // Scope the template port so that we don't accidentally modify it.
|
||||
auto templateIt = findById<AudioPort>(ports, templateId);
|
||||
if (templateIt == ports.end()) {
|
||||
LOG(ERROR) << __func__ << ": port id " << templateId << " not found";
|
||||
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
|
||||
}
|
||||
if (templateIt->ext.getTag() != AudioPortExt::Tag::device) {
|
||||
LOG(ERROR) << __func__ << ": port id " << templateId << " is not a device port";
|
||||
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
|
||||
}
|
||||
if (!templateIt->profiles.empty()) {
|
||||
LOG(ERROR) << __func__ << ": port id " << templateId
|
||||
<< " does not have dynamic profiles";
|
||||
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
|
||||
}
|
||||
auto& templateDevicePort = templateIt->ext.get<AudioPortExt::Tag::device>();
|
||||
if (templateDevicePort.device.type.connection.empty()) {
|
||||
LOG(ERROR) << __func__ << ": port id " << templateId << " is permanently attached";
|
||||
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
|
||||
}
|
||||
// Postpone id allocation until we ensure that there are no client errors.
|
||||
connectedPort = *templateIt;
|
||||
connectedPort.extraAudioDescriptors = in_templateIdAndAdditionalData.extraAudioDescriptors;
|
||||
const auto& inputDevicePort =
|
||||
in_templateIdAndAdditionalData.ext.get<AudioPortExt::Tag::device>();
|
||||
auto& connectedDevicePort = connectedPort.ext.get<AudioPortExt::Tag::device>();
|
||||
connectedDevicePort.device.address = inputDevicePort.device.address;
|
||||
LOG(DEBUG) << __func__ << ": device port " << connectedPort.id << " device set to "
|
||||
<< connectedDevicePort.device.toString();
|
||||
// Check if there is already a connected port with for the same external device.
|
||||
for (auto connectedPortId : mConnectedDevicePorts) {
|
||||
auto connectedPortIt = findById<AudioPort>(ports, connectedPortId);
|
||||
if (connectedPortIt->ext.get<AudioPortExt::Tag::device>().device ==
|
||||
connectedDevicePort.device) {
|
||||
LOG(ERROR) << __func__ << ": device " << connectedDevicePort.device.toString()
|
||||
<< " is already connected at the device port id " << connectedPortId;
|
||||
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mDebug.simulateDeviceConnections) {
|
||||
// In a real HAL here we would attempt querying the profiles from the device.
|
||||
LOG(ERROR) << __func__ << ": failed to query supported device profiles";
|
||||
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
|
||||
}
|
||||
|
||||
connectedPort.id = ++getConfig().nextPortId;
|
||||
mConnectedDevicePorts.insert(connectedPort.id);
|
||||
LOG(DEBUG) << __func__ << ": template port " << templateId << " external device connected, "
|
||||
<< "connected port ID " << connectedPort.id;
|
||||
auto& connectedProfiles = getConfig().connectedProfiles;
|
||||
if (auto connectedProfilesIt = connectedProfiles.find(templateId);
|
||||
connectedProfilesIt != connectedProfiles.end()) {
|
||||
connectedPort.profiles = connectedProfilesIt->second;
|
||||
}
|
||||
ports.push_back(connectedPort);
|
||||
*_aidl_return = std::move(connectedPort);
|
||||
|
||||
std::vector<AudioRoute> newRoutes;
|
||||
auto& routes = getConfig().routes;
|
||||
for (auto& r : routes) {
|
||||
if (r.sinkPortId == templateId) {
|
||||
AudioRoute newRoute;
|
||||
newRoute.sourcePortIds = r.sourcePortIds;
|
||||
newRoute.sinkPortId = connectedPort.id;
|
||||
newRoute.isExclusive = r.isExclusive;
|
||||
newRoutes.push_back(std::move(newRoute));
|
||||
} else {
|
||||
auto& srcs = r.sourcePortIds;
|
||||
if (std::find(srcs.begin(), srcs.end(), templateId) != srcs.end()) {
|
||||
srcs.push_back(connectedPort.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
routes.insert(routes.end(), newRoutes.begin(), newRoutes.end());
|
||||
|
||||
return ndk::ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
ndk::ScopedAStatus Module::disconnectExternalDevice(int32_t in_portId) {
|
||||
auto& ports = getConfig().ports;
|
||||
auto portIt = findById<AudioPort>(ports, in_portId);
|
||||
if (portIt == ports.end()) {
|
||||
LOG(ERROR) << __func__ << ": port id " << in_portId << " not found";
|
||||
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
|
||||
}
|
||||
if (portIt->ext.getTag() != AudioPortExt::Tag::device) {
|
||||
LOG(ERROR) << __func__ << ": port id " << in_portId << " is not a device port";
|
||||
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
|
||||
}
|
||||
if (mConnectedDevicePorts.count(in_portId) == 0) {
|
||||
LOG(ERROR) << __func__ << ": port id " << in_portId << " is not a connected device port";
|
||||
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
|
||||
}
|
||||
auto& configs = getConfig().portConfigs;
|
||||
auto& initials = getConfig().initialConfigs;
|
||||
auto configIt = std::find_if(configs.begin(), configs.end(), [&](const auto& config) {
|
||||
if (config.portId == in_portId) {
|
||||
// Check if the configuration was provided by the client.
|
||||
const auto& initialIt = findById<AudioPortConfig>(initials, config.id);
|
||||
return initialIt == initials.end() || config != *initialIt;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (configIt != configs.end()) {
|
||||
LOG(ERROR) << __func__ << ": port id " << in_portId << " has a non-default config with id "
|
||||
<< configIt->id;
|
||||
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
|
||||
}
|
||||
ports.erase(portIt);
|
||||
mConnectedDevicePorts.erase(in_portId);
|
||||
LOG(DEBUG) << __func__ << ": connected device port " << in_portId << " released";
|
||||
|
||||
auto& routes = getConfig().routes;
|
||||
for (auto routesIt = routes.begin(); routesIt != routes.end();) {
|
||||
if (routesIt->sinkPortId == in_portId) {
|
||||
routesIt = routes.erase(routesIt);
|
||||
} else {
|
||||
// Note: the list of sourcePortIds can't become empty because there must
|
||||
// be the id of the template port in the route.
|
||||
erase_if(routesIt->sourcePortIds, [in_portId](auto src) { return src == in_portId; });
|
||||
++routesIt;
|
||||
}
|
||||
}
|
||||
|
||||
return ndk::ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
ndk::ScopedAStatus Module::getAudioPatches(std::vector<AudioPatch>* _aidl_return) {
|
||||
*_aidl_return = getConfig().patches;
|
||||
LOG(DEBUG) << __func__ << ": returning " << _aidl_return->size() << " patches";
|
||||
@@ -171,6 +319,23 @@ ndk::ScopedAStatus Module::getAudioRoutes(std::vector<AudioRoute>* _aidl_return)
|
||||
return ndk::ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
ndk::ScopedAStatus Module::getAudioRoutesForAudioPort(int32_t in_portId,
|
||||
std::vector<AudioRoute>* _aidl_return) {
|
||||
auto& ports = getConfig().ports;
|
||||
if (auto portIt = findById<AudioPort>(ports, in_portId); portIt == ports.end()) {
|
||||
LOG(ERROR) << __func__ << ": port id " << in_portId << " not found";
|
||||
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
|
||||
}
|
||||
auto& routes = getConfig().routes;
|
||||
std::copy_if(routes.begin(), routes.end(), std::back_inserter(*_aidl_return),
|
||||
[&](const auto& r) {
|
||||
const auto& srcs = r.sourcePortIds;
|
||||
return r.sinkPortId == in_portId ||
|
||||
std::find(srcs.begin(), srcs.end(), in_portId) != srcs.end();
|
||||
});
|
||||
return ndk::ScopedAStatus::ok();
|
||||
}
|
||||
|
||||
ndk::ScopedAStatus Module::openInputStream(int32_t in_portConfigId,
|
||||
const SinkMetadata& in_sinkMetadata,
|
||||
std::shared_ptr<IStreamIn>* _aidl_return) {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include <aidl/android/hardware/audio/core/AudioPatch.h>
|
||||
@@ -29,6 +30,9 @@ struct Configuration {
|
||||
std::vector<::aidl::android::media::audio::common::AudioPort> ports;
|
||||
std::vector<::aidl::android::media::audio::common::AudioPortConfig> portConfigs;
|
||||
std::vector<::aidl::android::media::audio::common::AudioPortConfig> initialConfigs;
|
||||
// Port id -> List of profiles to use when the device port state is set to 'connected'.
|
||||
std::map<int32_t, std::vector<::aidl::android::media::audio::common::AudioProfile>>
|
||||
connectedProfiles;
|
||||
std::vector<AudioRoute> routes;
|
||||
std::vector<AudioPatch> patches;
|
||||
int32_t nextPortId = 1;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
#include <aidl/android/hardware/audio/core/BnModule.h>
|
||||
|
||||
@@ -27,6 +28,12 @@
|
||||
namespace aidl::android::hardware::audio::core {
|
||||
|
||||
class Module : public BnModule {
|
||||
ndk::ScopedAStatus setModuleDebug(
|
||||
const ::aidl::android::hardware::audio::core::ModuleDebug& in_debug) override;
|
||||
ndk::ScopedAStatus connectExternalDevice(
|
||||
const ::aidl::android::media::audio::common::AudioPort& in_templateIdAndAdditionalData,
|
||||
::aidl::android::media::audio::common::AudioPort* _aidl_return) override;
|
||||
ndk::ScopedAStatus disconnectExternalDevice(int32_t in_portId) override;
|
||||
ndk::ScopedAStatus getAudioPatches(std::vector<AudioPatch>* _aidl_return) override;
|
||||
ndk::ScopedAStatus getAudioPort(
|
||||
int32_t in_portId,
|
||||
@@ -37,6 +44,9 @@ class Module : public BnModule {
|
||||
ndk::ScopedAStatus getAudioPorts(
|
||||
std::vector<::aidl::android::media::audio::common::AudioPort>* _aidl_return) override;
|
||||
ndk::ScopedAStatus getAudioRoutes(std::vector<AudioRoute>* _aidl_return) override;
|
||||
ndk::ScopedAStatus getAudioRoutesForAudioPort(
|
||||
int32_t in_portId,
|
||||
std::vector<::aidl::android::hardware::audio::core::AudioRoute>* _aidl_return) override;
|
||||
ndk::ScopedAStatus openInputStream(
|
||||
int32_t in_portConfigId,
|
||||
const ::aidl::android::hardware::audio::common::SinkMetadata& in_sinkMetadata,
|
||||
@@ -63,6 +73,9 @@ class Module : public BnModule {
|
||||
void registerPatch(const AudioPatch& patch);
|
||||
|
||||
std::unique_ptr<internal::Configuration> mConfig;
|
||||
ModuleDebug mDebug;
|
||||
// ids of ports created at runtime via 'connectExternalDevice'.
|
||||
std::set<int32_t> mConnectedDevicePorts;
|
||||
Streams mStreams;
|
||||
// Maps port ids and port config ids to patch ids.
|
||||
// Multimap because both ports and configs can be used by multiple patches.
|
||||
|
||||
@@ -38,11 +38,11 @@ auto erase_all(C& c, const V& keys) {
|
||||
return oldSize - c.size();
|
||||
}
|
||||
|
||||
// Erase all the elements in the map that satisfy the provided predicate.
|
||||
// Erase all the elements in the container that satisfy the provided predicate.
|
||||
template <typename C, typename P>
|
||||
auto erase_if(C& c, P pred) {
|
||||
auto oldSize = c.size();
|
||||
for (auto it = c.begin(), last = c.end(); it != last;) {
|
||||
for (auto it = c.begin(); it != c.end();) {
|
||||
if (pred(*it)) {
|
||||
it = c.erase(it);
|
||||
} else {
|
||||
|
||||
@@ -46,14 +46,16 @@ ModuleConfig::ModuleConfig(IModule* module) {
|
||||
for (const auto& port : mPorts) {
|
||||
if (port.ext.getTag() != AudioPortExt::Tag::device) continue;
|
||||
const auto& devicePort = port.ext.get<AudioPortExt::Tag::device>();
|
||||
const bool isInput = port.flags.getTag() == AudioIoFlags::Tag::input;
|
||||
if (devicePort.device.type.connection.empty()) {
|
||||
const bool isInput = port.flags.getTag() == AudioIoFlags::Tag::input;
|
||||
// Permanently attached device.
|
||||
if (isInput) {
|
||||
mAttachedSourceDevicePorts.insert(port.id);
|
||||
} else {
|
||||
mAttachedSinkDevicePorts.insert(port.id);
|
||||
}
|
||||
} else if (port.profiles.empty()) {
|
||||
mExternalDevicePorts.insert(port.id);
|
||||
}
|
||||
}
|
||||
if (!mStatus.isOk()) return;
|
||||
@@ -62,6 +64,22 @@ ModuleConfig::ModuleConfig(IModule* module) {
|
||||
mStatus = module->getAudioPortConfigs(&mInitialConfigs);
|
||||
}
|
||||
|
||||
std::vector<AudioPort> ModuleConfig::getAttachedDevicePorts() const {
|
||||
std::vector<AudioPort> result;
|
||||
std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [&](const auto& port) {
|
||||
return mAttachedSinkDevicePorts.count(port.id) != 0 ||
|
||||
mAttachedSourceDevicePorts.count(port.id) != 0;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<AudioPort> ModuleConfig::getExternalDevicePorts() const {
|
||||
std::vector<AudioPort> result;
|
||||
std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result),
|
||||
[&](const auto& port) { return mExternalDevicePorts.count(port.id) != 0; });
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<AudioPort> ModuleConfig::getInputMixPorts() const {
|
||||
std::vector<AudioPort> result;
|
||||
std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [](const auto& port) {
|
||||
@@ -229,6 +247,23 @@ std::vector<ModuleConfig::SrcSinkGroup> ModuleConfig::getRoutableSrcSinkGroups(b
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ModuleConfig::toString() const {
|
||||
std::string result;
|
||||
result.append("Ports: ");
|
||||
result.append(android::internal::ToString(mPorts));
|
||||
result.append("Initial configs: ");
|
||||
result.append(android::internal::ToString(mInitialConfigs));
|
||||
result.append("Attached sink device ports: ");
|
||||
result.append(android::internal::ToString(mAttachedSinkDevicePorts));
|
||||
result.append("Attached source device ports: ");
|
||||
result.append(android::internal::ToString(mAttachedSourceDevicePorts));
|
||||
result.append("External device ports: ");
|
||||
result.append(android::internal::ToString(mExternalDevicePorts));
|
||||
result.append("Routes: ");
|
||||
result.append(android::internal::ToString(mRoutes));
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::vector<AudioPortConfig> combineAudioConfigs(const AudioPort& port,
|
||||
const AudioProfile& profile) {
|
||||
std::vector<AudioPortConfig> configs;
|
||||
@@ -319,10 +354,14 @@ std::vector<AudioPortConfig> ModuleConfig::generateAudioDevicePortConfigs(
|
||||
if (singleProfile && !result.empty()) return result;
|
||||
}
|
||||
if (resultSizeBefore == result.size()) {
|
||||
AudioPortConfig empty;
|
||||
empty.portId = devicePort.id;
|
||||
empty.ext = devicePort.ext;
|
||||
result.push_back(empty);
|
||||
std::copy_if(mInitialConfigs.begin(), mInitialConfigs.end(), std::back_inserter(result),
|
||||
[&](const auto& config) { return config.portId == devicePort.id; });
|
||||
if (resultSizeBefore == result.size()) {
|
||||
AudioPortConfig empty;
|
||||
empty.portId = devicePort.id;
|
||||
empty.ext = devicePort.ext;
|
||||
result.push_back(empty);
|
||||
}
|
||||
}
|
||||
if (singleProfile) return result;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ class ModuleConfig {
|
||||
android::binder::Status getStatus() const { return mStatus; }
|
||||
std::string getError() const { return mStatus.toString8().c_str(); }
|
||||
|
||||
std::vector<android::media::audio::common::AudioPort> getAttachedDevicePorts() const;
|
||||
std::vector<android::media::audio::common::AudioPort> getExternalDevicePorts() const;
|
||||
std::vector<android::media::audio::common::AudioPort> getInputMixPorts() const;
|
||||
std::vector<android::media::audio::common::AudioPort> getOutputMixPorts() const;
|
||||
std::vector<android::media::audio::common::AudioPort> getMixPorts(bool isInput) const {
|
||||
@@ -59,6 +61,10 @@ class ModuleConfig {
|
||||
std::optional<SrcSinkPair> getRoutableSrcSinkPair(bool isInput) const;
|
||||
std::vector<SrcSinkGroup> getRoutableSrcSinkGroups(bool isInput) const;
|
||||
|
||||
std::vector<android::media::audio::common::AudioPortConfig>
|
||||
getPortConfigsForAttachedDevicePorts() const {
|
||||
return generateAudioDevicePortConfigs(getAttachedDevicePorts(), false);
|
||||
}
|
||||
std::vector<android::media::audio::common::AudioPortConfig> getPortConfigsForMixPorts() const {
|
||||
auto inputs = generateInputAudioMixPortConfigs(getInputMixPorts(), false);
|
||||
auto outputs = generateOutputAudioMixPortConfigs(getOutputMixPorts(), false);
|
||||
@@ -98,15 +104,18 @@ class ModuleConfig {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<android::media::audio::common::AudioPortConfig> getPortConfigsForDevicePort(
|
||||
const android::media::audio::common::AudioPort& port) const {
|
||||
return generateAudioDevicePortConfigs({port}, false);
|
||||
}
|
||||
android::media::audio::common::AudioPortConfig getSingleConfigForDevicePort(
|
||||
const android::media::audio::common::AudioPort& port) const {
|
||||
for (const auto& config : mInitialConfigs) {
|
||||
if (config.portId == port.id) return config;
|
||||
}
|
||||
const auto config = generateAudioDevicePortConfigs({port}, true);
|
||||
return *config.begin();
|
||||
}
|
||||
|
||||
std::string toString() const;
|
||||
|
||||
private:
|
||||
std::vector<android::media::audio::common::AudioPortConfig> generateInputAudioMixPortConfigs(
|
||||
const std::vector<android::media::audio::common::AudioPort>& ports,
|
||||
@@ -117,7 +126,8 @@ class ModuleConfig {
|
||||
|
||||
// Unlike MixPorts, the generator for DevicePorts always returns a non-empty
|
||||
// vector for a non-empty input port list. If there are no profiles in the
|
||||
// port, a vector with an empty config is returned.
|
||||
// port, its initial configs are looked up, if there are none,
|
||||
// then an empty config is used, assuming further negotiation via setAudioPortConfig.
|
||||
std::vector<android::media::audio::common::AudioPortConfig> generateAudioDevicePortConfigs(
|
||||
const std::vector<android::media::audio::common::AudioPort>& ports,
|
||||
bool singleProfile) const;
|
||||
@@ -127,5 +137,6 @@ class ModuleConfig {
|
||||
std::vector<android::media::audio::common::AudioPortConfig> mInitialConfigs;
|
||||
std::set<int32_t> mAttachedSinkDevicePorts;
|
||||
std::set<int32_t> mAttachedSourceDevicePorts;
|
||||
std::set<int32_t> mExternalDevicePorts;
|
||||
std::vector<android::hardware::audio::core::AudioRoute> mRoutes;
|
||||
};
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#define LOG_TAG "VtsHalAudioCore"
|
||||
#include <android-base/logging.h>
|
||||
|
||||
#include <aidl/Gtest.h>
|
||||
#include <aidl/Vintf.h>
|
||||
#include <android-base/properties.h>
|
||||
@@ -45,9 +48,12 @@ using android::hardware::audio::core::AudioRoute;
|
||||
using android::hardware::audio::core::IModule;
|
||||
using android::hardware::audio::core::IStreamIn;
|
||||
using android::hardware::audio::core::IStreamOut;
|
||||
using android::hardware::audio::core::ModuleDebug;
|
||||
using android::media::audio::common::AudioContentType;
|
||||
using android::media::audio::common::AudioDevice;
|
||||
using android::media::audio::common::AudioDeviceAddress;
|
||||
using android::media::audio::common::AudioDeviceType;
|
||||
using android::media::audio::common::AudioFormatType;
|
||||
using android::media::audio::common::AudioIoFlags;
|
||||
using android::media::audio::common::AudioOutputFlags;
|
||||
using android::media::audio::common::AudioPort;
|
||||
@@ -63,7 +69,7 @@ auto findById(std::vector<T>& v, int32_t id) {
|
||||
}
|
||||
|
||||
template <typename C>
|
||||
std::vector<int32_t> getNonExistentIds(const C& allIds) {
|
||||
std::vector<int32_t> GetNonExistentIds(const C& allIds) {
|
||||
if (allIds.empty()) {
|
||||
return std::vector<int32_t>{-1, 0, 1};
|
||||
}
|
||||
@@ -73,6 +79,12 @@ std::vector<int32_t> getNonExistentIds(const C& allIds) {
|
||||
return nonExistentIds;
|
||||
}
|
||||
|
||||
AudioDeviceAddress GenerateUniqueDeviceAddress() {
|
||||
static int nextId = 1;
|
||||
// TODO: Use connection-specific ID.
|
||||
return AudioDeviceAddress::make<AudioDeviceAddress::Tag::id>(std::to_string(++nextId));
|
||||
}
|
||||
|
||||
struct AidlDeathRecipient : IBinder::DeathRecipient {
|
||||
std::mutex mutex;
|
||||
std::condition_variable condition;
|
||||
@@ -107,70 +119,28 @@ constexpr IsInput<IStreamOut>::operator bool() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
class AudioCoreModule : public testing::TestWithParam<std::string> {
|
||||
class WithDebugFlags {
|
||||
public:
|
||||
void SetUp() override { ASSERT_NO_FATAL_FAILURE(ConnectToService()); }
|
||||
|
||||
void ConnectToService() {
|
||||
module = android::waitForDeclaredService<IModule>(String16(GetParam().c_str()));
|
||||
ASSERT_NE(module, nullptr);
|
||||
}
|
||||
|
||||
void RestartService() {
|
||||
ASSERT_NE(module, nullptr);
|
||||
moduleConfig.reset();
|
||||
deathHandler = sp<AidlDeathRecipient>::make();
|
||||
ASSERT_EQ(NO_ERROR, IModule::asBinder(module)->linkToDeath(deathHandler));
|
||||
ASSERT_TRUE(base::SetProperty("sys.audio.restart.hal", "1"));
|
||||
EXPECT_TRUE(deathHandler->waitForFired(3000));
|
||||
deathHandler = nullptr;
|
||||
ASSERT_NO_FATAL_FAILURE(ConnectToService());
|
||||
}
|
||||
|
||||
template <typename Entity>
|
||||
void GetAllEntityIds(std::set<int32_t>* entityIds,
|
||||
Status (IModule::*getter)(std::vector<Entity>*),
|
||||
const std::string& errorMessage) {
|
||||
std::vector<Entity> entities;
|
||||
{
|
||||
Status status = (module.get()->*getter)(&entities);
|
||||
ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
|
||||
}
|
||||
std::transform(entities.begin(), entities.end(),
|
||||
std::inserter(*entityIds, entityIds->begin()),
|
||||
[](const auto& entity) { return entity.id; });
|
||||
EXPECT_EQ(entities.size(), entityIds->size()) << errorMessage;
|
||||
}
|
||||
|
||||
void GetAllPatchIds(std::set<int32_t>* patchIds) {
|
||||
return GetAllEntityIds<AudioPatch>(
|
||||
patchIds, &IModule::getAudioPatches,
|
||||
"IDs of audio patches returned by IModule.getAudioPatches are not unique");
|
||||
}
|
||||
|
||||
void GetAllPortIds(std::set<int32_t>* portIds) {
|
||||
return GetAllEntityIds<AudioPort>(
|
||||
portIds, &IModule::getAudioPorts,
|
||||
"IDs of audio ports returned by IModule.getAudioPorts are not unique");
|
||||
}
|
||||
|
||||
void GetAllPortConfigIds(std::set<int32_t>* portConfigIds) {
|
||||
return GetAllEntityIds<AudioPortConfig>(
|
||||
portConfigIds, &IModule::getAudioPortConfigs,
|
||||
"IDs of audio port configs returned by IModule.getAudioPortConfigs are not unique");
|
||||
}
|
||||
|
||||
void SetUpModuleConfig() {
|
||||
if (moduleConfig == nullptr) {
|
||||
moduleConfig = std::make_unique<ModuleConfig>(module.get());
|
||||
ASSERT_EQ(Status::EX_NONE, moduleConfig->getStatus().exceptionCode())
|
||||
<< "ModuleConfig init error: " << moduleConfig->getError();
|
||||
WithDebugFlags() {}
|
||||
explicit WithDebugFlags(const ModuleDebug& initial) : mInitial(initial), mFlags(initial) {}
|
||||
explicit WithDebugFlags(const WithDebugFlags& initial)
|
||||
: mInitial(initial.mFlags), mFlags(initial.mFlags) {}
|
||||
~WithDebugFlags() {
|
||||
if (mModule != nullptr) {
|
||||
Status status = mModule->setModuleDebug(mInitial);
|
||||
EXPECT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
|
||||
}
|
||||
}
|
||||
void SetUp(IModule* module) {
|
||||
Status status = module->setModuleDebug(mFlags);
|
||||
ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
|
||||
}
|
||||
ModuleDebug& flags() { return mFlags; }
|
||||
|
||||
sp<IModule> module;
|
||||
sp<AidlDeathRecipient> deathHandler;
|
||||
std::unique_ptr<ModuleConfig> moduleConfig;
|
||||
private:
|
||||
ModuleDebug mInitial;
|
||||
ModuleDebug mFlags;
|
||||
IModule* mModule = nullptr;
|
||||
};
|
||||
|
||||
// For consistency, WithAudioPortConfig can start both with a non-existent
|
||||
@@ -226,6 +196,147 @@ class WithAudioPortConfig {
|
||||
AudioPortConfig mConfig;
|
||||
};
|
||||
|
||||
class AudioCoreModule : public testing::TestWithParam<std::string> {
|
||||
public:
|
||||
void SetUp() override {
|
||||
ASSERT_NO_FATAL_FAILURE(ConnectToService());
|
||||
debug.flags().simulateDeviceConnections = true;
|
||||
ASSERT_NO_FATAL_FAILURE(debug.SetUp(module.get()));
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
if (module != nullptr) {
|
||||
Status status = module->setModuleDebug(ModuleDebug{});
|
||||
EXPECT_EQ(Status::EX_NONE, status.exceptionCode())
|
||||
<< status << " returned when resetting debug flags";
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectToService() {
|
||||
module = android::waitForDeclaredService<IModule>(String16(GetParam().c_str()));
|
||||
ASSERT_NE(module, nullptr);
|
||||
}
|
||||
|
||||
void RestartService() {
|
||||
ASSERT_NE(module, nullptr);
|
||||
moduleConfig.reset();
|
||||
deathHandler = sp<AidlDeathRecipient>::make();
|
||||
ASSERT_EQ(NO_ERROR, IModule::asBinder(module)->linkToDeath(deathHandler));
|
||||
ASSERT_TRUE(base::SetProperty("sys.audio.restart.hal", "1"));
|
||||
EXPECT_TRUE(deathHandler->waitForFired(3000));
|
||||
deathHandler = nullptr;
|
||||
ASSERT_NO_FATAL_FAILURE(ConnectToService());
|
||||
}
|
||||
|
||||
void ApplyEveryConfig(const std::vector<AudioPortConfig>& configs) {
|
||||
for (const auto& config : configs) {
|
||||
ASSERT_NE(0, config.portId);
|
||||
WithAudioPortConfig portConfig(config);
|
||||
ASSERT_NO_FATAL_FAILURE(portConfig.SetUp(module.get())); // calls setAudioPortConfig
|
||||
EXPECT_EQ(config.portId, portConfig.get().portId);
|
||||
std::vector<AudioPortConfig> retrievedPortConfigs;
|
||||
Status status = module->getAudioPortConfigs(&retrievedPortConfigs);
|
||||
ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
|
||||
const int32_t portConfigId = portConfig.getId();
|
||||
auto configIt = std::find_if(
|
||||
retrievedPortConfigs.begin(), retrievedPortConfigs.end(),
|
||||
[&portConfigId](const auto& retrConf) { return retrConf.id == portConfigId; });
|
||||
EXPECT_NE(configIt, retrievedPortConfigs.end())
|
||||
<< "Port config id returned by setAudioPortConfig: " << portConfigId
|
||||
<< " is not found in the list returned by getAudioPortConfigs";
|
||||
if (configIt != retrievedPortConfigs.end()) {
|
||||
EXPECT_EQ(portConfig.get(), *configIt)
|
||||
<< "Applied port config returned by setAudioPortConfig: "
|
||||
<< portConfig.get().toString()
|
||||
<< " is not the same as retrieved via getAudioPortConfigs: "
|
||||
<< configIt->toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Entity>
|
||||
void GetAllEntityIds(std::set<int32_t>* entityIds,
|
||||
Status (IModule::*getter)(std::vector<Entity>*),
|
||||
const std::string& errorMessage) {
|
||||
std::vector<Entity> entities;
|
||||
{
|
||||
Status status = (module.get()->*getter)(&entities);
|
||||
ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
|
||||
}
|
||||
std::transform(entities.begin(), entities.end(),
|
||||
std::inserter(*entityIds, entityIds->begin()),
|
||||
[](const auto& entity) { return entity.id; });
|
||||
EXPECT_EQ(entities.size(), entityIds->size()) << errorMessage;
|
||||
}
|
||||
|
||||
void GetAllPatchIds(std::set<int32_t>* patchIds) {
|
||||
return GetAllEntityIds<AudioPatch>(
|
||||
patchIds, &IModule::getAudioPatches,
|
||||
"IDs of audio patches returned by IModule.getAudioPatches are not unique");
|
||||
}
|
||||
|
||||
void GetAllPortIds(std::set<int32_t>* portIds) {
|
||||
return GetAllEntityIds<AudioPort>(
|
||||
portIds, &IModule::getAudioPorts,
|
||||
"IDs of audio ports returned by IModule.getAudioPorts are not unique");
|
||||
}
|
||||
|
||||
void GetAllPortConfigIds(std::set<int32_t>* portConfigIds) {
|
||||
return GetAllEntityIds<AudioPortConfig>(
|
||||
portConfigIds, &IModule::getAudioPortConfigs,
|
||||
"IDs of audio port configs returned by IModule.getAudioPortConfigs are not unique");
|
||||
}
|
||||
|
||||
void SetUpModuleConfig() {
|
||||
if (moduleConfig == nullptr) {
|
||||
moduleConfig = std::make_unique<ModuleConfig>(module.get());
|
||||
ASSERT_EQ(Status::EX_NONE, moduleConfig->getStatus().exceptionCode())
|
||||
<< "ModuleConfig init error: " << moduleConfig->getError();
|
||||
}
|
||||
}
|
||||
|
||||
sp<IModule> module;
|
||||
sp<AidlDeathRecipient> deathHandler;
|
||||
std::unique_ptr<ModuleConfig> moduleConfig;
|
||||
WithDebugFlags debug;
|
||||
};
|
||||
|
||||
class WithDevicePortConnectedState {
|
||||
public:
|
||||
explicit WithDevicePortConnectedState(const AudioPort& idAndData) : mIdAndData(idAndData) {}
|
||||
WithDevicePortConnectedState(const AudioPort& id, const AudioDeviceAddress& address)
|
||||
: mIdAndData(setAudioPortAddress(id, address)) {}
|
||||
~WithDevicePortConnectedState() {
|
||||
if (mModule != nullptr) {
|
||||
Status status = mModule->disconnectExternalDevice(getId());
|
||||
EXPECT_EQ(Status::EX_NONE, status.exceptionCode())
|
||||
<< status << " returned when disconnecting device port ID " << getId();
|
||||
}
|
||||
}
|
||||
void SetUp(IModule* module) {
|
||||
Status status = module->connectExternalDevice(mIdAndData, &mConnectedPort);
|
||||
ASSERT_EQ(Status::EX_NONE, status.exceptionCode())
|
||||
<< status << " returned when connecting device port ID & data "
|
||||
<< mIdAndData.toString();
|
||||
ASSERT_NE(mIdAndData.id, getId())
|
||||
<< "ID of the connected port must not be the same as the ID of the template port";
|
||||
mModule = module;
|
||||
}
|
||||
int32_t getId() const { return mConnectedPort.id; }
|
||||
const AudioPort& get() { return mConnectedPort; }
|
||||
|
||||
private:
|
||||
static AudioPort setAudioPortAddress(const AudioPort& id, const AudioDeviceAddress& address) {
|
||||
AudioPort result = id;
|
||||
result.ext.get<AudioPortExt::Tag::device>().device.address = address;
|
||||
return result;
|
||||
}
|
||||
|
||||
const AudioPort mIdAndData;
|
||||
IModule* mModule = nullptr;
|
||||
AudioPort mConnectedPort;
|
||||
};
|
||||
|
||||
template <typename Stream>
|
||||
class WithStream {
|
||||
public:
|
||||
@@ -332,7 +443,7 @@ TEST_P(AudioCoreModule, PortIdsAreUnique) {
|
||||
ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds));
|
||||
}
|
||||
|
||||
TEST_P(AudioCoreModule, GetAudioPortsIsStatic) {
|
||||
TEST_P(AudioCoreModule, GetAudioPortsIsStable) {
|
||||
std::vector<AudioPort> ports1;
|
||||
{
|
||||
Status status = module->getAudioPorts(&ports1);
|
||||
@@ -344,13 +455,13 @@ TEST_P(AudioCoreModule, GetAudioPortsIsStatic) {
|
||||
ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
|
||||
}
|
||||
ASSERT_EQ(ports1.size(), ports2.size())
|
||||
<< "Sizes of audio port arrays do not match across calls to getAudioPorts";
|
||||
<< "Sizes of audio port arrays do not match across consequent calls to getAudioPorts";
|
||||
std::sort(ports1.begin(), ports1.end());
|
||||
std::sort(ports2.begin(), ports2.end());
|
||||
EXPECT_EQ(ports1, ports2);
|
||||
}
|
||||
|
||||
TEST_P(AudioCoreModule, GetAudioRoutesIsStatic) {
|
||||
TEST_P(AudioCoreModule, GetAudioRoutesIsStable) {
|
||||
std::vector<AudioRoute> routes1;
|
||||
{
|
||||
Status status = module->getAudioRoutes(&routes1);
|
||||
@@ -362,7 +473,7 @@ TEST_P(AudioCoreModule, GetAudioRoutesIsStatic) {
|
||||
ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
|
||||
}
|
||||
ASSERT_EQ(routes1.size(), routes2.size())
|
||||
<< "Sizes of audio route arrays do not match across calls to getAudioRoutes";
|
||||
<< "Sizes of audio route arrays do not match across consequent calls to getAudioRoutes";
|
||||
std::sort(routes1.begin(), routes1.end());
|
||||
std::sort(routes2.begin(), routes2.end());
|
||||
EXPECT_EQ(routes1, routes2);
|
||||
@@ -401,6 +512,32 @@ TEST_P(AudioCoreModule, GetAudioRoutesPortIdsAreValid) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(AudioCoreModule, GetAudioRoutesForAudioPort) {
|
||||
std::set<int32_t> portIds;
|
||||
ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds));
|
||||
if (portIds.empty()) {
|
||||
GTEST_SKIP() << "No ports in the module.";
|
||||
}
|
||||
for (const auto portId : portIds) {
|
||||
std::vector<AudioRoute> routes;
|
||||
Status status = module->getAudioRoutesForAudioPort(portId, &routes);
|
||||
EXPECT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
|
||||
for (const auto& r : routes) {
|
||||
if (r.sinkPortId != portId) {
|
||||
const auto& srcs = r.sourcePortIds;
|
||||
EXPECT_TRUE(std::find(srcs.begin(), srcs.end(), portId) != srcs.end())
|
||||
<< " port ID " << portId << " does not used by the route " << r.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto portId : GetNonExistentIds(portIds)) {
|
||||
std::vector<AudioRoute> routes;
|
||||
Status status = module->getAudioRoutesForAudioPort(portId, &routes);
|
||||
EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
|
||||
<< status << " returned for port ID " << portId;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(AudioCoreModule, CheckDevicePorts) {
|
||||
std::vector<AudioPort> ports;
|
||||
{
|
||||
@@ -486,7 +623,7 @@ TEST_P(AudioCoreModule, GetAudioPort) {
|
||||
EXPECT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
|
||||
EXPECT_EQ(portId, port.id);
|
||||
}
|
||||
for (const auto portId : getNonExistentIds(portIds)) {
|
||||
for (const auto portId : GetNonExistentIds(portIds)) {
|
||||
AudioPort port;
|
||||
Status status = module->getAudioPort(portId, &port);
|
||||
EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
|
||||
@@ -494,10 +631,59 @@ TEST_P(AudioCoreModule, GetAudioPort) {
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that HAL module reports for a connected device port at least one non-dynamic profile,
|
||||
// that is, a profile with actual supported configuration.
|
||||
// Note: This test relies on simulation of external device connections by the HAL module.
|
||||
TEST_P(AudioCoreModule, GetAudioPortWithExternalDevices) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
|
||||
std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
|
||||
if (ports.empty()) {
|
||||
GTEST_SKIP() << "No external devices in the module.";
|
||||
}
|
||||
for (const auto& port : ports) {
|
||||
AudioPort portWithData = port;
|
||||
portWithData.ext.get<AudioPortExt::Tag::device>().device.address =
|
||||
GenerateUniqueDeviceAddress();
|
||||
WithDevicePortConnectedState portConnected(portWithData);
|
||||
ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
|
||||
const int32_t connectedPortId = portConnected.getId();
|
||||
ASSERT_NE(portWithData.id, connectedPortId);
|
||||
ASSERT_EQ(portWithData.ext.getTag(), portConnected.get().ext.getTag());
|
||||
EXPECT_EQ(portWithData.ext.get<AudioPortExt::Tag::device>().device,
|
||||
portConnected.get().ext.get<AudioPortExt::Tag::device>().device);
|
||||
// Verify that 'getAudioPort' and 'getAudioPorts' return the same connected port.
|
||||
AudioPort connectedPort;
|
||||
Status status = module->getAudioPort(connectedPortId, &connectedPort);
|
||||
EXPECT_EQ(Status::EX_NONE, status.exceptionCode())
|
||||
<< status << " returned for getAudioPort port ID " << connectedPortId;
|
||||
EXPECT_EQ(portConnected.get(), connectedPort);
|
||||
const auto& portProfiles = connectedPort.profiles;
|
||||
EXPECT_NE(0, portProfiles.size())
|
||||
<< "Connected port has no profiles: " << connectedPort.toString();
|
||||
const auto dynamicProfileIt =
|
||||
std::find_if(portProfiles.begin(), portProfiles.end(), [](const auto& profile) {
|
||||
return profile.format.type == AudioFormatType::DEFAULT;
|
||||
});
|
||||
EXPECT_EQ(portProfiles.end(), dynamicProfileIt) << "Connected port contains dynamic "
|
||||
<< "profiles: " << connectedPort.toString();
|
||||
|
||||
std::vector<AudioPort> allPorts;
|
||||
{
|
||||
Status status = module->getAudioPorts(&allPorts);
|
||||
ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
|
||||
}
|
||||
const auto allPortsIt = findById(allPorts, connectedPortId);
|
||||
EXPECT_NE(allPorts.end(), allPortsIt);
|
||||
if (allPortsIt != allPorts.end()) {
|
||||
EXPECT_EQ(portConnected.get(), *allPortsIt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(AudioCoreModule, OpenStreamInvalidPortConfigId) {
|
||||
std::set<int32_t> portConfigIds;
|
||||
ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds));
|
||||
for (const auto portConfigId : getNonExistentIds(portConfigIds)) {
|
||||
for (const auto portConfigId : GetNonExistentIds(portConfigIds)) {
|
||||
{
|
||||
sp<IStreamIn> stream;
|
||||
Status status = module->openInputStream(portConfigId, {}, &stream);
|
||||
@@ -537,7 +723,7 @@ TEST_P(AudioCoreModule, PortConfigPortIdsAreValid) {
|
||||
TEST_P(AudioCoreModule, ResetAudioPortConfigInvalidId) {
|
||||
std::set<int32_t> portConfigIds;
|
||||
ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds));
|
||||
for (const auto portConfigId : getNonExistentIds(portConfigIds)) {
|
||||
for (const auto portConfigId : GetNonExistentIds(portConfigIds)) {
|
||||
Status status = module->resetAudioPortConfig(portConfigId);
|
||||
EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
|
||||
<< status << " returned for port config ID " << portConfigId;
|
||||
@@ -608,39 +794,35 @@ TEST_P(AudioCoreModule, SetAudioPortConfigSuggestedConfig) {
|
||||
EXPECT_EQ(suggestedConfig.flags.value(), appliedConfig.flags.value());
|
||||
}
|
||||
|
||||
TEST_P(AudioCoreModule, SetAllAttachedDevicePortConfigs) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
|
||||
ASSERT_NO_FATAL_FAILURE(ApplyEveryConfig(moduleConfig->getPortConfigsForAttachedDevicePorts()));
|
||||
}
|
||||
|
||||
// Note: This test relies on simulation of external device connections by the HAL module.
|
||||
TEST_P(AudioCoreModule, SetAllExternalDevicePortConfigs) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
|
||||
std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
|
||||
if (ports.empty()) {
|
||||
GTEST_SKIP() << "No external devices in the module.";
|
||||
}
|
||||
for (const auto& port : ports) {
|
||||
WithDevicePortConnectedState portConnected(port, GenerateUniqueDeviceAddress());
|
||||
ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
ApplyEveryConfig(moduleConfig->getPortConfigsForDevicePort(portConnected.get())));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(AudioCoreModule, SetAllStaticAudioPortConfigs) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
|
||||
const auto allPortConfigs = moduleConfig->getPortConfigsForMixPorts();
|
||||
for (const auto& config : allPortConfigs) {
|
||||
ASSERT_NE(0, config.portId);
|
||||
WithAudioPortConfig portConfig(config);
|
||||
ASSERT_NO_FATAL_FAILURE(portConfig.SetUp(module.get()));
|
||||
EXPECT_EQ(config.portId, portConfig.get().portId);
|
||||
std::vector<AudioPortConfig> retrievedPortConfigs;
|
||||
{
|
||||
Status status = module->getAudioPortConfigs(&retrievedPortConfigs);
|
||||
ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
|
||||
}
|
||||
const int32_t portConfigId = portConfig.getId();
|
||||
auto configIt = std::find_if(
|
||||
retrievedPortConfigs.begin(), retrievedPortConfigs.end(),
|
||||
[&portConfigId](const auto& retrConf) { return retrConf.id == portConfigId; });
|
||||
EXPECT_NE(configIt, retrievedPortConfigs.end())
|
||||
<< "Port config id returned by setAudioPortConfig: " << portConfigId
|
||||
<< " is not found in the list returned by getPortConfigsForMixPorts";
|
||||
if (configIt != retrievedPortConfigs.end()) {
|
||||
EXPECT_EQ(portConfig.get(), *configIt)
|
||||
<< "Port config returned by getPortConfigsForMixPorts: " << configIt->toString()
|
||||
<< " is not the same as returned by setAudioPortConfig: "
|
||||
<< portConfig.get().toString();
|
||||
}
|
||||
}
|
||||
ASSERT_NO_FATAL_FAILURE(ApplyEveryConfig(moduleConfig->getPortConfigsForMixPorts()));
|
||||
}
|
||||
|
||||
TEST_P(AudioCoreModule, SetAudioPortConfigInvalidPortId) {
|
||||
std::set<int32_t> portIds;
|
||||
ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds));
|
||||
for (const auto portId : getNonExistentIds(portIds)) {
|
||||
for (const auto portId : GetNonExistentIds(portIds)) {
|
||||
AudioPortConfig portConfig, suggestedConfig;
|
||||
bool applied;
|
||||
portConfig.portId = portId;
|
||||
@@ -656,7 +838,7 @@ TEST_P(AudioCoreModule, SetAudioPortConfigInvalidPortId) {
|
||||
TEST_P(AudioCoreModule, SetAudioPortConfigInvalidPortConfigId) {
|
||||
std::set<int32_t> portConfigIds;
|
||||
ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds));
|
||||
for (const auto portConfigId : getNonExistentIds(portConfigIds)) {
|
||||
for (const auto portConfigId : GetNonExistentIds(portConfigIds)) {
|
||||
AudioPortConfig portConfig, suggestedConfig;
|
||||
bool applied;
|
||||
portConfig.id = portConfigId;
|
||||
@@ -669,6 +851,203 @@ TEST_P(AudioCoreModule, SetAudioPortConfigInvalidPortConfigId) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(AudioCoreModule, TryConnectMissingDevice) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
|
||||
std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
|
||||
if (ports.empty()) {
|
||||
GTEST_SKIP() << "No external devices in the module.";
|
||||
}
|
||||
AudioPort ignored;
|
||||
WithDebugFlags doNotSimulateConnections(debug);
|
||||
doNotSimulateConnections.flags().simulateDeviceConnections = false;
|
||||
ASSERT_NO_FATAL_FAILURE(doNotSimulateConnections.SetUp(module.get()));
|
||||
for (const auto& port : ports) {
|
||||
AudioPort portWithData = port;
|
||||
portWithData.ext.get<AudioPortExt::Tag::device>().device.address =
|
||||
GenerateUniqueDeviceAddress();
|
||||
Status status = module->connectExternalDevice(portWithData, &ignored);
|
||||
EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode())
|
||||
<< status << " returned for static port " << portWithData.toString();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(AudioCoreModule, TryChangingConnectionSimulationMidway) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
|
||||
std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
|
||||
if (ports.empty()) {
|
||||
GTEST_SKIP() << "No external devices in the module.";
|
||||
}
|
||||
WithDevicePortConnectedState portConnected(*ports.begin(), GenerateUniqueDeviceAddress());
|
||||
ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
|
||||
ModuleDebug midwayDebugChange = debug.flags();
|
||||
midwayDebugChange.simulateDeviceConnections = false;
|
||||
Status status = module->setModuleDebug(midwayDebugChange);
|
||||
EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode())
|
||||
<< status << " returned when trying to disable connections simulation "
|
||||
<< "while having a connected device";
|
||||
}
|
||||
|
||||
TEST_P(AudioCoreModule, ConnectDisconnectExternalDeviceInvalidPorts) {
|
||||
AudioPort ignored;
|
||||
std::set<int32_t> portIds;
|
||||
ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds));
|
||||
for (const auto portId : GetNonExistentIds(portIds)) {
|
||||
AudioPort invalidPort;
|
||||
invalidPort.id = portId;
|
||||
Status status = module->connectExternalDevice(invalidPort, &ignored);
|
||||
EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
|
||||
<< status << " returned for port ID " << portId << " when setting CONNECTED state";
|
||||
status = module->disconnectExternalDevice(portId);
|
||||
EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
|
||||
<< status << " returned for port ID " << portId
|
||||
<< " when setting DISCONNECTED state";
|
||||
}
|
||||
|
||||
std::vector<AudioPort> ports;
|
||||
{
|
||||
Status status = module->getAudioPorts(&ports);
|
||||
ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
|
||||
}
|
||||
for (const auto& port : ports) {
|
||||
if (port.ext.getTag() != AudioPortExt::Tag::device) {
|
||||
Status status = module->connectExternalDevice(port, &ignored);
|
||||
EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
|
||||
<< status << " returned for non-device port ID " << port.id
|
||||
<< " when setting CONNECTED state";
|
||||
status = module->disconnectExternalDevice(port.id);
|
||||
EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
|
||||
<< status << " returned for non-device port ID " << port.id
|
||||
<< " when setting DISCONNECTED state";
|
||||
} else {
|
||||
const auto& devicePort = port.ext.get<AudioPortExt::Tag::device>();
|
||||
if (devicePort.device.type.connection.empty()) {
|
||||
Status status = module->connectExternalDevice(port, &ignored);
|
||||
EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
|
||||
<< status << " returned for permanently attached device port ID " << port.id
|
||||
<< " when setting CONNECTED state";
|
||||
status = module->disconnectExternalDevice(port.id);
|
||||
EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
|
||||
<< status << " returned for permanently attached device port ID " << port.id
|
||||
<< " when setting DISCONNECTED state";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note: This test relies on simulation of external device connections by the HAL module.
|
||||
TEST_P(AudioCoreModule, ConnectDisconnectExternalDeviceTwice) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
|
||||
AudioPort ignored;
|
||||
std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
|
||||
if (ports.empty()) {
|
||||
GTEST_SKIP() << "No external devices in the module.";
|
||||
}
|
||||
for (const auto& port : ports) {
|
||||
Status status = module->disconnectExternalDevice(port.id);
|
||||
EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
|
||||
<< status << " returned when disconnecting already disconnected device port ID "
|
||||
<< port.id;
|
||||
AudioPort portWithData = port;
|
||||
portWithData.ext.get<AudioPortExt::Tag::device>().device.address =
|
||||
GenerateUniqueDeviceAddress();
|
||||
WithDevicePortConnectedState portConnected(portWithData);
|
||||
ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
|
||||
status = module->connectExternalDevice(portConnected.get(), &ignored);
|
||||
EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
|
||||
<< status << " returned when trying to connect a connected device port "
|
||||
<< portConnected.get().toString();
|
||||
status = module->connectExternalDevice(portWithData, &ignored);
|
||||
EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode())
|
||||
<< status << " returned when connecting again the external device "
|
||||
<< portWithData.ext.get<AudioPortExt::Tag::device>().device.toString();
|
||||
if (status.exceptionCode() == Status::EX_NONE) {
|
||||
ADD_FAILURE() << "Returned connected port " << ignored.toString() << " for template "
|
||||
<< portWithData.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note: This test relies on simulation of external device connections by the HAL module.
|
||||
TEST_P(AudioCoreModule, DisconnectExternalDeviceNonResetPortConfig) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
|
||||
std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
|
||||
if (ports.empty()) {
|
||||
GTEST_SKIP() << "No external devices in the module.";
|
||||
}
|
||||
for (const auto& port : ports) {
|
||||
WithDevicePortConnectedState portConnected(port, GenerateUniqueDeviceAddress());
|
||||
ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
|
||||
const auto portConfig = moduleConfig->getSingleConfigForDevicePort(portConnected.get());
|
||||
{
|
||||
WithAudioPortConfig config(portConfig);
|
||||
// Note: if SetUp fails, check the status of 'GetAudioPortWithExternalDevices' test.
|
||||
// Our test assumes that 'getAudioPort' returns at least one profile, and it
|
||||
// is not a dynamic profile.
|
||||
ASSERT_NO_FATAL_FAILURE(config.SetUp(module.get()));
|
||||
Status status = module->disconnectExternalDevice(portConnected.getId());
|
||||
EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode())
|
||||
<< status << " returned when trying to disconnect device port ID " << port.id
|
||||
<< " with active configuration " << config.getId();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(AudioCoreModule, ExternalDevicePortRoutes) {
|
||||
ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
|
||||
std::vector<AudioPort> ports = moduleConfig->getExternalDevicePorts();
|
||||
if (ports.empty()) {
|
||||
GTEST_SKIP() << "No external devices in the module.";
|
||||
}
|
||||
for (const auto& port : ports) {
|
||||
std::vector<AudioRoute> routesBefore;
|
||||
{
|
||||
Status status = module->getAudioRoutes(&routesBefore);
|
||||
ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
|
||||
}
|
||||
|
||||
int32_t connectedPortId;
|
||||
{
|
||||
WithDevicePortConnectedState portConnected(port, GenerateUniqueDeviceAddress());
|
||||
ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
|
||||
connectedPortId = portConnected.getId();
|
||||
std::vector<AudioRoute> connectedPortRoutes;
|
||||
{
|
||||
Status status =
|
||||
module->getAudioRoutesForAudioPort(connectedPortId, &connectedPortRoutes);
|
||||
ASSERT_EQ(Status::EX_NONE, status.exceptionCode())
|
||||
<< status << " returned when retrieving routes for connected port id "
|
||||
<< connectedPortId;
|
||||
}
|
||||
// There must be routes for the port to be useful.
|
||||
if (connectedPortRoutes.empty()) {
|
||||
std::vector<AudioRoute> allRoutes;
|
||||
Status status = module->getAudioRoutes(&allRoutes);
|
||||
ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
|
||||
ADD_FAILURE() << " no routes returned for the connected port "
|
||||
<< portConnected.get().toString()
|
||||
<< "; all routes: " << android::internal::ToString(allRoutes);
|
||||
}
|
||||
}
|
||||
std::vector<AudioRoute> ignored;
|
||||
Status status = module->getAudioRoutesForAudioPort(connectedPortId, &ignored);
|
||||
ASSERT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
|
||||
<< status << " returned when retrieving routes for released connected port id "
|
||||
<< connectedPortId;
|
||||
|
||||
std::vector<AudioRoute> routesAfter;
|
||||
{
|
||||
Status status = module->getAudioRoutes(&routesAfter);
|
||||
ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status;
|
||||
}
|
||||
ASSERT_EQ(routesBefore.size(), routesAfter.size())
|
||||
<< "Sizes of audio route arrays do not match after creating and "
|
||||
<< "releasing a connected port";
|
||||
std::sort(routesBefore.begin(), routesBefore.end());
|
||||
std::sort(routesAfter.begin(), routesAfter.end());
|
||||
EXPECT_EQ(routesBefore, routesAfter);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Stream>
|
||||
class AudioStream : public AudioCoreModule {
|
||||
public:
|
||||
@@ -899,7 +1278,7 @@ class AudioModulePatch : public AudioCoreModule {
|
||||
|
||||
std::set<int32_t> portConfigIds;
|
||||
ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds));
|
||||
for (const auto portConfigId : getNonExistentIds(portConfigIds)) {
|
||||
for (const auto portConfigId : GetNonExistentIds(portConfigIds)) {
|
||||
EXPECT_NO_FATAL_FAILURE(SetInvalidPatchHelper(
|
||||
Status::EX_ILLEGAL_ARGUMENT, {portConfigId}, {sinkPortConfig.getId()}));
|
||||
EXPECT_NO_FATAL_FAILURE(SetInvalidPatchHelper(Status::EX_ILLEGAL_ARGUMENT,
|
||||
@@ -969,7 +1348,7 @@ class AudioModulePatch : public AudioCoreModule {
|
||||
// Then use the same patch setting, except for having an invalid ID.
|
||||
std::set<int32_t> patchIds;
|
||||
ASSERT_NO_FATAL_FAILURE(GetAllPatchIds(&patchIds));
|
||||
for (const auto patchId : getNonExistentIds(patchIds)) {
|
||||
for (const auto patchId : GetNonExistentIds(patchIds)) {
|
||||
AudioPatch patchWithNonExistendId = patch.get();
|
||||
patchWithNonExistendId.id = patchId;
|
||||
Status status = module->setAudioPatch(patchWithNonExistendId, &patchWithNonExistendId);
|
||||
@@ -995,7 +1374,7 @@ TEST_PATCH_BOTH_DIRECTIONS(UpdatePatch);
|
||||
TEST_P(AudioModulePatch, ResetInvalidPatchId) {
|
||||
std::set<int32_t> patchIds;
|
||||
ASSERT_NO_FATAL_FAILURE(GetAllPatchIds(&patchIds));
|
||||
for (const auto patchId : getNonExistentIds(patchIds)) {
|
||||
for (const auto patchId : GetNonExistentIds(patchIds)) {
|
||||
Status status = module->resetAudioPatch(patchId);
|
||||
EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode())
|
||||
<< status << " returned for patch ID " << patchId;
|
||||
|
||||
Reference in New Issue
Block a user