| /* |
| * Copyright (C) 2020 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 PATH(APM_XSD_ENUMS_H_FILENAME) |
| #include <android-base/properties.h> |
| #include <chrono> |
| #include <thread> |
| #include <log/log.h> |
| #include <utils/Mutex.h> |
| #include <utils/Timers.h> |
| #include <utils/ThreadDefs.h> |
| #include "device_port_sink.h" |
| #include "talsa.h" |
| #include "audio_ops.h" |
| #include "ring_buffer.h" |
| #include "util.h" |
| #include "debug.h" |
| |
| using ::android::base::GetBoolProperty; |
| |
| namespace xsd { |
| using namespace ::android::audio::policy::configuration::CPP_VERSION; |
| } |
| |
| namespace android { |
| namespace hardware { |
| namespace audio { |
| namespace CPP_VERSION { |
| namespace implementation { |
| |
| namespace { |
| |
| constexpr int kMaxJitterUs = 3000; // Enforced by CTS, should be <= 6ms |
| |
| struct TinyalsaSink : public DevicePortSink { |
| TinyalsaSink(unsigned pcmCard, unsigned pcmDevice, |
| const AudioConfig &cfg, |
| uint64_t &frames) |
| : mStartNs(systemTime(SYSTEM_TIME_MONOTONIC)) |
| , mSampleRateHz(cfg.base.sampleRateHz) |
| , mFrameSize(util::countChannels(cfg.base.channelMask) * sizeof(int16_t)) |
| , mWriteSizeFrames(cfg.frameCount) |
| , mInitialFrames(frames) |
| , mFrames(frames) |
| , mRingBuffer(mFrameSize * cfg.frameCount * 3) |
| , mMixer(pcmCard) |
| , mPcm(talsa::pcmOpen(pcmCard, pcmDevice, |
| util::countChannels(cfg.base.channelMask), |
| cfg.base.sampleRateHz, |
| cfg.frameCount, |
| true /* isOut */)) { |
| if (mPcm) { |
| mConsumeThread = std::thread(&TinyalsaSink::consumeThread, this); |
| } else { |
| mConsumeThread = std::thread([](){}); |
| } |
| } |
| |
| ~TinyalsaSink() { |
| mConsumeThreadRunning = false; |
| mConsumeThread.join(); |
| } |
| |
| static int getLatencyMs(const AudioConfig &cfg) { |
| constexpr size_t inMs = 1000; |
| const talsa::PcmPeriodSettings periodSettings = |
| talsa::pcmGetPcmPeriodSettings(); |
| const size_t numerator = periodSettings.periodSizeMultiplier * cfg.frameCount; |
| const size_t denominator = periodSettings.periodCount * cfg.base.sampleRateHz / inMs; |
| |
| // integer division with rounding |
| return (numerator + (denominator >> 1)) / denominator + talsa::pcmGetHostLatencyMs(); |
| } |
| |
| Result getPresentationPosition(uint64_t &frames, TimeSpec &ts) override { |
| const AutoMutex lock(mFrameCountersMutex); |
| |
| nsecs_t nowNs = systemTime(SYSTEM_TIME_MONOTONIC); |
| const uint64_t nowFrames = getPresentationFramesLocked(nowNs); |
| auto presentedFrames = nowFrames - mMissedFrames; |
| if (presentedFrames > mReceivedFrames) { |
| // There is another underrun that is not yet accounted for in mMissedFrames |
| auto delta = presentedFrames - mReceivedFrames; |
| presentedFrames -= delta; |
| // The last frame was presented some time ago, reflect that in the result |
| nowNs -= delta * 1000000000 / mSampleRateHz; |
| } |
| mFrames = presentedFrames + mInitialFrames; |
| |
| frames = mFrames; |
| ts = util::nsecs2TimeSpec(nowNs); |
| return Result::OK; |
| } |
| |
| uint64_t getPresentationFramesLocked(const nsecs_t nowNs) const { |
| return uint64_t(mSampleRateHz) * ns2us(nowNs - mStartNs) / 1000000; |
| } |
| |
| size_t calcAvailableFramesNowLocked() { |
| const nsecs_t nowNs = systemTime(SYSTEM_TIME_MONOTONIC); |
| auto presentationFrames = getPresentationFramesLocked(nowNs); |
| if (mReceivedFrames + mMissedFrames < presentationFrames) { |
| // There has been an underrun |
| mMissedFrames = presentationFrames - mReceivedFrames; |
| } |
| size_t pendingFrames = mReceivedFrames + mMissedFrames - presentationFrames; |
| return mRingBuffer.capacity() / mFrameSize - pendingFrames; |
| } |
| |
| size_t calcWaitFramesNowLocked(const size_t requestedFrames) { |
| const size_t availableFrames = calcAvailableFramesNowLocked(); |
| return (requestedFrames > availableFrames) |
| ? (requestedFrames - availableFrames) : 0; |
| } |
| |
| size_t write(float volume, size_t bytesToWrite, IReader &reader) { |
| const AutoMutex lock(mFrameCountersMutex); |
| |
| size_t framesLost = 0; |
| const size_t waitFrames = calcWaitFramesNowLocked(bytesToWrite / mFrameSize); |
| const auto blockUntil = |
| std::chrono::high_resolution_clock::now() + |
| + std::chrono::microseconds(waitFrames * 1000000 / mSampleRateHz); |
| |
| while (bytesToWrite > 0) { |
| if (mRingBuffer.waitForProduceAvailable(blockUntil |
| + std::chrono::microseconds(kMaxJitterUs))) { |
| auto produceChunk = mRingBuffer.getProduceChunk(); |
| if (produceChunk.size >= bytesToWrite) { |
| // Since the ring buffer has more bytes free than we need, |
| // make sure we are not too early here: tinyalsa is jittery, |
| // we don't want to go faster than SYSTEM_TIME_MONOTONIC |
| std::this_thread::sleep_until(blockUntil); |
| } |
| |
| const size_t szFrames = |
| std::min(produceChunk.size, bytesToWrite) / mFrameSize; |
| const size_t szBytes = szFrames * mFrameSize; |
| LOG_ALWAYS_FATAL_IF(reader(produceChunk.data, szBytes) < szBytes); |
| |
| aops::multiplyByVolume(volume, |
| static_cast<int16_t *>(produceChunk.data), |
| szBytes / sizeof(int16_t)); |
| |
| LOG_ALWAYS_FATAL_IF(mRingBuffer.produce(szBytes) < szBytes); |
| mReceivedFrames += szFrames; |
| bytesToWrite -= szBytes; |
| } else { |
| ALOGV("TinyalsaSink::%s:%d pcm_write was late reading " |
| "frames, dropping %zu us of audio", |
| __func__, __LINE__, |
| size_t(1000000 * bytesToWrite / mFrameSize / mSampleRateHz)); |
| |
| // drop old audio to make room for new |
| const size_t bytesLost = mRingBuffer.makeRoomForProduce(bytesToWrite); |
| framesLost += bytesLost / mFrameSize; |
| |
| while (bytesToWrite > 0) { |
| auto produceChunk = mRingBuffer.getProduceChunk(); |
| const size_t szFrames = |
| std::min(produceChunk.size, bytesToWrite) / mFrameSize; |
| const size_t szBytes = szFrames * mFrameSize; |
| LOG_ALWAYS_FATAL_IF(reader(produceChunk.data, szBytes) < szBytes); |
| |
| aops::multiplyByVolume(volume, |
| static_cast<int16_t *>(produceChunk.data), |
| szBytes / sizeof(int16_t)); |
| |
| LOG_ALWAYS_FATAL_IF(mRingBuffer.produce(szBytes) < szBytes); |
| mReceivedFrames += szFrames; |
| bytesToWrite -= szBytes; |
| } |
| break; |
| } |
| } |
| |
| return framesLost; |
| } |
| |
| void consumeThread() { |
| util::setThreadPriority(PRIORITY_URGENT_AUDIO); |
| std::vector<uint8_t> writeBuffer(mWriteSizeFrames * mFrameSize); |
| |
| while (mConsumeThreadRunning) { |
| if (mRingBuffer.waitForConsumeAvailable( |
| std::chrono::high_resolution_clock::now() |
| + std::chrono::microseconds(100000))) { |
| size_t szBytes; |
| { |
| auto chunk = mRingBuffer.getConsumeChunk(); |
| szBytes = std::min(writeBuffer.size(), chunk.size); |
| // We have to memcpy because the consumer holds the lock |
| // into RingBuffer and pcm_write takes too long to hold |
| // this lock. |
| memcpy(writeBuffer.data(), chunk.data, szBytes); |
| LOG_ALWAYS_FATAL_IF(mRingBuffer.consume(chunk, szBytes) < szBytes); |
| } |
| |
| talsa::pcmWrite(mPcm.get(), writeBuffer.data(), szBytes); |
| } |
| } |
| } |
| |
| static std::unique_ptr<TinyalsaSink> create(unsigned pcmCard, |
| unsigned pcmDevice, |
| const AudioConfig &cfg, |
| size_t readerBufferSizeHint, |
| uint64_t &frames) { |
| (void)readerBufferSizeHint; |
| auto sink = std::make_unique<TinyalsaSink>(pcmCard, pcmDevice, |
| cfg, frames); |
| if (sink->mMixer && sink->mPcm) { |
| return sink; |
| } else { |
| return FAILURE(nullptr); |
| } |
| } |
| |
| private: |
| const nsecs_t mStartNs; |
| const unsigned mSampleRateHz; |
| const unsigned mFrameSize; |
| const unsigned mWriteSizeFrames; |
| const uint64_t mInitialFrames; |
| uint64_t &mFrames GUARDED_BY(mFrameCountersMutex); |
| uint64_t mMissedFrames GUARDED_BY(mFrameCountersMutex) = 0; |
| uint64_t mReceivedFrames GUARDED_BY(mFrameCountersMutex) = 0; |
| RingBuffer mRingBuffer; |
| talsa::Mixer mMixer; |
| talsa::PcmPtr mPcm; |
| std::thread mConsumeThread; |
| std::atomic<bool> mConsumeThreadRunning = true; |
| mutable Mutex mFrameCountersMutex; |
| }; |
| |
| struct NullSink : public DevicePortSink { |
| NullSink(const AudioConfig &cfg, uint64_t &frames) |
| : mStartNs(systemTime(SYSTEM_TIME_MONOTONIC)) |
| , mSampleRateHz(cfg.base.sampleRateHz) |
| , mFrameSize(util::countChannels(cfg.base.channelMask) * sizeof(int16_t)) |
| , mInitialFrames(frames) |
| , mFrames(frames) {} |
| |
| static int getLatencyMs(const AudioConfig &) { |
| return 1; |
| } |
| |
| Result getPresentationPosition(uint64_t &frames, TimeSpec &ts) override { |
| const AutoMutex lock(mFrameCountersMutex); |
| |
| nsecs_t nowNs = systemTime(SYSTEM_TIME_MONOTONIC); |
| const uint64_t nowFrames = getPresentationFramesLocked(nowNs); |
| auto presentedFrames = nowFrames - mMissedFrames; |
| if (presentedFrames > mReceivedFrames) { |
| // There is another underrun that is not yet accounted for in mMissedFrames |
| auto delta = presentedFrames - mReceivedFrames; |
| presentedFrames -= delta; |
| // The last frame was presented some time ago, reflect that in the result |
| nowNs -= delta * 1000000000 / mSampleRateHz; |
| } |
| mFrames = presentedFrames + mInitialFrames; |
| |
| frames = mFrames; |
| ts = util::nsecs2TimeSpec(nowNs); |
| return Result::OK; |
| } |
| |
| uint64_t getPresentationFramesLocked(const nsecs_t nowNs) const { |
| return uint64_t(mSampleRateHz) * ns2us(nowNs - mStartNs) / 1000000; |
| } |
| |
| size_t calcAvailableFramesNowLocked() { |
| const nsecs_t nowNs = systemTime(SYSTEM_TIME_MONOTONIC); |
| auto presentationFrames = getPresentationFramesLocked(nowNs); |
| if (mReceivedFrames + mMissedFrames < presentationFrames) { |
| // There has been an underrun |
| mMissedFrames = presentationFrames - mReceivedFrames; |
| } |
| size_t pendingFrames = mReceivedFrames + mMissedFrames - presentationFrames; |
| return sizeof(mWriteBuffer) / mFrameSize - pendingFrames; |
| } |
| |
| size_t calcWaitFramesNowLocked(const size_t requestedFrames) { |
| const size_t availableFrames = calcAvailableFramesNowLocked(); |
| return (requestedFrames > availableFrames) |
| ? (requestedFrames - availableFrames) : 0; |
| } |
| |
| size_t write(float volume, size_t bytesToWrite, IReader &reader) override { |
| (void)volume; |
| const AutoMutex lock(mFrameCountersMutex); |
| |
| const size_t waitFrames = calcWaitFramesNowLocked(bytesToWrite / mFrameSize); |
| const auto blockUntil = |
| std::chrono::high_resolution_clock::now() + |
| + std::chrono::microseconds(waitFrames * 1000000 / mSampleRateHz); |
| std::this_thread::sleep_until(blockUntil); |
| |
| while (bytesToWrite > 0) { |
| size_t chunkSize = |
| std::min(bytesToWrite, sizeof(mWriteBuffer)) / mFrameSize * mFrameSize; |
| chunkSize = reader(mWriteBuffer, chunkSize); |
| if (chunkSize > 0) { |
| mReceivedFrames += chunkSize / mFrameSize; |
| bytesToWrite -= chunkSize; |
| } else { |
| break; // reader failed |
| } |
| } |
| |
| return 0; |
| } |
| |
| static std::unique_ptr<NullSink> create(const AudioConfig &cfg, |
| size_t readerBufferSizeHint, |
| uint64_t &frames) { |
| (void)readerBufferSizeHint; |
| return std::make_unique<NullSink>(cfg, frames); |
| } |
| |
| private: |
| const nsecs_t mStartNs; |
| const unsigned mSampleRateHz; |
| const unsigned mFrameSize; |
| const uint64_t mInitialFrames; |
| uint64_t &mFrames GUARDED_BY(mFrameCountersMutex); |
| uint64_t mMissedFrames GUARDED_BY(mFrameCountersMutex) = 0; |
| uint64_t mReceivedFrames GUARDED_BY(mFrameCountersMutex) = 0; |
| char mWriteBuffer[1024]; |
| mutable Mutex mFrameCountersMutex; |
| }; |
| |
| } // namespace |
| |
| std::unique_ptr<DevicePortSink> |
| DevicePortSink::create(size_t readerBufferSizeHint, |
| const DeviceAddress &address, |
| const AudioConfig &cfg, |
| const hidl_vec<AudioInOutFlag> &flags, |
| uint64_t &frames) { |
| (void)flags; |
| |
| if (xsd::stringToAudioFormat(cfg.base.format) != xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT) { |
| ALOGE("%s:%d, unexpected format: '%s'", __func__, __LINE__, cfg.base.format.c_str()); |
| return FAILURE(nullptr); |
| } |
| |
| if (GetBoolProperty("ro.boot.audio.tinyalsa.ignore_output", false)) { |
| goto nullsink; |
| } |
| |
| switch (xsd::stringToAudioDevice(address.deviceType)) { |
| case xsd::AudioDevice::AUDIO_DEVICE_OUT_DEFAULT: |
| case xsd::AudioDevice::AUDIO_DEVICE_OUT_SPEAKER: |
| { |
| auto sinkptr = TinyalsaSink::create(talsa::kPcmCard, talsa::kPcmDevice, |
| cfg, readerBufferSizeHint, frames); |
| if (sinkptr != nullptr) { |
| return sinkptr; |
| } else { |
| ALOGW("%s:%d failed to create alsa sink for '%s'; creating NullSink instead.", |
| __func__, __LINE__, address.deviceType.c_str()); |
| } |
| } |
| break; |
| |
| case xsd::AudioDevice::AUDIO_DEVICE_OUT_TELEPHONY_TX: |
| case xsd::AudioDevice::AUDIO_DEVICE_OUT_BUS: |
| ALOGW("%s:%d creating NullSink for '%s'.", __func__, __LINE__, address.deviceType.c_str()); |
| break; |
| |
| default: |
| ALOGW("%s:%d unsupported device: '%s', creating NullSink", __func__, __LINE__, address.deviceType.c_str()); |
| break; |
| } |
| |
| nullsink: |
| return NullSink::create(cfg, readerBufferSizeHint, frames); |
| } |
| |
| int DevicePortSink::getLatencyMs(const DeviceAddress &address, const AudioConfig &cfg) { |
| switch (xsd::stringToAudioDevice(address.deviceType)) { |
| default: |
| ALOGW("%s:%d unsupported device: '%s'", __func__, __LINE__, address.deviceType.c_str()); |
| return FAILURE(-1); |
| |
| case xsd::AudioDevice::AUDIO_DEVICE_OUT_DEFAULT: |
| case xsd::AudioDevice::AUDIO_DEVICE_OUT_SPEAKER: |
| return TinyalsaSink::getLatencyMs(cfg); |
| |
| case xsd::AudioDevice::AUDIO_DEVICE_OUT_TELEPHONY_TX: |
| case xsd::AudioDevice::AUDIO_DEVICE_OUT_BUS: |
| return NullSink::getLatencyMs(cfg); |
| } |
| } |
| |
| bool DevicePortSink::validateDeviceAddress(const DeviceAddress& address) { |
| switch (xsd::stringToAudioDevice(address.deviceType)) { |
| default: |
| ALOGW("%s:%d unsupported device: '%s'", __func__, __LINE__, address.deviceType.c_str()); |
| return FAILURE(false); |
| |
| case xsd::AudioDevice::AUDIO_DEVICE_OUT_DEFAULT: |
| case xsd::AudioDevice::AUDIO_DEVICE_OUT_SPEAKER: |
| case xsd::AudioDevice::AUDIO_DEVICE_OUT_TELEPHONY_TX: |
| case xsd::AudioDevice::AUDIO_DEVICE_OUT_BUS: |
| break; |
| } |
| |
| return true; |
| } |
| |
| } // namespace implementation |
| } // namespace CPP_VERSION |
| } // namespace audio |
| } // namespace hardware |
| } // namespace android |