blob: 7325a91b49bf99d5a6e78dacf7106c4e43b8bdff [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 <android-base/logging.h>
#include <android-base/properties.h>
#include <audio_utils/clock.h>
#include <error/Result.h>
#include <error/expected_utils.h>
#include "PrimaryMixer.h"
#include "core-impl/StreamPrimary.h"
#include "core-impl/StreamStub.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::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()) {
context->startStreamDataProcessor();
}
::android::status_t StreamPrimary::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) {
// 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;
}
std::vector<alsa::DeviceProfile> StreamPrimary::getDeviceProfiles() {
static const std::vector<alsa::DeviceProfile> kBuiltInSource{
alsa::DeviceProfile{.card = primary::PrimaryMixer::kAlsaCard,
.device = primary::PrimaryMixer::kAlsaDevice,
.direction = PCM_IN,
.isExternal = false}};
static const std::vector<alsa::DeviceProfile> kBuiltInSink{
alsa::DeviceProfile{.card = primary::PrimaryMixer::kAlsaCard,
.device = primary::PrimaryMixer::kAlsaDevice,
.direction = PCM_OUT,
.isExternal = false}};
return mIsInput ? kBuiltInSource : kBuiltInSink;
}
StreamInPrimary::StreamInPrimary(StreamContext&& context, const SinkMetadata& sinkMetadata,
const std::vector<MicrophoneInfo>& microphones)
: StreamIn(std::move(context), microphones),
StreamSwitcher(&mContextInstance, sinkMetadata),
StreamInHwGainHelper(&mContextInstance) {}
bool StreamInPrimary::useStubStream(const AudioDevice& device) {
static const bool kSimulateInput =
GetBoolProperty("ro.boot.audio.tinyalsa.simulate_input", false);
return kSimulateInput || device.type.type == AudioDeviceType::IN_TELEPHONY_RX ||
device.type.type == AudioDeviceType::IN_FM_TUNER ||
device.type.connection == AudioDeviceDescription::CONNECTION_BUS /*deprecated */ ||
(device.type.type == AudioDeviceType::IN_BUS && device.type.connection.empty());
}
StreamSwitcher::DeviceSwitchBehavior StreamInPrimary::switchCurrentStream(
const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices) {
LOG(DEBUG) << __func__;
if (devices.size() > 1) {
LOG(ERROR) << __func__ << ": primary stream can only be connected to one device, got: "
<< devices.size();
return DeviceSwitchBehavior::UNSUPPORTED_DEVICES;
}
if (devices.empty() || useStubStream(devices[0]) == isStubStream()) {
return DeviceSwitchBehavior::USE_CURRENT_STREAM;
}
return DeviceSwitchBehavior::CREATE_NEW_STREAM;
}
std::unique_ptr<StreamCommonInterfaceEx> StreamInPrimary::createNewStream(
const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices,
StreamContext* context, const Metadata& metadata) {
if (devices.empty()) {
LOG(FATAL) << __func__ << ": called with empty devices"; // see 'switchCurrentStream'
}
if (useStubStream(devices[0])) {
return std::unique_ptr<StreamCommonInterfaceEx>(
new InnerStreamWrapper<StreamStub>(context, metadata));
}
return std::unique_ptr<StreamCommonInterfaceEx>(
new InnerStreamWrapper<StreamPrimary>(context, metadata));
}
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),
StreamSwitcher(&mContextInstance, sourceMetadata),
StreamOutHwVolumeHelper(&mContextInstance) {}
bool StreamOutPrimary::useStubStream(const AudioDevice& device) {
static const bool kSimulateOutput =
GetBoolProperty("ro.boot.audio.tinyalsa.ignore_output", false);
return kSimulateOutput || device.type.type == AudioDeviceType::OUT_TELEPHONY_TX ||
device.type.connection == AudioDeviceDescription::CONNECTION_BUS /*deprecated*/ ||
(device.type.type == AudioDeviceType::OUT_BUS && device.type.connection.empty());
}
StreamSwitcher::DeviceSwitchBehavior StreamOutPrimary::switchCurrentStream(
const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices) {
LOG(DEBUG) << __func__;
if (devices.size() > 1) {
LOG(ERROR) << __func__ << ": primary stream can only be connected to one device, got: "
<< devices.size();
return DeviceSwitchBehavior::UNSUPPORTED_DEVICES;
}
if (devices.empty() || useStubStream(devices[0]) == isStubStream()) {
return DeviceSwitchBehavior::USE_CURRENT_STREAM;
}
return DeviceSwitchBehavior::CREATE_NEW_STREAM;
}
std::unique_ptr<StreamCommonInterfaceEx> StreamOutPrimary::createNewStream(
const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices,
StreamContext* context, const Metadata& metadata) {
if (devices.empty()) {
LOG(FATAL) << __func__ << ": called with empty devices"; // see 'switchCurrentStream'
}
if (useStubStream(devices[0])) {
return std::unique_ptr<StreamCommonInterfaceEx>(
new InnerStreamWrapper<StreamStub>(context, metadata));
}
return std::unique_ptr<StreamCommonInterfaceEx>(
new InnerStreamWrapper<StreamPrimary>(context, metadata));
}
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();
}
ndk::ScopedAStatus StreamOutPrimary::setConnectedDevices(
const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices) {
if (!devices.empty()) {
auto streamDataProcessor = mContextInstance.getStreamDataProcessor().lock();
if (streamDataProcessor != nullptr) {
streamDataProcessor->setAudioDevice(devices[0]);
}
}
return StreamSwitcher::setConnectedDevices(devices);
}
} // namespace aidl::android::hardware::audio::core