mirror of
https://github.com/Evolution-X/hardware_interfaces
synced 2026-02-01 11:36:00 +00:00
Since use of StreamSwitcher causes the worker thread to be changed during connected device change, its use for the primary HAL streams must be avoided. The reason is that switching of the FMQ reader thread accompanied with simultaneous writes from two writers: one on the framework side, another on the HAL side sending the "exit" command, violates threading assumptions of blocking FMQ and causes spurious races that eventually make FMQ non-functional. Bug: 300130515 Bug: 368723297 Bug: 369272078 Bug: 369289912 Bug: 369964381 Test: atest VtsHalAudioCoreTargetTest Change-Id: I14dc6fc08ae9e8aaaf3cd80e96b20dd1df54f633
277 lines
11 KiB
C++
277 lines
11 KiB
C++
/*
|
|
* 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
|