audio: Implementation of audio I/O, part I am: 6a4872dff0
am: 1909ccc5b6
Original change: https://android-review.googlesource.com/c/platform/hardware/interfaces/+/2197183 Change-Id: I66a489672d148dec0989e1b9e65239a945f2d87d Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
commit
9190804521
17 changed files with 793 additions and 152 deletions
|
@ -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",
|
||||
|
|
|
@ -37,4 +37,6 @@ parcelable AudioPatch {
|
|||
int id;
|
||||
int[] sourcePortConfigIds;
|
||||
int[] sinkPortConfigIds;
|
||||
int minimumStreamBufferSizeFrames;
|
||||
int[] latenciesMs;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
201
audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl
Normal file
201
audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl
Normal 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;
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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__;
|
||||
|
|
|
@ -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'.
|
||||
|
|
|
@ -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: [
|
||||
|
|
|
@ -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";
|
||||
|
|
Loading…
Reference in a new issue