blob: 1176d05316205494e7f05e9bec05569a28044699 [file] [log] [blame]
/*
* Copyright (C) 2023 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.
*/
#define LOG_TAG "AHAL_StreamPrimary"
#include <cstdio>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/properties.h>
#include <audio_utils/clock.h>
#include <error/Result.h>
#include <error/expected_utils.h>
#include "core-impl/StreamPrimary.h"
using aidl::android::hardware::audio::common::SinkMetadata;
using aidl::android::hardware::audio::common::SourceMetadata;
using aidl::android::media::audio::common::AudioDevice;
using aidl::android::media::audio::common::AudioDeviceAddress;
using aidl::android::media::audio::common::AudioDeviceDescription;
using aidl::android::media::audio::common::AudioDeviceType;
using aidl::android::media::audio::common::AudioOffloadInfo;
using aidl::android::media::audio::common::MicrophoneInfo;
using android::base::GetBoolProperty;
namespace aidl::android::hardware::audio::core {
StreamPrimary::StreamPrimary(StreamContext* context, const Metadata& metadata)
: StreamAlsa(context, metadata, 3 /*readWriteRetries*/),
mIsAsynchronous(!!getContext().getAsyncCallback()),
mStubDriver(getContext()) {
context->startStreamDataProcessor();
}
::android::status_t StreamPrimary::init() {
RETURN_STATUS_IF_ERROR(mStubDriver.init());
return StreamAlsa::init();
}
::android::status_t StreamPrimary::drain(StreamDescriptor::DrainMode mode) {
return isStubStreamOnWorker() ? mStubDriver.drain(mode) : StreamAlsa::drain(mode);
}
::android::status_t StreamPrimary::flush() {
return isStubStreamOnWorker() ? mStubDriver.flush() : StreamAlsa::flush();
}
::android::status_t StreamPrimary::pause() {
return isStubStreamOnWorker() ? mStubDriver.pause() : StreamAlsa::pause();
}
::android::status_t StreamPrimary::standby() {
return isStubStreamOnWorker() ? mStubDriver.standby() : StreamAlsa::standby();
}
::android::status_t StreamPrimary::start() {
bool isStub = true, shutdownAlsaStream = false;
{
std::lock_guard l(mLock);
isStub = mAlsaDeviceId == kStubDeviceId;
shutdownAlsaStream =
mCurrAlsaDeviceId != mAlsaDeviceId && mCurrAlsaDeviceId != kStubDeviceId;
mCurrAlsaDeviceId = mAlsaDeviceId;
}
if (shutdownAlsaStream) {
StreamAlsa::shutdown(); // Close currently opened ALSA devices.
}
if (isStub) {
return mStubDriver.start();
}
RETURN_STATUS_IF_ERROR(StreamAlsa::start());
mStartTimeNs = ::android::uptimeNanos();
mFramesSinceStart = 0;
mSkipNextTransfer = false;
return ::android::OK;
}
::android::status_t StreamPrimary::transfer(void* buffer, size_t frameCount,
size_t* actualFrameCount, int32_t* latencyMs) {
if (isStubStreamOnWorker()) {
return mStubDriver.transfer(buffer, frameCount, actualFrameCount, latencyMs);
}
// This is a workaround for the emulator implementation which has a host-side buffer
// and is not being able to achieve real-time behavior similar to ADSPs (b/302587331).
if (!mSkipNextTransfer) {
RETURN_STATUS_IF_ERROR(
StreamAlsa::transfer(buffer, frameCount, actualFrameCount, latencyMs));
} else {
LOG(DEBUG) << __func__ << ": skipping transfer (" << frameCount << " frames)";
*actualFrameCount = frameCount;
if (mIsInput) memset(buffer, 0, frameCount * mFrameSizeBytes);
mSkipNextTransfer = false;
}
if (!mIsAsynchronous) {
const long bufferDurationUs =
(*actualFrameCount) * MICROS_PER_SECOND / mContext.getSampleRate();
const auto totalDurationUs =
(::android::uptimeNanos() - mStartTimeNs) / NANOS_PER_MICROSECOND;
mFramesSinceStart += *actualFrameCount;
const long totalOffsetUs =
mFramesSinceStart * MICROS_PER_SECOND / mContext.getSampleRate() - totalDurationUs;
LOG(VERBOSE) << __func__ << ": totalOffsetUs " << totalOffsetUs;
if (totalOffsetUs > 0) {
const long sleepTimeUs = std::min(totalOffsetUs, bufferDurationUs);
LOG(VERBOSE) << __func__ << ": sleeping for " << sleepTimeUs << " us";
usleep(sleepTimeUs);
} else {
mSkipNextTransfer = true;
}
} else {
LOG(VERBOSE) << __func__ << ": asynchronous transfer";
}
return ::android::OK;
}
::android::status_t StreamPrimary::refinePosition(StreamDescriptor::Position*) {
// Since not all data is actually sent to the HAL, use the position maintained by Stream class
// which accounts for all frames passed from / to the client.
return ::android::OK;
}
void StreamPrimary::shutdown() {
StreamAlsa::shutdown();
mStubDriver.shutdown();
}
ndk::ScopedAStatus StreamPrimary::setConnectedDevices(const ConnectedDevices& devices) {
LOG(DEBUG) << __func__ << ": " << ::android::internal::ToString(devices);
if (devices.size() > 1) {
LOG(ERROR) << __func__ << ": primary stream can only be connected to one device, got: "
<< devices.size();
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
{
const bool useStubDriver = devices.empty() || useStubStream(mIsInput, devices[0]);
std::lock_guard l(mLock);
mAlsaDeviceId = useStubDriver ? kStubDeviceId : getCardAndDeviceId(devices);
}
if (!devices.empty()) {
auto streamDataProcessor = getContext().getStreamDataProcessor().lock();
if (streamDataProcessor != nullptr) {
streamDataProcessor->setAudioDevice(devices[0]);
}
}
return StreamAlsa::setConnectedDevices(devices);
}
std::vector<alsa::DeviceProfile> StreamPrimary::getDeviceProfiles() {
return {alsa::DeviceProfile{.card = mCurrAlsaDeviceId.first,
.device = mCurrAlsaDeviceId.second,
.direction = mIsInput ? PCM_IN : PCM_OUT,
.isExternal = false}};
}
bool StreamPrimary::isStubStream() {
std::lock_guard l(mLock);
return mAlsaDeviceId == kStubDeviceId;
}
// static
StreamPrimary::AlsaDeviceId StreamPrimary::getCardAndDeviceId(
const std::vector<AudioDevice>& devices) {
if (devices.empty() || devices[0].address.getTag() != AudioDeviceAddress::id) {
return kDefaultCardAndDeviceId;
}
std::string deviceAddress = devices[0].address.get<AudioDeviceAddress::id>();
AlsaDeviceId cardAndDeviceId;
if (const size_t suffixPos = deviceAddress.rfind("CARD_");
suffixPos == std::string::npos ||
sscanf(deviceAddress.c_str() + suffixPos, "CARD_%d_DEV_%d", &cardAndDeviceId.first,
&cardAndDeviceId.second) != 2) {
return kDefaultCardAndDeviceId;
}
LOG(DEBUG) << __func__ << ": parsed with card id " << cardAndDeviceId.first << ", device id "
<< cardAndDeviceId.second;
return cardAndDeviceId;
}
// static
bool StreamPrimary::useStubStream(
bool isInput, const ::aidl::android::media::audio::common::AudioDevice& device) {
static const bool kSimulateInput =
GetBoolProperty("ro.boot.audio.tinyalsa.simulate_input", false);
static const bool kSimulateOutput =
GetBoolProperty("ro.boot.audio.tinyalsa.ignore_output", false);
if (isInput) {
return kSimulateInput || device.type.type == AudioDeviceType::IN_TELEPHONY_RX ||
device.type.type == AudioDeviceType::IN_FM_TUNER ||
device.type.connection == AudioDeviceDescription::CONNECTION_BUS /*deprecated */;
}
return kSimulateOutput || device.type.type == AudioDeviceType::OUT_TELEPHONY_TX ||
device.type.connection == AudioDeviceDescription::CONNECTION_BUS /*deprecated*/;
}
StreamInPrimary::StreamInPrimary(StreamContext&& context, const SinkMetadata& sinkMetadata,
const std::vector<MicrophoneInfo>& microphones)
: StreamIn(std::move(context), microphones),
StreamPrimary(&mContextInstance, sinkMetadata),
StreamInHwGainHelper(&mContextInstance) {}
ndk::ScopedAStatus StreamInPrimary::getHwGain(std::vector<float>* _aidl_return) {
if (isStubStream()) {
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
float gain;
RETURN_STATUS_IF_ERROR(primary::PrimaryMixer::getInstance().getMicGain(&gain));
_aidl_return->resize(0);
_aidl_return->resize(mChannelCount, gain);
RETURN_STATUS_IF_ERROR(setHwGainImpl(*_aidl_return));
return getHwGainImpl(_aidl_return);
}
ndk::ScopedAStatus StreamInPrimary::setHwGain(const std::vector<float>& in_channelGains) {
if (isStubStream()) {
LOG(DEBUG) << __func__ << ": gains " << ::android::internal::ToString(in_channelGains);
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
auto currentGains = mHwGains;
RETURN_STATUS_IF_ERROR(setHwGainImpl(in_channelGains));
if (in_channelGains.size() < 1) {
LOG(FATAL) << __func__ << ": unexpected gain vector size: " << in_channelGains.size();
}
if (auto status = primary::PrimaryMixer::getInstance().setMicGain(in_channelGains[0]);
!status.isOk()) {
mHwGains = currentGains;
return status;
}
return ndk::ScopedAStatus::ok();
}
StreamOutPrimary::StreamOutPrimary(StreamContext&& context, const SourceMetadata& sourceMetadata,
const std::optional<AudioOffloadInfo>& offloadInfo)
: StreamOut(std::move(context), offloadInfo),
StreamPrimary(&mContextInstance, sourceMetadata),
StreamOutHwVolumeHelper(&mContextInstance) {}
ndk::ScopedAStatus StreamOutPrimary::getHwVolume(std::vector<float>* _aidl_return) {
if (isStubStream()) {
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
RETURN_STATUS_IF_ERROR(primary::PrimaryMixer::getInstance().getVolumes(_aidl_return));
_aidl_return->resize(mChannelCount);
RETURN_STATUS_IF_ERROR(setHwVolumeImpl(*_aidl_return));
return getHwVolumeImpl(_aidl_return);
}
ndk::ScopedAStatus StreamOutPrimary::setHwVolume(const std::vector<float>& in_channelVolumes) {
if (isStubStream()) {
LOG(DEBUG) << __func__ << ": volumes " << ::android::internal::ToString(in_channelVolumes);
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
auto currentVolumes = mHwVolumes;
RETURN_STATUS_IF_ERROR(setHwVolumeImpl(in_channelVolumes));
if (auto status = primary::PrimaryMixer::getInstance().setVolumes(in_channelVolumes);
!status.isOk()) {
mHwVolumes = currentVolumes;
return status;
}
return ndk::ScopedAStatus::ok();
}
} // namespace aidl::android::hardware::audio::core