You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
435 lines
15 KiB
435 lines
15 KiB
/*
|
|
* 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 <cmath>
|
|
#include <chrono>
|
|
#include <thread>
|
|
#include <unistd.h>
|
|
#include <audio_utils/channels.h>
|
|
#include <audio_utils/format.h>
|
|
#include <log/log.h>
|
|
#include <utils/ThreadDefs.h>
|
|
#include <utils/Timers.h>
|
|
#include "device_port_source.h"
|
|
#include "talsa.h"
|
|
#include "ring_buffer.h"
|
|
#include "audio_ops.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 TinyalsaSource : public DevicePortSource {
|
|
TinyalsaSource(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))
|
|
, mReadSizeFrames(cfg.frameCount)
|
|
, mFrames(frames)
|
|
, mRingBuffer(mFrameSize * cfg.frameCount * 3)
|
|
, mMixer(pcmCard)
|
|
, mPcm(talsa::pcmOpen(pcmCard, pcmDevice,
|
|
util::countChannels(cfg.channelMask),
|
|
cfg.sampleRateHz,
|
|
cfg.frameCount,
|
|
false /* isOut */)) {
|
|
mProduceThread = std::thread(&TinyalsaSource::producerThread, this);
|
|
}
|
|
|
|
~TinyalsaSource() {
|
|
mProduceThreadRunning = false;
|
|
mProduceThread.join();
|
|
}
|
|
|
|
Result getCapturePosition(uint64_t &frames, uint64_t &time) override {
|
|
const nsecs_t nowNs = systemTime(SYSTEM_TIME_MONOTONIC);
|
|
const uint64_t nowFrames = getCaptureFrames(nowNs);
|
|
mFrames += (nowFrames - mPreviousFrames);
|
|
mPreviousFrames = nowFrames;
|
|
|
|
frames = mFrames;
|
|
time = nowNs;
|
|
return Result::OK;
|
|
}
|
|
|
|
uint64_t getCaptureFrames(const nsecs_t nowNs) const {
|
|
return uint64_t(mSampleRateHz) * ns2us(nowNs - mStartNs) / 1000000;
|
|
}
|
|
|
|
uint64_t getAvailableFrames(const nsecs_t nowNs) const {
|
|
return getCaptureFrames(nowNs) - mSentFrames;
|
|
}
|
|
|
|
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 read(float volume, size_t bytesToRead, IWriter &writer) override {
|
|
const size_t waitFrames = getWaitFramesNow(bytesToRead / mFrameSize);
|
|
const auto blockUntil =
|
|
std::chrono::high_resolution_clock::now() +
|
|
+ std::chrono::microseconds(waitFrames * 1000000 / mSampleRateHz);
|
|
|
|
while (bytesToRead > 0) {
|
|
if (mRingBuffer.waitForConsumeAvailable(blockUntil
|
|
+ std::chrono::microseconds(kMaxJitterUs))) {
|
|
if (mRingBuffer.availableToConsume() >= bytesToRead) {
|
|
// Since the ring buffer has all bytes 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);
|
|
}
|
|
|
|
auto chunk = mRingBuffer.getConsumeChunk();
|
|
const size_t writeBufSzBytes = std::min(chunk.size, bytesToRead);
|
|
|
|
aops::multiplyByVolume(volume,
|
|
static_cast<int16_t *>(chunk.data),
|
|
writeBufSzBytes / sizeof(int16_t));
|
|
|
|
writer(chunk.data, writeBufSzBytes);
|
|
LOG_ALWAYS_FATAL_IF(mRingBuffer.consume(chunk, writeBufSzBytes) < writeBufSzBytes);
|
|
|
|
bytesToRead -= writeBufSzBytes;
|
|
mSentFrames += writeBufSzBytes / mFrameSize;
|
|
} else {
|
|
ALOGW("TinyalsaSource::%s:%d pcm_read was late delivering "
|
|
"frames, inserting %zu us of silence",
|
|
__func__, __LINE__,
|
|
size_t(1000000 * bytesToRead / mFrameSize / mSampleRateHz));
|
|
|
|
static const uint8_t zeroes[256] = {0};
|
|
|
|
while (bytesToRead > 0) {
|
|
const size_t nZeroFrames =
|
|
std::min(bytesToRead, sizeof(zeroes)) / mFrameSize;
|
|
const size_t nZeroBytes = nZeroFrames * mFrameSize;
|
|
|
|
writer(zeroes, nZeroBytes);
|
|
mSentFrames += nZeroFrames;
|
|
bytesToRead -= nZeroBytes;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return mFramesLost.exchange(0);
|
|
}
|
|
|
|
void producerThread() {
|
|
util::setThreadPriority(PRIORITY_URGENT_AUDIO);
|
|
std::vector<uint8_t> readBuf(mReadSizeFrames * mFrameSize);
|
|
|
|
while (mProduceThreadRunning) {
|
|
const size_t bytesLost = mRingBuffer.makeRoomForProduce(readBuf.size());
|
|
mFramesLost += bytesLost / mFrameSize;
|
|
|
|
auto produceChunk = mRingBuffer.getProduceChunk();
|
|
if (produceChunk.size < readBuf.size()) {
|
|
const size_t sz = doRead(readBuf.data(), readBuf.size());
|
|
if (sz > 0) {
|
|
LOG_ALWAYS_FATAL_IF(mRingBuffer.produce(readBuf.data(), sz) < sz);
|
|
}
|
|
} else {
|
|
const size_t sz = doRead(produceChunk.data, readBuf.size());
|
|
if (sz > 0) {
|
|
LOG_ALWAYS_FATAL_IF(mRingBuffer.produce(readBuf.size()) < sz);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t doRead(void *dst, size_t sz) {
|
|
const int res = ::pcm_read(mPcm.get(), dst, sz);
|
|
if (res < 0) {
|
|
ALOGW("TinyalsaSource::%s:%d pcm_read failed with res=%d",
|
|
__func__, __LINE__, res);
|
|
return 0;
|
|
}
|
|
|
|
return sz;
|
|
}
|
|
|
|
static std::unique_ptr<TinyalsaSource> create(unsigned pcmCard,
|
|
unsigned pcmDevice,
|
|
const AudioConfig &cfg,
|
|
size_t writerBufferSizeHint,
|
|
uint64_t &frames) {
|
|
(void)writerBufferSizeHint;
|
|
|
|
auto src = std::make_unique<TinyalsaSource>(pcmCard, pcmDevice,
|
|
cfg, frames);
|
|
if (src->mMixer && src->mPcm) {
|
|
return src;
|
|
} else {
|
|
return FAILURE(nullptr);
|
|
}
|
|
}
|
|
|
|
private:
|
|
const nsecs_t mStartNs;
|
|
const unsigned mSampleRateHz;
|
|
const unsigned mFrameSize;
|
|
const unsigned mReadSizeFrames;
|
|
uint64_t &mFrames;
|
|
uint64_t mPreviousFrames = 0;
|
|
uint64_t mSentFrames = 0;
|
|
std::atomic<uint32_t> mFramesLost = 0;
|
|
RingBuffer mRingBuffer;
|
|
talsa::Mixer mMixer;
|
|
talsa::PcmPtr mPcm;
|
|
std::thread mProduceThread;
|
|
std::atomic<bool> mProduceThreadRunning = true;
|
|
};
|
|
|
|
template <class G> struct GeneratedSource : public DevicePortSource {
|
|
GeneratedSource(const AudioConfig &cfg,
|
|
size_t writerBufferSizeHint,
|
|
uint64_t &frames,
|
|
G generator)
|
|
: mWriteBuffer(writerBufferSizeHint / sizeof(int16_t))
|
|
, mFrames(frames)
|
|
, mStartNs(systemTime(SYSTEM_TIME_MONOTONIC))
|
|
, mSampleRateHz(cfg.sampleRateHz)
|
|
, mNChannels(util::countChannels(cfg.channelMask))
|
|
, mGenerator(std::move(generator)) {}
|
|
|
|
Result getCapturePosition(uint64_t &frames, uint64_t &time) override {
|
|
const nsecs_t nowNs = systemTime(SYSTEM_TIME_MONOTONIC);
|
|
const uint64_t nowFrames = getCaptureFrames(nowNs);
|
|
mFrames += (nowFrames - mPreviousFrames);
|
|
mPreviousFrames = nowFrames;
|
|
frames = mFrames;
|
|
time = nowNs;
|
|
return Result::OK;
|
|
}
|
|
|
|
uint64_t getCaptureFrames(const nsecs_t nowNs) const {
|
|
return uint64_t(mSampleRateHz) * ns2us(nowNs - mStartNs) / 1000000;
|
|
}
|
|
|
|
uint64_t getAvailableFrames(const nsecs_t nowNs) const {
|
|
return getCaptureFrames(nowNs) - mSentFrames;
|
|
}
|
|
|
|
size_t read(float volume, size_t bytesToRead, IWriter &writer) override {
|
|
mWriteBuffer.resize(bytesToRead / sizeof(int16_t));
|
|
|
|
int16_t *samples = mWriteBuffer.data();
|
|
const unsigned nChannels = mNChannels;
|
|
const unsigned requestedFrames = bytesToRead / nChannels / sizeof(*samples);
|
|
|
|
unsigned availableFrames;
|
|
while (true) {
|
|
const nsecs_t nowNs = systemTime(SYSTEM_TIME_MONOTONIC);
|
|
availableFrames = getAvailableFrames(nowNs);
|
|
if (availableFrames < requestedFrames / 2) {
|
|
const unsigned neededMoreFrames = requestedFrames / 2 - availableFrames;
|
|
|
|
using namespace std::chrono_literals;
|
|
std::this_thread::sleep_for(1s * neededMoreFrames / mSampleRateHz);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
const unsigned nFrames = std::min(requestedFrames, availableFrames);
|
|
mGenerator(samples, nFrames);
|
|
const size_t sizeBytes = nFrames * nChannels * sizeof(*samples);
|
|
if (nChannels > 1) {
|
|
adjust_channels(samples, 1, samples, nChannels,
|
|
sizeof(*samples), sizeBytes);
|
|
}
|
|
mSentFrames += nFrames;
|
|
|
|
aops::multiplyByVolume(volume,
|
|
mWriteBuffer.data(),
|
|
sizeBytes / sizeof(int16_t));
|
|
|
|
writer(mWriteBuffer.data(), sizeBytes);
|
|
return 0;
|
|
}
|
|
|
|
private:
|
|
std::vector<int16_t> mWriteBuffer;
|
|
uint64_t &mFrames;
|
|
const nsecs_t mStartNs;
|
|
const unsigned mSampleRateHz;
|
|
const unsigned mNChannels;
|
|
uint64_t mPreviousFrames = 0;
|
|
uint64_t mSentFrames = 0;
|
|
G mGenerator;
|
|
};
|
|
|
|
std::vector<int16_t> convertFloatsToInt16(const std::vector<float> &pcmFloat) {
|
|
std::vector<int16_t> pcmI16(pcmFloat.size());
|
|
|
|
memcpy_by_audio_format(pcmI16.data(), AUDIO_FORMAT_PCM_16_BIT,
|
|
pcmFloat.data(), AUDIO_FORMAT_PCM_FLOAT,
|
|
pcmFloat.size());
|
|
|
|
return pcmI16;
|
|
}
|
|
|
|
// https://en.wikipedia.org/wiki/Busy_signal
|
|
struct BusySignalGenerator {
|
|
explicit BusySignalGenerator(const uint32_t sampleRateHz) : mSampleRateHz(sampleRateHz) {
|
|
// 24/480 = 31/620, mValues must contain 50ms of audio samples
|
|
const size_t sz = sampleRateHz / 20;
|
|
std::vector<float> pcm(sz);
|
|
for (unsigned i = 0; i < sz; ++i) {
|
|
const double a = double(i) * M_PI * 2 / sampleRateHz;
|
|
pcm[i] = .5 * (sin(480 * a) + sin(620 * a));
|
|
}
|
|
mValues = convertFloatsToInt16(pcm);
|
|
}
|
|
|
|
void operator()(int16_t* s, size_t n) {
|
|
const unsigned rate = mSampleRateHz;
|
|
const unsigned rateHalf = rate / 2;
|
|
const int16_t *const vals = mValues.data();
|
|
const size_t valsSz = mValues.size();
|
|
size_t i = mI;
|
|
|
|
while (n > 0) {
|
|
size_t len;
|
|
if (i < rateHalf) {
|
|
const size_t valsOff = i % valsSz;
|
|
len = std::min(n, std::min(rateHalf - i, valsSz - valsOff));
|
|
memcpy(s, vals + valsOff, len * sizeof(*s));
|
|
} else {
|
|
len = std::min(n, rate - i);
|
|
memset(s, 0, len * sizeof(*s));
|
|
}
|
|
s += len;
|
|
i = (i + len) % rate;
|
|
n -= len;
|
|
}
|
|
|
|
mI = i;
|
|
}
|
|
|
|
private:
|
|
const unsigned mSampleRateHz;
|
|
std::vector<int16_t> mValues;
|
|
size_t mI = 0;
|
|
};
|
|
|
|
struct RepeatGenerator {
|
|
explicit RepeatGenerator(const std::vector<float> &pcm)
|
|
: mValues(convertFloatsToInt16(pcm)) {}
|
|
|
|
void operator()(int16_t* s, size_t n) {
|
|
const int16_t *const vals = mValues.data();
|
|
const size_t valsSz = mValues.size();
|
|
size_t i = mI;
|
|
|
|
while (n > 0) {
|
|
const size_t len = std::min(n, valsSz - i);
|
|
memcpy(s, vals + i, len * sizeof(*s));
|
|
s += len;
|
|
i = (i + len) % valsSz;
|
|
n -= len;
|
|
}
|
|
|
|
mI = i;
|
|
}
|
|
|
|
private:
|
|
const std::vector<int16_t> mValues;
|
|
size_t mI = 0;
|
|
};
|
|
|
|
std::vector<float> generateSinePattern(uint32_t sampleRateHz,
|
|
double freq,
|
|
double amp) {
|
|
std::vector<float> result(3 * sampleRateHz / freq + .5);
|
|
|
|
for (size_t i = 0; i < result.size(); ++i) {
|
|
const double a = double(i) * M_PI * 2 / sampleRateHz;
|
|
result[i] = amp * sin(a * freq);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
template <class G> std::unique_ptr<GeneratedSource<G>>
|
|
createGeneratedSource(const AudioConfig &cfg,
|
|
size_t writerBufferSizeHint,
|
|
uint64_t &frames,
|
|
G generator) {
|
|
return std::make_unique<GeneratedSource<G>>(cfg,
|
|
writerBufferSizeHint,
|
|
frames,
|
|
std::move(generator));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::unique_ptr<DevicePortSource>
|
|
DevicePortSource::create(size_t writerBufferSizeHint,
|
|
const DeviceAddress &address,
|
|
const AudioConfig &cfg,
|
|
const hidl_bitfield<AudioOutputFlag> &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::IN_BUILTIN_MIC:
|
|
return TinyalsaSource::create(talsa::kPcmCard, talsa::kPcmDevice,
|
|
cfg, writerBufferSizeHint, frames);
|
|
|
|
case AudioDevice::IN_TELEPHONY_RX:
|
|
return createGeneratedSource(cfg, writerBufferSizeHint, frames,
|
|
BusySignalGenerator(cfg.sampleRateHz));
|
|
|
|
case AudioDevice::IN_FM_TUNER:
|
|
return createGeneratedSource(
|
|
cfg, writerBufferSizeHint, frames,
|
|
RepeatGenerator(generateSinePattern(cfg.sampleRateHz, 440.0, 1.0)));
|
|
|
|
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
|