diff --git a/audio/aidl/default/Android.bp b/audio/aidl/default/Android.bp index b6cfc1329c..8596466738 100644 --- a/audio/aidl/default/Android.bp +++ b/audio/aidl/default/Android.bp @@ -85,6 +85,8 @@ cc_library { "bluetooth/DevicePortProxy.cpp", "bluetooth/ModuleBluetooth.cpp", "bluetooth/StreamBluetooth.cpp", + "primary/PrimaryMixer.cpp", + "primary/StreamPrimary.cpp", "r_submix/ModuleRemoteSubmix.cpp", "r_submix/RemoteSubmixUtils.cpp", "r_submix/SubmixRoute.cpp", diff --git a/audio/aidl/default/Configuration.cpp b/audio/aidl/default/Configuration.cpp index 1fea100411..0fbf55bb12 100644 --- a/audio/aidl/default/Configuration.cpp +++ b/audio/aidl/default/Configuration.cpp @@ -136,7 +136,7 @@ static AudioRoute createRoute(const std::vector& sources, const Audio // Device ports: // * "Speaker", OUT_SPEAKER, default // - no profiles specified -// * "Built-in Mic", IN_MICROPHONE, default +// * "Built-In Mic", IN_MICROPHONE, default // - no profiles specified // * "Telephony Tx", OUT_TELEPHONY_TX // - no profiles specified @@ -148,45 +148,34 @@ static AudioRoute createRoute(const std::vector& sources, const Audio // Mix ports: // * "primary output", PRIMARY, 1 max open, 1 max active stream // - profile PCM 16-bit; MONO, STEREO; 8000, 11025, 16000, 32000, 44100, 48000 -// - profile PCM 24-bit; MONO, STEREO; 8000, 11025, 16000, 32000, 44100, 48000 -// * "compressed offload", DIRECT|COMPRESS_OFFLOAD|NON_BLOCKING, 1 max open, 1 max active stream -// - profile MP3; MONO, STEREO; 44100, 48000 -// * "primary input", 2 max open, 2 max active streams -// - profile PCM 16-bit; MONO, STEREO, FRONT_BACK; -// 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 -// - profile PCM 24-bit; MONO, STEREO, FRONT_BACK; -// 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +// * "primary input", 1 max open, 1 max active stream +// - profile PCM 16-bit; MONO, STEREO; +// 8000, 11025, 16000, 32000, 44100, 48000 // * "telephony_tx", 1 max open, 1 max active stream // - profile PCM 16-bit; MONO, STEREO; 8000, 11025, 16000, 32000, 44100, 48000 -// - profile PCM 24-bit; MONO, STEREO; 8000, 11025, 16000, 32000, 44100, 48000 // * "telephony_rx", 1 max open, 1 max active stream // - profile PCM 16-bit; MONO, STEREO; 8000, 11025, 16000, 32000, 44100, 48000 -// - profile PCM 24-bit; MONO, STEREO; 8000, 11025, 16000, 32000, 44100, 48000 // * "fm_tuner", 1 max open, 1 max active stream // - profile PCM 16-bit; MONO, STEREO; 8000, 11025, 16000, 32000, 44100, 48000 -// - profile PCM 24-bit; MONO, STEREO; 8000, 11025, 16000, 32000, 44100, 48000 // // Routes: -// "primary out", "compressed offload" -> "Speaker" -// "Built-in Mic" -> "primary input" -// "telephony_tx" -> "Telephony Tx" +// "primary out" -> "Speaker" +// "Built-In Mic" -> "primary input" // "Telephony Rx" -> "telephony_rx" +// "telephony_tx" -> "Telephony Tx" // "FM Tuner" -> "fm_tuner" // // Initial port configs: -// * "Speaker" device port: PCM 24-bit; STEREO; 48000 -// * "Built-in Mic" device port: PCM 24-bit; MONO; 48000 -// * "Telephony Tx" device port: PCM 24-bit; MONO; 48000 -// * "Telephony Rx" device port: PCM 24-bit; MONO; 48000 -// * "FM Tuner" device port: PCM 24-bit; STEREO; 48000 +// * "Speaker" device port: PCM 16-bit; STEREO; 48000 +// * "Built-In Mic" device port: PCM 16-bit; MONO; 48000 +// * "Telephony Tx" device port: PCM 16-bit; MONO; 48000 +// * "Telephony Rx" device port: PCM 16-bit; MONO; 48000 +// * "FM Tuner" device port: PCM 16-bit; STEREO; 48000 // std::unique_ptr getPrimaryConfiguration() { static const Configuration configuration = []() { const std::vector standardPcmAudioProfiles = { createProfile(PcmType::INT_16_BIT, - {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO}, - {8000, 11025, 16000, 32000, 44100, 48000}), - createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO}, {8000, 11025, 16000, 32000, 44100, 48000})}; Configuration c; @@ -199,17 +188,17 @@ std::unique_ptr getPrimaryConfiguration() { 1 << AudioPortDeviceExt::FLAG_INDEX_DEFAULT_DEVICE)); c.ports.push_back(speakerOutDevice); c.initialConfigs.push_back( - createPortConfig(speakerOutDevice.id, speakerOutDevice.id, PcmType::INT_24_BIT, + createPortConfig(speakerOutDevice.id, speakerOutDevice.id, PcmType::INT_16_BIT, AudioChannelLayout::LAYOUT_STEREO, 48000, 0, false, createDeviceExt(AudioDeviceType::OUT_SPEAKER, 0))); AudioPort micInDevice = - createPort(c.nextPortId++, "Built-in Mic", 0, true, + createPort(c.nextPortId++, "Built-In Mic", 0, true, createDeviceExt(AudioDeviceType::IN_MICROPHONE, 1 << AudioPortDeviceExt::FLAG_INDEX_DEFAULT_DEVICE)); c.ports.push_back(micInDevice); c.initialConfigs.push_back( - createPortConfig(micInDevice.id, micInDevice.id, PcmType::INT_24_BIT, + createPortConfig(micInDevice.id, micInDevice.id, PcmType::INT_16_BIT, AudioChannelLayout::LAYOUT_MONO, 48000, 0, true, createDeviceExt(AudioDeviceType::IN_MICROPHONE, 0))); @@ -219,7 +208,7 @@ std::unique_ptr getPrimaryConfiguration() { c.ports.push_back(telephonyTxOutDevice); c.initialConfigs.push_back( createPortConfig(telephonyTxOutDevice.id, telephonyTxOutDevice.id, - PcmType::INT_24_BIT, AudioChannelLayout::LAYOUT_MONO, 48000, 0, + PcmType::INT_16_BIT, AudioChannelLayout::LAYOUT_MONO, 48000, 0, false, createDeviceExt(AudioDeviceType::OUT_TELEPHONY_TX, 0))); AudioPort telephonyRxInDevice = @@ -228,14 +217,14 @@ std::unique_ptr getPrimaryConfiguration() { c.ports.push_back(telephonyRxInDevice); c.initialConfigs.push_back( createPortConfig(telephonyRxInDevice.id, telephonyRxInDevice.id, - PcmType::INT_24_BIT, AudioChannelLayout::LAYOUT_MONO, 48000, 0, + PcmType::INT_16_BIT, AudioChannelLayout::LAYOUT_MONO, 48000, 0, true, createDeviceExt(AudioDeviceType::IN_TELEPHONY_RX, 0))); AudioPort fmTunerInDevice = createPort(c.nextPortId++, "FM Tuner", 0, true, createDeviceExt(AudioDeviceType::IN_FM_TUNER, 0)); c.ports.push_back(fmTunerInDevice); c.initialConfigs.push_back( - createPortConfig(fmTunerInDevice.id, fmTunerInDevice.id, PcmType::INT_24_BIT, + createPortConfig(fmTunerInDevice.id, fmTunerInDevice.id, PcmType::INT_16_BIT, AudioChannelLayout::LAYOUT_STEREO, 48000, 0, true, createDeviceExt(AudioDeviceType::IN_FM_TUNER, 0))); @@ -249,30 +238,12 @@ std::unique_ptr getPrimaryConfiguration() { standardPcmAudioProfiles.end()); c.ports.push_back(primaryOutMix); - AudioPort compressedOffloadOutMix = - createPort(c.nextPortId++, "compressed offload", - makeBitPositionFlagMask({AudioOutputFlags::DIRECT, - AudioOutputFlags::COMPRESS_OFFLOAD, - AudioOutputFlags::NON_BLOCKING}), - false, createPortMixExt(1, 1)); - compressedOffloadOutMix.profiles.push_back( - createProfile(::android::MEDIA_MIMETYPE_AUDIO_MPEG, - {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO}, - {44100, 48000})); - c.ports.push_back(compressedOffloadOutMix); - AudioPort primaryInMix = - createPort(c.nextPortId++, "primary input", 0, true, createPortMixExt(2, 2)); + createPort(c.nextPortId++, "primary input", 0, true, createPortMixExt(1, 1)); primaryInMix.profiles.push_back( createProfile(PcmType::INT_16_BIT, - {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO, - AudioChannelLayout::LAYOUT_FRONT_BACK}, - {8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000})); - primaryInMix.profiles.push_back( - createProfile(PcmType::INT_24_BIT, - {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO, - AudioChannelLayout::LAYOUT_FRONT_BACK}, - {8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000})); + {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO}, + {8000, 11025, 16000, 32000, 44100, 48000})); c.ports.push_back(primaryInMix); AudioPort telephonyTxOutMix = @@ -296,10 +267,10 @@ std::unique_ptr getPrimaryConfiguration() { standardPcmAudioProfiles.end()); c.ports.push_back(fmTunerInMix); - c.routes.push_back(createRoute({primaryOutMix, compressedOffloadOutMix}, speakerOutDevice)); + c.routes.push_back(createRoute({primaryOutMix}, speakerOutDevice)); c.routes.push_back(createRoute({micInDevice}, primaryInMix)); - c.routes.push_back(createRoute({telephonyTxOutMix}, telephonyTxOutDevice)); c.routes.push_back(createRoute({telephonyRxInDevice}, telephonyRxInMix)); + c.routes.push_back(createRoute({telephonyTxOutMix}, telephonyTxOutDevice)); c.routes.push_back(createRoute({fmTunerInDevice}, fmTunerInMix)); c.portConfigs.insert(c.portConfigs.end(), c.initialConfigs.begin(), c.initialConfigs.end()); @@ -320,15 +291,15 @@ std::unique_ptr getPrimaryConfiguration() { // // Device ports: // * "Remote Submix Out", OUT_SUBMIX -// - profile PCM 24-bit; STEREO; 48000 +// - profile PCM 16-bit; STEREO; 48000 // * "Remote Submix In", IN_SUBMIX -// - profile PCM 24-bit; STEREO; 48000 +// - profile PCM 16-bit; STEREO; 48000 // // Mix ports: -// * "r_submix output", stream count unlimited -// - profile PCM 24-bit; STEREO; 48000 -// * "r_submix input", stream count unlimited -// - profile PCM 24-bit; STEREO; 48000 +// * "r_submix output", 1 max open, 1 max active stream +// - profile PCM 16-bit; STEREO; 48000 +// * "r_submix input", 1 max open, 1 max active stream +// - profile PCM 16-bit; STEREO; 48000 // // Routes: // "r_submix output" -> "Remote Submix Out" @@ -345,27 +316,27 @@ std::unique_ptr getRSubmixConfiguration() { createDeviceExt(AudioDeviceType::OUT_SUBMIX, 0, AudioDeviceDescription::CONNECTION_VIRTUAL)); rsubmixOutDevice.profiles.push_back( - createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000})); + createProfile(PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000})); c.ports.push_back(rsubmixOutDevice); AudioPort rsubmixInDevice = createPort(c.nextPortId++, "Remote Submix In", 0, true, createDeviceExt(AudioDeviceType::IN_SUBMIX, 0)); rsubmixInDevice.profiles.push_back( - createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000})); + createProfile(PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000})); c.ports.push_back(rsubmixInDevice); // Mix ports AudioPort rsubmixOutMix = - createPort(c.nextPortId++, "r_submix output", 0, false, createPortMixExt(0, 0)); + createPort(c.nextPortId++, "r_submix output", 0, false, createPortMixExt(1, 1)); rsubmixOutMix.profiles.push_back( - createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000})); + createProfile(PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000})); c.ports.push_back(rsubmixOutMix); AudioPort rsubmixInMix = - createPort(c.nextPortId++, "r_submix input", 0, true, createPortMixExt(0, 0)); + createPort(c.nextPortId++, "r_submix input", 0, true, createPortMixExt(1, 1)); rsubmixInMix.profiles.push_back( - createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000})); + createProfile(PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000})); c.ports.push_back(rsubmixInMix); c.routes.push_back(createRoute({rsubmixOutMix}, rsubmixOutDevice)); @@ -486,7 +457,7 @@ std::unique_ptr getUsbConfiguration() { // - profile MP3; MONO, STEREO; 44100, 48000 // * "test input", 2 max open, 2 max active streams // - profile PCM 24-bit; MONO, STEREO, FRONT_BACK; -// 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +// 8000, 11025, 16000, 22050, 32000, 44100, 48000 // // Routes: // "test output", "test fast output", "test compressed offload" -> "Test Out" @@ -553,12 +524,12 @@ std::unique_ptr getStubConfiguration() { createProfile(PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO, AudioChannelLayout::LAYOUT_FRONT_BACK}, - {8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000})); + {8000, 11025, 16000, 22050, 32000, 44100, 48000})); testInMIx.profiles.push_back( createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO, AudioChannelLayout::LAYOUT_FRONT_BACK}, - {8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000})); + {8000, 11025, 16000, 22050, 32000, 44100, 48000})); c.ports.push_back(testInMIx); c.routes.push_back( @@ -577,7 +548,7 @@ std::unique_ptr getStubConfiguration() { // Device ports: // * "BT A2DP Out", OUT_DEVICE, CONNECTION_BT_A2DP // - profile PCM 16-bit; STEREO; 44100, 48000, 88200, 96000 -// * "BT A2DP Headphones", OUT_HEADSET, CONNECTION_BT_A2DP +// * "BT A2DP Headphones", OUT_HEADPHONE, CONNECTION_BT_A2DP // - profile PCM 16-bit; STEREO; 44100, 48000, 88200, 96000 // * "BT A2DP Speaker", OUT_SPEAKER, CONNECTION_BT_A2DP // - profile PCM 16-bit; STEREO; 44100, 48000, 88200, 96000 @@ -608,13 +579,18 @@ std::unique_ptr getBluetoothConfiguration() { createPort(c.nextPortId++, "BT A2DP Out", 0, false, createDeviceExt(AudioDeviceType::OUT_DEVICE, 0, AudioDeviceDescription::CONNECTION_BT_A2DP)); + btOutDevice.profiles.insert(btOutDevice.profiles.begin(), standardPcmAudioProfiles.begin(), + standardPcmAudioProfiles.end()); c.ports.push_back(btOutDevice); c.connectedProfiles[btOutDevice.id] = standardPcmAudioProfiles; AudioPort btOutHeadphone = createPort(c.nextPortId++, "BT A2DP Headphones", 0, false, - createDeviceExt(AudioDeviceType::OUT_HEADSET, 0, + createDeviceExt(AudioDeviceType::OUT_HEADPHONE, 0, AudioDeviceDescription::CONNECTION_BT_A2DP)); + btOutHeadphone.profiles.insert(btOutHeadphone.profiles.begin(), + standardPcmAudioProfiles.begin(), + standardPcmAudioProfiles.end()); c.ports.push_back(btOutHeadphone); c.connectedProfiles[btOutHeadphone.id] = standardPcmAudioProfiles; @@ -622,6 +598,9 @@ std::unique_ptr getBluetoothConfiguration() { createPort(c.nextPortId++, "BT A2DP Speaker", 0, false, createDeviceExt(AudioDeviceType::OUT_SPEAKER, 0, AudioDeviceDescription::CONNECTION_BT_A2DP)); + btOutSpeaker.profiles.insert(btOutSpeaker.profiles.begin(), + standardPcmAudioProfiles.begin(), + standardPcmAudioProfiles.end()); c.ports.push_back(btOutSpeaker); c.connectedProfiles[btOutSpeaker.id] = standardPcmAudioProfiles; @@ -634,20 +613,20 @@ std::unique_ptr getBluetoothConfiguration() { {createProfile(PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {16000})}); // Mix ports - AudioPort btInMix = - createPort(c.nextPortId++, "a2dp output", 0, true, createPortMixExt(1, 1)); - c.ports.push_back(btInMix); + AudioPort btOutMix = + createPort(c.nextPortId++, "a2dp output", 0, false, createPortMixExt(1, 1)); + c.ports.push_back(btOutMix); - AudioPort btHeadsetInMix = - createPort(c.nextPortId++, "hearing aid output", 0, true, createPortMixExt(1, 1)); - btHeadsetInMix.profiles.push_back(createProfile( + AudioPort btHearingOutMix = + createPort(c.nextPortId++, "hearing aid output", 0, false, createPortMixExt(1, 1)); + btHearingOutMix.profiles.push_back(createProfile( PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {16000, 24000})); - c.ports.push_back(btHeadsetInMix); + c.ports.push_back(btHearingOutMix); - c.routes.push_back(createRoute({btInMix}, btOutDevice)); - c.routes.push_back(createRoute({btInMix}, btOutHeadphone)); - c.routes.push_back(createRoute({btInMix}, btOutSpeaker)); - c.routes.push_back(createRoute({btHeadsetInMix}, btOutHearingAid)); + c.routes.push_back(createRoute({btOutMix}, btOutDevice)); + c.routes.push_back(createRoute({btOutMix}, btOutHeadphone)); + c.routes.push_back(createRoute({btOutMix}, btOutSpeaker)); + c.routes.push_back(createRoute({btHearingOutMix}, btOutHearingAid)); return c; }(); diff --git a/audio/aidl/default/ModulePrimary.cpp b/audio/aidl/default/ModulePrimary.cpp index 29e81264ac..9919c7f6cc 100644 --- a/audio/aidl/default/ModulePrimary.cpp +++ b/audio/aidl/default/ModulePrimary.cpp @@ -21,7 +21,7 @@ #include #include "core-impl/ModulePrimary.h" -#include "core-impl/StreamStub.h" +#include "core-impl/StreamPrimary.h" #include "core-impl/Telephony.h" using aidl::android::hardware::audio::common::SinkMetadata; @@ -47,15 +47,15 @@ ndk::ScopedAStatus ModulePrimary::createInputStream(StreamContext&& context, const SinkMetadata& sinkMetadata, const std::vector& microphones, std::shared_ptr* result) { - return createStreamInstance(result, std::move(context), sinkMetadata, - microphones); + return createStreamInstance(result, std::move(context), sinkMetadata, + microphones); } ndk::ScopedAStatus ModulePrimary::createOutputStream( StreamContext&& context, const SourceMetadata& sourceMetadata, const std::optional& offloadInfo, std::shared_ptr* result) { - return createStreamInstance(result, std::move(context), sourceMetadata, - offloadInfo); + return createStreamInstance(result, std::move(context), sourceMetadata, + offloadInfo); } } // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/Stream.cpp b/audio/aidl/default/Stream.cpp index 64d53b5bd8..af89f5fce8 100644 --- a/audio/aidl/default/Stream.cpp +++ b/audio/aidl/default/Stream.cpp @@ -242,8 +242,8 @@ StreamInWorkerLogic::Status StreamInWorkerLogic::cycle() { break; case Tag::standby: if (mState == StreamDescriptor::State::IDLE) { + populateReply(&reply, mIsConnected); if (::android::status_t status = mDriver->standby(); status == ::android::OK) { - populateReply(&reply, mIsConnected); mState = StreamDescriptor::State::STANDBY; } else { LOG(ERROR) << __func__ << ": standby failed: " << status; @@ -496,8 +496,8 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() { break; case Tag::standby: if (mState == StreamDescriptor::State::IDLE) { + populateReply(&reply, mIsConnected); if (::android::status_t status = mDriver->standby(); status == ::android::OK) { - populateReply(&reply, mIsConnected); mState = StreamDescriptor::State::STANDBY; } else { LOG(ERROR) << __func__ << ": standby failed: " << status; @@ -825,6 +825,32 @@ ndk::ScopedAStatus StreamIn::setHwGain(const std::vector& in_channelGains return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } +StreamInHwGainHelper::StreamInHwGainHelper(const StreamContext* context) + : mChannelCount(getChannelCount(context->getChannelLayout())) {} + +ndk::ScopedAStatus StreamInHwGainHelper::getHwGainImpl(std::vector* _aidl_return) { + *_aidl_return = mHwGains; + LOG(DEBUG) << __func__ << ": returning " << ::android::internal::ToString(*_aidl_return); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus StreamInHwGainHelper::setHwGainImpl(const std::vector& in_channelGains) { + LOG(DEBUG) << __func__ << ": gains " << ::android::internal::ToString(in_channelGains); + if (in_channelGains.size() != mChannelCount) { + LOG(ERROR) << __func__ + << ": channel count does not match stream channel count: " << mChannelCount; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + for (float gain : in_channelGains) { + if (gain < StreamIn::HW_GAIN_MIN || gain > StreamIn::HW_GAIN_MAX) { + LOG(ERROR) << __func__ << ": gain value out of range: " << gain; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + } + mHwGains = in_channelGains; + return ndk::ScopedAStatus::ok(); +} + StreamOut::StreamOut(StreamContext&& context, const std::optional& offloadInfo) : mContextInstance(std::move(context)), mOffloadInfo(offloadInfo) { LOG(DEBUG) << __func__; @@ -930,4 +956,31 @@ ndk::ScopedAStatus StreamOut::selectPresentation(int32_t in_presentationId, int3 return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } +StreamOutHwVolumeHelper::StreamOutHwVolumeHelper(const StreamContext* context) + : mChannelCount(getChannelCount(context->getChannelLayout())) {} + +ndk::ScopedAStatus StreamOutHwVolumeHelper::getHwVolumeImpl(std::vector* _aidl_return) { + *_aidl_return = mHwVolumes; + LOG(DEBUG) << __func__ << ": returning " << ::android::internal::ToString(*_aidl_return); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus StreamOutHwVolumeHelper::setHwVolumeImpl( + const std::vector& in_channelVolumes) { + LOG(DEBUG) << __func__ << ": volumes " << ::android::internal::ToString(in_channelVolumes); + if (in_channelVolumes.size() != mChannelCount) { + LOG(ERROR) << __func__ + << ": channel count does not match stream channel count: " << mChannelCount; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + for (float volume : in_channelVolumes) { + if (volume < StreamOut::HW_VOLUME_MIN || volume > StreamOut::HW_VOLUME_MAX) { + LOG(ERROR) << __func__ << ": volume value out of range: " << volume; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + } + mHwVolumes = in_channelVolumes; + return ndk::ScopedAStatus::ok(); +} + } // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/alsa/StreamAlsa.cpp b/audio/aidl/default/alsa/StreamAlsa.cpp index 1d22a607e5..0605d6f464 100644 --- a/audio/aidl/default/alsa/StreamAlsa.cpp +++ b/audio/aidl/default/alsa/StreamAlsa.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #include #define LOG_TAG "AHAL_StreamAlsa" @@ -29,7 +30,9 @@ namespace aidl::android::hardware::audio::core { StreamAlsa::StreamAlsa(StreamContext* context, const Metadata& metadata, int readWriteRetries) : StreamCommonImpl(context, metadata), + mBufferSizeFrames(getContext().getBufferSizeInFrames()), mFrameSizeBytes(getContext().getFrameSize()), + mSampleRate(getContext().getSampleRate()), mIsInput(isInput(metadata)), mConfig(alsa::getPcmConfig(getContext(), mIsInput)), mReadWriteRetries(readWriteRetries) {} @@ -39,17 +42,20 @@ StreamAlsa::StreamAlsa(StreamContext* context, const Metadata& metadata, int rea } ::android::status_t StreamAlsa::drain(StreamDescriptor::DrainMode) { - usleep(1000); + if (!mIsInput) { + static constexpr float kMicrosPerSecond = MICROS_PER_SECOND; + const size_t delayUs = static_cast( + std::roundf(mBufferSizeFrames * kMicrosPerSecond / mSampleRate)); + usleep(delayUs); + } return ::android::OK; } ::android::status_t StreamAlsa::flush() { - usleep(1000); return ::android::OK; } ::android::status_t StreamAlsa::pause() { - usleep(1000); return ::android::OK; } @@ -59,6 +65,10 @@ StreamAlsa::StreamAlsa(StreamContext* context, const Metadata& metadata, int rea } ::android::status_t StreamAlsa::start() { + if (!mAlsaDeviceProxies.empty()) { + // This is a resume after a pause. + return ::android::OK; + } decltype(mAlsaDeviceProxies) alsaDeviceProxies; for (const auto& device : getDeviceProfiles()) { alsa::DeviceProxy proxy; @@ -71,8 +81,7 @@ StreamAlsa::StreamAlsa(StreamContext* context, const Metadata& metadata, int rea true /*require_exact_match*/); } else { proxy = alsa::openProxyForAttachedDevice( - device, const_cast(&mConfig.value()), - getContext().getBufferSizeInFrames()); + device, const_cast(&mConfig.value()), mBufferSizeFrames); } if (!proxy) { return ::android::NO_INIT; @@ -85,13 +94,13 @@ StreamAlsa::StreamAlsa(StreamContext* context, const Metadata& metadata, int rea ::android::status_t StreamAlsa::transfer(void* buffer, size_t frameCount, size_t* actualFrameCount, int32_t* latencyMs) { + if (mAlsaDeviceProxies.empty()) { + LOG(FATAL) << __func__ << ": no opened devices"; + return ::android::NO_INIT; + } const size_t bytesToTransfer = frameCount * mFrameSizeBytes; unsigned maxLatency = 0; if (mIsInput) { - if (mAlsaDeviceProxies.empty()) { - LOG(FATAL) << __func__ << ": no input devices"; - return ::android::NO_INIT; - } // For input case, only support single device. proxy_read_with_retries(mAlsaDeviceProxies[0].get(), buffer, bytesToTransfer, mReadWriteRetries); @@ -110,9 +119,12 @@ StreamAlsa::StreamAlsa(StreamContext* context, const Metadata& metadata, int rea ::android::status_t StreamAlsa::refinePosition(StreamDescriptor::Position* position) { if (mAlsaDeviceProxies.empty()) { - LOG(FATAL) << __func__ << ": no input devices"; + LOG(FATAL) << __func__ << ": no opened devices"; return ::android::NO_INIT; } + // Since the proxy can only count transferred frames since its creation, + // we override its counter value with ours and let it to correct for buffered frames. + alsa::resetTransferredFrames(mAlsaDeviceProxies[0], position->frames); if (mIsInput) { if (int ret = proxy_get_capture_position(mAlsaDeviceProxies[0].get(), &position->frames, &position->timeNs); diff --git a/audio/aidl/default/alsa/Utils.cpp b/audio/aidl/default/alsa/Utils.cpp index 20f77978fd..9dcd024d1c 100644 --- a/audio/aidl/default/alsa/Utils.cpp +++ b/audio/aidl/default/alsa/Utils.cpp @@ -262,12 +262,14 @@ std::vector getSampleRatesFromProfile(const alsa_device_profile* profile) { } DeviceProxy makeDeviceProxy() { - return DeviceProxy(new alsa_device_proxy, [](alsa_device_proxy* proxy) { + DeviceProxy proxy(new alsa_device_proxy, [](alsa_device_proxy* proxy) { if (proxy != nullptr) { proxy_close(proxy); delete proxy; } }); + memset(proxy.get(), 0, sizeof(alsa_device_proxy)); + return proxy; } DeviceProxy openProxyForAttachedDevice(const DeviceProfile& deviceProfile, @@ -334,6 +336,12 @@ std::optional readAlsaDeviceInfo(const DeviceProfile& devic return profile; } +void resetTransferredFrames(DeviceProxy& proxy, uint64_t frames) { + if (proxy != nullptr) { + proxy->transferred = frames; + } +} + AudioFormatDescription c2aidl_pcm_format_AudioFormatDescription(enum pcm_format legacy) { return findValueOrDefault(getPcmFormatToAudioFormatDescMap(), legacy, AudioFormatDescription()); } diff --git a/audio/aidl/default/alsa/Utils.h b/audio/aidl/default/alsa/Utils.h index 615e657064..37414b3d75 100644 --- a/audio/aidl/default/alsa/Utils.h +++ b/audio/aidl/default/alsa/Utils.h @@ -66,6 +66,7 @@ DeviceProxy openProxyForAttachedDevice(const DeviceProfile& deviceProfile, DeviceProxy openProxyForExternalDevice(const DeviceProfile& deviceProfile, struct pcm_config* pcmConfig, bool requireExactMatch); std::optional readAlsaDeviceInfo(const DeviceProfile& deviceProfile); +void resetTransferredFrames(DeviceProxy& proxy, uint64_t frames); ::aidl::android::media::audio::common::AudioFormatDescription c2aidl_pcm_format_AudioFormatDescription(enum pcm_format legacy); diff --git a/audio/aidl/default/include/core-impl/Stream.h b/audio/aidl/default/include/core-impl/Stream.h index e74c3ba9ea..a02655f1b7 100644 --- a/audio/aidl/default/include/core-impl/Stream.h +++ b/audio/aidl/default/include/core-impl/Stream.h @@ -514,6 +514,17 @@ class StreamIn : virtual public StreamCommonInterface, public BnStreamIn { const std::map<::aidl::android::media::audio::common::AudioDevice, std::string> mMicrophones; }; +class StreamInHwGainHelper { + protected: + explicit StreamInHwGainHelper(const StreamContext* context); + + ndk::ScopedAStatus getHwGainImpl(std::vector* _aidl_return); + ndk::ScopedAStatus setHwGainImpl(const std::vector& in_channelGains); + + const size_t mChannelCount; + std::vector mHwGains; +}; + class StreamOut : virtual public StreamCommonInterface, public BnStreamOut { protected: void defaultOnClose(); @@ -560,6 +571,17 @@ class StreamOut : virtual public StreamCommonInterface, public BnStreamOut { std::optional<::aidl::android::hardware::audio::common::AudioOffloadMetadata> mOffloadMetadata; }; +class StreamOutHwVolumeHelper { + protected: + explicit StreamOutHwVolumeHelper(const StreamContext* context); + + ndk::ScopedAStatus getHwVolumeImpl(std::vector* _aidl_return); + ndk::ScopedAStatus setHwVolumeImpl(const std::vector& in_channelVolumes); + + const size_t mChannelCount; + std::vector mHwVolumes; +}; + // The recommended way to create a stream instance. // 'StreamImpl' is the concrete stream implementation, 'StreamInOrOut' is either 'StreamIn' or // 'StreamOut', the rest are the arguments forwarded to the constructor of 'StreamImpl'. diff --git a/audio/aidl/default/include/core-impl/StreamAlsa.h b/audio/aidl/default/include/core-impl/StreamAlsa.h index 555b27a772..2c3b284448 100644 --- a/audio/aidl/default/include/core-impl/StreamAlsa.h +++ b/audio/aidl/default/include/core-impl/StreamAlsa.h @@ -48,7 +48,9 @@ class StreamAlsa : public StreamCommonImpl { // Called from 'start' to initialize 'mAlsaDeviceProxies', the vector must be non-empty. virtual std::vector getDeviceProfiles() = 0; + const size_t mBufferSizeFrames; const size_t mFrameSizeBytes; + const int mSampleRate; const bool mIsInput; const std::optional mConfig; const int mReadWriteRetries; diff --git a/audio/aidl/default/include/core-impl/StreamPrimary.h b/audio/aidl/default/include/core-impl/StreamPrimary.h new file mode 100644 index 0000000000..b3ddd0bd53 --- /dev/null +++ b/audio/aidl/default/include/core-impl/StreamPrimary.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "StreamAlsa.h" +#include "StreamSwitcher.h" + +namespace aidl::android::hardware::audio::core { + +class StreamPrimary : public StreamAlsa { + public: + StreamPrimary(StreamContext* context, const Metadata& metadata); + + protected: + std::vector getDeviceProfiles() override; + + const bool mIsInput; +}; + +class StreamInPrimary final : public StreamIn, public StreamSwitcher, public StreamInHwGainHelper { + public: + friend class ndk::SharedRefBase; + StreamInPrimary( + StreamContext&& context, + const ::aidl::android::hardware::audio::common::SinkMetadata& sinkMetadata, + const std::vector<::aidl::android::media::audio::common::MicrophoneInfo>& microphones); + + private: + static bool useStubStream(const ::aidl::android::media::audio::common::AudioDevice& device); + + DeviceSwitchBehavior switchCurrentStream( + const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices) + override; + std::unique_ptr createNewStream( + const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices, + StreamContext* context, const Metadata& metadata) override; + void onClose(StreamDescriptor::State) override { defaultOnClose(); } + + ndk::ScopedAStatus getHwGain(std::vector* _aidl_return) override; + ndk::ScopedAStatus setHwGain(const std::vector& in_channelGains) override; +}; + +class StreamOutPrimary final : public StreamOut, + public StreamSwitcher, + public StreamOutHwVolumeHelper { + public: + friend class ndk::SharedRefBase; + StreamOutPrimary(StreamContext&& context, + const ::aidl::android::hardware::audio::common::SourceMetadata& sourceMetadata, + const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>& + offloadInfo); + + private: + static bool useStubStream(const ::aidl::android::media::audio::common::AudioDevice& device); + + DeviceSwitchBehavior switchCurrentStream( + const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices) + override; + std::unique_ptr createNewStream( + const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices, + StreamContext* context, const Metadata& metadata) override; + void onClose(StreamDescriptor::State) override { defaultOnClose(); } + + ndk::ScopedAStatus getHwVolume(std::vector* _aidl_return) override; + ndk::ScopedAStatus setHwVolume(const std::vector& in_channelVolumes) override; +}; + +} // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/include/core-impl/StreamStub.h b/audio/aidl/default/include/core-impl/StreamStub.h index a8a3fc4961..3857e0e75e 100644 --- a/audio/aidl/default/include/core-impl/StreamStub.h +++ b/audio/aidl/default/include/core-impl/StreamStub.h @@ -35,6 +35,7 @@ class StreamStub : public StreamCommonImpl { void shutdown() override; private: + const size_t mBufferSizeFrames; const size_t mFrameSizeBytes; const int mSampleRate; const bool mIsAsynchronous; diff --git a/audio/aidl/default/include/core-impl/StreamUsb.h b/audio/aidl/default/include/core-impl/StreamUsb.h index 74e30ff97b..608f27d410 100644 --- a/audio/aidl/default/include/core-impl/StreamUsb.h +++ b/audio/aidl/default/include/core-impl/StreamUsb.h @@ -59,7 +59,7 @@ class StreamInUsb final : public StreamIn, public StreamUsb { override; }; -class StreamOutUsb final : public StreamOut, public StreamUsb { +class StreamOutUsb final : public StreamOut, public StreamUsb, public StreamOutHwVolumeHelper { public: friend class ndk::SharedRefBase; StreamOutUsb(StreamContext&& context, @@ -71,9 +71,6 @@ class StreamOutUsb final : public StreamOut, public StreamUsb { void onClose(StreamDescriptor::State) override { defaultOnClose(); } ndk::ScopedAStatus getHwVolume(std::vector* _aidl_return) override; ndk::ScopedAStatus setHwVolume(const std::vector& in_channelVolumes) override; - - const int mChannelCount; - std::vector mHwVolumes; }; } // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/primary/PrimaryMixer.cpp b/audio/aidl/default/primary/PrimaryMixer.cpp new file mode 100644 index 0000000000..577d010f20 --- /dev/null +++ b/audio/aidl/default/primary/PrimaryMixer.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "AHAL_PrimaryMixer" + +#include "PrimaryMixer.h" + +namespace aidl::android::hardware::audio::core::primary { + +// static +PrimaryMixer& PrimaryMixer::getInstance() { + static PrimaryMixer gInstance; + return gInstance; +} + +} // namespace aidl::android::hardware::audio::core::primary diff --git a/audio/aidl/default/primary/PrimaryMixer.h b/audio/aidl/default/primary/PrimaryMixer.h new file mode 100644 index 0000000000..3806428cfd --- /dev/null +++ b/audio/aidl/default/primary/PrimaryMixer.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include "alsa/Mixer.h" + +namespace aidl::android::hardware::audio::core::primary { + +class PrimaryMixer : public alsa::Mixer { + public: + static constexpr int kAlsaCard = 0; + static constexpr int kAlsaDevice = 0; + + static PrimaryMixer& getInstance(); + + private: + PrimaryMixer() : alsa::Mixer(kAlsaCard) {} +}; + +} // namespace aidl::android::hardware::audio::core::primary diff --git a/audio/aidl/default/primary/StreamPrimary.cpp b/audio/aidl/default/primary/StreamPrimary.cpp new file mode 100644 index 0000000000..e01be8a3c6 --- /dev/null +++ b/audio/aidl/default/primary/StreamPrimary.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#define LOG_TAG "AHAL_StreamPrimary" +#include +#include +#include + +#include "PrimaryMixer.h" +#include "core-impl/StreamPrimary.h" +#include "core-impl/StreamStub.h" + +using aidl::android::hardware::audio::common::SinkMetadata; +using aidl::android::hardware::audio::common::SourceMetadata; +using aidl::android::media::audio::common::AudioDevice; +using aidl::android::media::audio::common::AudioDeviceDescription; +using aidl::android::media::audio::common::AudioDeviceType; +using aidl::android::media::audio::common::AudioOffloadInfo; +using aidl::android::media::audio::common::MicrophoneInfo; +using android::base::GetBoolProperty; + +namespace aidl::android::hardware::audio::core { + +StreamPrimary::StreamPrimary(StreamContext* context, const Metadata& metadata) + : StreamAlsa(context, metadata, 3 /*readWriteRetries*/), mIsInput(isInput(metadata)) {} + +std::vector StreamPrimary::getDeviceProfiles() { + static const std::vector kBuiltInSource{ + alsa::DeviceProfile{.card = primary::PrimaryMixer::kAlsaCard, + .device = primary::PrimaryMixer::kAlsaDevice, + .direction = PCM_IN, + .isExternal = false}}; + static const std::vector kBuiltInSink{ + alsa::DeviceProfile{.card = primary::PrimaryMixer::kAlsaCard, + .device = primary::PrimaryMixer::kAlsaDevice, + .direction = PCM_OUT, + .isExternal = false}}; + return mIsInput ? kBuiltInSource : kBuiltInSink; +} + +StreamInPrimary::StreamInPrimary(StreamContext&& context, const SinkMetadata& sinkMetadata, + const std::vector& microphones) + : StreamIn(std::move(context), microphones), + StreamSwitcher(&mContextInstance, sinkMetadata), + StreamInHwGainHelper(&mContextInstance) {} + +bool StreamInPrimary::useStubStream(const AudioDevice& device) { + static const bool kSimulateInput = + GetBoolProperty("ro.boot.audio.tinyalsa.simulate_input", false); + return kSimulateInput || device.type.type == AudioDeviceType::IN_TELEPHONY_RX || + device.type.type == AudioDeviceType::IN_FM_TUNER || + device.type.connection == AudioDeviceDescription::CONNECTION_BUS; +} + +StreamSwitcher::DeviceSwitchBehavior StreamInPrimary::switchCurrentStream( + const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices) { + LOG(DEBUG) << __func__; + if (devices.size() > 1) { + LOG(ERROR) << __func__ << ": primary stream can only be connected to one device, got: " + << devices.size(); + return DeviceSwitchBehavior::UNSUPPORTED_DEVICES; + } + if (devices.empty() || useStubStream(devices[0]) == isStubStream()) { + return DeviceSwitchBehavior::USE_CURRENT_STREAM; + } + return DeviceSwitchBehavior::CREATE_NEW_STREAM; +} + +std::unique_ptr StreamInPrimary::createNewStream( + const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices, + StreamContext* context, const Metadata& metadata) { + if (devices.empty()) { + LOG(FATAL) << __func__ << ": called with empty devices"; // see 'switchCurrentStream' + } + if (useStubStream(devices[0])) { + return std::unique_ptr( + new InnerStreamWrapper(context, metadata)); + } + return std::unique_ptr( + new InnerStreamWrapper(context, metadata)); +} + +ndk::ScopedAStatus StreamInPrimary::getHwGain(std::vector* _aidl_return) { + if (isStubStream()) { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + return getHwGainImpl(_aidl_return); +} + +ndk::ScopedAStatus StreamInPrimary::setHwGain(const std::vector& in_channelGains) { + if (isStubStream()) { + LOG(DEBUG) << __func__ << ": gains " << ::android::internal::ToString(in_channelGains); + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + auto currentGains = mHwGains; + RETURN_STATUS_IF_ERROR(setHwGainImpl(in_channelGains)); + if (in_channelGains.size() < 1) { + LOG(FATAL) << __func__ << ": unexpected gain vector size: " << in_channelGains.size(); + } + if (auto status = primary::PrimaryMixer::getInstance().setMicGain(in_channelGains[0]); + !status.isOk()) { + mHwGains = currentGains; + return status; + } + return ndk::ScopedAStatus::ok(); +} + +StreamOutPrimary::StreamOutPrimary(StreamContext&& context, const SourceMetadata& sourceMetadata, + const std::optional& offloadInfo) + : StreamOut(std::move(context), offloadInfo), + StreamSwitcher(&mContextInstance, sourceMetadata), + StreamOutHwVolumeHelper(&mContextInstance) {} + +bool StreamOutPrimary::useStubStream(const AudioDevice& device) { + static const bool kSimulateOutput = + GetBoolProperty("ro.boot.audio.tinyalsa.ignore_output", false); + return kSimulateOutput || device.type.type == AudioDeviceType::OUT_TELEPHONY_TX || + device.type.connection == AudioDeviceDescription::CONNECTION_BUS; +} + +StreamSwitcher::DeviceSwitchBehavior StreamOutPrimary::switchCurrentStream( + const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices) { + LOG(DEBUG) << __func__; + if (devices.size() > 1) { + LOG(ERROR) << __func__ << ": primary stream can only be connected to one device, got: " + << devices.size(); + return DeviceSwitchBehavior::UNSUPPORTED_DEVICES; + } + if (devices.empty() || useStubStream(devices[0]) == isStubStream()) { + return DeviceSwitchBehavior::USE_CURRENT_STREAM; + } + return DeviceSwitchBehavior::CREATE_NEW_STREAM; +} + +std::unique_ptr StreamOutPrimary::createNewStream( + const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices, + StreamContext* context, const Metadata& metadata) { + if (devices.empty()) { + LOG(FATAL) << __func__ << ": called with empty devices"; // see 'switchCurrentStream' + } + if (useStubStream(devices[0])) { + return std::unique_ptr( + new InnerStreamWrapper(context, metadata)); + } + return std::unique_ptr( + new InnerStreamWrapper(context, metadata)); +} + +ndk::ScopedAStatus StreamOutPrimary::getHwVolume(std::vector* _aidl_return) { + if (isStubStream()) { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + return getHwVolumeImpl(_aidl_return); +} + +ndk::ScopedAStatus StreamOutPrimary::setHwVolume(const std::vector& in_channelVolumes) { + if (isStubStream()) { + LOG(DEBUG) << __func__ << ": volumes " << ::android::internal::ToString(in_channelVolumes); + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + auto currentVolumes = mHwVolumes; + RETURN_STATUS_IF_ERROR(setHwVolumeImpl(in_channelVolumes)); + if (auto status = primary::PrimaryMixer::getInstance().setVolumes(in_channelVolumes); + !status.isOk()) { + mHwVolumes = currentVolumes; + return status; + } + return ndk::ScopedAStatus::ok(); +} + +} // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/stub/StreamStub.cpp b/audio/aidl/default/stub/StreamStub.cpp index 9b6a759c65..660a51e2e0 100644 --- a/audio/aidl/default/stub/StreamStub.cpp +++ b/audio/aidl/default/stub/StreamStub.cpp @@ -33,6 +33,7 @@ namespace aidl::android::hardware::audio::core { StreamStub::StreamStub(StreamContext* context, const Metadata& metadata) : StreamCommonImpl(context, metadata), + mBufferSizeFrames(getContext().getBufferSizeInFrames()), mFrameSizeBytes(getContext().getFrameSize()), mSampleRate(getContext().getSampleRate()), mIsAsynchronous(!!getContext().getAsyncCallback()), @@ -40,7 +41,6 @@ StreamStub::StreamStub(StreamContext* context, const Metadata& metadata) ::android::status_t StreamStub::init() { mIsInitialized = true; - usleep(500); return ::android::OK; } @@ -48,7 +48,16 @@ StreamStub::StreamStub(StreamContext* context, const Metadata& metadata) if (!mIsInitialized) { LOG(FATAL) << __func__ << ": must not happen for an uninitialized driver"; } - usleep(500); + if (!mIsInput) { + if (!mIsAsynchronous) { + static constexpr float kMicrosPerSecond = MICROS_PER_SECOND; + const size_t delayUs = static_cast( + std::roundf(mBufferSizeFrames * kMicrosPerSecond / mSampleRate)); + usleep(delayUs); + } else { + usleep(500); + } + } return ::android::OK; } @@ -56,7 +65,6 @@ StreamStub::StreamStub(StreamContext* context, const Metadata& metadata) if (!mIsInitialized) { LOG(FATAL) << __func__ << ": must not happen for an uninitialized driver"; } - usleep(500); return ::android::OK; } @@ -64,7 +72,6 @@ StreamStub::StreamStub(StreamContext* context, const Metadata& metadata) if (!mIsInitialized) { LOG(FATAL) << __func__ << ": must not happen for an uninitialized driver"; } - usleep(500); return ::android::OK; } diff --git a/audio/aidl/default/usb/StreamUsb.cpp b/audio/aidl/default/usb/StreamUsb.cpp index 4efe0d8b42..b60b4fd020 100644 --- a/audio/aidl/default/usb/StreamUsb.cpp +++ b/audio/aidl/default/usb/StreamUsb.cpp @@ -18,14 +18,11 @@ #define LOG_TAG "AHAL_StreamUsb" #include - -#include #include #include "UsbAlsaMixerControl.h" #include "core-impl/StreamUsb.h" -using aidl::android::hardware::audio::common::getChannelCount; using aidl::android::hardware::audio::common::SinkMetadata; using aidl::android::hardware::audio::common::SourceMetadata; using aidl::android::media::audio::common::AudioDevice; @@ -97,14 +94,15 @@ StreamOutUsb::StreamOutUsb(StreamContext&& context, const SourceMetadata& source const std::optional& offloadInfo) : StreamOut(std::move(context), offloadInfo), StreamUsb(&mContextInstance, sourceMetadata), - mChannelCount(getChannelCount(getContext().getChannelLayout())) {} + StreamOutHwVolumeHelper(&mContextInstance) {} ndk::ScopedAStatus StreamOutUsb::getHwVolume(std::vector* _aidl_return) { - *_aidl_return = mHwVolumes; - return ndk::ScopedAStatus::ok(); + return getHwVolumeImpl(_aidl_return); } ndk::ScopedAStatus StreamOutUsb::setHwVolume(const std::vector& in_channelVolumes) { + auto currentVolumes = mHwVolumes; + RETURN_STATUS_IF_ERROR(setHwVolumeImpl(in_channelVolumes)); // Avoid using mConnectedDeviceProfiles because it requires a lock. for (const auto& device : getConnectedDevices()) { if (auto deviceProfile = alsa::getDeviceProfile(device, mIsInput); @@ -114,11 +112,11 @@ ndk::ScopedAStatus StreamOutUsb::setHwVolume(const std::vector& in_channe !result.isOk()) { LOG(ERROR) << __func__ << ": failed to set volume for device address=" << *deviceProfile; + mHwVolumes = currentVolumes; return result; } } } - mHwVolumes = in_channelVolumes; return ndk::ScopedAStatus::ok(); } diff --git a/audio/aidl/vts/Android.bp b/audio/aidl/vts/Android.bp index 852255d433..f7cf4cece3 100644 --- a/audio/aidl/vts/Android.bp +++ b/audio/aidl/vts/Android.bp @@ -55,6 +55,7 @@ cc_test { "VtsHalAudioCoreConfigTargetTest.cpp", "VtsHalAudioCoreModuleTargetTest.cpp", ], + test_config: "VtsHalAudioCoreTargetTest.xml", } cc_test { diff --git a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp index a2e2ef7606..6ad130e728 100644 --- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp +++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp @@ -3189,10 +3189,17 @@ class StreamLogicDefaultDriver : public StreamLogicDriver { std::string mUnexpectedTransition; }; -enum { NAMED_CMD_NAME, NAMED_CMD_DELAY_MS, NAMED_CMD_STREAM_TYPE, NAMED_CMD_CMDS }; +enum { + NAMED_CMD_NAME, + NAMED_CMD_DELAY_MS, + NAMED_CMD_STREAM_TYPE, + NAMED_CMD_CMDS, + NAMED_CMD_VALIDATE_POS_INCREASE +}; enum class StreamTypeFilter { ANY, SYNC, ASYNC }; using NamedCommandSequence = - std::tuple>; + std::tuple, bool /*validatePositionIncrease*/>; enum { PARAM_MODULE_NAME, PARAM_CMD_SEQ, PARAM_SETUP_SEQ }; using StreamIoTestParameters = std::tuple; @@ -3236,10 +3243,14 @@ class AudioStreamIo : public AudioCoreModuleBase, ASSERT_NO_FATAL_FAILURE(delayTransientStates.SetUp(module.get())); const auto& commandsAndStates = std::get(std::get(GetParam())); + const bool validatePositionIncrease = + std::get(std::get(GetParam())); if (!std::get(GetParam())) { - ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq1(portConfig, commandsAndStates)); + ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq1(portConfig, commandsAndStates, + validatePositionIncrease)); } else { - ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq2(portConfig, commandsAndStates)); + ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq2(portConfig, commandsAndStates, + validatePositionIncrease)); } if (isNonBlocking) { // Also try running the same sequence with "aosp.forceTransientBurst" set. @@ -3250,11 +3261,11 @@ class AudioStreamIo : public AudioCoreModuleBase, if (forceTransientBurst.SetUpNoChecks(module.get(), true /*failureExpected*/) .isOk()) { if (!std::get(GetParam())) { - ASSERT_NO_FATAL_FAILURE( - RunStreamIoCommandsImplSeq1(portConfig, commandsAndStates)); + ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq1( + portConfig, commandsAndStates, validatePositionIncrease)); } else { - ASSERT_NO_FATAL_FAILURE( - RunStreamIoCommandsImplSeq2(portConfig, commandsAndStates)); + ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq2( + portConfig, commandsAndStates, validatePositionIncrease)); } } } else if (!IOTraits::is_input) { @@ -3267,11 +3278,11 @@ class AudioStreamIo : public AudioCoreModuleBase, if (forceSynchronousDrain.SetUpNoChecks(module.get(), true /*failureExpected*/) .isOk()) { if (!std::get(GetParam())) { - ASSERT_NO_FATAL_FAILURE( - RunStreamIoCommandsImplSeq1(portConfig, commandsAndStates)); + ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq1( + portConfig, commandsAndStates, validatePositionIncrease)); } else { - ASSERT_NO_FATAL_FAILURE( - RunStreamIoCommandsImplSeq2(portConfig, commandsAndStates)); + ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq2( + portConfig, commandsAndStates, validatePositionIncrease)); } } } @@ -3285,11 +3296,13 @@ class AudioStreamIo : public AudioCoreModuleBase, // Set up a patch first, then open a stream. void RunStreamIoCommandsImplSeq1(const AudioPortConfig& portConfig, - std::shared_ptr commandsAndStates) { + std::shared_ptr commandsAndStates, + bool validatePositionIncrease) { auto devicePorts = moduleConfig->getAttachedDevicesPortsForMixPort( IOTraits::is_input, portConfig); ASSERT_FALSE(devicePorts.empty()); auto devicePortConfig = moduleConfig->getSingleConfigForDevicePort(devicePorts[0]); + SCOPED_TRACE(devicePortConfig.toString()); WithAudioPatch patch(IOTraits::is_input, portConfig, devicePortConfig); ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get())); @@ -3307,14 +3320,17 @@ class AudioStreamIo : public AudioCoreModuleBase, EXPECT_FALSE(worker.hasError()) << worker.getError(); EXPECT_EQ("", driver.getUnexpectedStateTransition()); if (ValidateObservablePosition(devicePortConfig)) { - EXPECT_TRUE(driver.hasObservablePositionIncrease()); + if (validatePositionIncrease) { + EXPECT_TRUE(driver.hasObservablePositionIncrease()); + } EXPECT_FALSE(driver.hasRetrogradeObservablePosition()); } } // Open a stream, then set up a patch for it. void RunStreamIoCommandsImplSeq2(const AudioPortConfig& portConfig, - std::shared_ptr commandsAndStates) { + std::shared_ptr commandsAndStates, + bool validatePositionIncrease) { WithStream stream(portConfig); ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames)); StreamLogicDefaultDriver driver(commandsAndStates, @@ -3326,6 +3342,7 @@ class AudioStreamIo : public AudioCoreModuleBase, IOTraits::is_input, portConfig); ASSERT_FALSE(devicePorts.empty()); auto devicePortConfig = moduleConfig->getSingleConfigForDevicePort(devicePorts[0]); + SCOPED_TRACE(devicePortConfig.toString()); WithAudioPatch patch(IOTraits::is_input, stream.getPortConfig(), devicePortConfig); ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get())); @@ -3336,7 +3353,9 @@ class AudioStreamIo : public AudioCoreModuleBase, EXPECT_FALSE(worker.hasError()) << worker.getError(); EXPECT_EQ("", driver.getUnexpectedStateTransition()); if (ValidateObservablePosition(devicePortConfig)) { - EXPECT_TRUE(driver.hasObservablePositionIncrease()); + if (validatePositionIncrease) { + EXPECT_TRUE(driver.hasObservablePositionIncrease()); + } EXPECT_FALSE(driver.hasRetrogradeObservablePosition()); } } @@ -3668,22 +3687,28 @@ std::shared_ptr makeBurstCommands(bool isSync) { using State = StreamDescriptor::State; auto d = std::make_unique(); StateDag::Node last = d->makeFinalNode(State::ACTIVE); - StateDag::Node active = d->makeNode(State::ACTIVE, kBurstCommand, last); + // Use a couple of bursts to ensure that the driver starts reporting the position. + StateDag::Node active2 = d->makeNode(State::ACTIVE, kBurstCommand, last); + StateDag::Node active = d->makeNode(State::ACTIVE, kBurstCommand, active2); StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active); if (!isSync) { // Allow optional routing via the TRANSFERRING state on bursts. - active.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, last)); + active2.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, last)); + active.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, active2)); idle.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, active)); } d->makeNode(State::STANDBY, kStartCommand, idle); return std::make_shared(std::move(d)); } static const NamedCommandSequence kReadSeq = - std::make_tuple(std::string("Read"), 0, StreamTypeFilter::ANY, makeBurstCommands(true)); + std::make_tuple(std::string("Read"), 0, StreamTypeFilter::ANY, makeBurstCommands(true), + true /*validatePositionIncrease*/); static const NamedCommandSequence kWriteSyncSeq = - std::make_tuple(std::string("Write"), 0, StreamTypeFilter::SYNC, makeBurstCommands(true)); + std::make_tuple(std::string("Write"), 0, StreamTypeFilter::SYNC, makeBurstCommands(true), + true /*validatePositionIncrease*/); static const NamedCommandSequence kWriteAsyncSeq = - std::make_tuple(std::string("Write"), 0, StreamTypeFilter::ASYNC, makeBurstCommands(false)); + std::make_tuple(std::string("Write"), 0, StreamTypeFilter::ASYNC, makeBurstCommands(false), + true /*validatePositionIncrease*/); std::shared_ptr makeAsyncDrainCommands(bool isInput) { using State = StreamDescriptor::State; @@ -3711,11 +3736,12 @@ std::shared_ptr makeAsyncDrainCommands(bool isInput) { } return std::make_shared(std::move(d)); } -static const NamedCommandSequence kWriteDrainAsyncSeq = - std::make_tuple(std::string("WriteDrain"), kStreamTransientStateTransitionDelayMs, - StreamTypeFilter::ASYNC, makeAsyncDrainCommands(false)); -static const NamedCommandSequence kDrainInSeq = std::make_tuple( - std::string("Drain"), 0, StreamTypeFilter::ANY, makeAsyncDrainCommands(true)); +static const NamedCommandSequence kWriteDrainAsyncSeq = std::make_tuple( + std::string("WriteDrain"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC, + makeAsyncDrainCommands(false), false /*validatePositionIncrease*/); +static const NamedCommandSequence kDrainInSeq = + std::make_tuple(std::string("Drain"), 0, StreamTypeFilter::ANY, + makeAsyncDrainCommands(true), false /*validatePositionIncrease*/); std::shared_ptr makeDrainOutCommands(bool isSync) { using State = StreamDescriptor::State; @@ -3735,10 +3761,12 @@ std::shared_ptr makeDrainOutCommands(bool isSync) { d->makeNode(State::STANDBY, kStartCommand, idle); return std::make_shared(std::move(d)); } -static const NamedCommandSequence kDrainOutSyncSeq = std::make_tuple( - std::string("Drain"), 0, StreamTypeFilter::SYNC, makeDrainOutCommands(true)); -static const NamedCommandSequence kDrainOutAsyncSeq = std::make_tuple( - std::string("Drain"), 0, StreamTypeFilter::ASYNC, makeDrainOutCommands(false)); +static const NamedCommandSequence kDrainOutSyncSeq = + std::make_tuple(std::string("Drain"), 0, StreamTypeFilter::SYNC, makeDrainOutCommands(true), + false /*validatePositionIncrease*/); +static const NamedCommandSequence kDrainOutAsyncSeq = + std::make_tuple(std::string("Drain"), 0, StreamTypeFilter::ASYNC, + makeDrainOutCommands(false), false /*validatePositionIncrease*/); std::shared_ptr makeDrainPauseOutCommands(bool isSync) { using State = StreamDescriptor::State; @@ -3759,12 +3787,12 @@ std::shared_ptr makeDrainPauseOutCommands(bool isSync) { d->makeNode(State::STANDBY, kStartCommand, idle); return std::make_shared(std::move(d)); } -static const NamedCommandSequence kDrainPauseOutSyncSeq = - std::make_tuple(std::string("DrainPause"), kStreamTransientStateTransitionDelayMs, - StreamTypeFilter::SYNC, makeDrainPauseOutCommands(true)); -static const NamedCommandSequence kDrainPauseOutAsyncSeq = - std::make_tuple(std::string("DrainPause"), kStreamTransientStateTransitionDelayMs, - StreamTypeFilter::ASYNC, makeDrainPauseOutCommands(false)); +static const NamedCommandSequence kDrainPauseOutSyncSeq = std::make_tuple( + std::string("DrainPause"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::SYNC, + makeDrainPauseOutCommands(true), false /*validatePositionIncrease*/); +static const NamedCommandSequence kDrainPauseOutAsyncSeq = std::make_tuple( + std::string("DrainPause"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC, + makeDrainPauseOutCommands(false), false /*validatePositionIncrease*/); // This sequence also verifies that the capture / presentation position is not reset on standby. std::shared_ptr makeStandbyCommands(bool isInput, bool isSync) { @@ -3805,13 +3833,15 @@ std::shared_ptr makeStandbyCommands(bool isInput, bool isSync) { } return std::make_shared(std::move(d)); } -static const NamedCommandSequence kStandbyInSeq = std::make_tuple( - std::string("Standby"), 0, StreamTypeFilter::ANY, makeStandbyCommands(true, false)); -static const NamedCommandSequence kStandbyOutSyncSeq = std::make_tuple( - std::string("Standby"), 0, StreamTypeFilter::SYNC, makeStandbyCommands(false, true)); -static const NamedCommandSequence kStandbyOutAsyncSeq = - std::make_tuple(std::string("Standby"), kStreamTransientStateTransitionDelayMs, - StreamTypeFilter::ASYNC, makeStandbyCommands(false, false)); +static const NamedCommandSequence kStandbyInSeq = + std::make_tuple(std::string("Standby"), 0, StreamTypeFilter::ANY, + makeStandbyCommands(true, false), false /*validatePositionIncrease*/); +static const NamedCommandSequence kStandbyOutSyncSeq = + std::make_tuple(std::string("Standby"), 0, StreamTypeFilter::SYNC, + makeStandbyCommands(false, true), false /*validatePositionIncrease*/); +static const NamedCommandSequence kStandbyOutAsyncSeq = std::make_tuple( + std::string("Standby"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC, + makeStandbyCommands(false, false), false /*validatePositionIncrease*/); std::shared_ptr makePauseCommands(bool isInput, bool isSync) { using State = StreamDescriptor::State; @@ -3846,13 +3876,15 @@ std::shared_ptr makePauseCommands(bool isInput, bool isSync) { } return std::make_shared(std::move(d)); } -static const NamedCommandSequence kPauseInSeq = std::make_tuple( - std::string("Pause"), 0, StreamTypeFilter::ANY, makePauseCommands(true, false)); -static const NamedCommandSequence kPauseOutSyncSeq = std::make_tuple( - std::string("Pause"), 0, StreamTypeFilter::SYNC, makePauseCommands(false, true)); -static const NamedCommandSequence kPauseOutAsyncSeq = - std::make_tuple(std::string("Pause"), kStreamTransientStateTransitionDelayMs, - StreamTypeFilter::ASYNC, makePauseCommands(false, false)); +static const NamedCommandSequence kPauseInSeq = + std::make_tuple(std::string("Pause"), 0, StreamTypeFilter::ANY, + makePauseCommands(true, false), false /*validatePositionIncrease*/); +static const NamedCommandSequence kPauseOutSyncSeq = + std::make_tuple(std::string("Pause"), 0, StreamTypeFilter::SYNC, + makePauseCommands(false, true), false /*validatePositionIncrease*/); +static const NamedCommandSequence kPauseOutAsyncSeq = std::make_tuple( + std::string("Pause"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC, + makePauseCommands(false, false), false /*validatePositionIncrease*/); std::shared_ptr makeFlushCommands(bool isInput, bool isSync) { using State = StreamDescriptor::State; @@ -3879,13 +3911,15 @@ std::shared_ptr makeFlushCommands(bool isInput, bool isSync) { } return std::make_shared(std::move(d)); } -static const NamedCommandSequence kFlushInSeq = std::make_tuple( - std::string("Flush"), 0, StreamTypeFilter::ANY, makeFlushCommands(true, false)); -static const NamedCommandSequence kFlushOutSyncSeq = std::make_tuple( - std::string("Flush"), 0, StreamTypeFilter::SYNC, makeFlushCommands(false, true)); -static const NamedCommandSequence kFlushOutAsyncSeq = - std::make_tuple(std::string("Flush"), kStreamTransientStateTransitionDelayMs, - StreamTypeFilter::ASYNC, makeFlushCommands(false, false)); +static const NamedCommandSequence kFlushInSeq = + std::make_tuple(std::string("Flush"), 0, StreamTypeFilter::ANY, + makeFlushCommands(true, false), false /*validatePositionIncrease*/); +static const NamedCommandSequence kFlushOutSyncSeq = + std::make_tuple(std::string("Flush"), 0, StreamTypeFilter::SYNC, + makeFlushCommands(false, true), false /*validatePositionIncrease*/); +static const NamedCommandSequence kFlushOutAsyncSeq = std::make_tuple( + std::string("Flush"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC, + makeFlushCommands(false, false), false /*validatePositionIncrease*/); std::shared_ptr makeDrainPauseFlushOutCommands(bool isSync) { using State = StreamDescriptor::State; @@ -3906,10 +3940,12 @@ std::shared_ptr makeDrainPauseFlushOutCommands(bool isSync) { } static const NamedCommandSequence kDrainPauseFlushOutSyncSeq = std::make_tuple(std::string("DrainPauseFlush"), kStreamTransientStateTransitionDelayMs, - StreamTypeFilter::SYNC, makeDrainPauseFlushOutCommands(true)); + StreamTypeFilter::SYNC, makeDrainPauseFlushOutCommands(true), + false /*validatePositionIncrease*/); static const NamedCommandSequence kDrainPauseFlushOutAsyncSeq = std::make_tuple(std::string("DrainPauseFlush"), kStreamTransientStateTransitionDelayMs, - StreamTypeFilter::ASYNC, makeDrainPauseFlushOutCommands(false)); + StreamTypeFilter::ASYNC, makeDrainPauseFlushOutCommands(false), + false /*validatePositionIncrease*/); // Note, this isn't the "official" enum printer, it is only used to make the test name suffix. std::string PrintStreamFilterToString(StreamTypeFilter filter) { diff --git a/audio/aidl/vts/VtsHalAudioCoreTargetTest.xml b/audio/aidl/vts/VtsHalAudioCoreTargetTest.xml index dfc10397f7..9d3adc1d0f 100644 --- a/audio/aidl/vts/VtsHalAudioCoreTargetTest.xml +++ b/audio/aidl/vts/VtsHalAudioCoreTargetTest.xml @@ -25,6 +25,11 @@