Files
hardware_interfaces/audio/aidl/default/primary/StreamPrimary.cpp
Mikhail Naganov 05fc6aa383 audio: Fix AudioRecordTest#testTimestamp CTS on CF
After experimenting, it seems that the input poll
is more stable after the PCM input on CF has just
been opened. Since calling 'flush' on an input
stream puts it into the 'STANDBY' state, explicitly
call 'StreamAlsa::standby' after 'flush' in the
primary input stream implementation.

Bug: 340899868
Bug: 362852052
Bug: 372951987
Test: atest CtsMediaAudioTestCases
Test: atest VtsHalAudioCoreTargetTest
Change-Id: Ic1df6835ce00323ca3f0905ea46d3bc151e05fc4
2024-10-11 14:09:40 -07:00

279 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_STATUS_IF_ERROR(isStubStreamOnWorker() ? mStubDriver.flush() : StreamAlsa::flush());
// TODO(b/372951987): consider if this needs to be done from 'StreamInWorkerLogic::cycle'.
return mIsInput ? standby() : ::android::OK;
}
::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