Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2023 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 17 | #define LOG_TAG "AHAL_StreamPrimary" |
Weilin Xu | 29e5168 | 2024-10-02 17:16:32 +0000 | [diff] [blame] | 18 | |
| 19 | #include <cstdio> |
| 20 | |
Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 21 | #include <android-base/logging.h> |
Weilin Xu | 29e5168 | 2024-10-02 17:16:32 +0000 | [diff] [blame] | 22 | #include <android-base/parseint.h> |
Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 23 | #include <android-base/properties.h> |
Mikhail Naganov | 1350187 | 2023-10-18 16:15:46 -0700 | [diff] [blame] | 24 | #include <audio_utils/clock.h> |
Mikhail Naganov | 6c41935 | 2023-11-03 18:33:57 -0700 | [diff] [blame] | 25 | #include <error/Result.h> |
Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 26 | #include <error/expected_utils.h> |
| 27 | |
Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 28 | #include "core-impl/StreamPrimary.h" |
Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 29 | |
| 30 | using aidl::android::hardware::audio::common::SinkMetadata; |
| 31 | using aidl::android::hardware::audio::common::SourceMetadata; |
| 32 | using aidl::android::media::audio::common::AudioDevice; |
Weilin Xu | 29e5168 | 2024-10-02 17:16:32 +0000 | [diff] [blame] | 33 | using aidl::android::media::audio::common::AudioDeviceAddress; |
Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 34 | using aidl::android::media::audio::common::AudioDeviceDescription; |
| 35 | using aidl::android::media::audio::common::AudioDeviceType; |
| 36 | using aidl::android::media::audio::common::AudioOffloadInfo; |
| 37 | using aidl::android::media::audio::common::MicrophoneInfo; |
| 38 | using android::base::GetBoolProperty; |
| 39 | |
| 40 | namespace aidl::android::hardware::audio::core { |
| 41 | |
Mikhail Naganov | f5ec73e | 2024-10-02 11:02:52 -0700 | [diff] [blame] | 42 | StreamPrimary::StreamPrimary(StreamContext* context, const Metadata& metadata) |
Mikhail Naganov | 1350187 | 2023-10-18 16:15:46 -0700 | [diff] [blame] | 43 | : StreamAlsa(context, metadata, 3 /*readWriteRetries*/), |
Weilin Xu | 29e5168 | 2024-10-02 17:16:32 +0000 | [diff] [blame] | 44 | mIsAsynchronous(!!getContext().getAsyncCallback()), |
Mikhail Naganov | f5ec73e | 2024-10-02 11:02:52 -0700 | [diff] [blame] | 45 | mStubDriver(getContext()) { |
Mikhail Naganov | 3c8b6ce | 2023-10-31 11:20:30 -0700 | [diff] [blame] | 46 | context->startStreamDataProcessor(); |
| 47 | } |
Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 48 | |
Mikhail Naganov | f5ec73e | 2024-10-02 11:02:52 -0700 | [diff] [blame] | 49 | ::android::status_t StreamPrimary::init() { |
| 50 | RETURN_STATUS_IF_ERROR(mStubDriver.init()); |
| 51 | return StreamAlsa::init(); |
| 52 | } |
| 53 | |
| 54 | ::android::status_t StreamPrimary::drain(StreamDescriptor::DrainMode mode) { |
| 55 | return isStubStreamOnWorker() ? mStubDriver.drain(mode) : StreamAlsa::drain(mode); |
| 56 | } |
| 57 | |
| 58 | ::android::status_t StreamPrimary::flush() { |
Mikhail Naganov | 05fc6aa | 2024-10-11 13:55:54 -0700 | [diff] [blame] | 59 | RETURN_STATUS_IF_ERROR(isStubStreamOnWorker() ? mStubDriver.flush() : StreamAlsa::flush()); |
| 60 | // TODO(b/372951987): consider if this needs to be done from 'StreamInWorkerLogic::cycle'. |
| 61 | return mIsInput ? standby() : ::android::OK; |
Mikhail Naganov | f5ec73e | 2024-10-02 11:02:52 -0700 | [diff] [blame] | 62 | } |
| 63 | |
| 64 | ::android::status_t StreamPrimary::pause() { |
| 65 | return isStubStreamOnWorker() ? mStubDriver.pause() : StreamAlsa::pause(); |
| 66 | } |
| 67 | |
| 68 | ::android::status_t StreamPrimary::standby() { |
| 69 | return isStubStreamOnWorker() ? mStubDriver.standby() : StreamAlsa::standby(); |
| 70 | } |
| 71 | |
Mikhail Naganov | 6c41935 | 2023-11-03 18:33:57 -0700 | [diff] [blame] | 72 | ::android::status_t StreamPrimary::start() { |
Mikhail Naganov | f5ec73e | 2024-10-02 11:02:52 -0700 | [diff] [blame] | 73 | bool isStub = true, shutdownAlsaStream = false; |
| 74 | { |
| 75 | std::lock_guard l(mLock); |
| 76 | isStub = mAlsaDeviceId == kStubDeviceId; |
| 77 | shutdownAlsaStream = |
| 78 | mCurrAlsaDeviceId != mAlsaDeviceId && mCurrAlsaDeviceId != kStubDeviceId; |
| 79 | mCurrAlsaDeviceId = mAlsaDeviceId; |
| 80 | } |
| 81 | if (shutdownAlsaStream) { |
| 82 | StreamAlsa::shutdown(); // Close currently opened ALSA devices. |
| 83 | } |
| 84 | if (isStub) { |
| 85 | return mStubDriver.start(); |
| 86 | } |
Mikhail Naganov | 6c41935 | 2023-11-03 18:33:57 -0700 | [diff] [blame] | 87 | RETURN_STATUS_IF_ERROR(StreamAlsa::start()); |
| 88 | mStartTimeNs = ::android::uptimeNanos(); |
| 89 | mFramesSinceStart = 0; |
| 90 | mSkipNextTransfer = false; |
| 91 | return ::android::OK; |
| 92 | } |
| 93 | |
Mikhail Naganov | 1350187 | 2023-10-18 16:15:46 -0700 | [diff] [blame] | 94 | ::android::status_t StreamPrimary::transfer(void* buffer, size_t frameCount, |
| 95 | size_t* actualFrameCount, int32_t* latencyMs) { |
Mikhail Naganov | f5ec73e | 2024-10-02 11:02:52 -0700 | [diff] [blame] | 96 | if (isStubStreamOnWorker()) { |
| 97 | return mStubDriver.transfer(buffer, frameCount, actualFrameCount, latencyMs); |
| 98 | } |
Mikhail Naganov | 1350187 | 2023-10-18 16:15:46 -0700 | [diff] [blame] | 99 | // This is a workaround for the emulator implementation which has a host-side buffer |
Mikhail Naganov | 6c41935 | 2023-11-03 18:33:57 -0700 | [diff] [blame] | 100 | // and is not being able to achieve real-time behavior similar to ADSPs (b/302587331). |
| 101 | if (!mSkipNextTransfer) { |
| 102 | RETURN_STATUS_IF_ERROR( |
| 103 | StreamAlsa::transfer(buffer, frameCount, actualFrameCount, latencyMs)); |
| 104 | } else { |
| 105 | LOG(DEBUG) << __func__ << ": skipping transfer (" << frameCount << " frames)"; |
| 106 | *actualFrameCount = frameCount; |
Mikhail Naganov | d664a63 | 2023-11-27 17:31:04 -0800 | [diff] [blame] | 107 | if (mIsInput) memset(buffer, 0, frameCount * mFrameSizeBytes); |
Mikhail Naganov | 6c41935 | 2023-11-03 18:33:57 -0700 | [diff] [blame] | 108 | mSkipNextTransfer = false; |
Mikhail Naganov | 1350187 | 2023-10-18 16:15:46 -0700 | [diff] [blame] | 109 | } |
Mikhail Naganov | 6c41935 | 2023-11-03 18:33:57 -0700 | [diff] [blame] | 110 | if (!mIsAsynchronous) { |
| 111 | const long bufferDurationUs = |
| 112 | (*actualFrameCount) * MICROS_PER_SECOND / mContext.getSampleRate(); |
| 113 | const auto totalDurationUs = |
| 114 | (::android::uptimeNanos() - mStartTimeNs) / NANOS_PER_MICROSECOND; |
| 115 | mFramesSinceStart += *actualFrameCount; |
| 116 | const long totalOffsetUs = |
| 117 | mFramesSinceStart * MICROS_PER_SECOND / mContext.getSampleRate() - totalDurationUs; |
| 118 | LOG(VERBOSE) << __func__ << ": totalOffsetUs " << totalOffsetUs; |
| 119 | if (totalOffsetUs > 0) { |
| 120 | const long sleepTimeUs = std::min(totalOffsetUs, bufferDurationUs); |
| 121 | LOG(VERBOSE) << __func__ << ": sleeping for " << sleepTimeUs << " us"; |
| 122 | usleep(sleepTimeUs); |
| 123 | } else { |
| 124 | mSkipNextTransfer = true; |
| 125 | } |
| 126 | } else { |
| 127 | LOG(VERBOSE) << __func__ << ": asynchronous transfer"; |
| 128 | } |
| 129 | return ::android::OK; |
| 130 | } |
| 131 | |
| 132 | ::android::status_t StreamPrimary::refinePosition(StreamDescriptor::Position*) { |
| 133 | // Since not all data is actually sent to the HAL, use the position maintained by Stream class |
| 134 | // which accounts for all frames passed from / to the client. |
Mikhail Naganov | 1350187 | 2023-10-18 16:15:46 -0700 | [diff] [blame] | 135 | return ::android::OK; |
| 136 | } |
| 137 | |
Mikhail Naganov | f5ec73e | 2024-10-02 11:02:52 -0700 | [diff] [blame] | 138 | void StreamPrimary::shutdown() { |
| 139 | StreamAlsa::shutdown(); |
| 140 | mStubDriver.shutdown(); |
| 141 | } |
| 142 | |
| 143 | ndk::ScopedAStatus StreamPrimary::setConnectedDevices(const ConnectedDevices& devices) { |
| 144 | LOG(DEBUG) << __func__ << ": " << ::android::internal::ToString(devices); |
| 145 | if (devices.size() > 1) { |
| 146 | LOG(ERROR) << __func__ << ": primary stream can only be connected to one device, got: " |
| 147 | << devices.size(); |
| 148 | return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| 149 | } |
| 150 | { |
| 151 | const bool useStubDriver = devices.empty() || useStubStream(mIsInput, devices[0]); |
| 152 | std::lock_guard l(mLock); |
| 153 | mAlsaDeviceId = useStubDriver ? kStubDeviceId : getCardAndDeviceId(devices); |
| 154 | } |
| 155 | if (!devices.empty()) { |
| 156 | auto streamDataProcessor = getContext().getStreamDataProcessor().lock(); |
| 157 | if (streamDataProcessor != nullptr) { |
| 158 | streamDataProcessor->setAudioDevice(devices[0]); |
| 159 | } |
| 160 | } |
| 161 | return StreamAlsa::setConnectedDevices(devices); |
| 162 | } |
| 163 | |
Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 164 | std::vector<alsa::DeviceProfile> StreamPrimary::getDeviceProfiles() { |
Mikhail Naganov | f5ec73e | 2024-10-02 11:02:52 -0700 | [diff] [blame] | 165 | return {alsa::DeviceProfile{.card = mCurrAlsaDeviceId.first, |
| 166 | .device = mCurrAlsaDeviceId.second, |
Weilin Xu | 29e5168 | 2024-10-02 17:16:32 +0000 | [diff] [blame] | 167 | .direction = mIsInput ? PCM_IN : PCM_OUT, |
Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 168 | .isExternal = false}}; |
Weilin Xu | 29e5168 | 2024-10-02 17:16:32 +0000 | [diff] [blame] | 169 | } |
| 170 | |
Mikhail Naganov | f5ec73e | 2024-10-02 11:02:52 -0700 | [diff] [blame] | 171 | bool StreamPrimary::isStubStream() { |
| 172 | std::lock_guard l(mLock); |
| 173 | return mAlsaDeviceId == kStubDeviceId; |
| 174 | } |
| 175 | |
| 176 | // static |
| 177 | StreamPrimary::AlsaDeviceId StreamPrimary::getCardAndDeviceId( |
| 178 | const std::vector<AudioDevice>& devices) { |
Weilin Xu | 29e5168 | 2024-10-02 17:16:32 +0000 | [diff] [blame] | 179 | if (devices.empty() || devices[0].address.getTag() != AudioDeviceAddress::id) { |
| 180 | return kDefaultCardAndDeviceId; |
| 181 | } |
| 182 | std::string deviceAddress = devices[0].address.get<AudioDeviceAddress::id>(); |
Mikhail Naganov | f5ec73e | 2024-10-02 11:02:52 -0700 | [diff] [blame] | 183 | AlsaDeviceId cardAndDeviceId; |
Weilin Xu | 29e5168 | 2024-10-02 17:16:32 +0000 | [diff] [blame] | 184 | if (const size_t suffixPos = deviceAddress.rfind("CARD_"); |
| 185 | suffixPos == std::string::npos || |
| 186 | sscanf(deviceAddress.c_str() + suffixPos, "CARD_%d_DEV_%d", &cardAndDeviceId.first, |
| 187 | &cardAndDeviceId.second) != 2) { |
| 188 | return kDefaultCardAndDeviceId; |
| 189 | } |
| 190 | LOG(DEBUG) << __func__ << ": parsed with card id " << cardAndDeviceId.first << ", device id " |
| 191 | << cardAndDeviceId.second; |
| 192 | return cardAndDeviceId; |
Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 193 | } |
| 194 | |
Mikhail Naganov | f5ec73e | 2024-10-02 11:02:52 -0700 | [diff] [blame] | 195 | // static |
| 196 | bool StreamPrimary::useStubStream( |
| 197 | bool isInput, const ::aidl::android::media::audio::common::AudioDevice& device) { |
| 198 | static const bool kSimulateInput = |
| 199 | GetBoolProperty("ro.boot.audio.tinyalsa.simulate_input", false); |
| 200 | static const bool kSimulateOutput = |
| 201 | GetBoolProperty("ro.boot.audio.tinyalsa.ignore_output", false); |
| 202 | if (isInput) { |
| 203 | return kSimulateInput || device.type.type == AudioDeviceType::IN_TELEPHONY_RX || |
| 204 | device.type.type == AudioDeviceType::IN_FM_TUNER || |
| 205 | device.type.connection == AudioDeviceDescription::CONNECTION_BUS /*deprecated */; |
| 206 | } |
| 207 | return kSimulateOutput || device.type.type == AudioDeviceType::OUT_TELEPHONY_TX || |
| 208 | device.type.connection == AudioDeviceDescription::CONNECTION_BUS /*deprecated*/; |
| 209 | } |
| 210 | |
Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 211 | StreamInPrimary::StreamInPrimary(StreamContext&& context, const SinkMetadata& sinkMetadata, |
| 212 | const std::vector<MicrophoneInfo>& microphones) |
| 213 | : StreamIn(std::move(context), microphones), |
Mikhail Naganov | f5ec73e | 2024-10-02 11:02:52 -0700 | [diff] [blame] | 214 | StreamPrimary(&mContextInstance, sinkMetadata), |
Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 215 | StreamInHwGainHelper(&mContextInstance) {} |
| 216 | |
Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 217 | ndk::ScopedAStatus StreamInPrimary::getHwGain(std::vector<float>* _aidl_return) { |
| 218 | if (isStubStream()) { |
| 219 | return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| 220 | } |
Jindong Yue | 5c7e78b | 2024-10-21 11:18:08 +0800 | [diff] [blame] | 221 | if (mHwGains.empty()) { |
| 222 | float gain; |
| 223 | RETURN_STATUS_IF_ERROR(primary::PrimaryMixer::getInstance().getMicGain(&gain)); |
| 224 | _aidl_return->resize(mChannelCount, gain); |
| 225 | RETURN_STATUS_IF_ERROR(setHwGainImpl(*_aidl_return)); |
| 226 | } |
Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 227 | return getHwGainImpl(_aidl_return); |
| 228 | } |
| 229 | |
| 230 | ndk::ScopedAStatus StreamInPrimary::setHwGain(const std::vector<float>& in_channelGains) { |
| 231 | if (isStubStream()) { |
| 232 | LOG(DEBUG) << __func__ << ": gains " << ::android::internal::ToString(in_channelGains); |
| 233 | return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| 234 | } |
| 235 | auto currentGains = mHwGains; |
| 236 | RETURN_STATUS_IF_ERROR(setHwGainImpl(in_channelGains)); |
| 237 | if (in_channelGains.size() < 1) { |
| 238 | LOG(FATAL) << __func__ << ": unexpected gain vector size: " << in_channelGains.size(); |
| 239 | } |
| 240 | if (auto status = primary::PrimaryMixer::getInstance().setMicGain(in_channelGains[0]); |
| 241 | !status.isOk()) { |
| 242 | mHwGains = currentGains; |
| 243 | return status; |
| 244 | } |
Jindong Yue | 214c86e | 2024-10-21 13:42:29 +0800 | [diff] [blame] | 245 | float gain; |
| 246 | RETURN_STATUS_IF_ERROR(primary::PrimaryMixer::getInstance().getMicGain(&gain)); |
| 247 | // Due to rounding errors, round trip conversions between percents and indexed values may not |
| 248 | // match. |
| 249 | if (gain != in_channelGains[0]) { |
| 250 | LOG(WARNING) << __func__ << ": unmatched gain: set: " << in_channelGains[0] |
| 251 | << ", from mixer: " << gain; |
| 252 | } |
Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 253 | return ndk::ScopedAStatus::ok(); |
| 254 | } |
| 255 | |
| 256 | StreamOutPrimary::StreamOutPrimary(StreamContext&& context, const SourceMetadata& sourceMetadata, |
| 257 | const std::optional<AudioOffloadInfo>& offloadInfo) |
| 258 | : StreamOut(std::move(context), offloadInfo), |
Mikhail Naganov | f5ec73e | 2024-10-02 11:02:52 -0700 | [diff] [blame] | 259 | StreamPrimary(&mContextInstance, sourceMetadata), |
Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 260 | StreamOutHwVolumeHelper(&mContextInstance) {} |
| 261 | |
Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 262 | ndk::ScopedAStatus StreamOutPrimary::getHwVolume(std::vector<float>* _aidl_return) { |
| 263 | if (isStubStream()) { |
| 264 | return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| 265 | } |
Jindong Yue | 5c7e78b | 2024-10-21 11:18:08 +0800 | [diff] [blame] | 266 | if (mHwVolumes.empty()) { |
| 267 | RETURN_STATUS_IF_ERROR(primary::PrimaryMixer::getInstance().getVolumes(_aidl_return)); |
| 268 | _aidl_return->resize(mChannelCount); |
| 269 | RETURN_STATUS_IF_ERROR(setHwVolumeImpl(*_aidl_return)); |
| 270 | } |
Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 271 | return getHwVolumeImpl(_aidl_return); |
| 272 | } |
| 273 | |
| 274 | ndk::ScopedAStatus StreamOutPrimary::setHwVolume(const std::vector<float>& in_channelVolumes) { |
| 275 | if (isStubStream()) { |
| 276 | LOG(DEBUG) << __func__ << ": volumes " << ::android::internal::ToString(in_channelVolumes); |
| 277 | return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); |
| 278 | } |
| 279 | auto currentVolumes = mHwVolumes; |
| 280 | RETURN_STATUS_IF_ERROR(setHwVolumeImpl(in_channelVolumes)); |
| 281 | if (auto status = primary::PrimaryMixer::getInstance().setVolumes(in_channelVolumes); |
| 282 | !status.isOk()) { |
| 283 | mHwVolumes = currentVolumes; |
| 284 | return status; |
| 285 | } |
Jindong Yue | 214c86e | 2024-10-21 13:42:29 +0800 | [diff] [blame] | 286 | std::vector<float> volumes; |
| 287 | RETURN_STATUS_IF_ERROR(primary::PrimaryMixer::getInstance().getVolumes(&volumes)); |
| 288 | // Due to rounding errors, round trip conversions between percents and indexed values may not |
| 289 | // match. |
| 290 | if (volumes != in_channelVolumes) { |
| 291 | LOG(WARNING) << __func__ << ": unmatched volumes: set: " |
| 292 | << ::android::internal::ToString(in_channelVolumes) |
| 293 | << ", from mixer: " << ::android::internal::ToString(volumes); |
| 294 | } |
Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 295 | return ndk::ScopedAStatus::ok(); |
| 296 | } |
| 297 | |
Mikhail Naganov | cf824f6 | 2023-07-24 14:51:36 -0700 | [diff] [blame] | 298 | } // namespace aidl::android::hardware::audio::core |