/* * 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 #include #include #include #include #include "device_port_sink.h" #include "talsa.h" #include "audio_ops.h" #include "ring_buffer.h" #include "util.h" #include "debug.h" namespace android { namespace hardware { namespace audio { namespace V6_0 { 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.sampleRateHz) , mFrameSize(util::countChannels(cfg.channelMask) * sizeof(int16_t)) , mWriteSizeFrames(cfg.frameCount) , mFrames(frames) , mRingBuffer(mFrameSize * cfg.frameCount * 3) , mMixer(pcmCard) , mPcm(talsa::pcmOpen(pcmCard, pcmDevice, util::countChannels(cfg.channelMask), cfg.sampleRateHz, cfg.frameCount, true /* isOut */)) { mConsumeThread = std::thread(&TinyalsaSink::consumeThread, this); } ~TinyalsaSink() { mConsumeThreadRunning = false; mConsumeThread.join(); } Result getPresentationPosition(uint64_t &frames, TimeSpec &ts) override { const nsecs_t nowNs = systemTime(SYSTEM_TIME_MONOTONIC); const uint64_t nowFrames = getPresentationFrames(nowNs); mFrames += (nowFrames - mPreviousFrames); mPreviousFrames = nowFrames; frames = mFrames; ts = util::nsecs2TimeSpec(nowNs); return Result::OK; } uint64_t getPresentationFrames(const nsecs_t nowNs) const { return uint64_t(mSampleRateHz) * ns2us(nowNs - mStartNs) / 1000000; } uint64_t getAvailableFrames(const nsecs_t nowNs) const { return getPresentationFrames(nowNs) - mReceivedFrames; } uint64_t getAvailableFramesNow() const { return getAvailableFrames(systemTime(SYSTEM_TIME_MONOTONIC)); } size_t getWaitFramesNow(const size_t requestedFrames) const { const size_t availableFrames = getAvailableFramesNow(); return (requestedFrames > availableFrames) ? (requestedFrames - availableFrames) : 0; } size_t write(float volume, size_t bytesToWrite, IReader &reader) { size_t framesLost = 0; const size_t waitFrames = getWaitFramesNow(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(produceChunk.data), szBytes / sizeof(int16_t)); LOG_ALWAYS_FATAL_IF(mRingBuffer.produce(szBytes) < szBytes); mReceivedFrames += szFrames; bytesToWrite -= szBytes; } else { ALOGW("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(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 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); } int res = ::pcm_write(mPcm.get(), writeBuffer.data(), szBytes); if (res < 0) { ALOGW("TinyalsaSink::%s:%d pcm_write failed with res=%d", __func__, __LINE__, res); } } } } static std::unique_ptr create(unsigned pcmCard, unsigned pcmDevice, const AudioConfig &cfg, size_t readerBufferSizeHint, uint64_t &frames) { (void)readerBufferSizeHint; auto sink = std::make_unique(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; uint64_t &mFrames; uint64_t mPreviousFrames = 0; uint64_t mReceivedFrames = 0; RingBuffer mRingBuffer; talsa::Mixer mMixer; talsa::PcmPtr mPcm; std::thread mConsumeThread; std::atomic mConsumeThreadRunning = true; }; struct NullSink : public DevicePortSink { NullSink(const AudioConfig &cfg, uint64_t &frames) : mFrames(frames) , mSampleRateHz(cfg.sampleRateHz) , mNChannels(util::countChannels(cfg.channelMask)) , mTimestamp(systemTime(SYSTEM_TIME_MONOTONIC)) {} Result getPresentationPosition(uint64_t &frames, TimeSpec &ts) override { simulatePresentationPosition(); frames = mFrames; ts = util::nsecs2TimeSpec(mTimestamp); return Result::OK; } size_t write(float volume, size_t bytesToWrite, IReader &reader) override { (void)volume; while (bytesToWrite > 0) { size_t chunkSize = std::min(bytesToWrite, sizeof(mWriteBuffer)); chunkSize = reader(mWriteBuffer, chunkSize); if (chunkSize > 0) { bytesToWrite -= chunkSize; } else { break; // reader failed } } return 0; } void simulatePresentationPosition() { const nsecs_t nowNs = systemTime(SYSTEM_TIME_MONOTONIC); const nsecs_t deltaNs = nowNs - mTimestamp; const uint64_t deltaFrames = uint64_t(mSampleRateHz) * ns2ms(deltaNs) / 1000; const uint64_t f = std::min(deltaFrames, mAvailableFrames); mFrames += f; mAvailableFrames -= f; if (mAvailableFrames) { mTimestamp += us2ns(f * 1000000 / mSampleRateHz); } else { mTimestamp = nowNs; } } static std::unique_ptr create(const AudioConfig &cfg, size_t readerBufferSizeHint, uint64_t &frames) { (void)readerBufferSizeHint; return std::make_unique(cfg, frames); } private: uint64_t &mFrames; const unsigned mSampleRateHz; const unsigned mNChannels; uint64_t mAvailableFrames = 0; nsecs_t mTimestamp; char mWriteBuffer[1024]; }; } // namespace std::unique_ptr DevicePortSink::create(size_t readerBufferSizeHint, const DeviceAddress &address, const AudioConfig &cfg, const hidl_bitfield &flags, uint64_t &frames) { (void)flags; if (cfg.format != AudioFormat::PCM_16_BIT) { ALOGE("%s:%d Only PCM_16_BIT is supported", __func__, __LINE__); return FAILURE(nullptr); } switch (address.device) { case AudioDevice::OUT_SPEAKER: return TinyalsaSink::create(talsa::kPcmCard, talsa::kPcmDevice, cfg, readerBufferSizeHint, frames); case AudioDevice::OUT_TELEPHONY_TX: return NullSink::create(cfg, readerBufferSizeHint, frames); default: ALOGE("%s:%d unsupported device: %x", __func__, __LINE__, address.device); return FAILURE(nullptr); } } } // namespace implementation } // namespace V6_0 } // namespace audio } // namespace hardware } // namespace android