| /* |
| * 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_StreamPrimary" |
| |
| #include <cstdio> |
| |
| #include <android-base/logging.h> |
| #include <android-base/parseint.h> |
| #include <android-base/properties.h> |
| #include <audio_utils/clock.h> |
| #include <error/Result.h> |
| #include <error/expected_utils.h> |
| |
| #include "core-impl/StreamPrimary.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::AudioDeviceAddress; |
| 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*/), |
| mIsAsynchronous(!!getContext().getAsyncCallback()), |
| mStubDriver(getContext()) { |
| context->startStreamDataProcessor(); |
| } |
| |
| ::android::status_t StreamPrimary::init() { |
| RETURN_STATUS_IF_ERROR(mStubDriver.init()); |
| return StreamAlsa::init(); |
| } |
| |
| ::android::status_t StreamPrimary::drain(StreamDescriptor::DrainMode mode) { |
| return isStubStreamOnWorker() ? mStubDriver.drain(mode) : StreamAlsa::drain(mode); |
| } |
| |
| ::android::status_t StreamPrimary::flush() { |
| return isStubStreamOnWorker() ? mStubDriver.flush() : StreamAlsa::flush(); |
| } |
| |
| ::android::status_t StreamPrimary::pause() { |
| return isStubStreamOnWorker() ? mStubDriver.pause() : StreamAlsa::pause(); |
| } |
| |
| ::android::status_t StreamPrimary::standby() { |
| return isStubStreamOnWorker() ? mStubDriver.standby() : StreamAlsa::standby(); |
| } |
| |
| ::android::status_t StreamPrimary::start() { |
| bool isStub = true, shutdownAlsaStream = false; |
| { |
| std::lock_guard l(mLock); |
| isStub = mAlsaDeviceId == kStubDeviceId; |
| shutdownAlsaStream = |
| mCurrAlsaDeviceId != mAlsaDeviceId && mCurrAlsaDeviceId != kStubDeviceId; |
| mCurrAlsaDeviceId = mAlsaDeviceId; |
| } |
| if (shutdownAlsaStream) { |
| StreamAlsa::shutdown(); // Close currently opened ALSA devices. |
| } |
| if (isStub) { |
| return mStubDriver.start(); |
| } |
| RETURN_STATUS_IF_ERROR(StreamAlsa::start()); |
| mStartTimeNs = ::android::uptimeNanos(); |
| mFramesSinceStart = 0; |
| mSkipNextTransfer = false; |
| return ::android::OK; |
| } |
| |
| ::android::status_t StreamPrimary::transfer(void* buffer, size_t frameCount, |
| size_t* actualFrameCount, int32_t* latencyMs) { |
| if (isStubStreamOnWorker()) { |
| return mStubDriver.transfer(buffer, frameCount, actualFrameCount, latencyMs); |
| } |
| // This is a workaround for the emulator implementation which has a host-side buffer |
| // and is not being able to achieve real-time behavior similar to ADSPs (b/302587331). |
| if (!mSkipNextTransfer) { |
| RETURN_STATUS_IF_ERROR( |
| StreamAlsa::transfer(buffer, frameCount, actualFrameCount, latencyMs)); |
| } else { |
| LOG(DEBUG) << __func__ << ": skipping transfer (" << frameCount << " frames)"; |
| *actualFrameCount = frameCount; |
| if (mIsInput) memset(buffer, 0, frameCount * mFrameSizeBytes); |
| mSkipNextTransfer = false; |
| } |
| if (!mIsAsynchronous) { |
| const long bufferDurationUs = |
| (*actualFrameCount) * MICROS_PER_SECOND / mContext.getSampleRate(); |
| const auto totalDurationUs = |
| (::android::uptimeNanos() - mStartTimeNs) / NANOS_PER_MICROSECOND; |
| mFramesSinceStart += *actualFrameCount; |
| const long totalOffsetUs = |
| mFramesSinceStart * MICROS_PER_SECOND / mContext.getSampleRate() - totalDurationUs; |
| LOG(VERBOSE) << __func__ << ": totalOffsetUs " << totalOffsetUs; |
| if (totalOffsetUs > 0) { |
| const long sleepTimeUs = std::min(totalOffsetUs, bufferDurationUs); |
| LOG(VERBOSE) << __func__ << ": sleeping for " << sleepTimeUs << " us"; |
| usleep(sleepTimeUs); |
| } else { |
| mSkipNextTransfer = true; |
| } |
| } else { |
| LOG(VERBOSE) << __func__ << ": asynchronous transfer"; |
| } |
| return ::android::OK; |
| } |
| |
| ::android::status_t StreamPrimary::refinePosition(StreamDescriptor::Position*) { |
| // Since not all data is actually sent to the HAL, use the position maintained by Stream class |
| // which accounts for all frames passed from / to the client. |
| return ::android::OK; |
| } |
| |
| void StreamPrimary::shutdown() { |
| StreamAlsa::shutdown(); |
| mStubDriver.shutdown(); |
| } |
| |
| ndk::ScopedAStatus StreamPrimary::setConnectedDevices(const ConnectedDevices& devices) { |
| LOG(DEBUG) << __func__ << ": " << ::android::internal::ToString(devices); |
| if (devices.size() > 1) { |
| LOG(ERROR) << __func__ << ": primary stream can only be connected to one device, got: " |
| << devices.size(); |
| return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| } |
| { |
| const bool useStubDriver = devices.empty() || useStubStream(mIsInput, devices[0]); |
| std::lock_guard l(mLock); |
| mAlsaDeviceId = useStubDriver ? kStubDeviceId : getCardAndDeviceId(devices); |
| } |
| if (!devices.empty()) { |
| auto streamDataProcessor = getContext().getStreamDataProcessor().lock(); |
| if (streamDataProcessor != nullptr) { |
| streamDataProcessor->setAudioDevice(devices[0]); |
| } |
| } |
| return StreamAlsa::setConnectedDevices(devices); |
| } |
| |
| std::vector<alsa::DeviceProfile> StreamPrimary::getDeviceProfiles() { |
| return {alsa::DeviceProfile{.card = mCurrAlsaDeviceId.first, |
| .device = mCurrAlsaDeviceId.second, |
| .direction = mIsInput ? PCM_IN : PCM_OUT, |
| .isExternal = false}}; |
| } |
| |
| bool StreamPrimary::isStubStream() { |
| std::lock_guard l(mLock); |
| return mAlsaDeviceId == kStubDeviceId; |
| } |
| |
| // static |
| StreamPrimary::AlsaDeviceId StreamPrimary::getCardAndDeviceId( |
| const std::vector<AudioDevice>& devices) { |
| if (devices.empty() || devices[0].address.getTag() != AudioDeviceAddress::id) { |
| return kDefaultCardAndDeviceId; |
| } |
| std::string deviceAddress = devices[0].address.get<AudioDeviceAddress::id>(); |
| AlsaDeviceId cardAndDeviceId; |
| if (const size_t suffixPos = deviceAddress.rfind("CARD_"); |
| suffixPos == std::string::npos || |
| sscanf(deviceAddress.c_str() + suffixPos, "CARD_%d_DEV_%d", &cardAndDeviceId.first, |
| &cardAndDeviceId.second) != 2) { |
| return kDefaultCardAndDeviceId; |
| } |
| LOG(DEBUG) << __func__ << ": parsed with card id " << cardAndDeviceId.first << ", device id " |
| << cardAndDeviceId.second; |
| return cardAndDeviceId; |
| } |
| |
| // static |
| bool StreamPrimary::useStubStream( |
| bool isInput, const ::aidl::android::media::audio::common::AudioDevice& device) { |
| static const bool kSimulateInput = |
| GetBoolProperty("ro.boot.audio.tinyalsa.simulate_input", false); |
| static const bool kSimulateOutput = |
| GetBoolProperty("ro.boot.audio.tinyalsa.ignore_output", false); |
| if (isInput) { |
| return kSimulateInput || device.type.type == AudioDeviceType::IN_TELEPHONY_RX || |
| device.type.type == AudioDeviceType::IN_FM_TUNER || |
| device.type.connection == AudioDeviceDescription::CONNECTION_BUS /*deprecated */; |
| } |
| return kSimulateOutput || device.type.type == AudioDeviceType::OUT_TELEPHONY_TX || |
| device.type.connection == AudioDeviceDescription::CONNECTION_BUS /*deprecated*/; |
| } |
| |
| StreamInPrimary::StreamInPrimary(StreamContext&& context, const SinkMetadata& sinkMetadata, |
| const std::vector<MicrophoneInfo>& microphones) |
| : StreamIn(std::move(context), microphones), |
| StreamPrimary(&mContextInstance, sinkMetadata), |
| StreamInHwGainHelper(&mContextInstance) {} |
| |
| ndk::ScopedAStatus StreamInPrimary::getHwGain(std::vector<float>* _aidl_return) { |
| if (isStubStream()) { |
| return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| } |
| float gain; |
| RETURN_STATUS_IF_ERROR(primary::PrimaryMixer::getInstance().getMicGain(&gain)); |
| _aidl_return->resize(0); |
| _aidl_return->resize(mChannelCount, gain); |
| RETURN_STATUS_IF_ERROR(setHwGainImpl(*_aidl_return)); |
| return getHwGainImpl(_aidl_return); |
| } |
| |
| ndk::ScopedAStatus StreamInPrimary::setHwGain(const std::vector<float>& 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<AudioOffloadInfo>& offloadInfo) |
| : StreamOut(std::move(context), offloadInfo), |
| StreamPrimary(&mContextInstance, sourceMetadata), |
| StreamOutHwVolumeHelper(&mContextInstance) {} |
| |
| ndk::ScopedAStatus StreamOutPrimary::getHwVolume(std::vector<float>* _aidl_return) { |
| if (isStubStream()) { |
| return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| } |
| RETURN_STATUS_IF_ERROR(primary::PrimaryMixer::getInstance().getVolumes(_aidl_return)); |
| _aidl_return->resize(mChannelCount); |
| RETURN_STATUS_IF_ERROR(setHwVolumeImpl(*_aidl_return)); |
| return getHwVolumeImpl(_aidl_return); |
| } |
| |
| ndk::ScopedAStatus StreamOutPrimary::setHwVolume(const std::vector<float>& 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 |