audio: Implementation of audio I/O, part I

This patch adds necessary structures and prepares for implementing
data flow for audio I/O.

Also in this patch we clarify the relationship between audio patches
and buffer size for audio I/O, and between buffer size and latency.

Bug: 205884982
Bug: 233816848
Test: atest VtsHalAudioCoreTargetTest
Merged-In: I8522632607d4cf50a112225c19b5dd5ad8848591
Change-Id: I8522632607d4cf50a112225c19b5dd5ad8848591
(cherry picked from commit 68bee70442)
This commit is contained in:
Mikhail Naganov 2022-06-15 21:39:04 +00:00
parent 48e2e8fe49
commit 6a4872dff0
17 changed files with 793 additions and 152 deletions

View file

@ -75,10 +75,14 @@ aidl_interface {
"android/hardware/audio/core/IModule.aidl",
"android/hardware/audio/core/IStreamIn.aidl",
"android/hardware/audio/core/IStreamOut.aidl",
"android/hardware/audio/core/MmapBufferDescriptor.aidl",
"android/hardware/audio/core/ModuleDebug.aidl",
"android/hardware/audio/core/StreamDescriptor.aidl",
],
imports: [
"android.hardware.audio.common-V1",
"android.hardware.common-V2",
"android.hardware.common.fmq-V1",
"android.media.audio.common.types-V1",
],
stability: "vintf",

View file

@ -37,4 +37,6 @@ parcelable AudioPatch {
int id;
int[] sourcePortConfigIds;
int[] sinkPortConfigIds;
int minimumStreamBufferSizeFrames;
int[] latenciesMs;
}

View file

@ -43,10 +43,33 @@ interface IModule {
android.media.audio.common.AudioPort[] getAudioPorts();
android.hardware.audio.core.AudioRoute[] getAudioRoutes();
android.hardware.audio.core.AudioRoute[] getAudioRoutesForAudioPort(int portId);
android.hardware.audio.core.IStreamIn openInputStream(int portConfigId, in android.hardware.audio.common.SinkMetadata sinkMetadata);
android.hardware.audio.core.IStreamOut openOutputStream(int portConfigId, in android.hardware.audio.common.SourceMetadata sourceMetadata, in @nullable android.media.audio.common.AudioOffloadInfo offloadInfo);
android.hardware.audio.core.IModule.OpenInputStreamReturn openInputStream(in android.hardware.audio.core.IModule.OpenInputStreamArguments args);
android.hardware.audio.core.IModule.OpenOutputStreamReturn openOutputStream(in android.hardware.audio.core.IModule.OpenOutputStreamArguments args);
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);
@VintfStability
parcelable OpenInputStreamArguments {
int portConfigId;
android.hardware.audio.common.SinkMetadata sinkMetadata;
long bufferSizeFrames;
}
@VintfStability
parcelable OpenInputStreamReturn {
android.hardware.audio.core.IStreamIn stream;
android.hardware.audio.core.StreamDescriptor desc;
}
@VintfStability
parcelable OpenOutputStreamArguments {
int portConfigId;
android.hardware.audio.common.SourceMetadata sourceMetadata;
@nullable android.media.audio.common.AudioOffloadInfo offloadInfo;
long bufferSizeFrames;
}
@VintfStability
parcelable OpenOutputStreamReturn {
android.hardware.audio.core.IStreamOut stream;
android.hardware.audio.core.StreamDescriptor desc;
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.audio.core;
@JavaDerive(equals=true, toString=true) @VintfStability
parcelable MmapBufferDescriptor {
android.hardware.common.Ashmem sharedMemory;
long burstSizeFrames;
int flags;
const int FLAG_INDEX_APPLICATION_SHAREABLE = 0;
}

View file

@ -0,0 +1,69 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.audio.core;
@JavaDerive(equals=true, toString=true) @VintfStability
parcelable StreamDescriptor {
android.hardware.common.fmq.MQDescriptor<android.hardware.audio.core.StreamDescriptor.Command,android.hardware.common.fmq.SynchronizedReadWrite> command;
android.hardware.common.fmq.MQDescriptor<android.hardware.audio.core.StreamDescriptor.Reply,android.hardware.common.fmq.SynchronizedReadWrite> reply;
long bufferSizeFrames;
android.hardware.audio.core.StreamDescriptor.AudioBuffer audio;
const int COMMAND_EXIT = 0;
const int COMMAND_BURST = 1;
const int STATUS_OK = 0;
const int STATUS_ILLEGAL_ARGUMENT = 1;
const int STATUS_ILLEGAL_STATE = 2;
@FixedSize @VintfStability
parcelable Position {
long frames;
long timeNs;
}
@FixedSize @VintfStability
parcelable Command {
int code;
int fmqByteCount;
}
@FixedSize @VintfStability
parcelable Reply {
int status;
int fmqByteCount;
android.hardware.audio.core.StreamDescriptor.Position observable;
android.hardware.audio.core.StreamDescriptor.Position hardware;
int latencyMs;
}
@VintfStability
union AudioBuffer {
android.hardware.common.fmq.MQDescriptor<byte,android.hardware.common.fmq.UnsynchronizedWrite> fmq;
android.hardware.audio.core.MmapBufferDescriptor mmap;
}
}

View file

@ -37,4 +37,18 @@ parcelable AudioPatch {
* unique.
*/
int[] sinkPortConfigIds;
/**
* The minimum buffer size, in frames, which streams must use for
* this connection configuration. This field is filled out by the
* HAL module on creation of the patch and must be a positive number.
*/
int minimumStreamBufferSizeFrames;
/**
* Latencies, in milliseconds, associated with each sink port config from
* the 'sinkPortConfigIds' field. This field is filled out by the HAL module
* on creation or updating of the patch and must be a positive number. This
* is a nominal value. The current value of latency is provided via
* 'StreamDescriptor' command exchange on each audio I/O operation.
*/
int[] latenciesMs;
}

View file

@ -23,6 +23,7 @@ import android.hardware.audio.core.AudioRoute;
import android.hardware.audio.core.IStreamIn;
import android.hardware.audio.core.IStreamOut;
import android.hardware.audio.core.ModuleDebug;
import android.hardware.audio.core.StreamDescriptor;
import android.media.audio.common.AudioOffloadInfo;
import android.media.audio.common.AudioPort;
import android.media.audio.common.AudioPortConfig;
@ -241,22 +242,49 @@ interface IModule {
* 'setAudioPortConfig' method. Existence of an audio patch involving this
* port configuration is not required for successful opening of a stream.
*
* The requested buffer size is expressed in frames, thus the actual size
* in bytes depends on the audio port configuration. Also, the HAL module
* may end up providing a larger buffer, thus the requested size is treated
* as the minimum size that the client needs. The minimum buffer size
* suggested by the HAL is in the 'AudioPatch.minimumStreamBufferSizeFrames'
* field, returned as a result of calling the 'setAudioPatch' method.
*
* 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.
* Note that although it's not prohibited to open a stream on a mix port
* configuration which is not connected (using a patch) to any device port,
* and set up a patch afterwards, this is not the recommended sequence of
* calls, because setting up of a patch might fail due to an insufficient
* stream buffer size.
*
* @return An opened input stream and the associated descriptor.
* @param args Input arguments, see 'OpenInputStreamArguments' parcelable.
* @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.
* - If a buffer of the requested size can not be provided.
* @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);
@VintfStability
parcelable OpenInputStreamArguments {
/** The ID of the audio mix port config. */
int portConfigId;
/** Description of the audio that will be recorded. */
SinkMetadata sinkMetadata;
/** Requested audio I/O buffer minimum size, in frames. */
long bufferSizeFrames;
}
@VintfStability
parcelable OpenInputStreamReturn {
IStreamIn stream;
StreamDescriptor desc;
}
OpenInputStreamReturn openInputStream(in OpenInputStreamArguments args);
/**
* Open an output stream using an existing audio mix port configuration.
@ -269,21 +297,33 @@ interface IModule {
* the framework must provide additional information about the encoded
* audio stream in 'offloadInfo' argument.
*
* The requested buffer size is expressed in frames, thus the actual size
* in bytes depends on the audio port configuration. Also, the HAL module
* may end up providing a larger buffer, thus the requested size is treated
* as the minimum size that the client needs. The minimum buffer size
* suggested by the HAL is in the 'AudioPatch.minimumStreamBufferSizeFrames'
* field, returned as a result of calling the 'setAudioPatch' method.
*
* 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.
* Note that although it's not prohibited to open a stream on a mix port
* configuration which is not connected (using a patch) to any device port,
* and set up a patch afterwards, this is not the recommended sequence of
* calls, because setting up of a patch might fail due to an insufficient
* stream buffer size.
*
* @return An opened output stream and the associated descriptor.
* @param args Input arguments, see 'OpenOutputStreamArguments' parcelable.
* @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.
* - If the offload info is not provided for an offload
* port configuration.
* - If a buffer of the requested size can not be provided.
* @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
@ -291,8 +331,23 @@ interface IModule {
* - If another opened stream already exists for the 'PRIMARY'
* output port.
*/
IStreamOut openOutputStream(int portConfigId, in SourceMetadata sourceMetadata,
in @nullable AudioOffloadInfo offloadInfo);
@VintfStability
parcelable OpenOutputStreamArguments {
/** The ID of the audio mix port config. */
int portConfigId;
/** Description of the audio that will be played. */
SourceMetadata sourceMetadata;
/** Additional information used for offloaded playback only. */
@nullable AudioOffloadInfo offloadInfo;
/** Requested audio I/O buffer minimum size, in frames. */
long bufferSizeFrames;
}
@VintfStability
parcelable OpenOutputStreamReturn {
IStreamOut stream;
StreamDescriptor desc;
}
OpenOutputStreamReturn openOutputStream(in OpenOutputStreamArguments args);
/**
* Set an audio patch.
@ -300,19 +355,27 @@ interface IModule {
* 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.
* attempt to update an existing patch is made.
*
* The operation of updating an existing audio patch must not change
* playback state of audio streams opened on the audio port configurations
* of the patch. That is, the HAL module must still be able to consume or
* to provide data from / to streams continuously during the patch
* switching. Natural intermittent audible loss of some audio frames due to
* switching between device ports which does not affect stream playback is
* allowed. If the HAL module is unable to avoid playback or recording
* state change when updating a certain patch, it must return an error. In
* that case, the client must take care of changing port configurations,
* patches, and recreating streams in a way which provides an acceptable
* user experience.
*
* 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.
*
* When updating an existing audio patch, nominal latency values may change
* and must be provided by the HAL module in the returned 'AudioPatch'
* structure.
*
* @return Resulting audio patch.
* @param requested Requested audio patch.
@ -324,6 +387,9 @@ interface IModule {
* @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.
* - If updating an existing patch will cause interruption
* of audio, or requires re-opening of streams due to
* change of minimum audio I/O buffer size.
* @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

View file

@ -28,7 +28,9 @@ interface IStreamIn {
*
* 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.
* interface throw EX_ILLEGAL_STATE for a closed stream.
*
* The associated stream descriptor can be released once this method returns.
*
* @throws EX_ILLEGAL_STATE If the stream has already been closed.
*/

View file

@ -28,7 +28,9 @@ interface IStreamOut {
*
* 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.
* interface throw EX_ILLEGAL_STATE for a closed stream.
*
* The associated stream descriptor can be released once this method returns.
*
* @throws EX_ILLEGAL_STATE If the stream has already been closed.
*/

View file

@ -0,0 +1,51 @@
/*
* 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.common.Ashmem;
/**
* MMap buffer descriptor is used by streams opened in MMap No IRQ mode.
*/
@JavaDerive(equals=true, toString=true)
@VintfStability
parcelable MmapBufferDescriptor {
/**
* MMap memory buffer.
*/
Ashmem sharedMemory;
/**
* Transfer size granularity in frames.
*/
long burstSizeFrames;
/**
* Attributes describing the buffer. Bitmask indexed by FLAG_INDEX_*
* constants.
*/
int flags;
/**
* Whether the buffer can be securely shared to untrusted applications
* through the AAudio exclusive mode.
*
* Only set this flag if applications are restricted from accessing the
* memory surrounding the audio data buffer by a kernel mechanism.
* See Linux kernel's dma-buf
* (https://www.kernel.org/doc/html/v4.16/driver-api/dma-buf.html).
*/
const int FLAG_INDEX_APPLICATION_SHAREABLE = 0;
}

View file

@ -0,0 +1,201 @@
/*
* 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.core.MmapBufferDescriptor;
import android.hardware.common.fmq.MQDescriptor;
import android.hardware.common.fmq.SynchronizedReadWrite;
import android.hardware.common.fmq.UnsynchronizedWrite;
/**
* Stream descriptor contains fast message queues and buffers used for sending
* and receiving audio data. The descriptor complements IStream* interfaces by
* providing communication channels that serve as an alternative to Binder
* transactions.
*
* Handling of audio data and commands must be done by the HAL module on a
* dedicated thread with high priority, for all modes, including MMap No
* IRQ. The HAL module is responsible for creating this thread and setting its
* priority. The HAL module is also responsible for serializing access to the
* internal components of the stream while serving commands invoked via the
* stream's AIDL interface and commands invoked via the command queue of the
* descriptor.
*/
@JavaDerive(equals=true, toString=true)
@VintfStability
parcelable StreamDescriptor {
/**
* Position binds together a position within the stream and time.
*
* The timestamp must use "monotonic" clock.
*
* The frame count must advance between consecutive I/O operations, and stop
* advancing when the stream was put into the 'standby' mode. On exiting the
* 'standby' mode, the frame count must not reset, but continue counting.
*/
@VintfStability
@FixedSize
parcelable Position {
/** Frame count. */
long frames;
/** Timestamp in nanoseconds. */
long timeNs;
}
/**
* The exit command is used to unblock the HAL thread and ask it to exit.
* This is the last command that the client sends via the StreamDescriptor.
* The HAL module must reply to this command in order to unblock the client,
* and cease waiting on the command queue.
*/
const int COMMAND_EXIT = 0;
/**
* The command used for audio I/O, see 'AudioBuffer'. For MMap No IRQ mode
* this command only provides updated positions and latency because actual
* audio I/O is done via the 'AudioBuffer.mmap' shared buffer.
*/
const int COMMAND_BURST = 1;
/**
* Used for sending commands to the HAL module. The client writes into
* the queue, the HAL module reads. The queue can only contain a single
* command.
*/
@VintfStability
@FixedSize
parcelable Command {
/**
* One of COMMAND_* codes.
*/
int code;
/**
* For output streams: the amount of bytes provided by the client in the
* 'audio.fmq' queue.
* For input streams: the amount of bytes requested by the client to read
* from the hardware into the 'audio.fmq' queue.
*/
int fmqByteCount;
}
MQDescriptor<Command, SynchronizedReadWrite> command;
/**
* No error, the command completed successfully.
*/
const int STATUS_OK = 0;
/**
* Invalid data provided in the command, e.g. unknown command code or
* negative 'fmqByteCount' value.
*/
const int STATUS_ILLEGAL_ARGUMENT = 1;
/**
* The HAL module is not in the state when it can complete the command.
*/
const int STATUS_ILLEGAL_STATE = 2;
/**
* Used for providing replies to commands. The HAL module writes into
* the queue, the client reads. The queue can only contain a single reply,
* corresponding to the last command sent by the client.
*/
@VintfStability
@FixedSize
parcelable Reply {
/**
* One of STATUS_* statuses.
*/
int status;
/**
* For output streams: the amount of bytes actually consumed by the HAL
* module from the 'audio.fmq' queue.
* For input streams: the amount of bytes actually provided by the HAL
* in the 'audio.fmq' queue.
*/
int fmqByteCount;
/**
* For output streams: the moment when the specified stream position
* was presented to an external observer (i.e. presentation position).
* For input streams: the moment when data at the specified stream position
* was acquired (i.e. capture position).
*/
Position observable;
/**
* Used only for MMap streams to provide the hardware read / write
* position for audio data in the shared memory buffer 'audio.mmap'.
*/
Position hardware;
/**
* Current latency reported by the hardware.
*/
int latencyMs;
}
MQDescriptor<Reply, SynchronizedReadWrite> reply;
/**
* Total buffer size in frames. This applies both to the size of the 'audio.fmq'
* queue and to the size of the shared memory buffer for MMap No IRQ streams.
* Note that this size may end up being slightly larger than the size requested
* in a call to 'IModule.openInputStream' or 'openOutputStream' due to memory
* alignment requirements.
*/
long bufferSizeFrames;
/**
* Used for sending or receiving audio data to/from the stream. In the case
* of MMap No IRQ streams this structure only contains the information about
* the shared memory buffer. Audio data is sent via the shared buffer
* directly.
*/
@VintfStability
union AudioBuffer {
/**
* The fast message queue used for all modes except MMap No IRQ. Access
* to this queue is synchronized via the 'command' and 'reply' queues
* as described below.
*
* For output streams the following sequence of operations is used:
* 1. The client puts audio data into the 'audio.fmq' queue.
* 2. The client writes the 'BURST' command into the 'command' queue,
* and hangs on waiting on a read from the 'reply' queue.
* 3. The high priority thread in the HAL module wakes up due to 2.
* 4. The HAL module reads the command and audio data.
* 5. The HAL module writes the command status and current positions
* into 'reply' queue, and hangs on waiting on a read from
* the 'command' queue.
*
* For input streams the following sequence of operations is used:
* 1. The client writes the 'BURST' command into the 'command' queue,
* and hangs on waiting on a read from the 'reply' queue.
* 2. The high priority thread in the HAL module wakes up due to 1.
* 3. The HAL module puts audio data into the 'audio.fmq' queue.
* 4. The HAL module writes the command status and current positions
* into 'reply' queue, and hangs on waiting on a read from
* the 'command' queue.
* 5. The client wakes up due to 4.
* 6. The client reads the reply and audio data.
*/
MQDescriptor<byte, UnsynchronizedWrite> fmq;
/**
* MMap buffers are shared directly with the DSP, which operates
* independently from the CPU. Writes and reads into these buffers
* are not synchronized with 'command' and 'reply' queues. However,
* the client still uses the 'BURST' command for obtaining current
* positions from the HAL module.
*/
MmapBufferDescriptor mmap;
}
AudioBuffer audio;
}

View file

@ -16,6 +16,8 @@ cc_library_static {
"libstagefright_foundation",
"android.media.audio.common.types-V1-ndk",
"android.hardware.audio.core-V1-ndk",
"android.hardware.common-V2-ndk",
"android.hardware.common.fmq-V1-ndk",
],
export_include_dirs: ["include"],
srcs: [
@ -41,6 +43,8 @@ cc_binary {
"libstagefright_foundation",
"android.media.audio.common.types-V1-ndk",
"android.hardware.audio.core-V1-ndk",
"android.hardware.common-V2-ndk",
"android.hardware.common.fmq-V1-ndk",
],
static_libs: [
"libaudioserviceexampleimpl",

View file

@ -27,7 +27,9 @@
using aidl::android::hardware::audio::common::SinkMetadata;
using aidl::android::hardware::audio::common::SourceMetadata;
using aidl::android::media::audio::common::AudioChannelLayout;
using aidl::android::media::audio::common::AudioFormatDescription;
using aidl::android::media::audio::common::AudioFormatType;
using aidl::android::media::audio::common::AudioIoFlags;
using aidl::android::media::audio::common::AudioOffloadInfo;
using aidl::android::media::audio::common::AudioOutputFlags;
@ -36,6 +38,7 @@ 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;
using aidl::android::media::audio::common::PcmType;
namespace aidl::android::hardware::audio::core {
@ -69,6 +72,49 @@ bool generateDefaultPortConfig(const AudioPort& port, AudioPortConfig* config) {
return true;
}
constexpr size_t getPcmSampleSizeInBytes(PcmType pcm) {
switch (pcm) {
case PcmType::UINT_8_BIT:
return 1;
case PcmType::INT_16_BIT:
return 2;
case PcmType::INT_32_BIT:
return 4;
case PcmType::FIXED_Q_8_24:
return 4;
case PcmType::FLOAT_32_BIT:
return 4;
case PcmType::INT_24_BIT:
return 3;
}
return 0;
}
constexpr size_t getChannelCount(const AudioChannelLayout& layout) {
using Tag = AudioChannelLayout::Tag;
switch (layout.getTag()) {
case Tag::none:
return 0;
case Tag::invalid:
return 0;
case Tag::indexMask:
return __builtin_popcount(layout.get<Tag::indexMask>());
case Tag::layoutMask:
return __builtin_popcount(layout.get<Tag::layoutMask>());
case Tag::voiceMask:
return __builtin_popcount(layout.get<Tag::voiceMask>());
}
return 0;
}
size_t getFrameSizeInBytes(const AudioFormatDescription& format, const AudioChannelLayout& layout) {
if (format.type == AudioFormatType::PCM) {
return getPcmSampleSizeInBytes(format.pcm) * getChannelCount(layout);
}
// For non-PCM formats always use frame size of 1.
return 1;
}
bool findAudioProfile(const AudioPort& port, const AudioFormatDescription& format,
AudioProfile* profile) {
if (auto profilesIt =
@ -111,6 +157,78 @@ void Module::cleanUpPatches(int32_t portConfigId) {
erase_all_values(mPatches, erasedPatches);
}
ndk::ScopedAStatus Module::createStreamDescriptor(int32_t in_portConfigId,
int64_t in_bufferSizeFrames,
StreamDescriptor* out_descriptor) {
if (in_bufferSizeFrames <= 0) {
LOG(ERROR) << __func__ << ": non-positive buffer size " << in_bufferSizeFrames;
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
if (in_bufferSizeFrames < kMinimumStreamBufferSizeFrames) {
LOG(ERROR) << __func__ << ": insufficient buffer size " << in_bufferSizeFrames
<< ", must be at least " << kMinimumStreamBufferSizeFrames;
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
auto& configs = getConfig().portConfigs;
auto portConfigIt = findById<AudioPortConfig>(configs, in_portConfigId);
// Since 'createStreamDescriptor' is an internal method, it is assumed that
// validity of the portConfigId has already been checked.
const size_t frameSize =
getFrameSizeInBytes(portConfigIt->format.value(), portConfigIt->channelMask.value());
if (frameSize == 0) {
LOG(ERROR) << __func__ << ": could not calculate frame size for port config "
<< portConfigIt->toString();
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
LOG(DEBUG) << __func__ << ": frame size " << frameSize << " bytes";
if (frameSize > kMaximumStreamBufferSizeBytes / in_bufferSizeFrames) {
LOG(ERROR) << __func__ << ": buffer size " << in_bufferSizeFrames
<< " frames is too large, maximum size is "
<< kMaximumStreamBufferSizeBytes / frameSize;
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
(void)out_descriptor;
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Module::findPortIdForNewStream(int32_t in_portConfigId, AudioPort** port) {
auto& configs = getConfig().portConfigs;
auto portConfigIt = findById<AudioPortConfig>(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<AudioPort>(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 (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);
}
if (portIt->ext.getTag() != AudioPortExt::Tag::mix) {
LOG(ERROR) << __func__ << ": port config id " << in_portConfigId
<< " does not correspond to a mix port";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
const int32_t maxOpenStreamCount = portIt->ext.get<AudioPortExt::Tag::mix>().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);
}
*port = &(*portIt);
return ndk::ScopedAStatus::ok();
}
internal::Configuration& Module::getConfig() {
if (!mConfig) {
mConfig.reset(new internal::Configuration(internal::getNullPrimaryConfiguration()));
@ -336,98 +454,59 @@ ndk::ScopedAStatus Module::getAudioRoutesForAudioPort(int32_t in_portId,
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Module::openInputStream(int32_t in_portConfigId,
const SinkMetadata& in_sinkMetadata,
std::shared_ptr<IStreamIn>* _aidl_return) {
auto& configs = getConfig().portConfigs;
auto portConfigIt = findById<AudioPortConfig>(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);
ndk::ScopedAStatus Module::openInputStream(const OpenInputStreamArguments& in_args,
OpenInputStreamReturn* _aidl_return) {
LOG(DEBUG) << __func__ << ": port config id " << in_args.portConfigId << ", buffer size "
<< in_args.bufferSizeFrames << " frames";
AudioPort* port = nullptr;
if (auto status = findPortIdForNewStream(in_args.portConfigId, &port); !status.isOk()) {
return status;
}
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<AudioPort>(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
if (port->flags.getTag() != AudioIoFlags::Tag::input) {
LOG(ERROR) << __func__ << ": port config id " << in_args.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);
if (auto status = createStreamDescriptor(in_args.portConfigId, in_args.bufferSizeFrames,
&_aidl_return->desc);
!status.isOk()) {
return status;
}
const int32_t maxOpenStreamCount = portIt->ext.get<AudioPortExt::Tag::mix>().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<StreamIn>(in_sinkMetadata);
mStreams.insert(portId, in_portConfigId, StreamWrapper(stream));
*_aidl_return = std::move(stream);
auto stream = ndk::SharedRefBase::make<StreamIn>(in_args.sinkMetadata);
mStreams.insert(port->id, in_args.portConfigId, StreamWrapper(stream));
_aidl_return->stream = std::move(stream);
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Module::openOutputStream(int32_t in_portConfigId,
const SourceMetadata& in_sourceMetadata,
const std::optional<AudioOffloadInfo>& in_offloadInfo,
std::shared_ptr<IStreamOut>* _aidl_return) {
auto& configs = getConfig().portConfigs;
auto portConfigIt = findById<AudioPortConfig>(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);
ndk::ScopedAStatus Module::openOutputStream(const OpenOutputStreamArguments& in_args,
OpenOutputStreamReturn* _aidl_return) {
LOG(DEBUG) << __func__ << ": port config id " << in_args.portConfigId << ", has offload info? "
<< (in_args.offloadInfo.has_value()) << ", buffer size " << in_args.bufferSizeFrames
<< " frames";
AudioPort* port = nullptr;
if (auto status = findPortIdForNewStream(in_args.portConfigId, &port); !status.isOk()) {
return status;
}
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<AudioPort>(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
if (port->flags.getTag() != AudioIoFlags::Tag::output) {
LOG(ERROR) << __func__ << ": port config id " << in_args.portConfigId
<< " does not correspond to an output mix port";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
if (portConfigIt->flags.has_value() &&
((portConfigIt->flags.value().get<AudioIoFlags::Tag::output>() &
1 << static_cast<int32_t>(AudioOutputFlags::COMPRESS_OFFLOAD)) != 0) &&
!in_offloadInfo.has_value()) {
LOG(ERROR) << __func__ << ": port config id " << in_portConfigId
if ((port->flags.get<AudioIoFlags::Tag::output>() &
1 << static_cast<int32_t>(AudioOutputFlags::COMPRESS_OFFLOAD)) != 0 &&
!in_args.offloadInfo.has_value()) {
LOG(ERROR) << __func__ << ": port id " << port->id
<< " has COMPRESS_OFFLOAD flag set, requires offload info";
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);
if (auto status = createStreamDescriptor(in_args.portConfigId, in_args.bufferSizeFrames,
&_aidl_return->desc);
!status.isOk()) {
return status;
}
const int32_t maxOpenStreamCount = portIt->ext.get<AudioPortExt::Tag::mix>().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<StreamOut>(in_sourceMetadata, in_offloadInfo);
mStreams.insert(portId, in_portConfigId, StreamWrapper(stream));
*_aidl_return = std::move(stream);
auto stream = ndk::SharedRefBase::make<StreamOut>(in_args.sourceMetadata, in_args.offloadInfo);
mStreams.insert(port->id, in_args.portConfigId, StreamWrapper(stream));
_aidl_return->stream = std::move(stream);
return ndk::ScopedAStatus::ok();
}
@ -512,6 +591,10 @@ ndk::ScopedAStatus Module::setAudioPatch(const AudioPatch& in_requested, AudioPa
}
}
*_aidl_return = in_requested;
_aidl_return->minimumStreamBufferSizeFrames = kMinimumStreamBufferSizeFrames;
_aidl_return->latenciesMs.clear();
_aidl_return->latenciesMs.insert(_aidl_return->latenciesMs.end(),
_aidl_return->sinkPortConfigIds.size(), kLatencyMs);
if (existing == patches.end()) {
_aidl_return->id = getConfig().nextPatchId++;
patches.push_back(*_aidl_return);

View file

@ -15,7 +15,6 @@
*/
#define LOG_TAG "AHAL_Stream"
#define LOG_NDEBUG 0
#include <android-base/logging.h>
#include "core-impl/Stream.h"
@ -26,7 +25,9 @@ using aidl::android::media::audio::common::AudioOffloadInfo;
namespace aidl::android::hardware::audio::core {
StreamIn::StreamIn(const SinkMetadata& sinkMetadata) : mMetadata(sinkMetadata) {}
StreamIn::StreamIn(const SinkMetadata& sinkMetadata) : mMetadata(sinkMetadata) {
LOG(DEBUG) << __func__;
}
ndk::ScopedAStatus StreamIn::close() {
LOG(DEBUG) << __func__;
@ -51,7 +52,9 @@ ndk::ScopedAStatus StreamIn::updateMetadata(const SinkMetadata& in_sinkMetadata)
StreamOut::StreamOut(const SourceMetadata& sourceMetadata,
const std::optional<AudioOffloadInfo>& offloadInfo)
: mMetadata(sourceMetadata), mOffloadInfo(offloadInfo) {}
: mMetadata(sourceMetadata), mOffloadInfo(offloadInfo) {
LOG(DEBUG) << __func__;
}
ndk::ScopedAStatus StreamOut::close() {
LOG(DEBUG) << __func__;

View file

@ -48,15 +48,15 @@ class Module : public BnModule {
int32_t in_portId,
std::vector<::aidl::android::hardware::audio::core::AudioRoute>* _aidl_return) override;
ndk::ScopedAStatus openInputStream(
int32_t in_portConfigId,
const ::aidl::android::hardware::audio::common::SinkMetadata& in_sinkMetadata,
std::shared_ptr<IStreamIn>* _aidl_return) override;
const ::aidl::android::hardware::audio::core::IModule::OpenInputStreamArguments&
in_args,
::aidl::android::hardware::audio::core::IModule::OpenInputStreamReturn* _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<IStreamOut>* _aidl_return) override;
const ::aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments&
in_args,
::aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn* _aidl_return)
override;
ndk::ScopedAStatus setAudioPatch(const AudioPatch& in_requested,
AudioPatch* _aidl_return) override;
ndk::ScopedAStatus setAudioPortConfig(
@ -69,9 +69,21 @@ class Module : public BnModule {
private:
void cleanUpPatch(int32_t patchId);
void cleanUpPatches(int32_t portConfigId);
ndk::ScopedAStatus createStreamDescriptor(
int32_t in_portConfigId, int64_t in_bufferSizeFrames,
::aidl::android::hardware::audio::core::StreamDescriptor* out_descriptor);
ndk::ScopedAStatus findPortIdForNewStream(
int32_t in_portConfigId, ::aidl::android::media::audio::common::AudioPort** port);
internal::Configuration& getConfig();
void registerPatch(const AudioPatch& patch);
// This value is used for all AudioPatches.
static constexpr int32_t kMinimumStreamBufferSizeFrames = 16;
// This value is used for all AudioPatches.
static constexpr int32_t kLatencyMs = 10;
// The maximum stream buffer size is 1 GiB = 2 ** 30 bytes;
static constexpr int32_t kMaximumStreamBufferSizeBytes = 1 << 30;
std::unique_ptr<internal::Configuration> mConfig;
ModuleDebug mDebug;
// ids of ports created at runtime via 'connectExternalDevice'.

View file

@ -23,6 +23,8 @@ cc_test {
static_libs: [
"android.hardware.audio.common-V1-ndk",
"android.hardware.audio.core-V1-ndk",
"android.hardware.common-V2-ndk",
"android.hardware.common.fmq-V1-ndk",
"android.media.audio.common.types-V1-ndk",
],
test_suites: [

View file

@ -16,6 +16,7 @@
#include <algorithm>
#include <condition_variable>
#include <limits>
#include <memory>
#include <mutex>
#include <optional>
@ -48,6 +49,7 @@ using aidl::android::hardware::audio::core::IModule;
using aidl::android::hardware::audio::core::IStreamIn;
using aidl::android::hardware::audio::core::IStreamOut;
using aidl::android::hardware::audio::core::ModuleDebug;
using aidl::android::hardware::audio::core::StreamDescriptor;
using aidl::android::media::audio::common::AudioContentType;
using aidl::android::media::audio::common::AudioDevice;
using aidl::android::media::audio::common::AudioDeviceAddress;
@ -225,6 +227,9 @@ class WithAudioPortConfig {
class AudioCoreModule : public testing::TestWithParam<std::string> {
public:
// The default buffer size is used mostly for negative tests.
static constexpr int kDefaultBufferSize = 256;
void SetUp() override {
ASSERT_NO_FATAL_FAILURE(ConnectToService());
debug.flags().simulateDeviceConnections = true;
@ -382,13 +387,14 @@ class WithStream {
}
}
void SetUpPortConfig(IModule* module) { ASSERT_NO_FATAL_FAILURE(mPortConfig.SetUp(module)); }
ScopedAStatus SetUpNoChecks(IModule* module) {
return SetUpNoChecks(module, mPortConfig.get());
ScopedAStatus SetUpNoChecks(IModule* module, long bufferSize) {
return SetUpNoChecks(module, mPortConfig.get(), bufferSize);
}
ScopedAStatus SetUpNoChecks(IModule* module, const AudioPortConfig& portConfig);
void SetUp(IModule* module) {
ScopedAStatus SetUpNoChecks(IModule* module, const AudioPortConfig& portConfig,
long bufferSize);
void SetUp(IModule* module, long bufferSize) {
ASSERT_NO_FATAL_FAILURE(SetUpPortConfig(module));
ScopedAStatus status = SetUpNoChecks(module);
ScopedAStatus status = SetUpNoChecks(module, bufferSize);
ASSERT_EQ(EX_NONE, status.getExceptionCode())
<< status << "; port config id " << getPortId();
ASSERT_NE(nullptr, mStream) << "; port config id " << getPortId();
@ -401,6 +407,7 @@ class WithStream {
private:
WithAudioPortConfig mPortConfig;
std::shared_ptr<Stream> mStream;
StreamDescriptor mDescriptor;
};
SinkMetadata GenerateSinkMetadata(const AudioPortConfig& portConfig) {
@ -415,8 +422,19 @@ SinkMetadata GenerateSinkMetadata(const AudioPortConfig& portConfig) {
template <>
ScopedAStatus WithStream<IStreamIn>::SetUpNoChecks(IModule* module,
const AudioPortConfig& portConfig) {
return module->openInputStream(portConfig.id, GenerateSinkMetadata(portConfig), &mStream);
const AudioPortConfig& portConfig,
long bufferSize) {
aidl::android::hardware::audio::core::IModule::OpenInputStreamArguments args;
args.portConfigId = portConfig.id;
args.sinkMetadata = GenerateSinkMetadata(portConfig);
args.bufferSizeFrames = bufferSize;
aidl::android::hardware::audio::core::IModule::OpenInputStreamReturn ret;
ScopedAStatus status = module->openInputStream(args, &ret);
if (status.isOk()) {
mStream = std::move(ret.stream);
mDescriptor = std::move(ret.desc);
}
return status;
}
SourceMetadata GenerateSourceMetadata(const AudioPortConfig& portConfig) {
@ -432,10 +450,20 @@ SourceMetadata GenerateSourceMetadata(const AudioPortConfig& portConfig) {
template <>
ScopedAStatus WithStream<IStreamOut>::SetUpNoChecks(IModule* module,
const AudioPortConfig& portConfig) {
return module->openOutputStream(portConfig.id, GenerateSourceMetadata(portConfig),
ModuleConfig::generateOffloadInfoIfNeeded(portConfig),
&mStream);
const AudioPortConfig& portConfig,
long bufferSize) {
aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args;
args.portConfigId = portConfig.id;
args.sourceMetadata = GenerateSourceMetadata(portConfig);
args.offloadInfo = ModuleConfig::generateOffloadInfoIfNeeded(portConfig);
args.bufferSizeFrames = bufferSize;
aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret;
ScopedAStatus status = module->openOutputStream(args, &ret);
if (status.isOk()) {
mStream = std::move(ret.stream);
mDescriptor = std::move(ret.desc);
}
return status;
}
class WithAudioPatch {
@ -467,6 +495,10 @@ class WithAudioPatch {
ASSERT_EQ(EX_NONE, status.getExceptionCode())
<< status << "; source port config id " << mSrcPortConfig.getId()
<< "; sink port config id " << mSinkPortConfig.getId();
EXPECT_GT(mPatch.minimumStreamBufferSizeFrames, 0) << "patch id " << getId();
for (auto latencyMs : mPatch.latenciesMs) {
EXPECT_GT(latencyMs, 0) << "patch id " << getId();
}
}
int32_t getId() const { return mPatch.id; }
const AudioPatch& get() const { return mPatch; }
@ -739,18 +771,24 @@ TEST_P(AudioCoreModule, OpenStreamInvalidPortConfigId) {
ASSERT_NO_FATAL_FAILURE(GetAllPortConfigIds(&portConfigIds));
for (const auto portConfigId : GetNonExistentIds(portConfigIds)) {
{
std::shared_ptr<IStreamIn> stream;
ScopedAStatus status = module->openInputStream(portConfigId, {}, &stream);
aidl::android::hardware::audio::core::IModule::OpenInputStreamArguments args;
args.portConfigId = portConfigId;
args.bufferSizeFrames = kDefaultBufferSize;
aidl::android::hardware::audio::core::IModule::OpenInputStreamReturn ret;
ScopedAStatus status = module->openInputStream(args, &ret);
EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
<< status << " openInputStream returned for port config ID " << portConfigId;
EXPECT_EQ(nullptr, stream);
EXPECT_EQ(nullptr, ret.stream);
}
{
std::shared_ptr<IStreamOut> stream;
ScopedAStatus status = module->openOutputStream(portConfigId, {}, {}, &stream);
aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args;
args.portConfigId = portConfigId;
args.bufferSizeFrames = kDefaultBufferSize;
aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret;
ScopedAStatus status = module->openOutputStream(args, &ret);
EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
<< status << " openOutputStream returned for port config ID " << portConfigId;
EXPECT_EQ(nullptr, stream);
EXPECT_EQ(nullptr, ret.stream);
}
}
}
@ -1120,7 +1158,7 @@ class AudioStream : public AudioCoreModule {
std::shared_ptr<Stream> heldStream;
{
WithStream<Stream> stream(portConfig.value());
ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get()));
ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSize));
heldStream = stream.getSharedPointer();
}
ScopedAStatus status = heldStream->close();
@ -1132,10 +1170,43 @@ class AudioStream : public AudioCoreModule {
const auto allPortConfigs = moduleConfig->getPortConfigsForMixPorts(IsInput<Stream>());
for (const auto& portConfig : allPortConfigs) {
WithStream<Stream> stream(portConfig);
ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get()));
ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSize));
}
}
void OpenInvalidBufferSize() {
const auto portConfig = moduleConfig->getSingleConfigForMixPort(IsInput<Stream>());
if (!portConfig.has_value()) {
GTEST_SKIP() << "No mix port for attached devices";
}
WithStream<Stream> stream(portConfig.value());
ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfig(module.get()));
// The buffer size of 1 frame should be impractically small, and thus
// less than any minimum buffer size suggested by any HAL.
for (long bufferSize : std::array<long, 4>{-1, 0, 1, std::numeric_limits<long>::max()}) {
ScopedAStatus status = stream.SetUpNoChecks(module.get(), bufferSize);
EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
<< status << " open" << direction(true) << "Stream returned for " << bufferSize
<< " buffer size";
EXPECT_EQ(nullptr, stream.get());
}
}
void OpenInvalidDirection() {
// Important! The direction of the port config must be reversed.
const auto portConfig = moduleConfig->getSingleConfigForMixPort(!IsInput<Stream>());
if (!portConfig.has_value()) {
GTEST_SKIP() << "No mix port for attached devices";
}
WithStream<Stream> stream(portConfig.value());
ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfig(module.get()));
ScopedAStatus status = stream.SetUpNoChecks(module.get(), kDefaultBufferSize);
EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
<< status << " open" << direction(true) << "Stream returned for port config ID "
<< stream.getPortId();
EXPECT_EQ(nullptr, stream.get());
}
void OpenOverMaxCount() {
constexpr bool isInput = IsInput<Stream>();
auto ports = moduleConfig->getMixPorts(isInput);
@ -1158,10 +1229,10 @@ class AudioStream : public AudioCoreModule {
streamWraps[i].emplace(portConfigs[i]);
WithStream<Stream>& stream = streamWraps[i].value();
if (i < maxStreamCount) {
ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get()));
ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSize));
} else {
ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfig(module.get()));
ScopedAStatus status = stream.SetUpNoChecks(module.get());
ScopedAStatus status = stream.SetUpNoChecks(module.get(), kDefaultBufferSize);
EXPECT_EQ(EX_ILLEGAL_STATE, status.getExceptionCode())
<< status << " open" << direction(true)
<< "Stream returned for port config ID " << stream.getPortId()
@ -1175,21 +1246,6 @@ class AudioStream : public AudioCoreModule {
}
}
void OpenInvalidDirection() {
// Important! The direction of the port config must be reversed.
const auto portConfig = moduleConfig->getSingleConfigForMixPort(!IsInput<Stream>());
if (!portConfig.has_value()) {
GTEST_SKIP() << "No mix port for attached devices";
}
WithStream<Stream> stream(portConfig.value());
ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfig(module.get()));
ScopedAStatus status = stream.SetUpNoChecks(module.get());
EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
<< 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<Stream>());
if (!portConfig.has_value()) {
@ -1204,7 +1260,7 @@ class AudioStream : public AudioCoreModule {
GTEST_SKIP() << "No mix port for attached devices";
}
WithStream<Stream> stream(portConfig.value());
ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get()));
ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSize));
ScopedAStatus status = module->resetAudioPortConfig(stream.getPortId());
EXPECT_EQ(EX_ILLEGAL_STATE, status.getExceptionCode())
<< status << " returned for port config ID " << stream.getPortId();
@ -1212,9 +1268,10 @@ class AudioStream : public AudioCoreModule {
void OpenTwiceSamePortConfigImpl(const AudioPortConfig& portConfig) {
WithStream<Stream> stream1(portConfig);
ASSERT_NO_FATAL_FAILURE(stream1.SetUp(module.get()));
ASSERT_NO_FATAL_FAILURE(stream1.SetUp(module.get(), kDefaultBufferSize));
WithStream<Stream> stream2;
ScopedAStatus status = stream2.SetUpNoChecks(module.get(), stream1.getPortConfig());
ScopedAStatus status =
stream2.SetUpNoChecks(module.get(), stream1.getPortConfig(), kDefaultBufferSize);
EXPECT_EQ(EX_ILLEGAL_STATE, status.getExceptionCode())
<< status << " when opening " << direction(false)
<< " stream twice for the same port config ID " << stream1.getPortId();
@ -1238,6 +1295,7 @@ std::string AudioStreamOut::direction(bool capitalize) {
TEST_IO_STREAM(CloseTwice);
TEST_IO_STREAM(OpenAllConfigs);
TEST_IO_STREAM(OpenInvalidBufferSize);
TEST_IO_STREAM(OpenInvalidDirection);
TEST_IO_STREAM(OpenOverMaxCount);
TEST_IO_STREAM(OpenTwiceSamePortConfig);
@ -1277,10 +1335,14 @@ TEST_P(AudioStreamOut, RequireOffloadInfo) {
const auto portConfig = moduleConfig->getSingleConfigForMixPort(false, *offloadPortIt);
ASSERT_TRUE(portConfig.has_value())
<< "No profiles specified for the compressed offload mix port";
StreamDescriptor descriptor;
std::shared_ptr<IStreamOut> ignored;
ScopedAStatus status = module->openOutputStream(portConfig.value().id,
GenerateSourceMetadata(portConfig.value()),
{} /* offloadInfo */, &ignored);
aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args;
args.portConfigId = portConfig.value().id;
args.sourceMetadata = GenerateSourceMetadata(portConfig.value());
args.bufferSizeFrames = kDefaultBufferSize;
aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret;
ScopedAStatus status = module->openOutputStream(args, &ret);
EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode())
<< status
<< " returned when no offload info is provided for a compressed offload mix port";