diff --git a/audio/aidl/Android.bp b/audio/aidl/Android.bp index fd893ec1ed..4927e834d8 100644 --- a/audio/aidl/Android.bp +++ b/audio/aidl/Android.bp @@ -64,3 +64,28 @@ aidl_interface { ], } + +aidl_interface { + name: "android.hardware.audio.core", + vendor_available: true, + srcs: [ + "android/hardware/audio/core/AudioPatch.aidl", + "android/hardware/audio/core/AudioRoute.aidl", + "android/hardware/audio/core/IConfig.aidl", + "android/hardware/audio/core/IModule.aidl", + "android/hardware/audio/core/IStreamIn.aidl", + "android/hardware/audio/core/IStreamOut.aidl", + ], + imports: [ + "android.hardware.audio.common-V1", + "android.media.audio.common.types-V1", + ], + stability: "vintf", + backend: { + java: { + platform_apis: true, + }, + }, + versions: [ + ], +} diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/AudioPatch.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/AudioPatch.aidl new file mode 100644 index 0000000000..1cef4cd1ae --- /dev/null +++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/AudioPatch.aidl @@ -0,0 +1,40 @@ +/* + * 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 -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 AudioPatch { + int id; + int[] sourcePortConfigIds; + int[] sinkPortConfigIds; +} diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/AudioRoute.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/AudioRoute.aidl new file mode 100644 index 0000000000..deeef872d7 --- /dev/null +++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/AudioRoute.aidl @@ -0,0 +1,40 @@ +/* + * 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 -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 AudioRoute { + int[] sourcePortIds; + int sinkPortId; + boolean isExclusive; +} diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IConfig.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IConfig.aidl new file mode 100644 index 0000000000..fd80715064 --- /dev/null +++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IConfig.aidl @@ -0,0 +1,37 @@ +/* + * 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 -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; +@VintfStability +interface IConfig { +} diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IModule.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IModule.aidl new file mode 100644 index 0000000000..33e8290608 --- /dev/null +++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IModule.aidl @@ -0,0 +1,48 @@ +/* + * 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 -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; +@VintfStability +interface IModule { + 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.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); + boolean setAudioPortConfig(in android.media.audio.common.AudioPortConfig requested, out android.media.audio.common.AudioPortConfig suggested); + void resetAudioPatch(int patchId); + void resetAudioPortConfig(int portConfigId); +} diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IStreamIn.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IStreamIn.aidl new file mode 100644 index 0000000000..d5ab3e8b22 --- /dev/null +++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IStreamIn.aidl @@ -0,0 +1,39 @@ +/* + * 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 -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; +@VintfStability +interface IStreamIn { + void close(); + void updateMetadata(in android.hardware.audio.common.SinkMetadata sinkMetadata); +} diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IStreamOut.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IStreamOut.aidl new file mode 100644 index 0000000000..3021d94c59 --- /dev/null +++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IStreamOut.aidl @@ -0,0 +1,39 @@ +/* + * 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 -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; +@VintfStability +interface IStreamOut { + void close(); + void updateMetadata(in android.hardware.audio.common.SourceMetadata sourceMetadata); +} diff --git a/audio/aidl/android/hardware/audio/core/AudioPatch.aidl b/audio/aidl/android/hardware/audio/core/AudioPatch.aidl new file mode 100644 index 0000000000..48ca2142f8 --- /dev/null +++ b/audio/aidl/android/hardware/audio/core/AudioPatch.aidl @@ -0,0 +1,40 @@ +/* + * 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; + +/** + * Audio patch specifies a connection between multiple audio port + * configurations. + */ +@JavaDerive(equals=true, toString=true) +@VintfStability +parcelable AudioPatch { + /** The ID of the patch, unique within the HAL module. */ + int id; + /** + * The list of IDs of source audio port configs ('AudioPortConfig.id'). + * There must be at least one source in a valid patch and all IDs must be + * unique. + */ + int[] sourcePortConfigIds; + /** + * The list of IDs of sink audio port configs ('AudioPortConfig.id'). + * There must be at least one sink in a valid patch and all IDs must be + * unique. + */ + int[] sinkPortConfigIds; +} diff --git a/audio/aidl/android/hardware/audio/core/AudioRoute.aidl b/audio/aidl/android/hardware/audio/core/AudioRoute.aidl new file mode 100644 index 0000000000..1e7b44141e --- /dev/null +++ b/audio/aidl/android/hardware/audio/core/AudioRoute.aidl @@ -0,0 +1,39 @@ +/* + * 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; + +/** + * Audio route specifies a path from multiple audio source ports to one audio + * sink port. As an example, when emitting audio output, source ports typically + * are mix ports (audio data from the framework), the sink is a device + * port. When acquiring audio, source ports are device ports, the sink is a mix + * port. + */ +@JavaDerive(equals=true, toString=true) +@VintfStability +parcelable AudioRoute { + /** + * The list of IDs of source audio ports ('AudioPort.id'). + * There must be at least one source in a valid route and all IDs must be + * unique. + */ + int[] sourcePortIds; + /** The ID of the sink audio port ('AudioPort.id'). */ + int sinkPortId; + /** If set, only one source can be active, mixing is not supported. */ + boolean isExclusive; +} diff --git a/audio/aidl/android/hardware/audio/core/IConfig.aidl b/audio/aidl/android/hardware/audio/core/IConfig.aidl new file mode 100644 index 0000000000..c7bb414e86 --- /dev/null +++ b/audio/aidl/android/hardware/audio/core/IConfig.aidl @@ -0,0 +1,24 @@ +/* + * 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 interface provides system-wide configuration parameters for audio I/O + * (by "system" here we mean the device running Android). + */ +@VintfStability +interface IConfig {} diff --git a/audio/aidl/android/hardware/audio/core/IModule.aidl b/audio/aidl/android/hardware/audio/core/IModule.aidl new file mode 100644 index 0000000000..d47ea3cf4d --- /dev/null +++ b/audio/aidl/android/hardware/audio/core/IModule.aidl @@ -0,0 +1,281 @@ +/* + * 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; + +import android.hardware.audio.common.SinkMetadata; +import android.hardware.audio.common.SourceMetadata; +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.media.audio.common.AudioOffloadInfo; +import android.media.audio.common.AudioPort; +import android.media.audio.common.AudioPortConfig; + +/** + * Each instance of IModule corresponds to a separate audio module. The system + * (the term "system" as used here applies to the entire device running Android) + * may have multiple modules due to the physical architecture, for example, it + * can have multiple DSPs or other audio I/O units which are not interconnected + * in hardware directly. Usually there is at least one audio module which is + * responsible for the "main" (or "built-in") audio functionality of the + * system. Even if the system lacks any physical audio I/O capabilities, there + * will be a "null" audio module. + * + * On a typical mobile phone there is usually a main DSP module which handles + * most of the phone's audio I/O via the built-in speakers and microphones. USB + * audio can exist as a separate module. Some audio modules can be implemented + * purely in software, for example, the remote submix module. + */ +@VintfStability +interface IModule { + /** + * Return all audio patches of this module. + * + * Returns a list of audio patches, that is, established connections between + * audio port configurations. + * + * @return The list of audio patches. + */ + AudioPatch[] getAudioPatches(); + + /** + * 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'. + * + * @return The current state of an audio port. + * @param portId The ID of the audio port. + * @throws EX_ILLEGAL_ARGUMENT If the port can not be found by the ID. + */ + AudioPort getAudioPort(int portId); + + /** + * Return all active audio port configurations of this module. + * + * Returns a list of active configurations that are currently set for mix + * ports and device ports. Each returned configuration must have an unique + * ID within this module ('AudioPortConfig.id' field), which can coincide + * with an ID of an audio port, if the port only supports a single active + * configuration. Each returned configuration must also have a reference to + * an existing port ('AudioPortConfig.portId' field). All optional + * (nullable) fields of the configurations must be initialized by the HAL + * module. + * + * @return The list of active audio port configurations. + */ + AudioPortConfig[] getAudioPortConfigs(); + + /** + * Return 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. + * + * @return The list of audio ports. + */ + AudioPort[] getAudioPorts(); + + /** + * Return all 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. + * + * @return The list of audio routes. + */ + AudioRoute[] getAudioRoutes(); + + /** + * Open an input stream using an existing audio mix port configuration. + * + * The audio port configuration ID must be obtained by calling + * 'setAudioPortConfig' method. Existence of an audio patch involving this + * port configuration is not required for successful opening of a stream. + * + * Only one stream is allowed per audio port configuration. HAL module can + * also set a limit on how many output streams can be opened for a particular + * mix port by using its 'AudioPortMixExt.maxOpenStreamCount' field. + * + * @return An opened input stream. + * @param portConfigId The ID of the audio mix port config. + * @param sinkMetadata Description of the audio that will be recorded. + * @throws EX_ILLEGAL_ARGUMENT In the following cases: + * - If the port config can not be found by the ID. + * - If the port config is not of an input mix port. + * @throws EX_ILLEGAL_STATE In the following cases: + * - If the port config already has a stream opened on it. + * - If the limit on the open stream count for the port has + * been reached. + */ + IStreamIn openInputStream(int portConfigId, in SinkMetadata sinkMetadata); + + /** + * Open an output stream using an existing audio mix port configuration. + * + * The audio port configuration ID must be obtained by calling + * 'setAudioPortConfig' method. Existence of an audio patch involving this + * port configuration is not required for successful opening of a stream. + * + * If the port configuration has 'COMPRESS_OFFLOAD' output flag set, + * the framework must provide additional information about the encoded + * audio stream in 'offloadInfo' argument. + * + * Only one stream is allowed per audio port configuration. HAL module can + * also set a limit on how many output streams can be opened for a particular + * mix port by using its 'AudioPortMixExt.maxOpenStreamCount' field. + * Only one stream can be opened on the audio port with 'PRIMARY' output + * flag. This rule can not be overridden with 'maxOpenStreamCount' field. + * + * @return An opened output stream. + * @param portConfigId The ID of the audio mix port config. + * @param sourceMetadata Description of the audio that will be played. + * @param offloadInfo Additional information for offloaded playback. + * @throws EX_ILLEGAL_ARGUMENT In the following cases: + * - If the port config can not be found by the ID. + * - If the port config is not of an output mix port. + * @throws EX_ILLEGAL_STATE In the following cases: + * - If the port config already has a stream opened on it. + * - If the limit on the open stream count for the port has + * been reached. + * - If another opened stream already exists for the 'PRIMARY' + * output port. + */ + IStreamOut openOutputStream(int portConfigId, in SourceMetadata sourceMetadata, + in @nullable AudioOffloadInfo offloadInfo); + + /** + * Set an audio patch. + * + * This method creates new or updates an existing audio patch. If the + * requested audio patch does not have a specified id, then a new patch is + * created and an ID is allocated for it by the HAL module. Otherwise an + * attempt to update an existing patch is made. It is recommended that + * updating of an existing audio patch should be performed by the HAL module + * in a way that does not interrupt active audio streams involving audio + * port configurations of the patch. If the HAL module is unable to avoid + * interruption when updating a certain patch, it is permitted to allocate a + * new patch ID for the result. The returned audio patch contains all the + * information about the new or updated audio patch. + * + * Audio port configurations specified in the patch must be obtained by + * calling 'setAudioPortConfig' method. There must be an audio route which + * allows connection between the audio ports whose configurations are used. + * An audio patch may be created before or after an audio steam is created + * for this configuration. + * + * @return Resulting audio patch. + * @param requested Requested audio patch. + * @throws EX_ILLEGAL_ARGUMENT In the following cases: + * - If the patch is invalid (see AudioPatch). + * - If a port config can not be found from the specified IDs. + * - If there are no routes satisfying the patch. + * - If an existing patch can not be found by the ID. + * @throws EX_ILLEGAL_STATE In the following cases: + * - If application of the patch can only use a route with an + * exclusive use the sink port, and it is already patched. + * @throws EX_UNSUPPORTED_OPERATION If the patch can not be established because + * the HAL module does not support this otherwise valid + * patch configuration. For example, if it's a patch + * between multiple sources and sinks, and the HAL module + * does not support this. + */ + AudioPatch setAudioPatch(in AudioPatch requested); + + /** + * Set the active configuration of an audio port. + * + * This method is used to create or update an active configuration for a mix + * port or a device port. The port is specified using the + * 'AudioPortConfig.portId' field. If the requested audio port + * configuration does not have a specified id in the 'AudioPortConfig.id' + * field, then a new configuration is created and an ID is allocated for it + * by the HAL module. Otherwise an attempt to update an existing port + * configuration is made. The HAL module returns the resulting audio port + * configuration. Depending on the port and on the capabilities of the HAL + * module, it can either update an existing port configuration (same port + * configuration ID remains), or create a new one. The resulting port + * configuration ID is returned in the 'id' field of the 'suggested' + * argument. + * + * If the specified port configuration can not be set, this method must + * return 'false' and provide its own suggestion in the output + * parameter. The framework can then set the suggested configuration on a + * subsequent retry call to this 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. + * Suggested audio port configuration if the requested + * configuration can't be applied. + * @throws EX_ILLEGAL_ARGUMENT In the following cases: + * - If neither port config ID, nor port ID are specified. + * - If an existing port config can not be found by the ID. + * - 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. + */ + boolean setAudioPortConfig(in AudioPortConfig requested, out AudioPortConfig suggested); + + /** + * Reset the audio patch. + * + * Resets previously created audio patch using its ID ('AudioPatch.id'). It + * is allowed to reset a patch which uses audio port configurations having + * associated streams. In this case the mix port becomes disconnected from + * the hardware, but the stream does not close. + * + * @param patchId The ID of the audio patch. + * @throws EX_ILLEGAL_ARGUMENT If an existing patch can not be found by the ID. + */ + void resetAudioPatch(int patchId); + + /** + * Reset the audio port configuration. + * + * Resets the specified audio port configuration, discarding all changes + * previously done by the framework. That means, if a call to this method is + * a success, the effect of all previous calls to 'setAudioPortConfig' which + * used or initially have generated the provided 'portConfigId', since the + * module start, or since the last call to this method, has been canceled. + * + * Audio port configurations of mix ports with streams opened on them can + * not be reset. Also can not be reset port configurations currently used by + * any patches. + * + * @param portConfigId The ID of the audio port config. + * @throws EX_ILLEGAL_ARGUMENT If the port config can not be found by the ID. + * @throws EX_ILLEGAL_STATE In the following cases: + * - If the port config has a stream opened on it; + * - If the port config is used by a patch. + */ + void resetAudioPortConfig(int portConfigId); +} diff --git a/audio/aidl/android/hardware/audio/core/IStreamIn.aidl b/audio/aidl/android/hardware/audio/core/IStreamIn.aidl new file mode 100644 index 0000000000..b770449d52 --- /dev/null +++ b/audio/aidl/android/hardware/audio/core/IStreamIn.aidl @@ -0,0 +1,46 @@ +/* + * 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; + +import android.hardware.audio.common.SinkMetadata; + +/** + * This interface provides means for receiving audio data from input devices. + */ +@VintfStability +interface IStreamIn { + /** + * Close the stream. + * + * Releases any resources allocated for this stream on the HAL module side. + * The stream can not be operated after it has been closed. Methods of this + * interface throw EX_ILLEGAL_STATE in for a closed stream. + * + * @throws EX_ILLEGAL_STATE If the stream has already been closed. + */ + void close(); + + /** + * Update stream metadata. + * + * Updates the metadata initially provided at the stream creation. + * + * @param sinkMetadata Updated metadata. + * @throws EX_ILLEGAL_STATE If the stream is closed. + */ + void updateMetadata(in SinkMetadata sinkMetadata); +} diff --git a/audio/aidl/android/hardware/audio/core/IStreamOut.aidl b/audio/aidl/android/hardware/audio/core/IStreamOut.aidl new file mode 100644 index 0000000000..60212fc891 --- /dev/null +++ b/audio/aidl/android/hardware/audio/core/IStreamOut.aidl @@ -0,0 +1,46 @@ +/* + * 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; + +import android.hardware.audio.common.SourceMetadata; + +/** + * This interface provides means for sending audio data to output devices. + */ +@VintfStability +interface IStreamOut { + /** + * Close the stream. + * + * Releases any resources allocated for this stream on the HAL module side. + * The stream can not be operated after it has been closed. Methods of this + * interface throw EX_ILLEGAL_STATE in for a closed stream. + * + * @throws EX_ILLEGAL_STATE If the stream has already been closed. + */ + void close(); + + /** + * Update stream metadata. + * + * Updates the metadata initially provided at the stream creation. + * + * @param sourceMetadata Updated metadata. + * @throws EX_ILLEGAL_STATE If the stream is closed. + */ + void updateMetadata(in SourceMetadata sourceMetadata); +} diff --git a/audio/aidl/default/Android.bp b/audio/aidl/default/Android.bp new file mode 100644 index 0000000000..0a6fe60c39 --- /dev/null +++ b/audio/aidl/default/Android.bp @@ -0,0 +1,45 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "hardware_interfaces_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["hardware_interfaces_license"], +} + +cc_library_static { + name: "libaudioserviceexampleimpl", + vendor: true, + shared_libs: [ + "libbase", + "libbinder_ndk", + "android.hardware.audio.core-V1-ndk", + ], + export_include_dirs: ["include"], + srcs: [ + "Config.cpp", + "Configuration.cpp", + "Module.cpp", + "Stream.cpp", + ], + visibility: [ + ":__subpackages__", + ], +} + +cc_binary { + name: "android.hardware.audio.service-aidl.example", + relative_install_path: "hw", + init_rc: ["android.hardware.audio.service-aidl.example.rc"], + vintf_fragments: ["android.hardware.audio.service-aidl.xml"], + vendor: true, + shared_libs: [ + "libbase", + "libbinder_ndk", + "android.hardware.audio.core-V1-ndk", + ], + static_libs: [ + "libaudioserviceexampleimpl", + ], + srcs: ["main.cpp"], +} diff --git a/audio/aidl/default/Config.cpp b/audio/aidl/default/Config.cpp new file mode 100644 index 0000000000..3f7a3d3e6f --- /dev/null +++ b/audio/aidl/default/Config.cpp @@ -0,0 +1,19 @@ +/* + * 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. + */ + +#include "core-impl/Config.h" + +namespace aidl::android::hardware::audio::core {} // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/Configuration.cpp b/audio/aidl/default/Configuration.cpp new file mode 100644 index 0000000000..1104caadcf --- /dev/null +++ b/audio/aidl/default/Configuration.cpp @@ -0,0 +1,196 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include + +#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::AudioDeviceType; +using aidl::android::media::audio::common::AudioFormatDescription; +using aidl::android::media::audio::common::AudioFormatType; +using aidl::android::media::audio::common::AudioGainConfig; +using aidl::android::media::audio::common::AudioIoFlags; +using aidl::android::media::audio::common::AudioOutputFlags; +using aidl::android::media::audio::common::AudioPort; +using aidl::android::media::audio::common::AudioPortConfig; +using aidl::android::media::audio::common::AudioPortDeviceExt; +using aidl::android::media::audio::common::AudioPortExt; +using aidl::android::media::audio::common::AudioPortMixExt; +using aidl::android::media::audio::common::AudioProfile; +using aidl::android::media::audio::common::Int; +using aidl::android::media::audio::common::PcmType; + +namespace aidl::android::hardware::audio::core::internal { + +static AudioProfile createProfile(PcmType pcmType, const std::vector& channelLayouts, + const std::vector& sampleRates) { + AudioProfile profile; + profile.format.type = AudioFormatType::PCM; + profile.format.pcm = pcmType; + for (auto layout : channelLayouts) { + profile.channelMasks.push_back( + AudioChannelLayout::make(layout)); + } + profile.sampleRates.insert(profile.sampleRates.end(), sampleRates.begin(), sampleRates.end()); + return profile; +} + +static AudioPortExt createDeviceExt(AudioDeviceType devType, int32_t flags) { + AudioPortDeviceExt deviceExt; + deviceExt.device.type.type = devType; + deviceExt.flags = flags; + return AudioPortExt::make(deviceExt); +} + +static AudioPortExt createPortMixExt(int32_t maxOpenStreamCount, int32_t maxActiveStreamCount) { + AudioPortMixExt mixExt; + mixExt.maxOpenStreamCount = maxOpenStreamCount; + mixExt.maxActiveStreamCount = maxActiveStreamCount; + return AudioPortExt::make(mixExt); +} + +static AudioPort createPort(int32_t id, const std::string& name, int32_t flags, bool isInput, + const AudioPortExt& ext) { + AudioPort port; + port.id = id; + port.name = name; + port.flags = isInput ? AudioIoFlags::make(flags) + : AudioIoFlags::make(flags); + port.ext = ext; + return port; +} + +static AudioPortConfig createPortConfig(int32_t id, int32_t portId, PcmType pcmType, int32_t layout, + int32_t sampleRate, int32_t flags, bool isInput, + const AudioPortExt& ext) { + AudioPortConfig config; + config.id = id; + config.portId = portId; + config.sampleRate = Int{.value = sampleRate}; + config.channelMask = AudioChannelLayout::make(layout); + config.format = AudioFormatDescription{.type = AudioFormatType::PCM, .pcm = pcmType}; + config.gain = AudioGainConfig(); + config.flags = isInput ? AudioIoFlags::make(flags) + : AudioIoFlags::make(flags); + config.ext = ext; + return config; +} + +static AudioRoute createRoute(const std::vector& sources, int32_t sink) { + AudioRoute route; + route.sinkPortId = sink; + route.sourcePortIds.insert(route.sourcePortIds.end(), sources.begin(), sources.end()); + return route; +} + +Configuration& getNullPrimaryConfiguration() { + static Configuration configuration = []() { + Configuration c; + + AudioPort nullOutDevice = + createPort(c.nextPortId++, "Null", 0, false, + 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(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 loopOutDevice = createPort(c.nextPortId++, "Loopback Out", 0, false, + createDeviceExt(AudioDeviceType::OUT_SUBMIX, 0)); + loopOutDevice.profiles.push_back( + createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000})); + c.ports.push_back(loopOutDevice); + + AudioPort loopOutMix = + createPort(c.nextPortId++, "loopback output", 0, false, createPortMixExt(0, 0)); + loopOutMix.profiles.push_back( + createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000})); + c.ports.push_back(loopOutMix); + + c.routes.push_back(createRoute({loopOutMix.id}, loopOutDevice.id)); + + AudioPort zeroInDevice = + createPort(c.nextPortId++, "Zero", 0, true, + createDeviceExt(AudioDeviceType::IN_MICROPHONE, + 1 << AudioPortDeviceExt::FLAG_INDEX_DEFAULT_DEVICE)); + c.ports.push_back(zeroInDevice); + + AudioPort primaryInMix = + createPort(c.nextPortId++, "primary input", 0, true, createPortMixExt(2, 2)); + primaryInMix.profiles.push_back( + createProfile(PcmType::INT_16_BIT, + {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO, + AudioChannelLayout::LAYOUT_FRONT_BACK}, + {8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000})); + primaryInMix.profiles.push_back( + createProfile(PcmType::INT_24_BIT, + {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO, + AudioChannelLayout::LAYOUT_FRONT_BACK}, + {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( + createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000})); + c.ports.push_back(loopInDevice); + + AudioPort loopInMix = + createPort(c.nextPortId++, "loopback input", 0, true, createPortMixExt(0, 0)); + loopInMix.profiles.push_back( + createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000})); + c.ports.push_back(loopInMix); + + c.routes.push_back(createRoute({loopInDevice.id}, loopInMix.id)); + + c.portConfigs.insert(c.portConfigs.end(), c.initialConfigs.begin(), c.initialConfigs.end()); + return c; + }(); + return configuration; +} + +} // namespace aidl::android::hardware::audio::core::internal diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp new file mode 100644 index 0000000000..e0a68a5fcd --- /dev/null +++ b/audio/aidl/default/Module.cpp @@ -0,0 +1,522 @@ +/* + * 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. + */ + +#include +#include + +#define LOG_TAG "AHAL_Module" +#define LOG_NDEBUG 0 +#include + +#include + +#include "core-impl/Module.h" +#include "core-impl/utils.h" + +using aidl::android::hardware::audio::common::SinkMetadata; +using aidl::android::hardware::audio::common::SourceMetadata; +using aidl::android::media::audio::common::AudioFormatDescription; +using aidl::android::media::audio::common::AudioIoFlags; +using aidl::android::media::audio::common::AudioOffloadInfo; +using aidl::android::media::audio::common::AudioOutputFlags; +using aidl::android::media::audio::common::AudioPort; +using aidl::android::media::audio::common::AudioPortConfig; +using aidl::android::media::audio::common::AudioPortExt; +using aidl::android::media::audio::common::AudioProfile; +using aidl::android::media::audio::common::Int; + +namespace aidl::android::hardware::audio::core { + +namespace { + +bool generateDefaultPortConfig(const AudioPort& port, AudioPortConfig* config) { + *config = {}; + config->portId = port.id; + if (port.profiles.empty()) { + LOG(ERROR) << __func__ << ": port " << port.id << " has no profiles"; + return false; + } + const auto& profile = port.profiles.begin(); + config->format = profile->format; + if (profile->channelMasks.empty()) { + LOG(ERROR) << __func__ << ": the first profile in port " << port.id + << " has no channel masks"; + return false; + } + config->channelMask = *profile->channelMasks.begin(); + if (profile->sampleRates.empty()) { + LOG(ERROR) << __func__ << ": the first profile in port " << port.id + << " has no sample rates"; + return false; + } + Int sampleRate; + sampleRate.value = *profile->sampleRates.begin(); + config->sampleRate = sampleRate; + config->flags = port.flags; + config->ext = port.ext; + return true; +} + +bool findAudioProfile(const AudioPort& port, const AudioFormatDescription& format, + AudioProfile* profile) { + if (auto profilesIt = + find_if(port.profiles.begin(), port.profiles.end(), + [&format](const auto& profile) { return profile.format == format; }); + profilesIt != port.profiles.end()) { + *profile = *profilesIt; + return true; + } + return false; +} +} // namespace + +void Module::cleanUpPatch(int32_t patchId) { + erase_all_values(mPatches, std::set{patchId}); +} + +void Module::cleanUpPatches(int32_t portConfigId) { + auto& patches = getConfig().patches; + if (patches.size() == 0) return; + auto range = mPatches.equal_range(portConfigId); + for (auto it = range.first; it != range.second; ++it) { + auto patchIt = findById(patches, it->second); + if (patchIt != patches.end()) { + erase_if(patchIt->sourcePortConfigIds, + [portConfigId](auto e) { return e == portConfigId; }); + erase_if(patchIt->sinkPortConfigIds, + [portConfigId](auto e) { return e == portConfigId; }); + } + } + std::set erasedPatches; + for (size_t i = patches.size() - 1; i != 0; --i) { + const auto& patch = patches[i]; + if (patch.sourcePortConfigIds.empty() || patch.sinkPortConfigIds.empty()) { + erasedPatches.insert(patch.id); + patches.erase(patches.begin() + i); + } + } + erase_all_values(mPatches, erasedPatches); +} + +internal::Configuration& Module::getConfig() { + if (!mConfig) { + mConfig.reset(new internal::Configuration(internal::getNullPrimaryConfiguration())); + } + return *mConfig; +} + +void Module::registerPatch(const AudioPatch& patch) { + auto& configs = getConfig().portConfigs; + auto do_insert = [&](const std::vector& portConfigIds) { + for (auto portConfigId : portConfigIds) { + auto configIt = findById(configs, portConfigId); + if (configIt != configs.end()) { + mPatches.insert(std::pair{portConfigId, patch.id}); + if (configIt->portId != portConfigId) { + mPatches.insert(std::pair{configIt->portId, patch.id}); + } + } + }; + }; + do_insert(patch.sourcePortConfigIds); + do_insert(patch.sinkPortConfigIds); +} + +ndk::ScopedAStatus Module::getAudioPatches(std::vector* _aidl_return) { + *_aidl_return = getConfig().patches; + LOG(DEBUG) << __func__ << ": returning " << _aidl_return->size() << " patches"; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Module::getAudioPort(int32_t in_portId, AudioPort* _aidl_return) { + auto& ports = getConfig().ports; + auto portIt = findById(ports, in_portId); + if (portIt != ports.end()) { + *_aidl_return = *portIt; + LOG(DEBUG) << __func__ << ": returning port by id " << in_portId; + return ndk::ScopedAStatus::ok(); + } + LOG(ERROR) << __func__ << ": port id " << in_portId << " not found"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); +} + +ndk::ScopedAStatus Module::getAudioPortConfigs(std::vector* _aidl_return) { + *_aidl_return = getConfig().portConfigs; + LOG(DEBUG) << __func__ << ": returning " << _aidl_return->size() << " port configs"; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Module::getAudioPorts(std::vector* _aidl_return) { + *_aidl_return = getConfig().ports; + LOG(DEBUG) << __func__ << ": returning " << _aidl_return->size() << " ports"; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Module::getAudioRoutes(std::vector* _aidl_return) { + *_aidl_return = getConfig().routes; + LOG(DEBUG) << __func__ << ": returning " << _aidl_return->size() << " routes"; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Module::openInputStream(int32_t in_portConfigId, + const SinkMetadata& in_sinkMetadata, + std::shared_ptr* _aidl_return) { + auto& configs = getConfig().portConfigs; + auto portConfigIt = findById(configs, in_portConfigId); + if (portConfigIt == configs.end()) { + LOG(ERROR) << __func__ << ": existing port config id " << in_portConfigId << " not found"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + const int32_t portId = portConfigIt->portId; + // In our implementation, configs of mix ports always have unique IDs. + CHECK(portId != in_portConfigId); + auto& ports = getConfig().ports; + auto portIt = findById(ports, portId); + if (portIt == ports.end()) { + LOG(ERROR) << __func__ << ": port id " << portId << " used by port config id " + << in_portConfigId << " not found"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (portIt->flags.getTag() != AudioIoFlags::Tag::input || + portIt->ext.getTag() != AudioPortExt::Tag::mix) { + LOG(ERROR) << __func__ << ": port config id " << in_portConfigId + << " does not correspond to an input mix port"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (mStreams.count(in_portConfigId) != 0) { + LOG(ERROR) << __func__ << ": port config id " << in_portConfigId + << " already has a stream opened on it"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + const int32_t maxOpenStreamCount = portIt->ext.get().maxOpenStreamCount; + if (maxOpenStreamCount != 0 && mStreams.count(portId) >= maxOpenStreamCount) { + LOG(ERROR) << __func__ << ": port id " << portId + << " has already reached maximum allowed opened stream count: " + << maxOpenStreamCount; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + auto stream = ndk::SharedRefBase::make(in_sinkMetadata); + mStreams.insert(portId, in_portConfigId, StreamWrapper(stream)); + *_aidl_return = std::move(stream); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Module::openOutputStream(int32_t in_portConfigId, + const SourceMetadata& in_sourceMetadata, + const std::optional& in_offloadInfo, + std::shared_ptr* _aidl_return) { + auto& configs = getConfig().portConfigs; + auto portConfigIt = findById(configs, in_portConfigId); + if (portConfigIt == configs.end()) { + LOG(ERROR) << __func__ << ": existing port config id " << in_portConfigId << " not found"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + const int32_t portId = portConfigIt->portId; + // In our implementation, configs of mix ports always have unique IDs. + CHECK(portId != in_portConfigId); + auto& ports = getConfig().ports; + auto portIt = findById(ports, portId); + if (portIt == ports.end()) { + LOG(ERROR) << __func__ << ": port id " << portId << " used by port config id " + << in_portConfigId << " not found"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (portIt->flags.getTag() != AudioIoFlags::Tag::output || + portIt->ext.getTag() != AudioPortExt::Tag::mix) { + LOG(ERROR) << __func__ << ": port config id " << in_portConfigId + << " does not correspond to an output mix port"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (mStreams.count(in_portConfigId) != 0) { + LOG(ERROR) << __func__ << ": port config id " << in_portConfigId + << " already has a stream opened on it"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + const int32_t maxOpenStreamCount = portIt->ext.get().maxOpenStreamCount; + if (maxOpenStreamCount != 0 && mStreams.count(portId) >= maxOpenStreamCount) { + LOG(ERROR) << __func__ << ": port id " << portId + << " has already reached maximum allowed opened stream count: " + << maxOpenStreamCount; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + auto stream = ndk::SharedRefBase::make(in_sourceMetadata, in_offloadInfo); + mStreams.insert(portId, in_portConfigId, StreamWrapper(stream)); + *_aidl_return = std::move(stream); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Module::setAudioPatch(const AudioPatch& in_requested, AudioPatch* _aidl_return) { + if (in_requested.sourcePortConfigIds.empty()) { + LOG(ERROR) << __func__ << ": requested patch has empty sources list"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (!all_unique(in_requested.sourcePortConfigIds)) { + LOG(ERROR) << __func__ << ": requested patch has duplicate ids in the sources list"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (in_requested.sinkPortConfigIds.empty()) { + LOG(ERROR) << __func__ << ": requested patch has empty sinks list"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (!all_unique(in_requested.sinkPortConfigIds)) { + LOG(ERROR) << __func__ << ": requested patch has duplicate ids in the sinks list"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + auto& configs = getConfig().portConfigs; + std::vector missingIds; + auto sources = + selectByIds(configs, in_requested.sourcePortConfigIds, &missingIds); + if (!missingIds.empty()) { + LOG(ERROR) << __func__ << ": following source port config ids not found: " + << ::android::internal::ToString(missingIds); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + auto sinks = selectByIds(configs, in_requested.sinkPortConfigIds, &missingIds); + if (!missingIds.empty()) { + LOG(ERROR) << __func__ << ": following sink port config ids not found: " + << ::android::internal::ToString(missingIds); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + // bool indicates whether a non-exclusive route is available. + // If only an exclusive route is available, that means the patch can not be + // established if there is any other patch which currently uses the sink port. + std::map allowedSinkPorts; + auto& routes = getConfig().routes; + for (auto src : sources) { + for (const auto& r : routes) { + const auto& srcs = r.sourcePortIds; + if (std::find(srcs.begin(), srcs.end(), src->portId) != srcs.end()) { + if (!allowedSinkPorts[r.sinkPortId]) { // prefer non-exclusive + allowedSinkPorts[r.sinkPortId] = !r.isExclusive; + } + } + } + } + for (auto sink : sinks) { + if (allowedSinkPorts.count(sink->portId) == 0) { + LOG(ERROR) << __func__ << ": there is no route to the sink port id " << sink->portId; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + } + + auto& patches = getConfig().patches; + auto existing = patches.end(); + std::optional patchesBackup; + if (in_requested.id != 0) { + existing = findById(patches, in_requested.id); + if (existing != patches.end()) { + patchesBackup = mPatches; + cleanUpPatch(existing->id); + } else { + LOG(ERROR) << __func__ << ": not found existing patch id " << in_requested.id; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + } + // Validate the requested patch. + for (const auto& [sinkPortId, nonExclusive] : allowedSinkPorts) { + if (!nonExclusive && mPatches.count(sinkPortId) != 0) { + LOG(ERROR) << __func__ << ": sink port id " << sinkPortId + << "is exclusive and is already used by some other patch"; + if (patchesBackup.has_value()) { + mPatches = std::move(*patchesBackup); + } + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + } + *_aidl_return = in_requested; + if (existing == patches.end()) { + _aidl_return->id = getConfig().nextPatchId++; + patches.push_back(*_aidl_return); + existing = patches.begin() + (patches.size() - 1); + } else { + *existing = *_aidl_return; + } + registerPatch(*existing); + LOG(DEBUG) << __func__ << ": created or updated patch id " << _aidl_return->id; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Module::setAudioPortConfig(const AudioPortConfig& in_requested, + AudioPortConfig* out_suggested, bool* _aidl_return) { + LOG(DEBUG) << __func__ << ": requested " << in_requested.toString(); + auto& configs = getConfig().portConfigs; + auto existing = configs.end(); + if (in_requested.id != 0) { + if (existing = findById(configs, in_requested.id); + existing == configs.end()) { + LOG(ERROR) << __func__ << ": existing port config id " << in_requested.id + << " not found"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + } + + const int portId = existing != configs.end() ? existing->portId : in_requested.portId; + if (portId == 0) { + LOG(ERROR) << __func__ << ": input port config does not specify portId"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + auto& ports = getConfig().ports; + auto portIt = findById(ports, portId); + if (portIt == ports.end()) { + LOG(ERROR) << __func__ << ": input port config points to non-existent portId " << portId; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (existing != configs.end()) { + *out_suggested = *existing; + } else { + AudioPortConfig newConfig; + if (generateDefaultPortConfig(*portIt, &newConfig)) { + *out_suggested = newConfig; + } else { + LOG(ERROR) << __func__ << ": unable generate a default config for port " << portId; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + } + // From this moment, 'out_suggested' is either an existing port config, + // or a new generated config. Now attempt to update it according to the specified + // fields of 'in_requested'. + + bool requestedIsValid = true, requestedIsFullySpecified = true; + + AudioIoFlags portFlags = portIt->flags; + if (in_requested.flags.has_value()) { + if (in_requested.flags.value() != portFlags) { + LOG(WARNING) << __func__ << ": requested flags " + << in_requested.flags.value().toString() << " do not match port's " + << portId << " flags " << portFlags.toString(); + requestedIsValid = false; + } + } else { + requestedIsFullySpecified = false; + } + + AudioProfile portProfile; + if (in_requested.format.has_value()) { + const auto& format = in_requested.format.value(); + if (findAudioProfile(*portIt, format, &portProfile)) { + out_suggested->format = format; + } else { + LOG(WARNING) << __func__ << ": requested format " << format.toString() + << " is not found in port's " << portId << " profiles"; + requestedIsValid = false; + } + } else { + requestedIsFullySpecified = false; + } + if (!findAudioProfile(*portIt, out_suggested->format.value(), &portProfile)) { + LOG(ERROR) << __func__ << ": port " << portId << " does not support format " + << out_suggested->format.value().toString() << " anymore"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + if (in_requested.channelMask.has_value()) { + const auto& channelMask = in_requested.channelMask.value(); + if (find(portProfile.channelMasks.begin(), portProfile.channelMasks.end(), channelMask) != + portProfile.channelMasks.end()) { + out_suggested->channelMask = channelMask; + } else { + LOG(WARNING) << __func__ << ": requested channel mask " << channelMask.toString() + << " is not supported for the format " << portProfile.format.toString() + << " by the port " << portId; + requestedIsValid = false; + } + } else { + requestedIsFullySpecified = false; + } + + if (in_requested.sampleRate.has_value()) { + const auto& sampleRate = in_requested.sampleRate.value(); + if (find(portProfile.sampleRates.begin(), portProfile.sampleRates.end(), + sampleRate.value) != portProfile.sampleRates.end()) { + out_suggested->sampleRate = sampleRate; + } else { + LOG(WARNING) << __func__ << ": requested sample rate " << sampleRate.value + << " is not supported for the format " << portProfile.format.toString() + << " by the port " << portId; + requestedIsValid = false; + } + } else { + requestedIsFullySpecified = false; + } + + if (in_requested.gain.has_value()) { + // Let's pretend that gain can always be applied. + out_suggested->gain = in_requested.gain.value(); + } + + if (existing == configs.end() && requestedIsValid && requestedIsFullySpecified) { + out_suggested->id = getConfig().nextPortId++; + configs.push_back(*out_suggested); + *_aidl_return = true; + LOG(DEBUG) << __func__ << ": created new port config " << out_suggested->toString(); + } else if (existing != configs.end() && requestedIsValid) { + *existing = *out_suggested; + *_aidl_return = true; + LOG(DEBUG) << __func__ << ": updated port config " << out_suggested->toString(); + } else { + LOG(DEBUG) << __func__ << ": not applied; existing config ? " << (existing != configs.end()) + << "; requested is valid? " << requestedIsValid << ", fully specified? " + << requestedIsFullySpecified; + *_aidl_return = false; + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Module::resetAudioPatch(int32_t in_patchId) { + auto& patches = getConfig().patches; + auto patchIt = findById(patches, in_patchId); + if (patchIt != patches.end()) { + cleanUpPatch(patchIt->id); + patches.erase(patchIt); + LOG(DEBUG) << __func__ << ": erased patch " << in_patchId; + return ndk::ScopedAStatus::ok(); + } + LOG(ERROR) << __func__ << ": patch id " << in_patchId << " not found"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); +} + +ndk::ScopedAStatus Module::resetAudioPortConfig(int32_t in_portConfigId) { + auto& configs = getConfig().portConfigs; + auto configIt = findById(configs, in_portConfigId); + if (configIt != configs.end()) { + if (mStreams.count(in_portConfigId) != 0) { + LOG(ERROR) << __func__ << ": port config id " << in_portConfigId + << " has a stream opened on it"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + auto patchIt = mPatches.find(in_portConfigId); + if (patchIt != mPatches.end()) { + LOG(ERROR) << __func__ << ": port config id " << in_portConfigId + << " is used by the patch with id " << patchIt->second; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + auto& initials = getConfig().initialConfigs; + auto initialIt = findById(initials, in_portConfigId); + if (initialIt == initials.end()) { + configs.erase(configIt); + LOG(DEBUG) << __func__ << ": erased port config " << in_portConfigId; + } else if (*configIt != *initialIt) { + *configIt = *initialIt; + LOG(DEBUG) << __func__ << ": reset port config " << in_portConfigId; + } + return ndk::ScopedAStatus::ok(); + } + LOG(ERROR) << __func__ << ": port config id " << in_portConfigId << " not found"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); +} + +} // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/Stream.cpp b/audio/aidl/default/Stream.cpp new file mode 100644 index 0000000000..e16b2c62b9 --- /dev/null +++ b/audio/aidl/default/Stream.cpp @@ -0,0 +1,76 @@ +/* + * 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. + */ + +#define LOG_TAG "AHAL_Stream" +#define LOG_NDEBUG 0 +#include + +#include "core-impl/Stream.h" + +using aidl::android::hardware::audio::common::SinkMetadata; +using aidl::android::hardware::audio::common::SourceMetadata; +using aidl::android::media::audio::common::AudioOffloadInfo; + +namespace aidl::android::hardware::audio::core { + +StreamIn::StreamIn(const SinkMetadata& sinkMetadata) : mMetadata(sinkMetadata) {} + +ndk::ScopedAStatus StreamIn::close() { + LOG(DEBUG) << __func__; + if (!mIsClosed) { + mIsClosed = true; + return ndk::ScopedAStatus::ok(); + } else { + LOG(ERROR) << __func__ << ": stream was already closed"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } +} + +ndk::ScopedAStatus StreamIn::updateMetadata(const SinkMetadata& in_sinkMetadata) { + LOG(DEBUG) << __func__; + if (!mIsClosed) { + mMetadata = in_sinkMetadata; + return ndk::ScopedAStatus::ok(); + } + LOG(ERROR) << __func__ << ": stream was closed"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); +} + +StreamOut::StreamOut(const SourceMetadata& sourceMetadata, + const std::optional& offloadInfo) + : mMetadata(sourceMetadata), mOffloadInfo(offloadInfo) {} + +ndk::ScopedAStatus StreamOut::close() { + LOG(DEBUG) << __func__; + if (!mIsClosed) { + mIsClosed = true; + return ndk::ScopedAStatus::ok(); + } + LOG(ERROR) << __func__ << ": stream was already closed"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); +} + +ndk::ScopedAStatus StreamOut::updateMetadata(const SourceMetadata& in_sourceMetadata) { + LOG(DEBUG) << __func__; + if (!mIsClosed) { + mMetadata = in_sourceMetadata; + return ndk::ScopedAStatus::ok(); + } + LOG(ERROR) << __func__ << ": stream was closed"; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); +} + +} // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/android.hardware.audio.service-aidl.example.rc b/audio/aidl/default/android.hardware.audio.service-aidl.example.rc new file mode 100644 index 0000000000..02a9c37d42 --- /dev/null +++ b/audio/aidl/default/android.hardware.audio.service-aidl.example.rc @@ -0,0 +1,9 @@ +service vendor.audio-hal-aidl /vendor/bin/hw/android.hardware.audio.service-aidl.example + class hal + user audioserver + # media gid needed for /dev/fm (radio) and for /data/misc/media (tee) + group audio camera drmrpc inet media mediadrm net_bt net_bt_admin net_bw_acct wakelock context_hub + capabilities BLOCK_SUSPEND + ioprio rt 4 + task_profiles ProcessCapacityHigh HighPerformance + onrestart restart audioserver diff --git a/audio/aidl/default/android.hardware.audio.service-aidl.xml b/audio/aidl/default/android.hardware.audio.service-aidl.xml new file mode 100644 index 0000000000..bb4b01a737 --- /dev/null +++ b/audio/aidl/default/android.hardware.audio.service-aidl.xml @@ -0,0 +1,12 @@ + + + android.hardware.audio.core + 1 + IModule/default + + + android.hardware.audio.core + 1 + IConfig/default + + diff --git a/audio/aidl/default/include/core-impl/Config.h b/audio/aidl/default/include/core-impl/Config.h new file mode 100644 index 0000000000..b62a14be4d --- /dev/null +++ b/audio/aidl/default/include/core-impl/Config.h @@ -0,0 +1,25 @@ +/* + * 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. + */ + +#pragma once + +#include + +namespace aidl::android::hardware::audio::core { + +class Config : public BnConfig {}; + +} // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/include/core-impl/Configuration.h b/audio/aidl/default/include/core-impl/Configuration.h new file mode 100644 index 0000000000..17e342d80a --- /dev/null +++ b/audio/aidl/default/include/core-impl/Configuration.h @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include +#include +#include +#include + +namespace aidl::android::hardware::audio::core::internal { + +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; + std::vector routes; + std::vector patches; + int32_t nextPortId = 1; + int32_t nextPatchId = 1; +}; + +Configuration& getNullPrimaryConfiguration(); + +} // namespace aidl::android::hardware::audio::core::internal diff --git a/audio/aidl/default/include/core-impl/Module.h b/audio/aidl/default/include/core-impl/Module.h new file mode 100644 index 0000000000..359626cc22 --- /dev/null +++ b/audio/aidl/default/include/core-impl/Module.h @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +#include + +#include "core-impl/Configuration.h" +#include "core-impl/Stream.h" + +namespace aidl::android::hardware::audio::core { + +class Module : public BnModule { + ndk::ScopedAStatus getAudioPatches(std::vector* _aidl_return) override; + ndk::ScopedAStatus getAudioPort( + int32_t in_portId, + ::aidl::android::media::audio::common::AudioPort* _aidl_return) override; + ndk::ScopedAStatus getAudioPortConfigs( + std::vector<::aidl::android::media::audio::common::AudioPortConfig>* _aidl_return) + override; + ndk::ScopedAStatus getAudioPorts( + std::vector<::aidl::android::media::audio::common::AudioPort>* _aidl_return) override; + ndk::ScopedAStatus getAudioRoutes(std::vector* _aidl_return) override; + ndk::ScopedAStatus openInputStream( + int32_t in_portConfigId, + const ::aidl::android::hardware::audio::common::SinkMetadata& in_sinkMetadata, + std::shared_ptr* _aidl_return) override; + ndk::ScopedAStatus openOutputStream( + int32_t in_portConfigId, + const ::aidl::android::hardware::audio::common::SourceMetadata& in_sourceMetadata, + const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>& + in_offloadInfo, + std::shared_ptr* _aidl_return) override; + ndk::ScopedAStatus setAudioPatch(const AudioPatch& in_requested, + AudioPatch* _aidl_return) override; + ndk::ScopedAStatus setAudioPortConfig( + const ::aidl::android::media::audio::common::AudioPortConfig& in_requested, + ::aidl::android::media::audio::common::AudioPortConfig* out_suggested, + bool* _aidl_return) override; + ndk::ScopedAStatus resetAudioPatch(int32_t in_patchId) override; + ndk::ScopedAStatus resetAudioPortConfig(int32_t in_portConfigId) override; + + private: + void cleanUpPatch(int32_t patchId); + void cleanUpPatches(int32_t portConfigId); + internal::Configuration& getConfig(); + void registerPatch(const AudioPatch& patch); + + std::unique_ptr mConfig; + Streams mStreams; + // Maps port ids and port config ids to patch ids. + // Multimap because both ports and configs can be used by multiple patches. + std::multimap mPatches; +}; + +} // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/include/core-impl/Stream.h b/audio/aidl/default/include/core-impl/Stream.h new file mode 100644 index 0000000000..87104dd1c1 --- /dev/null +++ b/audio/aidl/default/include/core-impl/Stream.h @@ -0,0 +1,103 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "core-impl/utils.h" + +namespace aidl::android::hardware::audio::core { + +class StreamIn : public BnStreamIn { + ndk::ScopedAStatus close() override; + ndk::ScopedAStatus updateMetadata( + const ::aidl::android::hardware::audio::common::SinkMetadata& in_sinkMetadata) override; + + public: + explicit StreamIn(const ::aidl::android::hardware::audio::common::SinkMetadata& sinkMetadata); + bool isClosed() const { return mIsClosed; } + + private: + ::aidl::android::hardware::audio::common::SinkMetadata mMetadata; + bool mIsClosed = false; +}; + +class StreamOut : public BnStreamOut { + ndk::ScopedAStatus close() override; + ndk::ScopedAStatus updateMetadata( + const ::aidl::android::hardware::audio::common::SourceMetadata& in_sourceMetadata) + override; + + public: + StreamOut(const ::aidl::android::hardware::audio::common::SourceMetadata& sourceMetadata, + const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>& + offloadInfo); + bool isClosed() const { return mIsClosed; } + + private: + ::aidl::android::hardware::audio::common::SourceMetadata mMetadata; + std::optional<::aidl::android::media::audio::common::AudioOffloadInfo> mOffloadInfo; + bool mIsClosed = false; +}; + +class StreamWrapper { + public: + explicit StreamWrapper(std::shared_ptr streamIn) : mStream(streamIn) {} + explicit StreamWrapper(std::shared_ptr streamOut) : mStream(streamOut) {} + bool isStreamOpen() const { + return std::visit( + [](auto&& ws) -> bool { + auto s = ws.lock(); + return s && !s->isClosed(); + }, + mStream); + } + + private: + std::variant, std::weak_ptr> mStream; +}; + +class Streams { + public: + Streams() = default; + Streams(const Streams&) = delete; + Streams& operator=(const Streams&) = delete; + size_t count(int32_t id) { + // Streams do not remove themselves from the collection on close. + erase_if(mStreams, [](const auto& pair) { return !pair.second.isStreamOpen(); }); + return mStreams.count(id); + } + void insert(int32_t portId, int32_t portConfigId, StreamWrapper sw) { + mStreams.insert(std::pair{portConfigId, sw}); + mStreams.insert(std::pair{portId, sw}); + } + + private: + // Maps port ids and port config ids to streams. Multimap because a port + // (not port config) can have multiple streams opened on it. + std::multimap mStreams; +}; + +} // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/include/core-impl/utils.h b/audio/aidl/default/include/core-impl/utils.h new file mode 100644 index 0000000000..7101012639 --- /dev/null +++ b/audio/aidl/default/include/core-impl/utils.h @@ -0,0 +1,104 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include + +namespace aidl::android::hardware::audio::core { + +// Return whether all the elements in the vector are unique. +template +bool all_unique(const std::vector& v) { + return std::set(v.begin(), v.end()).size() == v.size(); +} + +// Erase all the specified elements from a map. +template +auto erase_all(C& c, const V& keys) { + auto oldSize = c.size(); + for (auto& k : keys) { + c.erase(k); + } + return oldSize - c.size(); +} + +// Erase all the elements in the map that satisfy the provided predicate. +template +auto erase_if(C& c, P pred) { + auto oldSize = c.size(); + for (auto it = c.begin(), last = c.end(); it != last;) { + if (pred(*it)) { + it = c.erase(it); + } else { + ++it; + } + } + return oldSize - c.size(); +} + +// Erase all the elements in the map that have specified values. +template +auto erase_all_values(C& c, const V& values) { + return erase_if(c, [values](const auto& pair) { return values.count(pair.second) != 0; }); +} + +// Return non-zero count of elements for any of the provided keys. +template +size_t count_any(const M& m, const V& keys) { + for (auto& k : keys) { + if (size_t c = m.count(k); c != 0) return c; + } + return 0; +} + +// Assuming that M is a map whose values have an 'id' field, +// find an element with the specified id. +template +auto findById(M& m, int32_t id) { + return std::find_if(m.begin(), m.end(), [&](const auto& p) { return p.second.id == id; }); +} + +// Assuming that the vector contains elements with an 'id' field, +// find an element with the specified id. +template +auto findById(std::vector& v, int32_t id) { + return std::find_if(v.begin(), v.end(), [&](const auto& e) { return e.id == id; }); +} + +// Return elements from the vector that have specified ids, also +// optionally return which ids were not found. +template +std::vector selectByIds(std::vector& v, const std::vector& ids, + std::vector* missingIds = nullptr) { + std::vector result; + std::set idsSet(ids.begin(), ids.end()); + for (size_t i = 0; i < v.size(); ++i) { + T& e = v[i]; + if (idsSet.count(e.id) != 0) { + result.push_back(&v[i]); + idsSet.erase(e.id); + } + } + if (missingIds) { + *missingIds = std::vector(idsSet.begin(), idsSet.end()); + } + return result; +} + +} // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/main.cpp b/audio/aidl/default/main.cpp new file mode 100644 index 0000000000..0de6047f6e --- /dev/null +++ b/audio/aidl/default/main.cpp @@ -0,0 +1,45 @@ +/* + * 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. + */ + +#include "core-impl/Config.h" +#include "core-impl/Module.h" + +#include +#include +#include + +using aidl::android::hardware::audio::core::Config; +using aidl::android::hardware::audio::core::Module; + +int main() { + ABinderProcess_setThreadPoolMaxThreadCount(16); + + // make the default config service + auto config = ndk::SharedRefBase::make(); + const std::string configName = std::string() + Config::descriptor + "/default"; + binder_status_t status = + AServiceManager_addService(config->asBinder().get(), configName.c_str()); + CHECK(status == STATUS_OK); + + // make the default module + auto moduleDefault = ndk::SharedRefBase::make(); + const std::string moduleDefaultName = std::string() + Module::descriptor + "/default"; + status = AServiceManager_addService(moduleDefault->asBinder().get(), moduleDefaultName.c_str()); + CHECK(status == STATUS_OK); + + ABinderProcess_joinThreadPool(); + return EXIT_FAILURE; // should not reach +} diff --git a/audio/aidl/vts/Android.bp b/audio/aidl/vts/Android.bp new file mode 100644 index 0000000000..c160d1f5cc --- /dev/null +++ b/audio/aidl/vts/Android.bp @@ -0,0 +1,32 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "hardware_interfaces_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["hardware_interfaces_license"], +} + +cc_test { + name: "VtsHalAudioCoreTargetTest", + defaults: [ + "VtsHalTargetTestDefaults", + "use_libaidlvintf_gtest_helper_static", + ], + srcs: [ + "ModuleConfig.cpp", + "VtsHalAudioCoreTargetTest.cpp", + ], + shared_libs: [ + "libbinder", + ], + static_libs: [ + "android.hardware.audio.common-V1-cpp", + "android.hardware.audio.core-V1-cpp", + "android.media.audio.common.types-V1-cpp", + ], + test_suites: [ + "general-tests", + "vts", + ], +} diff --git a/audio/aidl/vts/ModuleConfig.cpp b/audio/aidl/vts/ModuleConfig.cpp new file mode 100644 index 0000000000..3faa39ab4f --- /dev/null +++ b/audio/aidl/vts/ModuleConfig.cpp @@ -0,0 +1,330 @@ +/* + * 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. + */ + +#include + +#include +#include + +#include "ModuleConfig.h" + +using namespace android; + +using android::hardware::audio::core::IModule; +using android::media::audio::common::AudioChannelLayout; +using android::media::audio::common::AudioFormatDescription; +using android::media::audio::common::AudioFormatType; +using android::media::audio::common::AudioIoFlags; +using android::media::audio::common::AudioOutputFlags; +using android::media::audio::common::AudioPort; +using android::media::audio::common::AudioPortConfig; +using android::media::audio::common::AudioPortExt; +using android::media::audio::common::AudioProfile; +using android::media::audio::common::Int; + +template +auto findById(const std::vector& v, int32_t id) { + return std::find_if(v.begin(), v.end(), [&](const auto& p) { return p.id == id; }); +} + +ModuleConfig::ModuleConfig(IModule* module) { + mStatus = module->getAudioPorts(&mPorts); + if (!mStatus.isOk()) return; + for (const auto& port : mPorts) { + if (port.ext.getTag() != AudioPortExt::Tag::device) continue; + const auto& devicePort = port.ext.get(); + const bool isInput = port.flags.getTag() == AudioIoFlags::Tag::input; + if (devicePort.device.type.connection.empty()) { + // Permanently attached device. + if (isInput) { + mAttachedSourceDevicePorts.insert(port.id); + } else { + mAttachedSinkDevicePorts.insert(port.id); + } + } + } + if (!mStatus.isOk()) return; + mStatus = module->getAudioRoutes(&mRoutes); + if (!mStatus.isOk()) return; + mStatus = module->getAudioPortConfigs(&mInitialConfigs); +} + +std::vector ModuleConfig::getInputMixPorts() const { + std::vector result; + std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [](const auto& port) { + return port.ext.getTag() == AudioPortExt::Tag::mix && + port.flags.getTag() == AudioIoFlags::Tag::input; + }); + return result; +} + +std::vector ModuleConfig::getOutputMixPorts() const { + std::vector result; + std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [](const auto& port) { + return port.ext.getTag() == AudioPortExt::Tag::mix && + port.flags.getTag() == AudioIoFlags::Tag::output; + }); + return result; +} + +std::vector ModuleConfig::getAttachedSinkDevicesPortsForMixPort( + const AudioPort& mixPort) const { + std::vector result; + for (const auto& route : mRoutes) { + if (mAttachedSinkDevicePorts.count(route.sinkPortId) != 0 && + std::find(route.sourcePortIds.begin(), route.sourcePortIds.end(), mixPort.id) != + route.sourcePortIds.end()) { + const auto devicePortIt = findById(mPorts, route.sinkPortId); + if (devicePortIt != mPorts.end()) result.push_back(*devicePortIt); + } + } + return result; +} + +std::vector ModuleConfig::getAttachedSourceDevicesPortsForMixPort( + const AudioPort& mixPort) const { + std::vector result; + for (const auto& route : mRoutes) { + if (route.sinkPortId == mixPort.id) { + for (const auto srcId : route.sourcePortIds) { + if (mAttachedSourceDevicePorts.count(srcId) != 0) { + const auto devicePortIt = findById(mPorts, srcId); + if (devicePortIt != mPorts.end()) result.push_back(*devicePortIt); + } + } + } + } + return result; +} + +std::optional ModuleConfig::getSourceMixPortForAttachedDevice() const { + for (const auto& route : mRoutes) { + if (mAttachedSinkDevicePorts.count(route.sinkPortId) != 0) { + const auto mixPortIt = findById(mPorts, route.sourcePortIds[0]); + if (mixPortIt != mPorts.end()) return *mixPortIt; + } + } + return {}; +} + +std::optional ModuleConfig::getNonRoutableSrcSinkPair( + bool isInput) const { + const auto mixPorts = getMixPorts(isInput); + std::set> allowedRoutes; + for (const auto& route : mRoutes) { + for (const auto srcPortId : route.sourcePortIds) { + allowedRoutes.emplace(std::make_pair(srcPortId, route.sinkPortId)); + } + } + auto make_pair = [isInput](auto& device, auto& mix) { + return isInput ? std::make_pair(device, mix) : std::make_pair(mix, device); + }; + for (const auto portId : isInput ? mAttachedSourceDevicePorts : mAttachedSinkDevicePorts) { + const auto devicePortIt = findById(mPorts, portId); + if (devicePortIt == mPorts.end()) continue; + auto devicePortConfig = getSingleConfigForDevicePort(*devicePortIt); + for (const auto& mixPort : mixPorts) { + if (std::find(allowedRoutes.begin(), allowedRoutes.end(), + make_pair(portId, mixPort.id)) == allowedRoutes.end()) { + auto mixPortConfig = getSingleConfigForMixPort(isInput, mixPort); + if (mixPortConfig.has_value()) { + return make_pair(devicePortConfig, mixPortConfig.value()); + } + } + } + } + return {}; +} + +std::optional ModuleConfig::getRoutableSrcSinkPair(bool isInput) const { + if (isInput) { + for (const auto& route : mRoutes) { + auto srcPortIdIt = std::find_if( + route.sourcePortIds.begin(), route.sourcePortIds.end(), + [&](const auto& portId) { return mAttachedSourceDevicePorts.count(portId); }); + if (srcPortIdIt == route.sourcePortIds.end()) continue; + const auto devicePortIt = findById(mPorts, *srcPortIdIt); + const auto mixPortIt = findById(mPorts, route.sinkPortId); + if (devicePortIt == mPorts.end() || mixPortIt == mPorts.end()) continue; + auto devicePortConfig = getSingleConfigForDevicePort(*devicePortIt); + auto mixPortConfig = getSingleConfigForMixPort(isInput, *mixPortIt); + if (!mixPortConfig.has_value()) continue; + return std::make_pair(devicePortConfig, mixPortConfig.value()); + } + } else { + for (const auto& route : mRoutes) { + if (mAttachedSinkDevicePorts.count(route.sinkPortId) == 0) continue; + const auto mixPortIt = findById(mPorts, route.sourcePortIds[0]); + const auto devicePortIt = findById(mPorts, route.sinkPortId); + if (devicePortIt == mPorts.end() || mixPortIt == mPorts.end()) continue; + auto mixPortConfig = getSingleConfigForMixPort(isInput, *mixPortIt); + auto devicePortConfig = getSingleConfigForDevicePort(*devicePortIt); + if (!mixPortConfig.has_value()) continue; + return std::make_pair(mixPortConfig.value(), devicePortConfig); + } + } + return {}; +} + +std::vector ModuleConfig::getRoutableSrcSinkGroups(bool isInput) const { + std::vector result; + if (isInput) { + for (const auto& route : mRoutes) { + std::vector srcPortIds; + std::copy_if(route.sourcePortIds.begin(), route.sourcePortIds.end(), + std::back_inserter(srcPortIds), [&](const auto& portId) { + return mAttachedSourceDevicePorts.count(portId); + }); + if (srcPortIds.empty()) continue; + const auto mixPortIt = findById(mPorts, route.sinkPortId); + if (mixPortIt == mPorts.end()) continue; + auto mixPortConfig = getSingleConfigForMixPort(isInput, *mixPortIt); + if (!mixPortConfig.has_value()) continue; + std::vector pairs; + for (const auto srcPortId : srcPortIds) { + const auto devicePortIt = findById(mPorts, srcPortId); + if (devicePortIt == mPorts.end()) continue; + // Using all configs for every source would be too much. + auto devicePortConfig = getSingleConfigForDevicePort(*devicePortIt); + pairs.emplace_back(devicePortConfig, mixPortConfig.value()); + } + if (!pairs.empty()) { + result.emplace_back(route, std::move(pairs)); + } + } + } else { + for (const auto& route : mRoutes) { + if (mAttachedSinkDevicePorts.count(route.sinkPortId) == 0) continue; + const auto devicePortIt = findById(mPorts, route.sinkPortId); + if (devicePortIt == mPorts.end()) continue; + auto devicePortConfig = getSingleConfigForDevicePort(*devicePortIt); + std::vector pairs; + for (const auto srcPortId : route.sourcePortIds) { + const auto mixPortIt = findById(mPorts, srcPortId); + if (mixPortIt == mPorts.end()) continue; + // Using all configs for every source would be too much. + auto mixPortConfig = getSingleConfigForMixPort(isInput, *mixPortIt); + if (mixPortConfig.has_value()) { + pairs.emplace_back(mixPortConfig.value(), devicePortConfig); + } + } + if (!pairs.empty()) { + result.emplace_back(route, std::move(pairs)); + } + } + } + return result; +} + +static std::vector combineAudioConfigs(const AudioPort& port, + const AudioProfile& profile) { + std::vector configs; + configs.reserve(profile.channelMasks.size() * profile.sampleRates.size()); + for (auto channelMask : profile.channelMasks) { + for (auto sampleRate : profile.sampleRates) { + AudioPortConfig config{}; + config.portId = port.id; + Int sr; + sr.value = sampleRate; + config.sampleRate = sr; + config.channelMask = channelMask; + config.format = profile.format; + config.ext = port.ext; + configs.push_back(config); + } + } + return configs; +} + +std::vector ModuleConfig::generateInputAudioMixPortConfigs( + const std::vector& ports, bool singleProfile) const { + std::vector result; + for (const auto& mixPort : ports) { + if (getAttachedSourceDevicesPortsForMixPort(mixPort).empty()) { + continue; // no attached devices + } + for (const auto& profile : mixPort.profiles) { + if (profile.format.type == AudioFormatType::DEFAULT || profile.sampleRates.empty() || + profile.channelMasks.empty()) { + continue; // dynamic profile + } + auto configs = combineAudioConfigs(mixPort, profile); + for (auto& config : configs) { + config.flags = mixPort.flags; + result.push_back(config); + if (singleProfile) return result; + } + } + } + return result; +} + +static std::tuple generateOutFlags(const AudioPort& mixPort) { + static const AudioIoFlags offloadFlags = AudioIoFlags::make( + (1 << static_cast(AudioOutputFlags::COMPRESS_OFFLOAD)) | + (1 << static_cast(AudioOutputFlags::DIRECT))); + const bool isOffload = (mixPort.flags.get() & + (1 << static_cast(AudioOutputFlags::COMPRESS_OFFLOAD))) != 0; + return {isOffload ? offloadFlags : mixPort.flags, isOffload}; +} + +std::vector ModuleConfig::generateOutputAudioMixPortConfigs( + const std::vector& ports, bool singleProfile) const { + std::vector result; + for (const auto& mixPort : ports) { + if (getAttachedSinkDevicesPortsForMixPort(mixPort).empty()) { + continue; // no attached devices + } + auto [flags, isOffload] = generateOutFlags(mixPort); + (void)isOffload; + for (const auto& profile : mixPort.profiles) { + if (profile.format.type == AudioFormatType::DEFAULT) continue; + auto configs = combineAudioConfigs(mixPort, profile); + for (auto& config : configs) { + // Some combinations of flags declared in the config file require special + // treatment. + // if (isOffload) { + // config.offloadInfo.info(generateOffloadInfo(config.base)); + // } + config.flags = flags; + result.push_back(config); + if (singleProfile) return result; + } + } + } + return result; +} + +std::vector ModuleConfig::generateAudioDevicePortConfigs( + const std::vector& ports, bool singleProfile) const { + std::vector result; + for (const auto& devicePort : ports) { + const size_t resultSizeBefore = result.size(); + for (const auto& profile : devicePort.profiles) { + auto configs = combineAudioConfigs(devicePort, profile); + result.insert(result.end(), configs.begin(), configs.end()); + if (singleProfile && !result.empty()) return result; + } + if (resultSizeBefore == result.size()) { + AudioPortConfig empty; + empty.portId = devicePort.id; + empty.ext = devicePort.ext; + result.push_back(empty); + } + if (singleProfile) return result; + } + return result; +} diff --git a/audio/aidl/vts/ModuleConfig.h b/audio/aidl/vts/ModuleConfig.h new file mode 100644 index 0000000000..2e86b97cf5 --- /dev/null +++ b/audio/aidl/vts/ModuleConfig.h @@ -0,0 +1,131 @@ +/* + * 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. + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include + +class ModuleConfig { + public: + using SrcSinkPair = std::pair; + using SrcSinkGroup = + std::pair>; + + explicit ModuleConfig(android::hardware::audio::core::IModule* module); + android::binder::Status getStatus() const { return mStatus; } + std::string getError() const { return mStatus.toString8().c_str(); } + + std::vector getInputMixPorts() const; + std::vector getOutputMixPorts() const; + std::vector getMixPorts(bool isInput) const { + return isInput ? getInputMixPorts() : getOutputMixPorts(); + } + + std::vector getAttachedDevicesPortsForMixPort( + bool isInput, const android::media::audio::common::AudioPort& mixPort) const { + return isInput ? getAttachedSourceDevicesPortsForMixPort(mixPort) + : getAttachedSinkDevicesPortsForMixPort(mixPort); + } + std::vector getAttachedSinkDevicesPortsForMixPort( + const android::media::audio::common::AudioPort& mixPort) const; + std::vector getAttachedSourceDevicesPortsForMixPort( + const android::media::audio::common::AudioPort& mixPort) const; + std::optional getSourceMixPortForAttachedDevice() + const; + + std::optional getNonRoutableSrcSinkPair(bool isInput) const; + std::optional getRoutableSrcSinkPair(bool isInput) const; + std::vector getRoutableSrcSinkGroups(bool isInput) const; + + std::vector getPortConfigsForMixPorts() const { + auto inputs = generateInputAudioMixPortConfigs(getInputMixPorts(), false); + auto outputs = generateOutputAudioMixPortConfigs(getOutputMixPorts(), false); + inputs.insert(inputs.end(), outputs.begin(), outputs.end()); + return inputs; + } + std::vector getPortConfigsForMixPorts( + bool isInput) const { + return isInput ? generateInputAudioMixPortConfigs(getInputMixPorts(), false) + : generateOutputAudioMixPortConfigs(getOutputMixPorts(), false); + } + std::vector getPortConfigsForMixPorts( + bool isInput, const android::media::audio::common::AudioPort& port) const { + return isInput ? generateInputAudioMixPortConfigs({port}, false) + : generateOutputAudioMixPortConfigs({port}, false); + } + std::optional getSingleConfigForMixPort( + bool isInput) const { + const auto config = isInput ? generateInputAudioMixPortConfigs(getInputMixPorts(), true) + : generateOutputAudioMixPortConfigs(getOutputMixPorts(), true); + // TODO: Avoid returning configs for offload since they require an extra + // argument to openOutputStream. + if (!config.empty()) { + return *config.begin(); + } else { + return {}; + } + } + std::optional getSingleConfigForMixPort( + bool isInput, const android::media::audio::common::AudioPort& port) const { + const auto config = isInput ? generateInputAudioMixPortConfigs({port}, true) + : generateOutputAudioMixPortConfigs({port}, true); + if (!config.empty()) { + return *config.begin(); + } else { + return {}; + } + } + + 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(); + } + + private: + std::vector generateInputAudioMixPortConfigs( + const std::vector& ports, + bool singleProfile) const; + std::vector generateOutputAudioMixPortConfigs( + const std::vector& ports, + bool singleProfile) const; + + // 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. + std::vector generateAudioDevicePortConfigs( + const std::vector& ports, + bool singleProfile) const; + + android::binder::Status mStatus = android::binder::Status::ok(); + std::vector mPorts; + std::vector mInitialConfigs; + std::set mAttachedSinkDevicePorts; + std::set mAttachedSourceDevicePorts; + std::vector mRoutes; +}; diff --git a/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp new file mode 100644 index 0000000000..cadeb0c50e --- /dev/null +++ b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp @@ -0,0 +1,1027 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ModuleConfig.h" + +using namespace android; +using android::binder::Status; +using android::hardware::audio::common::PlaybackTrackMetadata; +using android::hardware::audio::common::RecordTrackMetadata; +using android::hardware::audio::common::SinkMetadata; +using android::hardware::audio::common::SourceMetadata; +using android::hardware::audio::core::AudioPatch; +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::media::audio::common::AudioContentType; +using android::media::audio::common::AudioDevice; +using android::media::audio::common::AudioDeviceType; +using android::media::audio::common::AudioIoFlags; +using android::media::audio::common::AudioOutputFlags; +using android::media::audio::common::AudioPort; +using android::media::audio::common::AudioPortConfig; +using android::media::audio::common::AudioPortDeviceExt; +using android::media::audio::common::AudioPortExt; +using android::media::audio::common::AudioSource; +using android::media::audio::common::AudioUsage; + +template +auto findById(std::vector& v, int32_t id) { + return std::find_if(v.begin(), v.end(), [&](const auto& e) { return e.id == id; }); +} + +template +std::vector getNonExistentIds(const C& allIds) { + if (allIds.empty()) { + return std::vector{-1, 0, 1}; + } + std::vector nonExistentIds; + nonExistentIds.push_back(*std::min_element(allIds.begin(), allIds.end()) - 1); + nonExistentIds.push_back(*std::max_element(allIds.begin(), allIds.end()) + 1); + return nonExistentIds; +} + +struct AidlDeathRecipient : IBinder::DeathRecipient { + std::mutex mutex; + std::condition_variable condition; + bool fired = false; + wp who; + + void binderDied(const wp& who) override { + std::unique_lock lock(mutex); + fired = true; + this->who = who; + condition.notify_one(); + }; + + bool waitForFired(int timeoutMs) { + std::unique_lock lock(mutex); + condition.wait_for(lock, std::chrono::milliseconds(timeoutMs), [this]() { return fired; }); + return fired; + } +}; + +template +struct IsInput { + constexpr operator bool() const; +}; + +template <> +constexpr IsInput::operator bool() const { + return true; +} +template <> +constexpr IsInput::operator bool() const { + return false; +} + +class AudioCoreModule : public testing::TestWithParam { + public: + void SetUp() override { ASSERT_NO_FATAL_FAILURE(ConnectToService()); } + + void ConnectToService() { + module = android::waitForDeclaredService(String16(GetParam().c_str())); + ASSERT_NE(module, nullptr); + } + + void RestartService() { + ASSERT_NE(module, nullptr); + moduleConfig.reset(); + deathHandler = sp::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 + void GetAllEntityIds(std::set* entityIds, + Status (IModule::*getter)(std::vector*), + const std::string& errorMessage) { + std::vector 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* patchIds) { + return GetAllEntityIds( + patchIds, &IModule::getAudioPatches, + "IDs of audio patches returned by IModule.getAudioPatches are not unique"); + } + + void GetAllPortIds(std::set* portIds) { + return GetAllEntityIds( + portIds, &IModule::getAudioPorts, + "IDs of audio ports returned by IModule.getAudioPorts are not unique"); + } + + void GetAllPortConfigIds(std::set* portConfigIds) { + return GetAllEntityIds( + portConfigIds, &IModule::getAudioPortConfigs, + "IDs of audio port configs returned by IModule.getAudioPortConfigs are not unique"); + } + + void SetUpModuleConfig() { + if (moduleConfig == nullptr) { + moduleConfig = std::make_unique(module.get()); + ASSERT_EQ(Status::EX_NONE, moduleConfig->getStatus().exceptionCode()) + << "ModuleConfig init error: " << moduleConfig->getError(); + } + } + + sp module; + sp deathHandler; + std::unique_ptr moduleConfig; +}; + +// For consistency, WithAudioPortConfig can start both with a non-existent +// port config, and with an existing one. Existence is determined by the +// id of the provided config. If it's not 0, then WithAudioPortConfig is +// essentially a no-op wrapper. +class WithAudioPortConfig { + public: + WithAudioPortConfig() {} + explicit WithAudioPortConfig(const AudioPortConfig& config) : mInitialConfig(config) {} + ~WithAudioPortConfig() { + if (mModule != nullptr) { + Status status = mModule->resetAudioPortConfig(getId()); + EXPECT_EQ(Status::EX_NONE, status.exceptionCode()) + << status << "; port config id " << getId(); + } + } + void SetUp(IModule* module) { + ASSERT_NE(AudioPortExt::Tag::unspecified, mInitialConfig.ext.getTag()) + << "config: " << mInitialConfig.toString(); + // Negotiation is allowed for device ports because the HAL module is + // allowed to provide an empty profiles list for attached devices. + ASSERT_NO_FATAL_FAILURE( + SetUpImpl(module, mInitialConfig.ext.getTag() == AudioPortExt::Tag::device)); + } + int32_t getId() const { return mConfig.id; } + const AudioPortConfig& get() const { return mConfig; } + + private: + void SetUpImpl(IModule* module, bool negotiate) { + if (mInitialConfig.id == 0) { + AudioPortConfig suggested; + bool applied = false; + Status status = module->setAudioPortConfig(mInitialConfig, &suggested, &applied); + ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) + << status << "; Config: " << mInitialConfig.toString(); + if (!applied && negotiate) { + mInitialConfig = suggested; + ASSERT_NO_FATAL_FAILURE(SetUpImpl(module, false)) + << " while applying suggested config: " << suggested.toString(); + } else { + ASSERT_TRUE(applied) << "Suggested: " << suggested.toString(); + mConfig = suggested; + mModule = module; + } + } else { + mConfig = mInitialConfig; + } + } + + AudioPortConfig mInitialConfig; + IModule* mModule = nullptr; + AudioPortConfig mConfig; +}; + +template +class WithStream { + public: + WithStream() {} + explicit WithStream(const AudioPortConfig& portConfig) : mPortConfig(portConfig) {} + ~WithStream() { + if (mStream != nullptr) { + Status status = mStream->close(); + EXPECT_EQ(Status::EX_NONE, status.exceptionCode()) + << status << "; port config id " << getPortId(); + } + } + void SetUpPortConfig(IModule* module) { ASSERT_NO_FATAL_FAILURE(mPortConfig.SetUp(module)); } + Status SetUpNoChecks(IModule* module) { return SetUpNoChecks(module, mPortConfig.get()); } + Status SetUpNoChecks(IModule* module, const AudioPortConfig& portConfig); + void SetUp(IModule* module) { + ASSERT_NO_FATAL_FAILURE(SetUpPortConfig(module)); + Status status = SetUpNoChecks(module); + ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) + << status << "; port config id " << getPortId(); + ASSERT_NE(nullptr, mStream) << "; port config id " << getPortId(); + } + Stream* get() const { return mStream.get(); } + const AudioPortConfig& getPortConfig() const { return mPortConfig.get(); } + int32_t getPortId() const { return mPortConfig.getId(); } + + private: + WithAudioPortConfig mPortConfig; + sp mStream; +}; + +template <> +Status WithStream::SetUpNoChecks(IModule* module, const AudioPortConfig& portConfig) { + RecordTrackMetadata trackMeta; + trackMeta.source = AudioSource::MIC; + trackMeta.gain = 1.0; + trackMeta.channelMask = portConfig.channelMask.value(); + SinkMetadata metadata; + metadata.tracks.push_back(trackMeta); + return module->openInputStream(portConfig.id, metadata, &mStream); +} + +template <> +Status WithStream::SetUpNoChecks(IModule* module, const AudioPortConfig& portConfig) { + PlaybackTrackMetadata trackMeta; + trackMeta.usage = AudioUsage::MEDIA; + trackMeta.contentType = AudioContentType::MUSIC; + trackMeta.gain = 1.0; + trackMeta.channelMask = portConfig.channelMask.value(); + SourceMetadata metadata; + metadata.tracks.push_back(trackMeta); + return module->openOutputStream(portConfig.id, metadata, {}, &mStream); +} + +class WithAudioPatch { + public: + WithAudioPatch() {} + WithAudioPatch(const AudioPortConfig& srcPortConfig, const AudioPortConfig& sinkPortConfig) + : mSrcPortConfig(srcPortConfig), mSinkPortConfig(sinkPortConfig) {} + ~WithAudioPatch() { + if (mModule != nullptr && mPatch.id != 0) { + Status status = mModule->resetAudioPatch(mPatch.id); + EXPECT_EQ(Status::EX_NONE, status.exceptionCode()) + << status << "; patch id " << getId(); + } + } + void SetUpPortConfigs(IModule* module) { + ASSERT_NO_FATAL_FAILURE(mSrcPortConfig.SetUp(module)); + ASSERT_NO_FATAL_FAILURE(mSinkPortConfig.SetUp(module)); + } + Status SetUpNoChecks(IModule* module) { + mModule = module; + mPatch.sourcePortConfigIds = std::vector{mSrcPortConfig.getId()}; + mPatch.sinkPortConfigIds = std::vector{mSinkPortConfig.getId()}; + return mModule->setAudioPatch(mPatch, &mPatch); + } + void SetUp(IModule* module) { + ASSERT_NO_FATAL_FAILURE(SetUpPortConfigs(module)); + Status status = SetUpNoChecks(module); + ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) + << status << "; source port config id " << mSrcPortConfig.getId() + << "; sink port config id " << mSinkPortConfig.getId(); + } + int32_t getId() const { return mPatch.id; } + const AudioPatch& get() const { return mPatch; } + + private: + WithAudioPortConfig mSrcPortConfig; + WithAudioPortConfig mSinkPortConfig; + IModule* mModule = nullptr; + AudioPatch mPatch; +}; + +TEST_P(AudioCoreModule, Published) { + // SetUp must complete with no failures. +} + +TEST_P(AudioCoreModule, CanBeRestarted) { + ASSERT_NO_FATAL_FAILURE(RestartService()); +} + +TEST_P(AudioCoreModule, PortIdsAreUnique) { + std::set portIds; + ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds)); +} + +TEST_P(AudioCoreModule, GetAudioPortsIsStatic) { + std::vector ports1; + { + Status status = module->getAudioPorts(&ports1); + ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status; + } + std::vector ports2; + { + Status status = module->getAudioPorts(&ports2); + 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"; + std::sort(ports1.begin(), ports1.end()); + std::sort(ports2.begin(), ports2.end()); + EXPECT_EQ(ports1, ports2); +} + +TEST_P(AudioCoreModule, GetAudioRoutesIsStatic) { + std::vector routes1; + { + Status status = module->getAudioRoutes(&routes1); + ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status; + } + std::vector routes2; + { + Status status = module->getAudioRoutes(&routes2); + 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"; + std::sort(routes1.begin(), routes1.end()); + std::sort(routes2.begin(), routes2.end()); + EXPECT_EQ(routes1, routes2); +} + +TEST_P(AudioCoreModule, GetAudioRoutesAreValid) { + std::vector routes; + { + Status status = module->getAudioRoutes(&routes); + ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status; + } + for (const auto& route : routes) { + std::set sources(route.sourcePortIds.begin(), route.sourcePortIds.end()); + EXPECT_NE(0, sources.size()) + << "empty audio port sinks in the audio route: " << route.toString(); + EXPECT_EQ(sources.size(), route.sourcePortIds.size()) + << "IDs of audio port sinks are not unique in the audio route: " + << route.toString(); + } +} + +TEST_P(AudioCoreModule, GetAudioRoutesPortIdsAreValid) { + std::set portIds; + ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds)); + std::vector routes; + { + Status status = module->getAudioRoutes(&routes); + ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status; + } + for (const auto& route : routes) { + EXPECT_EQ(1, portIds.count(route.sinkPortId)) + << route.sinkPortId << " sink port id is unknown"; + for (const auto& source : route.sourcePortIds) { + EXPECT_EQ(1, portIds.count(source)) << source << " source port id is unknown"; + } + } +} + +TEST_P(AudioCoreModule, CheckDevicePorts) { + std::vector ports; + { + Status status = module->getAudioPorts(&ports); + ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status; + } + std::optional defaultOutput, defaultInput; + std::set inputs, outputs; + const int defaultDeviceFlag = 1 << AudioPortDeviceExt::FLAG_INDEX_DEFAULT_DEVICE; + for (const auto& port : ports) { + if (port.ext.getTag() != AudioPortExt::Tag::device) continue; + const auto& devicePort = port.ext.get(); + EXPECT_NE(AudioDeviceType::NONE, devicePort.device.type.type); + EXPECT_NE(AudioDeviceType::IN_DEFAULT, devicePort.device.type.type); + EXPECT_NE(AudioDeviceType::OUT_DEFAULT, devicePort.device.type.type); + if (devicePort.device.type.type > AudioDeviceType::IN_DEFAULT && + devicePort.device.type.type < AudioDeviceType::OUT_DEFAULT) { + EXPECT_EQ(AudioIoFlags::Tag::input, port.flags.getTag()); + } else if (devicePort.device.type.type > AudioDeviceType::OUT_DEFAULT) { + EXPECT_EQ(AudioIoFlags::Tag::output, port.flags.getTag()); + } + EXPECT_FALSE((devicePort.flags & defaultDeviceFlag) != 0 && + !devicePort.device.type.connection.empty()) + << "Device port " << port.id + << " must be permanently attached to be set as default"; + if ((devicePort.flags & defaultDeviceFlag) != 0) { + if (port.flags.getTag() == AudioIoFlags::Tag::output) { + EXPECT_FALSE(defaultOutput.has_value()) + << "At least two output device ports are declared as default: " + << defaultOutput.value() << " and " << port.id; + defaultOutput = port.id; + EXPECT_EQ(0, outputs.count(devicePort.device)) + << "Non-unique output device: " << devicePort.device.toString(); + outputs.insert(devicePort.device); + } else if (port.flags.getTag() == AudioIoFlags::Tag::input) { + EXPECT_FALSE(defaultInput.has_value()) + << "At least two input device ports are declared as default: " + << defaultInput.value() << " and " << port.id; + defaultInput = port.id; + EXPECT_EQ(0, inputs.count(devicePort.device)) + << "Non-unique input device: " << devicePort.device.toString(); + inputs.insert(devicePort.device); + } else { + FAIL() << "Invalid AudioIoFlags Tag: " << toString(port.flags.getTag()); + } + } + } +} + +TEST_P(AudioCoreModule, CheckMixPorts) { + std::vector ports; + { + Status status = module->getAudioPorts(&ports); + ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status; + } + std::optional primaryMixPort; + constexpr int primaryOutputFlag = 1 << static_cast(AudioOutputFlags::PRIMARY); + for (const auto& port : ports) { + if (port.ext.getTag() != AudioPortExt::Tag::mix) continue; + const auto& mixPort = port.ext.get(); + if (port.flags.getTag() == AudioIoFlags::Tag::output && + ((port.flags.get() & primaryOutputFlag) != 0)) { + EXPECT_FALSE(primaryMixPort.has_value()) + << "At least two mix ports have PRIMARY flag set: " << primaryMixPort.value() + << " and " << port.id; + primaryMixPort = port.id; + EXPECT_EQ(1, mixPort.maxOpenStreamCount) + << "Primary mix port " << port.id << " can not have maxOpenStreamCount " + << mixPort.maxOpenStreamCount; + } + } +} + +TEST_P(AudioCoreModule, GetAudioPort) { + std::set portIds; + ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds)); + if (portIds.empty()) { + GTEST_SKIP() << "No ports in the module."; + } + for (const auto portId : portIds) { + AudioPort port; + Status status = module->getAudioPort(portId, &port); + EXPECT_EQ(Status::EX_NONE, status.exceptionCode()) << status; + EXPECT_EQ(portId, port.id); + } + for (const auto portId : getNonExistentIds(portIds)) { + AudioPort port; + Status status = module->getAudioPort(portId, &port); + EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode()) + << status << " returned for port ID " << portId; + } +} + +TEST_P(AudioCoreModule, OpenStreamInvalidPortConfigId) { + std::set portConfigIds; + ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds)); + for (const auto portConfigId : getNonExistentIds(portConfigIds)) { + { + sp stream; + Status status = module->openInputStream(portConfigId, {}, &stream); + EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode()) + << status << " openInputStream returned for port config ID " << portConfigId; + EXPECT_EQ(nullptr, stream); + } + { + sp stream; + Status status = module->openOutputStream(portConfigId, {}, {}, &stream); + EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode()) + << status << " openOutputStream returned for port config ID " << portConfigId; + EXPECT_EQ(nullptr, stream); + } + } +} + +TEST_P(AudioCoreModule, PortConfigIdsAreUnique) { + std::set portConfigIds; + ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds)); +} + +TEST_P(AudioCoreModule, PortConfigPortIdsAreValid) { + std::set portIds; + ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds)); + std::vector portConfigs; + { + Status status = module->getAudioPortConfigs(&portConfigs); + ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status; + } + for (const auto& config : portConfigs) { + EXPECT_EQ(1, portIds.count(config.portId)) + << config.portId << " port id is unknown, config id " << config.id; + } +} + +TEST_P(AudioCoreModule, ResetAudioPortConfigInvalidId) { + std::set portConfigIds; + ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&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; + } +} + +// Verify that for the audio port configs provided by the HAL after init, resetting +// the config does not delete it, but brings it back to the initial config. +TEST_P(AudioCoreModule, ResetAudioPortConfigToInitialValue) { + std::vector portConfigsBefore; + { + Status status = module->getAudioPortConfigs(&portConfigsBefore); + ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status; + } + // TODO: Change port configs according to port profiles. + for (const auto& c : portConfigsBefore) { + Status status = module->resetAudioPortConfig(c.id); + EXPECT_EQ(Status::EX_NONE, status.exceptionCode()) + << status << " returned for port config ID " << c.id; + } + std::vector portConfigsAfter; + { + Status status = module->getAudioPortConfigs(&portConfigsAfter); + ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) << status; + } + for (const auto& c : portConfigsBefore) { + auto afterIt = findById(portConfigsAfter, c.id); + EXPECT_NE(portConfigsAfter.end(), afterIt) + << " port config ID " << c.id << " was removed by reset"; + if (afterIt != portConfigsAfter.end()) { + EXPECT_EQ(c, *afterIt); + } + } +} + +TEST_P(AudioCoreModule, SetAudioPortConfigSuggestedConfig) { + ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig()); + auto srcMixPort = moduleConfig->getSourceMixPortForAttachedDevice(); + if (!srcMixPort.has_value()) { + GTEST_SKIP() << "No mix port for attached output devices"; + } + AudioPortConfig portConfig; + AudioPortConfig suggestedConfig; + portConfig.portId = srcMixPort.value().id; + { + bool applied = true; + Status status = module->setAudioPortConfig(portConfig, &suggestedConfig, &applied); + ASSERT_EQ(Status::EX_NONE, status.exceptionCode()) + << status << "; Config: " << portConfig.toString(); + EXPECT_FALSE(applied); + } + EXPECT_EQ(0, suggestedConfig.id); + EXPECT_TRUE(suggestedConfig.sampleRate.has_value()); + EXPECT_TRUE(suggestedConfig.channelMask.has_value()); + EXPECT_TRUE(suggestedConfig.format.has_value()); + EXPECT_TRUE(suggestedConfig.flags.has_value()); + WithAudioPortConfig applied(suggestedConfig); + ASSERT_NO_FATAL_FAILURE(applied.SetUp(module.get())); + const AudioPortConfig& appliedConfig = applied.get(); + EXPECT_NE(0, appliedConfig.id); + EXPECT_TRUE(appliedConfig.sampleRate.has_value()); + EXPECT_EQ(suggestedConfig.sampleRate.value(), appliedConfig.sampleRate.value()); + EXPECT_TRUE(appliedConfig.channelMask.has_value()); + EXPECT_EQ(suggestedConfig.channelMask.value(), appliedConfig.channelMask.value()); + EXPECT_TRUE(appliedConfig.format.has_value()); + EXPECT_EQ(suggestedConfig.format.value(), appliedConfig.format.value()); + EXPECT_TRUE(appliedConfig.flags.has_value()); + EXPECT_EQ(suggestedConfig.flags.value(), appliedConfig.flags.value()); +} + +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 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(); + } + } +} + +TEST_P(AudioCoreModule, SetAudioPortConfigInvalidPortId) { + std::set portIds; + ASSERT_NO_FATAL_FAILURE(GetAllPortIds(&portIds)); + for (const auto portId : getNonExistentIds(portIds)) { + AudioPortConfig portConfig, suggestedConfig; + bool applied; + portConfig.portId = portId; + Status status = module->setAudioPortConfig(portConfig, &suggestedConfig, &applied); + EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode()) + << status << " returned for port ID " << portId; + EXPECT_FALSE(suggestedConfig.format.has_value()); + EXPECT_FALSE(suggestedConfig.channelMask.has_value()); + EXPECT_FALSE(suggestedConfig.sampleRate.has_value()); + } +} + +TEST_P(AudioCoreModule, SetAudioPortConfigInvalidPortConfigId) { + std::set portConfigIds; + ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds)); + for (const auto portConfigId : getNonExistentIds(portConfigIds)) { + AudioPortConfig portConfig, suggestedConfig; + bool applied; + portConfig.id = portConfigId; + Status status = module->setAudioPortConfig(portConfig, &suggestedConfig, &applied); + EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode()) + << status << " returned for port config ID " << portConfigId; + EXPECT_FALSE(suggestedConfig.format.has_value()); + EXPECT_FALSE(suggestedConfig.channelMask.has_value()); + EXPECT_FALSE(suggestedConfig.sampleRate.has_value()); + } +} + +template +class AudioStream : public AudioCoreModule { + public: + static std::string direction(bool capitalize); + + void SetUp() override { + ASSERT_NO_FATAL_FAILURE(AudioCoreModule::SetUp()); + ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig()); + } + + void CloseTwice() { + const auto portConfig = moduleConfig->getSingleConfigForMixPort(IsInput()); + if (!portConfig.has_value()) { + GTEST_SKIP() << "No mix port for attached devices"; + } + sp heldStream; + { + WithStream stream(portConfig.value()); + ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get())); + heldStream = stream.get(); + } + Status status = heldStream->close(); + EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode()) + << status << " when closing the stream twice"; + } + + void OpenAllConfigs() { + const auto allPortConfigs = moduleConfig->getPortConfigsForMixPorts(IsInput()); + for (const auto& portConfig : allPortConfigs) { + WithStream stream(portConfig); + ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get())); + } + } + + void OpenOverMaxCount() { + constexpr bool isInput = IsInput(); + auto ports = moduleConfig->getMixPorts(isInput); + bool hasSingleRun = false; + for (const auto& port : ports) { + const size_t maxStreamCount = port.ext.get().maxOpenStreamCount; + if (maxStreamCount == 0 || + moduleConfig->getAttachedDevicesPortsForMixPort(isInput, port).empty()) { + // No restrictions or no permanently attached devices. + continue; + } + auto portConfigs = moduleConfig->getPortConfigsForMixPorts(isInput, port); + if (portConfigs.size() < maxStreamCount + 1) { + // Not able to open a sufficient number of streams for this port. + continue; + } + hasSingleRun = true; + std::optional> streamWraps[maxStreamCount + 1]; + for (size_t i = 0; i <= maxStreamCount; ++i) { + streamWraps[i].emplace(portConfigs[i]); + WithStream& stream = streamWraps[i].value(); + if (i < maxStreamCount) { + ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get())); + } else { + ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfig(module.get())); + Status status = stream.SetUpNoChecks(module.get()); + EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode()) + << status << " open" << direction(true) + << "Stream" + " returned for port config ID " + << stream.getPortId() << ", maxOpenStreamCount is " << maxStreamCount; + } + } + } + if (!hasSingleRun) { + GTEST_SKIP() << "Not enough " << direction(false) + << " ports to test max open stream count"; + } + } + + void OpenInvalidDirection() { + // Important! The direction of the port config must be reversed. + const auto portConfig = moduleConfig->getSingleConfigForMixPort(!IsInput()); + if (!portConfig.has_value()) { + GTEST_SKIP() << "No mix port for attached devices"; + } + WithStream stream(portConfig.value()); + ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfig(module.get())); + Status status = stream.SetUpNoChecks(module.get()); + EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode()) + << status << " open" << direction(true) << "Stream returned for port config ID " + << stream.getPortId(); + EXPECT_EQ(nullptr, stream.get()); + } + + void OpenTwiceSamePortConfig() { + const auto portConfig = moduleConfig->getSingleConfigForMixPort(IsInput()); + if (!portConfig.has_value()) { + GTEST_SKIP() << "No mix port for attached devices"; + } + EXPECT_NO_FATAL_FAILURE(OpenTwiceSamePortConfigImpl(portConfig.value())); + } + + void ResetPortConfigWithOpenStream() { + const auto portConfig = moduleConfig->getSingleConfigForMixPort(IsInput()); + if (!portConfig.has_value()) { + GTEST_SKIP() << "No mix port for attached devices"; + } + WithStream stream(portConfig.value()); + ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get())); + Status status = module->resetAudioPortConfig(stream.getPortId()); + EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode()) + << status << " returned for port config ID " << stream.getPortId(); + } + + void OpenTwiceSamePortConfigImpl(const AudioPortConfig& portConfig) { + WithStream stream1(portConfig); + ASSERT_NO_FATAL_FAILURE(stream1.SetUp(module.get())); + WithStream stream2; + Status status = stream2.SetUpNoChecks(module.get(), stream1.getPortConfig()); + EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode()) + << status << " when opening " << direction(false) + << " stream twice for the same port config ID " << stream1.getPortId(); + } +}; +using AudioStreamIn = AudioStream; +using AudioStreamOut = AudioStream; + +template <> +std::string AudioStreamIn::direction(bool capitalize) { + return capitalize ? "Input" : "input"; +} +template <> +std::string AudioStreamOut::direction(bool capitalize) { + return capitalize ? "Output" : "output"; +} + +#define TEST_IO_STREAM(method_name) \ + TEST_P(AudioStreamIn, method_name) { ASSERT_NO_FATAL_FAILURE(method_name()); } \ + TEST_P(AudioStreamOut, method_name) { ASSERT_NO_FATAL_FAILURE(method_name()); } + +TEST_IO_STREAM(CloseTwice); +TEST_IO_STREAM(OpenAllConfigs); +TEST_IO_STREAM(OpenInvalidDirection); +TEST_IO_STREAM(OpenOverMaxCount); +TEST_IO_STREAM(OpenTwiceSamePortConfig); +TEST_IO_STREAM(ResetPortConfigWithOpenStream); + +TEST_P(AudioStreamOut, OpenTwicePrimary) { + const auto mixPorts = moduleConfig->getMixPorts(false); + auto primaryPortIt = std::find_if(mixPorts.begin(), mixPorts.end(), [](const AudioPort& port) { + constexpr int primaryOutputFlag = 1 << static_cast(AudioOutputFlags::PRIMARY); + return port.flags.getTag() == AudioIoFlags::Tag::output && + ((port.flags.get() & primaryOutputFlag) != 0); + }); + if (primaryPortIt == mixPorts.end()) { + GTEST_SKIP() << "No primary mix port"; + } + if (moduleConfig->getAttachedSinkDevicesPortsForMixPort(*primaryPortIt).empty()) { + GTEST_SKIP() << "Primary mix port can not be routed to any of attached devices"; + } + const auto portConfig = moduleConfig->getSingleConfigForMixPort(false, *primaryPortIt); + ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for the primary mix port"; + EXPECT_NO_FATAL_FAILURE(OpenTwiceSamePortConfigImpl(portConfig.value())); +} + +// Tests specific to audio patches. The fixure class is named 'AudioModulePatch' +// to avoid clashing with 'AudioPatch' class. +class AudioModulePatch : public AudioCoreModule { + public: + static std::string direction(bool isInput, bool capitalize) { + return isInput ? (capitalize ? "Input" : "input") : (capitalize ? "Output" : "output"); + } + + void SetUp() override { + ASSERT_NO_FATAL_FAILURE(AudioCoreModule::SetUp()); + ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig()); + } + + void SetInvalidPatchHelper(int32_t expectedException, const std::vector& sources, + const std::vector& sinks) { + AudioPatch patch; + patch.sourcePortConfigIds = sources; + patch.sinkPortConfigIds = sinks; + Status status = module->setAudioPatch(patch, &patch); + ASSERT_EQ(expectedException, status.exceptionCode()) + << status << ": patch source ids: " << android::internal::ToString(sources) + << "; sink ids: " << android::internal::ToString(sinks); + } + + void ResetPortConfigUsedByPatch(bool isInput) { + auto srcSinkGroups = moduleConfig->getRoutableSrcSinkGroups(isInput); + if (srcSinkGroups.empty()) { + GTEST_SKIP() << "No routes to any attached " << direction(isInput, false) << " devices"; + } + auto srcSinkGroup = *srcSinkGroups.begin(); + auto srcSink = *srcSinkGroup.second.begin(); + WithAudioPatch patch(srcSink.first, srcSink.second); + ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get())); + std::vector sourceAndSinkPortConfigIds(patch.get().sourcePortConfigIds); + sourceAndSinkPortConfigIds.insert(sourceAndSinkPortConfigIds.end(), + patch.get().sinkPortConfigIds.begin(), + patch.get().sinkPortConfigIds.end()); + for (const auto portConfigId : sourceAndSinkPortConfigIds) { + Status status = module->resetAudioPortConfig(portConfigId); + EXPECT_EQ(Status::EX_ILLEGAL_STATE, status.exceptionCode()) + << status << " returned for port config ID " << portConfigId; + } + } + + void SetInvalidPatch(bool isInput) { + auto srcSinkPair = moduleConfig->getRoutableSrcSinkPair(isInput); + if (!srcSinkPair.has_value()) { + GTEST_SKIP() << "No routes to any attached " << direction(isInput, false) << " devices"; + } + WithAudioPortConfig srcPortConfig(srcSinkPair.value().first); + ASSERT_NO_FATAL_FAILURE(srcPortConfig.SetUp(module.get())); + WithAudioPortConfig sinkPortConfig(srcSinkPair.value().second); + ASSERT_NO_FATAL_FAILURE(sinkPortConfig.SetUp(module.get())); + { // Check that the pair can actually be used for setting up a patch. + WithAudioPatch patch(srcPortConfig.get(), sinkPortConfig.get()); + ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get())); + } + EXPECT_NO_FATAL_FAILURE( + SetInvalidPatchHelper(Status::EX_ILLEGAL_ARGUMENT, {}, {sinkPortConfig.getId()})); + EXPECT_NO_FATAL_FAILURE(SetInvalidPatchHelper( + Status::EX_ILLEGAL_ARGUMENT, {srcPortConfig.getId(), srcPortConfig.getId()}, + {sinkPortConfig.getId()})); + EXPECT_NO_FATAL_FAILURE( + SetInvalidPatchHelper(Status::EX_ILLEGAL_ARGUMENT, {srcPortConfig.getId()}, {})); + EXPECT_NO_FATAL_FAILURE( + SetInvalidPatchHelper(Status::EX_ILLEGAL_ARGUMENT, {srcPortConfig.getId()}, + {sinkPortConfig.getId(), sinkPortConfig.getId()})); + + std::set portConfigIds; + ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&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, + {srcPortConfig.getId()}, {portConfigId})); + } + } + + void SetNonRoutablePatch(bool isInput) { + auto srcSinkPair = moduleConfig->getNonRoutableSrcSinkPair(isInput); + if (!srcSinkPair.has_value()) { + GTEST_SKIP() << "All possible source/sink pairs are routable"; + } + WithAudioPatch patch(srcSinkPair.value().first, srcSinkPair.value().second); + ASSERT_NO_FATAL_FAILURE(patch.SetUpPortConfigs(module.get())); + Status status = patch.SetUpNoChecks(module.get()); + EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode()) + << status << ": when setting up a patch from " + << srcSinkPair.value().first.toString() << " to " + << srcSinkPair.value().second.toString() << " that does not have a route"; + } + + void SetPatch(bool isInput) { + auto srcSinkGroups = moduleConfig->getRoutableSrcSinkGroups(isInput); + if (srcSinkGroups.empty()) { + GTEST_SKIP() << "No routes to any attached " << direction(isInput, false) << " devices"; + } + for (const auto& srcSinkGroup : srcSinkGroups) { + const auto& route = srcSinkGroup.first; + std::vector patches; + for (const auto& srcSink : srcSinkGroup.second) { + if (!route.isExclusive) { + patches.emplace_back(srcSink.first, srcSink.second); + EXPECT_NO_FATAL_FAILURE(patches[patches.size() - 1].SetUp(module.get())); + } else { + WithAudioPatch patch(srcSink.first, srcSink.second); + EXPECT_NO_FATAL_FAILURE(patch.SetUp(module.get())); + } + } + } + } + + void UpdatePatch(bool isInput) { + auto srcSinkGroups = moduleConfig->getRoutableSrcSinkGroups(isInput); + if (srcSinkGroups.empty()) { + GTEST_SKIP() << "No routes to any attached " << direction(isInput, false) << " devices"; + } + for (const auto& srcSinkGroup : srcSinkGroups) { + for (const auto& srcSink : srcSinkGroup.second) { + WithAudioPatch patch(srcSink.first, srcSink.second); + ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get())); + AudioPatch ignored; + EXPECT_NO_FATAL_FAILURE(module->setAudioPatch(patch.get(), &ignored)); + } + } + } + + void UpdateInvalidPatchId(bool isInput) { + auto srcSinkGroups = moduleConfig->getRoutableSrcSinkGroups(isInput); + if (srcSinkGroups.empty()) { + GTEST_SKIP() << "No routes to any attached " << direction(isInput, false) << " devices"; + } + // First, set up a patch to ensure that its settings are accepted. + auto srcSinkGroup = *srcSinkGroups.begin(); + auto srcSink = *srcSinkGroup.second.begin(); + WithAudioPatch patch(srcSink.first, srcSink.second); + ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get())); + // Then use the same patch setting, except for having an invalid ID. + std::set patchIds; + ASSERT_NO_FATAL_FAILURE(GetAllPatchIds(&patchIds)); + for (const auto patchId : getNonExistentIds(patchIds)) { + AudioPatch patchWithNonExistendId = patch.get(); + patchWithNonExistendId.id = patchId; + Status status = module->setAudioPatch(patchWithNonExistendId, &patchWithNonExistendId); + EXPECT_EQ(Status::EX_ILLEGAL_ARGUMENT, status.exceptionCode()) + << status << " returned for patch ID " << patchId; + } + } +}; + +// Not all tests require both directions, so parametrization would require +// more abstractions. +#define TEST_PATCH_BOTH_DIRECTIONS(method_name) \ + TEST_P(AudioModulePatch, method_name##Input) { ASSERT_NO_FATAL_FAILURE(method_name(true)); } \ + TEST_P(AudioModulePatch, method_name##Output) { ASSERT_NO_FATAL_FAILURE(method_name(false)); } + +TEST_PATCH_BOTH_DIRECTIONS(ResetPortConfigUsedByPatch); +TEST_PATCH_BOTH_DIRECTIONS(SetInvalidPatch); +TEST_PATCH_BOTH_DIRECTIONS(SetNonRoutablePatch); +TEST_PATCH_BOTH_DIRECTIONS(SetPatch); +TEST_PATCH_BOTH_DIRECTIONS(UpdateInvalidPatchId); +TEST_PATCH_BOTH_DIRECTIONS(UpdatePatch); + +TEST_P(AudioModulePatch, ResetInvalidPatchId) { + std::set patchIds; + ASSERT_NO_FATAL_FAILURE(GetAllPatchIds(&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; + } +} + +INSTANTIATE_TEST_SUITE_P(AudioCoreModuleTest, AudioCoreModule, + testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)), + android::PrintInstanceNameToString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioCoreModule); +INSTANTIATE_TEST_SUITE_P(AudioStreamInTest, AudioStreamIn, + testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)), + android::PrintInstanceNameToString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioStreamIn); +INSTANTIATE_TEST_SUITE_P(AudioStreamOutTest, AudioStreamOut, + testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)), + android::PrintInstanceNameToString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioStreamOut); +INSTANTIATE_TEST_SUITE_P(AudioPatchTest, AudioModulePatch, + testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)), + android::PrintInstanceNameToString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioModulePatch); + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ProcessState::self()->setThreadPoolMaxThreadCount(1); + ProcessState::self()->startThreadPool(); + return RUN_ALL_TESTS(); +} diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml index 91a14bd1c2..0acb6ec14c 100644 --- a/compatibility_matrices/compatibility_matrix.current.xml +++ b/compatibility_matrices/compatibility_matrix.current.xml @@ -25,6 +25,18 @@ default + + android.hardware.audio.core + 1 + + IModule + default + + + IConfig + default + + android.hardware.authsecret 1