blob: 0a34eb21eef59dc21b3be8e3b73a3289d575af61 [file] [log] [blame]
/*
* 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 <log/log.h>
#include <utils/Timers.h>
#include "device_port_sink.h"
#include "talsa.h"
#include "util.h"
#include "debug.h"
namespace android {
namespace hardware {
namespace audio {
namespace V6_0 {
namespace implementation {
namespace {
struct TinyalsaSink : public DevicePortSink {
TinyalsaSink(unsigned pcmCard, unsigned pcmDevice,
const AudioConfig &cfg, uint64_t &frames)
: mFrames(frames)
, mPcm(talsa::pcmOpen(pcmCard, pcmDevice,
util::countChannels(cfg.channelMask),
cfg.sampleRateHz,
cfg.frameCount,
true /* isOut */)) {}
Result getPresentationPosition(uint64_t &frames, TimeSpec &ts) override {
frames = mFrames;
ts = util::nsecs2TimeSpec(systemTime(SYSTEM_TIME_MONOTONIC));
return Result::OK;
}
int write(const void *data, size_t nBytes) override {
const int res = ::pcm_write(mPcm.get(), data, nBytes);
if (res < 0) {
return FAILURE(res);
} else if (res == 0) {
mFrames += ::pcm_bytes_to_frames(mPcm.get(), nBytes);
return nBytes;
} else {
mFrames += ::pcm_bytes_to_frames(mPcm.get(), res);
return res;
}
}
static std::unique_ptr<TinyalsaSink> create(unsigned pcmCard,
unsigned pcmDevice,
const AudioConfig &cfg,
uint64_t &frames) {
auto src = std::make_unique<TinyalsaSink>(pcmCard, pcmDevice, cfg, frames);
if (src->mPcm) {
return src;
} else {
return FAILURE(nullptr);
}
}
private:
uint64_t &mFrames;
talsa::PcmPtr mPcm;
};
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;
}
int write(const void *, size_t nBytes) override {
simulatePresentationPosition();
mAvailableFrames += nBytes / mNChannels / sizeof(int16_t);
return nBytes;
}
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<NullSink> create(const AudioConfig &cfg,
uint64_t &frames) {
return std::make_unique<NullSink>(cfg, frames);
}
private:
uint64_t &mFrames;
const unsigned mSampleRateHz;
const unsigned mNChannels;
uint64_t mAvailableFrames = 0;
nsecs_t mTimestamp;
};
} // namespace
std::unique_ptr<DevicePortSink>
DevicePortSink::create(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::OUT_SPEAKER:
return TinyalsaSink::create(talsa::kPcmCard, talsa::kPcmDevice,
cfg, frames);
case AudioDevice::OUT_TELEPHONY_TX:
return NullSink::create(cfg, 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