1 Commits

Author SHA1 Message Date
oxmc 8d5f56fd05 Sync to upstream android-17.0 (AOSP17)
Pull in raspberry-vanilla's AOSP17 device tree changes for rpi4 while
keeping all PawletOS customizations (A/B partition layout, sepolicy,
zram, TWRP recovery scaffolding, ALSA HDMI pre-warm, product/overlay
structure) intact on top.

Upstream changes absorbed:
- AOSP17 audio HAL refactor (StreamAlsa split into StreamAlsaBase/
  StreamAlsaMonoPipe, Utils.cpp -> UtilsAlsa.cpp, new Bluetooth/stub
  helper classes, libeffects AIDL rebuild via *_ndk_shared defaults)
- API level 36 -> 37, FCM matrix level 202504 -> 202604
- QuickSettings tile default split (SystemUIRpiOverlay, ported into
  pawlet_rpi_common where this overlay now lives)
- vendor_tracing_descriptors added to enforce-product-packages-exist
- misc AOSP17 logging header switch (android-base/logging.h -> Log.h)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 00:16:17 -07:00
96 changed files with 3900 additions and 1534 deletions
+1 -1
View File
@@ -1 +1 @@
Raspberry Vanilla AOSP 16 device configuration for Raspberry Pi 4.
Raspberry Vanilla AOSP 17 device configuration for Raspberry Pi 4.
+1 -1
View File
@@ -15,7 +15,7 @@
*/
#define LOG_TAG "AHAL_AidlXsdc"
#include <android-base/logging.h>
#include <Log.h>
#include <error/expected_utils.h>
#include <media/AidlConversionCppNdk.h>
#include <media/TypeConverter.h>
+48 -9
View File
@@ -7,13 +7,9 @@ cc_defaults {
name: "aidlaudioservice_rpi_defaults",
vendor: true,
shared_libs: [
"android.hardware.audio.core-V3-ndk",
"android.hardware.audio.core.sounddose-V3-ndk",
"android.hardware.bluetooth.audio-V5-ndk",
"android.hardware.bluetooth.audio-impl",
"android.hardware.common-V2-ndk",
"android.hardware.common.fmq-V1-ndk",
"android.media.audio.common.types-V4-ndk",
"libalsautilsv2-rpi",
"libasound",
"libaudio_aidl_conversion_common_ndk",
@@ -43,6 +39,15 @@ cc_defaults {
"-Werror",
"-Wextra",
"-Wthread-safety",
] + select(release_flag("RELEASE_HARDWARE_AUDIO_USE_CAP_AIDL"), {
true: [],
default: ["-DDISABLE_CAP_AIDL"],
}),
defaults: [
"latest_android_hardware_audio_core_ndk_shared",
"latest_android_hardware_audio_core_sounddose_ndk_shared",
"latest_android_hardware_bluetooth_audio_ndk_shared",
"latest_android_media_audio_common_types_ndk_shared",
],
}
@@ -67,9 +72,12 @@ cc_library {
"alsa/Mixer.cpp",
"alsa/ModuleAlsa.cpp",
"alsa/StreamAlsa.cpp",
"alsa/Utils.cpp",
"alsa/StreamAlsaBase.cpp",
"alsa/StreamAlsaMonoPipe.cpp",
"alsa/UtilsAlsa.cpp",
"bluetooth/DevicePortProxy.cpp",
"bluetooth/ModuleBluetooth.cpp",
"bluetooth/ModuleBluetoothBase.cpp",
"bluetooth/StreamBluetooth.cpp",
"deprecated/StreamSwitcher.cpp",
"primary/PrimaryMixer.cpp",
@@ -80,6 +88,7 @@ cc_library {
"stub/ApeHeader.cpp",
"stub/DriverStubImpl.cpp",
"stub/ModuleStub.cpp",
"stub/MpegAudioHeader.cpp",
"stub/StreamMmapStub.cpp",
"stub/StreamOffloadStub.cpp",
"stub/StreamStub.cpp",
@@ -128,13 +137,38 @@ cc_library {
],
}
filegroup {
name: "effectCommonFileRpi",
cc_library_static {
name: "libaudioeffectaidlcommon-rpi",
srcs: [
"EffectContext.cpp",
"EffectImpl.cpp",
"EffectThread.cpp",
],
defaults: [
"latest_android_media_audio_common_types_ndk_shared",
"latest_android_hardware_audio_effect_ndk_shared",
],
shared_libs: [
"libaudioaidlcommon",
"libaudioutils",
"libfmq",
],
static_libs: [
"libaudioaidlranges",
],
header_libs: [
"libaudioaidl_headers",
],
cflags: [
"-Wall",
"-Wextra",
"-Werror",
"-Wthread-safety",
"-DBACKEND_NDK",
],
export_include_dirs: ["include"],
host_supported: true,
vendor: true,
}
cc_binary {
@@ -152,8 +186,6 @@ cc_defaults {
name: "aidlaudioeffectservice_rpi_defaults",
vendor: true,
shared_libs: [
"android.hardware.audio.effect-V3-ndk",
"android.media.audio.common.types-V4-ndk",
"libapexsupport",
"libaudio_aidl_conversion_common_ndk",
"libaudioutils",
@@ -172,6 +204,10 @@ cc_defaults {
"-Werror",
"-Wextra",
],
defaults: [
"latest_android_hardware_audio_effect_ndk_shared",
"latest_android_media_audio_common_types_ndk_shared",
],
}
cc_binary {
@@ -186,6 +222,9 @@ cc_binary {
"libeffectconfig-rpi",
"libfmq",
],
static_libs: [
"libaudioeffectaidlcommon-rpi",
],
header_libs: [
"libsystem_headers",
],
+1 -1
View File
@@ -22,7 +22,7 @@
#include <unordered_map>
#define LOG_TAG "AHAL_ApmXmlConverter"
#include <android-base/logging.h>
#include <Log.h>
#include <aidl/android/media/audio/common/AudioHalEngineConfig.h>
#include <media/stagefright/foundation/MediaDefs.h>
+13 -2
View File
@@ -15,7 +15,7 @@
*/
#define LOG_TAG "AHAL_Bluetooth"
#include <android-base/logging.h>
#include <Log.h>
#include "core-impl/Bluetooth.h"
@@ -31,30 +31,37 @@ Bluetooth::Bluetooth() {
mScoConfig.isNrecEnabled = Boolean{false};
mScoConfig.mode = ScoConfig::Mode::SCO;
mHfpConfig.isEnabled = Boolean{false};
mHfpConfig.sampleRate = Int{8000};
mHfpConfig.sampleRate = Int{32000};
mHfpConfig.volume = Float{HfpConfig::VOLUME_MAX};
}
ndk::ScopedAStatus Bluetooth::setScoConfig(const ScoConfig& in_config, ScoConfig* _aidl_return) {
bool updated = false;
if (in_config.isEnabled.has_value()) {
mScoConfig.isEnabled = in_config.isEnabled;
updated = true;
}
if (in_config.isNrecEnabled.has_value()) {
mScoConfig.isNrecEnabled = in_config.isNrecEnabled;
updated = true;
}
if (in_config.mode != ScoConfig::Mode::UNSPECIFIED) {
mScoConfig.mode = in_config.mode;
updated = true;
}
if (in_config.debugName.has_value()) {
mScoConfig.debugName = in_config.debugName;
updated = true;
}
*_aidl_return = mScoConfig;
LOG(DEBUG) << __func__ << ": received " << in_config.toString() << ", returning "
<< _aidl_return->toString();
if (updated && mHandler) return mHandler();
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Bluetooth::setHfpConfig(const HfpConfig& in_config, HfpConfig* _aidl_return) {
bool updated = false;
if (in_config.sampleRate.has_value() && in_config.sampleRate.value().value <= 0) {
LOG(ERROR) << __func__ << ": invalid sample rate: " << in_config.sampleRate.value().value;
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
@@ -67,16 +74,20 @@ ndk::ScopedAStatus Bluetooth::setHfpConfig(const HfpConfig& in_config, HfpConfig
if (in_config.isEnabled.has_value()) {
mHfpConfig.isEnabled = in_config.isEnabled;
updated = true;
}
if (in_config.sampleRate.has_value()) {
mHfpConfig.sampleRate = in_config.sampleRate;
updated = true;
}
if (in_config.volume.has_value()) {
mHfpConfig.volume = in_config.volume;
updated = true;
}
*_aidl_return = mHfpConfig;
LOG(DEBUG) << __func__ << ": received " << in_config.toString() << ", returning "
<< _aidl_return->toString();
if (updated && mHandler) return mHandler();
return ndk::ScopedAStatus::ok();
}
+1 -3
View File
@@ -16,8 +16,8 @@
#define LOG_TAG "AHAL_Config"
#include <Log.h>
#include <aidl/android/media/audio/common/AudioProductStrategyType.h>
#include <android-base/logging.h>
#include <media/AidlConversionCppNdk.h>
#include <media/TypeConverter.h>
#include <media/convert.h>
@@ -56,7 +56,6 @@ static constexpr const char* gLegacyStrategyPrefix = "STRATEGY_";
static constexpr const char* gLegacyOutputDevicePrefix = "AUDIO_DEVICE_OUT_";
static constexpr const char* gLegacyInputDevicePrefix = "AUDIO_DEVICE_IN_";
static constexpr const char* gLegacyStreamPrefix = "AUDIO_STREAM_";
static constexpr const char* gLegacySourcePrefix = "AUDIO_SOURCE_";
std::optional<std::vector<std::optional<AudioHalCapDomain>>>&
CapEngineConfigXmlConverter::getAidlCapEngineConfig() {
@@ -121,7 +120,6 @@ ConversionResult<AudioHalCapRule::CriterionRule> convertCriterionRuleToAidl(
ConversionResult<AudioHalCapRule> convertRule(const eng_xsd::CompoundRuleType& xsdcCompoundRule) {
AudioHalCapRule rule{};
bool isPreviousCompoundRule = true;
if (xsdcCompoundRule.getType() == eng_xsd::TypeEnum::Any) {
rule.compoundRule = AudioHalCapRule::CompoundRule::ANY;
} else if (xsdcCompoundRule.getType() == eng_xsd::TypeEnum::All) {
+1 -1
View File
@@ -15,7 +15,7 @@
*/
#define LOG_TAG "AHAL_Config"
#include <android-base/logging.h>
#include <Log.h>
#include <system/audio_config.h>
+39 -1
View File
@@ -264,7 +264,7 @@ std::unique_ptr<Configuration> getPrimaryConfiguration() {
c.ports.push_back(primaryInMix);
AudioPort telephonyTxOutMix =
createPort(c.nextPortId++, "telephony_tx", 0, false, createPortMixExt(1, 1));
createPort(c.nextPortId++, kPortNameTelephonyTx, 0, false, createPortMixExt(1, 1));
telephonyTxOutMix.profiles.insert(telephonyTxOutMix.profiles.begin(),
standardPcmAudioProfiles.begin(),
standardPcmAudioProfiles.end());
@@ -604,24 +604,36 @@ std::unique_ptr<Configuration> getStubConfiguration() {
// - profile PCM 16-bit; STEREO; 44100, 48000, 88200, 96000
// * "BT Hearing Aid Out", OUT_HEARING_AID, CONNECTION_WIRELESS
// - no profiles specified
// * "BLE Headset Out", OUT_HEADSET, CONNECTION_BT_LE
// - no profiles specified
// * "BLE Headset In", IN_HEADSET, CONNECTION_BT_LE
// - no profiles specified
//
// Mix ports:
// * "a2dp output", 1 max open, 1 max active stream
// - no profiles specified
// * "hearing aid output", 1 max open, 1 max active stream
// - profile PCM 16-bit; STEREO; 16000, 24000
// * "le audio output", 1 max open, 1 max active stream
// - profile PCM 16-bit; STEREO; 44100, 48000
// * "le audio input", 1 max open, 1 max active stream
// - profile PCM 16-bit; STEREO; 44100, 48000
//
// Routes:
// "a2dp output" -> "BT A2DP Out"
// "a2dp output" -> "BT A2DP Headphones"
// "a2dp output" -> "BT A2DP Speaker"
// "hearing aid output" -> "BT Hearing Aid Out"
// "le audio output" -> "BLE Headset Out"
// "BLE Headset In" -> "le audio input"
//
// Profiles for device port connected state (when simulating connections):
// * "BT A2DP Out", "BT A2DP Headphones", "BT A2DP Speaker":
// - profile PCM 16-bit; STEREO; 44100, 48000, 88200, 96000
// * "BT Hearing Aid Out":
// - profile PCM 16-bit; STEREO; 16000, 24000
// * "BLE Headset Out", "BLE Headset In"
// - profile PCM 16-bit; STEREO; 44100, 48000
//
std::unique_ptr<Configuration> getBluetoothConfiguration() {
static const Configuration configuration = []() {
@@ -630,6 +642,8 @@ std::unique_ptr<Configuration> getBluetoothConfiguration() {
{44100, 48000, 88200, 96000})};
const std::vector<AudioProfile> hearingAidAudioProfiles = {createProfile(
PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {16000, 24000})};
const std::vector<AudioProfile> leAudioProfiles = {createProfile(
PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {44100, 48000})};
Configuration c;
// Device ports
@@ -669,6 +683,20 @@ std::unique_ptr<Configuration> getBluetoothConfiguration() {
c.ports.push_back(btOutHearingAid);
c.connectedProfiles[btOutHearingAid.id] = hearingAidAudioProfiles;
AudioPort btLeOutHeadset =
createPort(c.nextPortId++, "BLE Headset Out", 0, false,
createDeviceExt(AudioDeviceType::OUT_HEADSET, 0,
AudioDeviceDescription::CONNECTION_BT_LE));
c.ports.push_back(btLeOutHeadset);
c.connectedProfiles[btLeOutHeadset.id] = leAudioProfiles;
AudioPort btLeInHeadset =
createPort(c.nextPortId++, "BLE Headset In", 0, true,
createDeviceExt(AudioDeviceType::IN_HEADSET, 0,
AudioDeviceDescription::CONNECTION_BT_LE));
c.ports.push_back(btLeInHeadset);
c.connectedProfiles[btLeInHeadset.id] = leAudioProfiles;
// Mix ports
AudioPort btOutMix =
createPort(c.nextPortId++, "a2dp output", 0, false, createPortMixExt(1, 1));
@@ -679,10 +707,20 @@ std::unique_ptr<Configuration> getBluetoothConfiguration() {
btHearingOutMix.profiles = hearingAidAudioProfiles;
c.ports.push_back(btHearingOutMix);
AudioPort btLeOutMix =
createPort(c.nextPortId++, "le audio output", 0, false, createPortMixExt(1, 1));
c.ports.push_back(btLeOutMix);
AudioPort btLeInMix =
createPort(c.nextPortId++, "le audio input", 0, true, createPortMixExt(1, 1));
c.ports.push_back(btLeInMix);
c.routes.push_back(createRoute({btOutMix}, btOutDevice));
c.routes.push_back(createRoute({btOutMix}, btOutHeadphone));
c.routes.push_back(createRoute({btOutMix}, btOutSpeaker));
c.routes.push_back(createRoute({btHearingOutMix}, btOutHearingAid));
c.routes.push_back(createRoute({btLeOutMix}, btLeOutHeadset));
c.routes.push_back(createRoute({btLeInHeadset}, btLeInMix));
return c;
}();
+1
View File
@@ -311,6 +311,7 @@ bool EffectConfig::findUuid(const std::pair<std::string, struct EffectLibraries>
V("downmix", Downmix) \
V("dynamics_processing", DynamicsProcessing) \
V("equalizer", Equalizer) \
V("eraser", Eraser) \
V("extensioneffect", Extension) \
V("haptic_generator", HapticGenerator) \
V("loudness_enhancer", LoudnessEnhancer) \
+11 -1
View File
@@ -300,7 +300,7 @@ ndk::ScopedAStatus EffectImpl::command(CommandId command) {
"CommandIdNotSupported");
}
LOG(VERBOSE) << getEffectNameWithVersion() << __func__
<< " transfer to state: " << toString(mState);
<< " transfer to state: " << toString(mState) << " with " << toString(command);
return ndk::ScopedAStatus::ok();
}
@@ -392,6 +392,7 @@ void EffectImpl::process() {
auto outputMQ = mImplContext->getOutputDataFmq();
auto buffer = mImplContext->getWorkBuffer();
if (!inputMQ || !outputMQ) {
LOG(WARNING) << __func__ << " skip processing with empty FMQs";
return;
}
@@ -403,6 +404,8 @@ void EffectImpl::process() {
IEffect::Status status = effectProcessImpl(buffer, buffer, processSamples);
outputMQ->write(buffer, status.fmqProduced);
statusMQ->writeBlocking(&status, 1);
} else {
drainingComplete_l();
}
}
}
@@ -415,4 +418,11 @@ IEffect::Status EffectImpl::effectProcessImpl(float* in, float* out, int samples
return {STATUS_OK, samples, samples};
}
void EffectImpl::drainingComplete_l() {
if (mState != State::DRAINING) return;
finishDraining();
mState = State::IDLE;
}
} // namespace aidl::android::hardware::audio::effect
+8 -5
View File
@@ -21,10 +21,10 @@
#include <unordered_map>
#define LOG_TAG "AHAL_Config"
#include <Log.h>
#include <aidl/android/media/audio/common/AudioFlag.h>
#include <aidl/android/media/audio/common/AudioHalEngineConfig.h>
#include <aidl/android/media/audio/common/AudioProductStrategyType.h>
#include <android-base/logging.h>
#include "core-impl/CapEngineConfigXmlConverter.h"
#include "core-impl/EngineConfigXmlConverter.h"
@@ -127,9 +127,6 @@ ConversionResult<AudioAttributes> EngineConfigXmlConverter::convertAudioAttribut
ConversionResult<AudioHalAttributesGroup> EngineConfigXmlConverter::convertAttributesGroupToAidl(
const eng_xsd::AttributesGroup& xsdcAttributesGroup) {
AudioHalAttributesGroup aidlAttributesGroup;
static const int kStreamTypeEnumOffset =
static_cast<int>(eng_xsd::Stream::AUDIO_STREAM_VOICE_CALL) -
static_cast<int>(AudioStreamType::VOICE_CALL);
aidlAttributesGroup.streamType = xsdcAttributesGroup.hasStreamType()
? VALUE_OR_FATAL(convertAudioStreamTypeToAidl(
xsdcAttributesGroup.getStreamType()))
@@ -173,7 +170,9 @@ ConversionResult<AudioHalProductStrategy> EngineConfigXmlConverter::convertProdu
VALUE_OR_FATAL(convertProductStrategyNameToAidl(xsdcProductStrategy.getName()));
}
aidlProductStrategy.name = xsdcProductStrategy.getName();
if (xsdcProductStrategy.hasZoneId()) {
aidlProductStrategy.zoneId = xsdcProductStrategy.getZoneId();
}
if (xsdcProductStrategy.hasAttributesGroup()) {
aidlProductStrategy.attributesGroups = VALUE_OR_FATAL(
(convertCollectionToAidl<eng_xsd::AttributesGroup, AudioHalAttributesGroup>(
@@ -254,6 +253,9 @@ void EngineConfigXmlConverter::init() {
}
if (getXsdcConfig()->hasCriteria() && getXsdcConfig()->hasCriterion_types()) {
AudioHalEngineConfig::CapSpecificConfig capSpecificConfig;
// In hybrid mode, we use legacy XML file, that would not be compatible with AIDL
// (e.g. some device like ambient or incommunication deprecated...)
#ifndef DISABLE_CAP_AIDL
capSpecificConfig.criteriaV2 =
std::make_optional<>(VALUE_OR_FATAL((convertCapCriteriaCollectionToAidl(
getXsdcConfig()->getCriteria(), getXsdcConfig()->getCriterion_types()))));
@@ -262,6 +264,7 @@ void EngineConfigXmlConverter::init() {
if (capEngConfigConverter.getStatus() == ::android::OK) {
capSpecificConfig.domains = std::move(capEngConfigConverter.getAidlCapEngineConfig());
}
#endif
mAidlEngineConfig.capSpecificConfig = capSpecificConfig;
}
}
+67 -9
View File
@@ -18,9 +18,9 @@
#include <set>
#define LOG_TAG "AHAL_Module"
#include <Log.h>
#include <aidl/android/media/audio/common/AudioInputFlags.h>
#include <aidl/android/media/audio/common/AudioOutputFlags.h>
#include <android-base/logging.h>
#include <android/binder_ibinder_platform.h>
#include <error/expected_utils.h>
@@ -61,6 +61,7 @@ using aidl::android::media::audio::common::AudioPortConfig;
using aidl::android::media::audio::common::AudioPortExt;
using aidl::android::media::audio::common::AudioProfile;
using aidl::android::media::audio::common::Boolean;
using aidl::android::media::audio::common::FlushFromFrameSupport;
using aidl::android::media::audio::common::Int;
using aidl::android::media::audio::common::MicrophoneInfo;
using aidl::android::media::audio::common::PcmType;
@@ -185,9 +186,10 @@ ndk::ScopedAStatus Module::createStreamContext(
// Since this is a private method, it is assumed that
// validity of the portConfigId has already been checked.
int32_t minimumStreamBufferSizeFrames = 0;
if (!calculateBufferSizeFrames(
portConfigIt->format.value(), nominalLatencyMs,
portConfigIt->sampleRate.value().value, &minimumStreamBufferSizeFrames).isOk()) {
if (!calculateBufferSizeFrames(portConfigIt->format.value(), portConfigIt->flags.value(),
nominalLatencyMs, portConfigIt->sampleRate.value().value,
&minimumStreamBufferSizeFrames)
.isOk()) {
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
if (in_bufferSizeFrames < minimumStreamBufferSizeFrames) {
@@ -392,8 +394,9 @@ int32_t Module::getNominalLatencyMs(const AudioPortConfig&) {
}
ndk::ScopedAStatus Module::calculateBufferSizeFrames(
const ::aidl::android::media::audio::common::AudioFormatDescription &format,
int32_t latencyMs, int32_t sampleRateHz, int32_t *bufferSizeFrames) {
const ::aidl::android::media::audio::common::AudioFormatDescription& format,
const AudioIoFlags& /*flags*/, int32_t latencyMs, int32_t sampleRateHz,
int32_t* bufferSizeFrames) {
if (format.type == AudioFormatType::PCM) {
*bufferSizeFrames = calculateBufferSizeFramesForPcm(latencyMs, sampleRateHz);
return ndk::ScopedAStatus::ok();
@@ -623,11 +626,21 @@ ndk::ScopedAStatus Module::updateStreamsConnectedState(const AudioPatch& oldPatc
}
binder_status_t Module::dump(int fd, const char** args, uint32_t numArgs) {
if (::aidl::android::hardware::audio::common::hasArgument(
args, numArgs,
::aidl::android::hardware::audio::common::kDumpFromAudioServerArgument)) {
std::ostringstream s;
s << mType;
// Not needed in the case of a dedicated module dump.
dprintf(fd, "\n[Module %s] ", s.str().c_str());
}
dprintf(fd, "Stream dumps:\n");
for (const auto& portConfig : getConfig().portConfigs) {
if (portConfig.ext.getTag() == AudioPortExt::Tag::mix) {
getStreams().dump(portConfig.id, fd, args, numArgs);
}
}
dprintf(fd, "\n");
return STATUS_OK;
}
@@ -972,10 +985,24 @@ ndk::ScopedAStatus Module::getAudioRoutesForAudioPort(int32_t in_portId,
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Module::validateMetadataAttributeTags(const std::vector<std::string>& tags) {
for (auto& tag : tags) {
if (!common::isVendorExtension(tag)) {
LOG(ERROR) << __func__ << ": " << mType << ": metadata attribute tag " << tag
<< " is invalid.";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
}
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Module::openInputStream(const OpenInputStreamArguments& in_args,
OpenInputStreamReturn* _aidl_return) {
LOG(DEBUG) << __func__ << ": " << mType << ": port config id " << in_args.portConfigId
<< ", buffer size " << in_args.bufferSizeFrames << " frames";
for (const auto& track : in_args.sinkMetadata.tracks) {
RETURN_STATUS_IF_ERROR(validateMetadataAttributeTags(track.tags));
}
AudioPort* port = nullptr;
RETURN_STATUS_IF_ERROR(findPortIdForNewStream(in_args.portConfigId, &port));
if (port->flags.getTag() != AudioIoFlags::Tag::input) {
@@ -1008,6 +1035,16 @@ ndk::ScopedAStatus Module::openOutputStream(const OpenOutputStreamArguments& in_
LOG(DEBUG) << __func__ << ": " << mType << ": port config id " << in_args.portConfigId
<< ", has offload info? " << (in_args.offloadInfo.has_value()) << ", buffer size "
<< in_args.bufferSizeFrames << " frames";
for (const auto& track : in_args.sourceMetadata.tracks) {
RETURN_STATUS_IF_ERROR(validateMetadataAttributeTags(track.tags));
if (const auto& codecMime = track.codecProvenance;
codecMime.has_value() && !codecMime->empty()) {
if (!aidl::android::hardware::audio::common::isAudioMimeType(*codecMime)) {
LOG(ERROR) << __func__ << ": invalid audio MIME type: \"" << *codecMime << "\"";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
}
}
AudioPort* port = nullptr;
RETURN_STATUS_IF_ERROR(findPortIdForNewStream(in_args.portConfigId, &port));
if (port->flags.getTag() != AudioIoFlags::Tag::output) {
@@ -1153,8 +1190,9 @@ ndk::ScopedAStatus Module::setAudioPatch(const AudioPatch& in_requested, AudioPa
auto maxSampleRateIt = std::max_element(sampleRates.begin(), sampleRates.end());
const int32_t latencyMs = getNominalLatencyMs(*(maxSampleRateIt->second));
if (!calculateBufferSizeFrames(
maxSampleRateIt->second->format.value(), latencyMs, maxSampleRateIt->first,
&_aidl_return->minimumStreamBufferSizeFrames).isOk()) {
maxSampleRateIt->second->format.value(), maxSampleRateIt->second->flags.value(),
latencyMs, maxSampleRateIt->first, &_aidl_return->minimumStreamBufferSizeFrames)
.isOk()) {
if (patchesBackup.has_value()) {
mPatches = std::move(*patchesBackup);
}
@@ -1227,6 +1265,15 @@ ndk::ScopedAStatus Module::setAudioPortConfigImpl(
<< ": requested port config points to non-existent portId " << portId;
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
if (portIt->ext.getTag() == AudioPortExt::Tag::device &&
!portIt->ext.get<AudioPortExt::Tag::device>().device.type.connection.empty() &&
mConnectedDevicePorts.find(portId) == mConnectedDevicePorts.end()) {
LOG(ERROR) << __func__ << ": " << mType << ": requested portId " << portId
<< " is not connected to an external device (is it a template port?).";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
if (existing != configs.end()) {
*out_suggested = *existing;
} else {
@@ -1362,11 +1409,14 @@ ndk::ScopedAStatus Module::setAudioPortConfigImpl(
}
bool Module::setAudioPortConfigGain(const AudioPort& port, const AudioGainConfig& gainRequested) {
auto& ports = getConfig().ports;
if (gainRequested.index < 0 || gainRequested.index >= (int)port.gains.size()) {
LOG(ERROR) << __func__ << ": gains for port " << port.id << " is undefined";
return false;
}
if (gainRequested.values.empty()) {
LOG(ERROR) << __func__ << ": received empty gain values";
return false;
}
int stepValue = port.gains[gainRequested.index].stepValue;
if (stepValue == 0) {
LOG(ERROR) << __func__ << ": port gain step value is 0";
@@ -1427,6 +1477,8 @@ ndk::ScopedAStatus Module::resetAudioPatch(int32_t in_patchId) {
ndk::ScopedAStatus Module::resetAudioPortConfig(int32_t in_portConfigId) {
auto& configs = getConfig().portConfigs;
LOG(DEBUG) << __func__ << ": " << mType << ": in_portConfigId " << in_portConfigId
<< " configs size: " << configs.size();
auto configIt = findById<AudioPortConfig>(configs, in_portConfigId);
if (configIt != configs.end()) {
if (mStreams.count(in_portConfigId) != 0) {
@@ -1838,4 +1890,10 @@ ndk::ScopedAStatus Module::bluetoothParametersUpdated() {
return mStreams.bluetoothParametersUpdated();
}
ndk::ScopedAStatus Module::getFlushFromFrameSupport(const AudioPortConfig& in_config __unused,
FlushFromFrameSupport* _aidl_return __unused) {
LOG(VERBOSE) << __func__ << ": " << mType;
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
} // namespace aidl::android::hardware::audio::core
+49 -8
View File
@@ -17,13 +17,15 @@
#include <vector>
#define LOG_TAG "AHAL_ModulePrimary"
#include <Log.h>
#include <Utils.h>
#include <android-base/logging.h>
#include "core-impl/Configuration.h"
#include "core-impl/ModulePrimary.h"
#include "core-impl/StreamMmapStub.h"
#include "core-impl/StreamOffloadStub.h"
#include "core-impl/StreamPrimary.h"
#include "core-impl/StreamStub.h"
#include "core-impl/Telephony.h"
using aidl::android::hardware::audio::common::areAllBitPositionFlagsSet;
@@ -38,6 +40,7 @@ using aidl::android::media::audio::common::AudioOutputFlags;
using aidl::android::media::audio::common::AudioPort;
using aidl::android::media::audio::common::AudioPortConfig;
using aidl::android::media::audio::common::AudioPortExt;
using aidl::android::media::audio::common::FlushFromFrameSupport;
using aidl::android::media::audio::common::MicrophoneInfo;
namespace aidl::android::hardware::audio::core {
@@ -54,13 +57,16 @@ ndk::ScopedAStatus ModulePrimary::getTelephony(std::shared_ptr<ITelephony>* _aid
ndk::ScopedAStatus ModulePrimary::calculateBufferSizeFrames(
const ::aidl::android::media::audio::common::AudioFormatDescription& format,
int32_t latencyMs, int32_t sampleRateHz, int32_t* bufferSizeFrames) {
if (format.type != ::aidl::android::media::audio::common::AudioFormatType::PCM &&
StreamOffloadStub::getSupportedEncodings().count(format.encoding)) {
const ::aidl::android::media::audio::common::AudioIoFlags& flags, int32_t latencyMs,
int32_t sampleRateHz, int32_t* bufferSizeFrames) {
if ((format.type != ::aidl::android::media::audio::common::AudioFormatType::PCM &&
StreamOffloadStub::getSupportedEncodings().count(format.encoding)) ||
common::isPcmOffload(format, flags)) {
*bufferSizeFrames = sampleRateHz / 2; // 1/2 of a second.
return ndk::ScopedAStatus::ok();
}
return Module::calculateBufferSizeFrames(format, latencyMs, sampleRateHz, bufferSizeFrames);
return Module::calculateBufferSizeFrames(format, flags, latencyMs, sampleRateHz,
bufferSizeFrames);
}
ndk::ScopedAStatus ModulePrimary::createInputStream(StreamContext&& context,
@@ -89,9 +95,32 @@ ndk::ScopedAStatus ModulePrimary::createOutputStream(
// "Stub" is used because there is no actual decoder. The stream just
// extracts the clip duration from the media file header and simulates
// playback over time.
if (context.getSampleRate() <= 0) {
LOG(ERROR) << __func__
<< ": Invalid sample rate for offload stream: " << context.getSampleRate();
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
return createStreamInstance<StreamOutOffloadStub>(result, std::move(context),
sourceMetadata, offloadInfo);
}
const auto& activeConfigs = getConfig().portConfigs;
int32_t handle = context.getMixPortHandle();
auto it = std::find_if(activeConfigs.begin(), activeConfigs.end(), [&](const auto& config) {
return config.ext.getTag() == AudioPortExt::Tag::mix &&
config.ext.template get<AudioPortExt::Tag::mix>().handle == handle;
});
if (it != activeConfigs.end()) {
int32_t portId = it->portId;
auto& ports = getConfig().ports;
auto foundPort = findById(ports, portId);
if (foundPort != ports.end() && foundPort->name == internal::kPortNameTelephonyTx) {
return createStreamInstance<StreamOutTelephonyStub>(result, std::move(context),
sourceMetadata, offloadInfo);
}
}
return createStreamInstance<StreamOutPrimary>(result, std::move(context), sourceMetadata,
offloadInfo);
}
@@ -114,14 +143,14 @@ ndk::ScopedAStatus ModulePrimary::createMmapBuffer(const AudioPortConfig& portCo
}
desc->sharedMemory.fd = ndk::ScopedFileDescriptor(fd);
desc->sharedMemory.size = bufferSizeBytes;
desc->burstSizeFrames = bufferSizeFrames / 2;
desc->flags = 0;
desc->burstSizeFrames = bufferSizeFrames / 4;
desc->flags = 1 << MmapBufferDescriptor::FLAG_INDEX_APPLICATION_SHAREABLE;
LOG(DEBUG) << __func__ << ": " << desc->toString();
return ndk::ScopedAStatus::ok();
}
int32_t ModulePrimary::getNominalLatencyMs(const AudioPortConfig& portConfig) {
static constexpr int32_t kLowLatencyMs = 5;
static constexpr int32_t kLowLatencyMs = 10;
// 85 ms is chosen considering 4096 frames @ 48 kHz. This is the value which allows
// the virtual Android device implementation to pass CTS. Hardware implementations
// should have significantly lower latency.
@@ -129,4 +158,16 @@ int32_t ModulePrimary::getNominalLatencyMs(const AudioPortConfig& portConfig) {
return hasMmapFlag(portConfig.flags.value()) ? kLowLatencyMs : kStandardLatencyMs;
}
ndk::ScopedAStatus ModulePrimary::getFlushFromFrameSupport(const AudioPortConfig& in_config,
FlushFromFrameSupport* _aidl_return) {
LOG(DEBUG) << __func__ << ": config=" << in_config.toString();
if (!in_config.flags.has_value() || !in_config.format.has_value()) {
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
*_aidl_return = common::isPcmOffload(in_config.format.value(), in_config.flags.value())
? FlushFromFrameSupport::SUPPORTED
: FlushFromFrameSupport::UNSUPPORTED;
return ndk::ScopedAStatus::ok();
}
} // namespace aidl::android::hardware::audio::core
+1 -1
View File
@@ -18,8 +18,8 @@
#include "core-impl/SoundDose.h"
#include <Log.h>
#include <aidl/android/hardware/audio/core/sounddose/ISoundDose.h>
#include <android-base/logging.h>
#include <media/AidlConversionCppNdk.h>
#include <utils/Timers.h>
+178 -10
View File
@@ -15,11 +15,12 @@
*/
#include <pthread.h>
#include <thread>
#define ATRACE_TAG ATRACE_TAG_AUDIO
#define LOG_TAG "AHAL_Stream"
#include <Log.h>
#include <Utils.h>
#include <android-base/logging.h>
#include <android/binder_ibinder_platform.h>
#include <cutils/properties.h>
#include <utils/SystemClock.h>
@@ -36,12 +37,14 @@ 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::AudioDualMonoMode;
using aidl::android::media::audio::common::AudioFormatType;
using aidl::android::media::audio::common::AudioInputFlags;
using aidl::android::media::audio::common::AudioIoFlags;
using aidl::android::media::audio::common::AudioLatencyMode;
using aidl::android::media::audio::common::AudioOffloadInfo;
using aidl::android::media::audio::common::AudioOutputFlags;
using aidl::android::media::audio::common::AudioPlaybackRate;
using aidl::android::media::audio::common::FlushFromFrameAccuracy;
using aidl::android::media::audio::common::MicrophoneDynamicInfo;
using aidl::android::media::audio::common::MicrophoneInfo;
@@ -196,6 +199,27 @@ void StreamWorkerCommonLogic::populateReplyWrongState(
reply->status = STATUS_INVALID_OPERATION;
}
void StreamWorkerCommonLogic::populateReplyUnsupportedCommand(
StreamDescriptor::Reply* reply, const StreamDescriptor::Command& command) const {
LOG(WARNING) << "command '" << toString(command.getTag()) << "' is not supported by the stream";
reply->status = STATUS_INVALID_OPERATION;
}
void StreamWorkerCommonLogic::switchFromTransientState(StreamDescriptor::State state) {
if (mTransientStateDelayMs.count() != 0) {
if (auto stateDurationMs = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - mTransientStateStart);
stateDurationMs < mTransientStateDelayMs) {
const auto delayMs = mTransientStateDelayMs - stateDurationMs;
LOG(DEBUG) << __func__ << ": inducing transient state delay when switching from "
<< toString(mState) << " to " << toString(state) << " of " << delayMs
<< "ms";
std::this_thread::sleep_for(delayMs);
}
}
mState = state;
}
const std::string StreamInWorkerLogic::kThreadName = "reader";
StreamInWorkerLogic::Status StreamInWorkerLogic::cycle() {
@@ -346,6 +370,10 @@ StreamInWorkerLogic::Status StreamInWorkerLogic::cycle() {
populateReplyWrongState(&reply, command);
}
break;
case Tag::flushFromFrame:
LOG(ERROR) << __func__ << ": flushFromFrame is not supported for input stream";
populateReplyUnsupportedCommand(&reply, command);
break;
}
reply.state = mState;
LOG(severity) << __func__ << ": writing reply " << reply.toString();
@@ -416,14 +444,31 @@ bool StreamInWorkerLogic::readMmap(StreamDescriptor::Reply* reply) {
const std::string StreamOutWorkerLogic::kThreadName = "writer";
using StreamOutWorkerLogicCall =
::android::hardware::audio::common::PostponedMethodCall<StreamOutWorkerLogic>;
void StreamOutWorkerLogic::onBufferStateChange(size_t bufferFramesLeft) {
// It is assumed that all 'on...StateChange' callbacks originate from the same thread,
// thus it is impossible to get one callback while processing another.
if (!mCallTasks.tryObtainOrPush(StreamOutWorkerLogicCall::create(
this, &StreamOutWorkerLogic::onBufferStateChangeImpl, (size_t)bufferFramesLeft))) {
// Call queued, will be executed after 'cycle' ends processing command.
return;
}
onBufferStateChangeImpl(bufferFramesLeft);
if (!mCallTasks.release()) {
LOG(FATAL) << __func__ << ": call tasks queue release failed";
}
}
void StreamOutWorkerLogic::onBufferStateChangeImpl(size_t bufferFramesLeft) {
const StreamDescriptor::State state = mState;
const DrainState drainState = mDrainState;
LOG(DEBUG) << __func__ << ": state: " << toString(state) << ", drainState: " << drainState
<< ", bufferFramesLeft: " << bufferFramesLeft;
if (state == StreamDescriptor::State::TRANSFERRING || drainState == DrainState::EN_SENT) {
if (state == StreamDescriptor::State::TRANSFERRING) {
mState = StreamDescriptor::State::ACTIVE;
switchFromTransientState(StreamDescriptor::State::ACTIVE);
}
std::shared_ptr<IStreamCallback> asyncCallback = mContext->getAsyncCallback();
if (asyncCallback != nullptr) {
@@ -437,18 +482,34 @@ void StreamOutWorkerLogic::onBufferStateChange(size_t bufferFramesLeft) {
}
void StreamOutWorkerLogic::onClipStateChange(size_t clipFramesLeft, bool hasNextClip) {
if (!mCallTasks.tryObtainOrPush(
StreamOutWorkerLogicCall::create(this, &StreamOutWorkerLogic::onClipStateChangeImpl,
(size_t)clipFramesLeft, (bool)hasNextClip))) {
// Call queued, will be executed after 'cycle' ends processing command.
return;
}
onClipStateChangeImpl(clipFramesLeft, hasNextClip);
if (!mCallTasks.release()) {
LOG(FATAL) << __func__ << ": call tasks queue release failed";
}
}
void StreamOutWorkerLogic::onClipStateChangeImpl(size_t clipFramesLeft, bool hasNextClip) {
const DrainState drainState = mDrainState;
std::shared_ptr<IStreamCallback> asyncCallback = mContext->getAsyncCallback();
LOG(DEBUG) << __func__ << ": drainState: " << drainState << "; clipFramesLeft "
<< clipFramesLeft << "; hasNextClip? " << hasNextClip << "; asyncCallback? "
<< (asyncCallback != nullptr);
if (drainState != DrainState::NONE && clipFramesLeft == 0) {
mState =
hasNextClip ? StreamDescriptor::State::TRANSFERRING : StreamDescriptor::State::IDLE;
if (hasNextClip) {
switchToTransientState(StreamDescriptor::State::TRANSFERRING);
} else {
switchFromTransientState(StreamDescriptor::State::IDLE);
}
mDrainState = DrainState::NONE;
if ((drainState == DrainState::ALL || drainState == DrainState::EN_SENT) &&
asyncCallback != nullptr) {
LOG(DEBUG) << __func__ << ": sending onDrainReady";
LOG(DEBUG) << __func__ << ": sending onDrainReady (end of clip)";
// For EN_SENT, this is the second onDrainReady which notifies about clip transition.
ndk::ScopedAStatus status = asyncCallback->onDrainReady();
if (!status.isOk()) {
@@ -459,7 +520,7 @@ void StreamOutWorkerLogic::onClipStateChange(size_t clipFramesLeft, bool hasNext
// The stream state does not change, it is still draining.
mDrainState = DrainState::EN_SENT;
if (asyncCallback != nullptr) {
LOG(DEBUG) << __func__ << ": sending onDrainReady";
LOG(DEBUG) << __func__ << ": sending onDrainReady (ready for next clip data)";
ndk::ScopedAStatus status = asyncCallback->onDrainReady();
if (!status.isOk()) {
LOG(ERROR) << __func__ << ": error from onDrainReady: " << status;
@@ -469,7 +530,7 @@ void StreamOutWorkerLogic::onClipStateChange(size_t clipFramesLeft, bool hasNext
}
StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
// Non-blocking mode is handled within 'onClipStateChange'
// Non-blocking mode is handled within 'on{Buffer|Clip}StateChange'
if (std::shared_ptr<IStreamCallback> asyncCallback = mContext->getAsyncCallback();
mState == StreamDescriptor::State::DRAINING && asyncCallback == nullptr) {
if (auto stateDurationMs = std::chrono::duration_cast<std::chrono::milliseconds>(
@@ -499,7 +560,14 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
<< kThreadName;
StreamDescriptor::Reply reply{};
reply.status = STATUS_BAD_VALUE;
::android::hardware::audio::common::PostponedMethodsCaller<StreamOutWorkerLogic> caller(
mCallTasks);
using Tag = StreamDescriptor::Command::Tag;
// Do not need to obtain the call tasks queue for commands that do not change the state.
if (command.getTag() != Tag::halReservedExit && command.getTag() != Tag::getStatus) {
// Waits until any ongoing execution of 'on...StateChange' call finishes.
caller.obtainQueue();
}
switch (command.getTag()) {
case Tag::halReservedExit: {
const int32_t cookie = command.get<Tag::halReservedExit>();
@@ -572,7 +640,7 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
if (asyncCallback == nullptr ||
mState != StreamDescriptor::State::DRAIN_PAUSED) {
mState = StreamDescriptor::State::PAUSED;
} else {
} else if (mDrainState != DrainState::EN_SENT) {
mState = StreamDescriptor::State::TRANSFER_PAUSED;
}
} else if (mState == StreamDescriptor::State::IDLE ||
@@ -680,6 +748,56 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
populateReplyWrongState(&reply, command);
}
break;
case Tag::flushFromFrame: {
if (mContext->isOffload() && mContext->getFormat().type == AudioFormatType::PCM) {
bool validState = false;
switch (mState) {
case StreamDescriptor::State::ACTIVE:
case StreamDescriptor::State::TRANSFERRING:
case StreamDescriptor::State::DRAINING:
case StreamDescriptor::State::TRANSFER_PAUSED:
case StreamDescriptor::State::PAUSED:
case StreamDescriptor::State::DRAIN_PAUSED:
validState = true;
break;
default:
LOG(ERROR)
<< __func__
<< ": flushFromFrame, invalid state: " << toString(mState.load());
populateReplyWrongState(&reply, command);
}
if (validState) {
const int32_t flushFromFrameRequest = command.get<Tag::flushFromFrame>();
const FlushFromFrameAccuracy accuracy =
(FlushFromFrameAccuracy)((flushFromFrameRequest >>
StreamDescriptor::
FLUSH_FROM_FRAME_POSITION_BITS) &
0x0f);
if (accuracy == FlushFromFrameAccuracy::BEST_EFFORT ||
accuracy == FlushFromFrameAccuracy::EXACT) {
static const int32_t kPositionMask =
(1 << StreamDescriptor::FLUSH_FROM_FRAME_POSITION_BITS) - 1;
int position = flushFromFrameRequest & kPositionMask;
const ::android::status_t status = mDriver->flushFromFrame(
accuracy, position, &reply.flushFromPosition);
populateReply(&reply, mIsConnected);
if (status == ::android::BAD_VALUE) {
LOG(INFO) << __func__ << ": flushFromFrame(" << position
<< "), flushFromPosition=" << reply.flushFromPosition;
reply.status = STATUS_BAD_VALUE;
} else if (status != ::android::OK) {
LOG(ERROR) << __func__ << ": flushFromFrame failed: " << status;
reply.status = STATUS_INVALID_OPERATION;
}
} else {
LOG(ERROR) << __func__ << ": invalid accuracy: " << toString(accuracy);
reply.status = STATUS_BAD_VALUE;
}
}
} else {
populateReplyUnsupportedCommand(&reply, command);
}
} break;
}
reply.state = mState;
LOG(severity) << __func__ << ": writing reply " << reply.toString();
@@ -705,8 +823,8 @@ bool StreamOutWorkerLogic::write(size_t clientSize, StreamDescriptor::Reply* rep
// Amount of data that the HAL module is going to actually use.
size_t byteCount = std::min({clientSize, readByteCount, mDataBufferSize});
if (byteCount >= frameSize && mContext->getForceTransientBurst()) {
// In order to prevent the state machine from going to ACTIVE state,
// simulate partial write.
// In order to prevent the state machine from going to ACTIVE state for full write
// (sync) transfers, simulate a partial write.
byteCount -= frameSize;
}
size_t actualFrameCount = 0;
@@ -839,6 +957,12 @@ ndk::ScopedAStatus StreamCommonImpl::removeEffect(
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
ndk::ScopedAStatus StreamCommonImpl::createMmapBuffer(MmapBufferDescriptor* _aidl_return) {
LOG(DEBUG) << __func__;
(void)_aidl_return;
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
ndk::ScopedAStatus StreamCommonImpl::close() {
LOG(DEBUG) << __func__;
if (!isClosed()) {
@@ -927,12 +1051,38 @@ void StreamCommonImpl::stopWorker() {
mWorkerStopIssued = true;
}
ndk::ScopedAStatus validateMetadataAttributeTags(const std::vector<std::string>& tags) {
for (auto& tag : tags) {
if (!common::isVendorExtension(tag)) {
LOG(ERROR) << __func__ << ": metadata attribute tag " << tag << " is invalid.";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
}
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus StreamCommonImpl::updateMetadataCommon(const Metadata& metadata) {
LOG(DEBUG) << __func__;
if (!isClosed()) {
if (metadata.index() != mMetadata.index()) {
LOG(FATAL) << __func__ << ": changing metadata variant is not allowed";
}
ndk::ScopedAStatus status = std::visit(
[&](const auto& data) -> ndk::ScopedAStatus {
for (const auto& track : data.tracks) {
ndk::ScopedAStatus status = validateMetadataAttributeTags(track.tags);
if (!status.isOk()) {
return status;
}
}
return ndk::ScopedAStatus::ok();
},
metadata);
if (!status.isOk()) {
return status;
}
mMetadata = metadata;
return ndk::ScopedAStatus::ok();
}
@@ -1066,6 +1216,11 @@ void StreamOut::defaultOnClose() {
mContextInstance.reset();
}
ndk::ScopedAStatus StreamOut::updateMetadata(const SourceMetadata& in_sourceMetadata) {
RETURN_STATUS_IF_ERROR(validateMetadata(in_sourceMetadata));
return updateMetadataCommon(in_sourceMetadata);
}
ndk::ScopedAStatus StreamOut::updateOffloadMetadata(
const AudioOffloadMetadata& in_offloadMetadata) {
LOG(DEBUG) << __func__;
@@ -1162,6 +1317,19 @@ ndk::ScopedAStatus StreamOut::selectPresentation(int32_t in_presentationId, int3
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
ndk::ScopedAStatus StreamOut::validateMetadata(const SourceMetadata& sourceMetadata) {
for (const auto& track : sourceMetadata.tracks) {
if (const auto& codecMime = track.codecProvenance;
codecMime.has_value() && !codecMime->empty()) {
if (!aidl::android::hardware::audio::common::isAudioMimeType(*codecMime)) {
LOG(ERROR) << __func__ << ": invalid audio MIME type: \"" << *codecMime << "\"";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
}
}
return ndk::ScopedAStatus::ok();
}
StreamOutHwVolumeHelper::StreamOutHwVolumeHelper(const StreamContext* context)
: mChannelCount(getChannelCount(context->getChannelLayout())) {}
+1 -1
View File
@@ -15,7 +15,7 @@
*/
#define LOG_TAG "AHAL_Telephony"
#include <android-base/logging.h>
#include <Log.h>
#include <android/binder_to_string.h>
#include <Utils.h>
+23 -14
View File
@@ -19,12 +19,13 @@
#include <unordered_set>
#define LOG_TAG "AHAL_Config"
#include <android-base/logging.h>
#include <Log.h>
#include <android-base/strings.h>
#include <android/binder_enums.h>
#include <aidl/android/media/audio/common/AudioPort.h>
#include <aidl/android/media/audio/common/AudioPortConfig.h>
#include <aidl/android/media/audio/common/AudioProductStrategyType.h>
#include <media/AidlConversionCppNdk.h>
#include <media/TypeConverter.h>
#include <media/convert.h>
@@ -59,6 +60,7 @@ using aidl::android::media::audio::common::AudioPortConfig;
using aidl::android::media::audio::common::AudioPortDeviceExt;
using aidl::android::media::audio::common::AudioPortExt;
using aidl::android::media::audio::common::AudioPortMixExt;
using aidl::android::media::audio::common::AudioProductStrategyType;
using aidl::android::media::audio::common::AudioProfile;
using aidl::android::media::audio::common::AudioSource;
using aidl::android::media::audio::common::AudioStreamType;
@@ -284,7 +286,14 @@ ConversionResult<int> convertGainModeToAidl(const std::vector<ap_xsd::AudioGainM
gainModeMask |= static_cast<int>(legacyGainMode);
}
}
return gainModeMask;
ConversionResult<int> result = legacy2aidl_audio_gain_mode_t_int32_t_mask(
static_cast<audio_gain_mode_t>(gainModeMask));
if (!result.ok()) {
LOG(ERROR) << __func__ << " Review Audio Policy config, " << gainModeMask
<< " has invalid gain mode(s).";
return unexpected(BAD_VALUE);
}
return result;
}
ConversionResult<AudioChannelLayout> convertChannelMaskToAidl(
@@ -817,21 +826,21 @@ ConversionResult<AudioHalVolumeCurve::CurvePoint> convertCurvePointToAidl(
return aidlCurvePoint;
}
/**
* The hard coded id must be in sync with policy.h definition of legacy strategy ids.
*/
std::unordered_map<std::string, int> getLegacyProductStrategyMap() {
#define STRATEGY_ENTRY(name, id) {"STRATEGY_" #name, static_cast<int>(id)}
return {STRATEGY_ENTRY(MEDIA, 5),
STRATEGY_ENTRY(PHONE, 0),
STRATEGY_ENTRY(SONIFICATION, 1),
STRATEGY_ENTRY(SONIFICATION_RESPECTFUL, 4),
STRATEGY_ENTRY(DTMF, 6),
STRATEGY_ENTRY(ENFORCED_AUDIBLE, 2),
STRATEGY_ENTRY(CALL_ASSISTANT, 7),
STRATEGY_ENTRY(TRANSMITTED_THROUGH_SPEAKER,8),
STRATEGY_ENTRY(ACCESSIBILITY, 3)};
return {STRATEGY_ENTRY(MEDIA, AudioProductStrategyType::MEDIA),
STRATEGY_ENTRY(PHONE, AudioProductStrategyType::PHONE),
STRATEGY_ENTRY(SONIFICATION, AudioProductStrategyType::SONIFICATION),
STRATEGY_ENTRY(SONIFICATION_RESPECTFUL,
AudioProductStrategyType::SONIFICATION_RESPECTFUL),
STRATEGY_ENTRY(DTMF, AudioProductStrategyType::DTMF),
STRATEGY_ENTRY(ENFORCED_AUDIBLE, AudioProductStrategyType::ENFORCED_AUDIBLE),
STRATEGY_ENTRY(CALL_ASSISTANT, AudioProductStrategyType::CALL_ASSISTANT),
STRATEGY_ENTRY(TRANSMITTED_THROUGH_SPEAKER,
AudioProductStrategyType::TRANSMITTED_THROUGH_SPEAKER),
STRATEGY_ENTRY(ACCESSIBILITY, AudioProductStrategyType::ACCESSIBILITY),
STRATEGY_ENTRY(ASSISTANT, AudioProductStrategyType::ASSISTANT)};
#undef STRATEGY_ENTRY
}
+1 -1
View File
@@ -18,7 +18,7 @@
#include <cmath>
#define LOG_TAG "AHAL_AlsaMixer"
#include <android-base/logging.h>
#include <Log.h>
#include <android/binder_status.h>
#include <error/expected_utils.h>
+11 -3
View File
@@ -18,9 +18,9 @@
#include <vector>
#include <android-base/logging.h>
#include <Log.h>
#include "Utils.h"
#include "UtilsAlsa.h"
#include "core-impl/ModuleAlsa.h"
extern "C" {
@@ -45,7 +45,8 @@ ndk::ScopedAStatus ModuleAlsa::populateConnectedDevicePort(AudioPort* audioPort,
}
alsa_device_profile* profile = proxy.getProfile();
std::vector<AudioChannelLayout> channels = alsa::getChannelMasksFromProfile(profile);
std::vector<AudioChannelLayout> channels =
alsa::getChannelMasksFromProfile(profile, isDevicePortSupportAmbisonics(*audioPort));
std::vector<int> sampleRates = alsa::getSampleRatesFromProfile(profile);
for (size_t i = 0; i < std::min(MAX_PROFILE_FORMATS, AUDIO_PORT_MAX_AUDIO_PROFILES) &&
@@ -65,4 +66,11 @@ ndk::ScopedAStatus ModuleAlsa::populateConnectedDevicePort(AudioPort* audioPort,
return ndk::ScopedAStatus::ok();
}
bool ModuleAlsa::isDevicePortSupportAmbisonics(const AudioPort&) {
// The default implementation always returns 'false'. Vendor implementations
// may supply logic which is based on the knowledge of SoC capabilities,
// or based on USB device ID.
return false;
}
} // namespace aidl::android::hardware::audio::core
+16 -258
View File
@@ -14,88 +14,25 @@
* limitations under the License.
*/
#include <cmath>
#include <limits>
#define LOG_TAG "AHAL_StreamAlsa"
#include <android-base/logging.h>
#include <Utils.h>
#include <audio_utils/clock.h>
#include <error/expected_utils.h>
#include <media/AidlConversionCppNdk.h>
#include <Log.h>
#include "UtilsAlsa.h"
#include "core-impl/StreamAlsa.h"
using aidl::android::hardware::audio::common::getChannelCount;
namespace aidl::android::hardware::audio::core {
StreamAlsa::StreamAlsa(StreamContext* context, const Metadata& metadata, int readWriteRetries)
: StreamCommonImpl(context, metadata),
mBufferSizeFrames(getContext().getBufferSizeInFrames()),
mFrameSizeBytes(getContext().getFrameSize()),
mSampleRate(getContext().getSampleRate()),
mIsInput(isInput(metadata)),
mConfig(alsa::getPcmConfig(getContext(), mIsInput)),
mReadWriteRetries(readWriteRetries) {}
: StreamAlsaBase(context, metadata, readWriteRetries) {}
StreamAlsa::~StreamAlsa() {
cleanupWorker();
}
::android::NBAIO_Format StreamAlsa::getPipeFormat() const {
const audio_format_t audioFormat = VALUE_OR_FATAL(
aidl2legacy_AudioFormatDescription_audio_format_t(getContext().getFormat()));
const int channelCount = getChannelCount(getContext().getChannelLayout());
return ::android::Format_from_SR_C(getContext().getSampleRate(), channelCount, audioFormat);
}
::android::sp<::android::MonoPipe> StreamAlsa::makeSink(bool writeCanBlock) {
const ::android::NBAIO_Format format = getPipeFormat();
auto sink = ::android::sp<::android::MonoPipe>::make(mBufferSizeFrames, format, writeCanBlock);
const ::android::NBAIO_Format offers[1] = {format};
size_t numCounterOffers = 0;
ssize_t index = sink->negotiate(offers, 1, nullptr, numCounterOffers);
LOG_IF(FATAL, index != 0) << __func__ << ": Negotiation for the sink failed, index = " << index;
return sink;
}
::android::sp<::android::MonoPipeReader> StreamAlsa::makeSource(::android::MonoPipe* pipe) {
const ::android::NBAIO_Format format = getPipeFormat();
const ::android::NBAIO_Format offers[1] = {format};
auto source = ::android::sp<::android::MonoPipeReader>::make(pipe);
size_t numCounterOffers = 0;
ssize_t index = source->negotiate(offers, 1, nullptr, numCounterOffers);
LOG_IF(FATAL, index != 0) << __func__
<< ": Negotiation for the source failed, index = " << index;
return source;
}
::android::status_t StreamAlsa::init(DriverCallbackInterface* /*callback*/) {
return mConfig.has_value() ? ::android::OK : ::android::NO_INIT;
}
::android::status_t StreamAlsa::drain(StreamDescriptor::DrainMode) {
if (!mIsInput) {
static constexpr float kMicrosPerSecond = MICROS_PER_SECOND;
const size_t delayUs = static_cast<size_t>(
std::roundf(mBufferSizeFrames * kMicrosPerSecond / mSampleRate));
usleep(delayUs);
}
return ::android::OK;
}
::android::status_t StreamAlsa::flush() {
return ::android::OK;
}
::android::status_t StreamAlsa::pause() {
return ::android::OK;
}
::android::status_t StreamAlsa::standby() {
teardownIo();
mAlsaDeviceProxies.clear();
return ::android::OK;
}
@@ -104,54 +41,7 @@ StreamAlsa::~StreamAlsa() {
// This is a resume after a pause.
return ::android::OK;
}
decltype(mAlsaDeviceProxies) alsaDeviceProxies;
decltype(mSources) sources;
decltype(mSinks) sinks;
for (const auto& device : getDeviceProfiles()) {
if ((device.direction == PCM_OUT && mIsInput) ||
(device.direction == PCM_IN && !mIsInput)) {
continue;
}
alsa::DeviceProxy proxy;
if (device.isExternal) {
// Always ask alsa configure as required since the configuration should be supported
// by the connected device. That is guaranteed by `setAudioPortConfig` and
// `setAudioPatch`.
proxy = alsa::openProxyForExternalDevice(
device, const_cast<struct pcm_config*>(&mConfig.value()),
true /*require_exact_match*/);
} else {
proxy = alsa::openProxyForAttachedDevice(
device, const_cast<struct pcm_config*>(&mConfig.value()), mBufferSizeFrames);
}
if (proxy.get() == nullptr) {
return ::android::NO_INIT;
}
alsaDeviceProxies.push_back(std::move(proxy));
auto sink = makeSink(mIsInput); // Do not block the writer when it is on our thread.
if (sink != nullptr) {
sinks.push_back(sink);
} else {
return ::android::NO_INIT;
}
if (auto source = makeSource(sink.get()); source != nullptr) {
sources.push_back(source);
} else {
return ::android::NO_INIT;
}
}
if (alsaDeviceProxies.empty()) {
return ::android::NO_INIT;
}
mAlsaDeviceProxies = std::move(alsaDeviceProxies);
mSources = std::move(sources);
mSinks = std::move(sinks);
mIoThreadIsRunning = true;
for (size_t i = 0; i < mAlsaDeviceProxies.size(); ++i) {
mIoThreads.emplace_back(mIsInput ? &StreamAlsa::inputIoThread : &StreamAlsa::outputIoThread,
this, i);
}
return ::android::OK;
return openDeviceProxies(&mAlsaDeviceProxies);
}
::android::status_t StreamAlsa::transfer(void* buffer, size_t frameCount, size_t* actualFrameCount,
@@ -163,28 +53,20 @@ StreamAlsa::~StreamAlsa() {
const size_t bytesToTransfer = frameCount * mFrameSizeBytes;
unsigned maxLatency = 0;
if (mIsInput) {
const size_t i = 0; // For the input case, only support a single device.
LOG(VERBOSE) << __func__ << ": reading from sink " << i;
ssize_t framesRead = mSources[i]->read(buffer, frameCount);
LOG_IF(FATAL, framesRead < 0) << "Error reading from the pipe: " << framesRead;
if (ssize_t framesMissing = static_cast<ssize_t>(frameCount) - framesRead;
framesMissing > 0) {
LOG(WARNING) << __func__ << ": incomplete data received, inserting " << framesMissing
<< " frames of silence";
memset(static_cast<char*>(buffer) + framesRead * mFrameSizeBytes, 0,
framesMissing * mFrameSizeBytes);
if (int ret = proxy_read_with_retries(mAlsaDeviceProxies[0].get(), buffer, bytesToTransfer,
mReadWriteRetries);
ret != 0) {
LOG(WARNING) << __func__ << ": read failed: " << ret;
return ::android::INVALID_OPERATION;
}
maxLatency = proxy_get_latency(mAlsaDeviceProxies[i].get());
maxLatency = proxy_get_latency(mAlsaDeviceProxies[0].get());
} else {
alsa::applyGain(buffer, mGain, bytesToTransfer, mConfig.value().format, mConfig->channels);
for (size_t i = 0; i < mAlsaDeviceProxies.size(); ++i) {
LOG(VERBOSE) << __func__ << ": writing into sink " << i;
ssize_t framesWritten = mSinks[i]->write(buffer, frameCount);
LOG_IF(FATAL, framesWritten < 0) << "Error writing into the pipe: " << framesWritten;
if (ssize_t framesLost = static_cast<ssize_t>(frameCount) - framesWritten;
framesLost > 0) {
LOG(WARNING) << __func__ << ": sink " << i << " incomplete data sent, dropping "
<< framesLost << " frames";
if (int ret = proxy_write_with_retries(mAlsaDeviceProxies[i].get(), buffer,
bytesToTransfer, mReadWriteRetries);
ret != 0) {
LOG(WARNING) << __func__ << ": write failed for device " << i << ": " << ret;
}
maxLatency = std::max(maxLatency, proxy_get_latency(mAlsaDeviceProxies[i].get()));
}
@@ -195,132 +77,8 @@ StreamAlsa::~StreamAlsa() {
return ::android::OK;
}
::android::status_t StreamAlsa::refinePosition(StreamDescriptor::Position* position) {
if (mAlsaDeviceProxies.empty()) {
LOG(WARNING) << __func__ << ": no opened devices";
return ::android::NO_INIT;
}
// Since the proxy can only count transferred frames since its creation,
// we override its counter value with ours and let it to correct for buffered frames.
alsa::resetTransferredFrames(mAlsaDeviceProxies[0], position->frames);
if (mIsInput) {
if (int ret = proxy_get_capture_position(mAlsaDeviceProxies[0].get(), &position->frames,
&position->timeNs);
ret != 0) {
LOG(WARNING) << __func__ << ": failed to retrieve capture position: " << ret;
return ::android::INVALID_OPERATION;
}
} else {
uint64_t hwFrames;
struct timespec timestamp;
if (int ret = proxy_get_presentation_position(mAlsaDeviceProxies[0].get(), &hwFrames,
&timestamp);
ret == 0) {
if (hwFrames > std::numeric_limits<int64_t>::max()) {
hwFrames -= std::numeric_limits<int64_t>::max();
}
position->frames = static_cast<int64_t>(hwFrames);
position->timeNs = audio_utils_ns_from_timespec(&timestamp);
} else {
LOG(WARNING) << __func__ << ": failed to retrieve presentation position: " << ret;
return ::android::INVALID_OPERATION;
}
}
return ::android::OK;
}
void StreamAlsa::shutdown() {
teardownIo();
}
ndk::ScopedAStatus StreamAlsa::setGain(float gain) {
mGain = gain;
return ndk::ScopedAStatus::ok();
}
void StreamAlsa::inputIoThread(size_t idx) {
#if defined(__ANDROID__)
setWorkerThreadPriority(pthread_gettid_np(pthread_self()));
const std::string threadName = (std::string("in_") + std::to_string(idx)).substr(0, 15);
pthread_setname_np(pthread_self(), threadName.c_str());
#endif
const size_t bufferSize = mBufferSizeFrames * mFrameSizeBytes;
std::vector<char> buffer(bufferSize);
while (mIoThreadIsRunning) {
if (int ret = proxy_read_with_retries(mAlsaDeviceProxies[idx].get(), &buffer[0], bufferSize,
mReadWriteRetries);
ret == 0) {
size_t bufferFramesWritten = 0;
while (bufferFramesWritten < mBufferSizeFrames) {
if (!mIoThreadIsRunning) return;
ssize_t framesWrittenOrError =
mSinks[idx]->write(&buffer[0], mBufferSizeFrames - bufferFramesWritten);
if (framesWrittenOrError >= 0) {
bufferFramesWritten += framesWrittenOrError;
} else {
LOG(WARNING) << __func__ << "[" << idx
<< "]: Error while writing into the pipe: "
<< framesWrittenOrError;
}
}
} else {
// Errors when the stream is being stopped are expected.
LOG_IF(WARNING, mIoThreadIsRunning)
<< __func__ << "[" << idx << "]: Error reading from ALSA: " << ret;
}
}
}
void StreamAlsa::outputIoThread(size_t idx) {
#if defined(__ANDROID__)
setWorkerThreadPriority(pthread_gettid_np(pthread_self()));
const std::string threadName = (std::string("out_") + std::to_string(idx)).substr(0, 15);
pthread_setname_np(pthread_self(), threadName.c_str());
#endif
const size_t bufferSize = mBufferSizeFrames * mFrameSizeBytes;
std::vector<char> buffer(bufferSize);
while (mIoThreadIsRunning) {
ssize_t framesReadOrError = mSources[idx]->read(&buffer[0], mBufferSizeFrames);
if (framesReadOrError > 0) {
int ret = proxy_write_with_retries(mAlsaDeviceProxies[idx].get(), &buffer[0],
framesReadOrError * mFrameSizeBytes,
mReadWriteRetries);
// Errors when the stream is being stopped are expected.
LOG_IF(WARNING, ret != 0 && mIoThreadIsRunning)
<< __func__ << "[" << idx << "]: Error writing into ALSA: " << ret;
} else if (framesReadOrError == 0) {
// MonoPipeReader does not have a blocking read, while use of std::condition_variable
// requires use of a mutex. For now, just do a 1ms sleep. Consider using a different
// pipe / ring buffer mechanism.
if (mIoThreadIsRunning) usleep(1000);
} else {
LOG(WARNING) << __func__ << "[" << idx
<< "]: Error while reading from the pipe: " << framesReadOrError;
}
}
}
void StreamAlsa::teardownIo() {
mIoThreadIsRunning = false;
if (mIsInput) {
LOG(DEBUG) << __func__ << ": shutting down pipes";
for (auto& sink : mSinks) {
sink->shutdown(true);
}
}
LOG(DEBUG) << __func__ << ": stopping PCM streams";
for (const auto& proxy : mAlsaDeviceProxies) {
proxy_stop(proxy.get());
}
LOG(DEBUG) << __func__ << ": joining threads";
for (auto& thread : mIoThreads) {
if (thread.joinable()) thread.join();
}
mIoThreads.clear();
LOG(DEBUG) << __func__ << ": closing PCM devices";
mAlsaDeviceProxies.clear();
mSources.clear();
mSinks.clear();
}
} // namespace aidl::android::hardware::audio::core
} // namespace aidl::android::hardware::audio::core
+136
View File
@@ -0,0 +1,136 @@
/*
* Copyright (C) 2026 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 <cmath>
#include <limits>
#define LOG_TAG "AHAL_StreamAlsaBase"
#include <Log.h>
#include <audio_utils/clock.h>
#include <error/expected_utils.h>
#include <media/AidlConversionCppNdk.h>
#include "UtilsAlsa.h"
#include "core-impl/StreamAlsaBase.h"
namespace aidl::android::hardware::audio::core {
StreamAlsaBase::StreamAlsaBase(StreamContext* context, const Metadata& metadata,
int readWriteRetries)
: StreamCommonImpl(context, metadata),
mBufferSizeFrames(getContext().getBufferSizeInFrames()),
mFrameSizeBytes(getContext().getFrameSize()),
mSampleRate(getContext().getSampleRate()),
mIsInput(isInput(metadata)),
mConfig(alsa::getPcmConfig(getContext(), mIsInput)),
mReadWriteRetries(readWriteRetries) {}
StreamAlsaBase::~StreamAlsaBase() {
cleanupWorker();
}
::android::status_t StreamAlsaBase::init(DriverCallbackInterface* /*callback*/) {
return mConfig.has_value() ? ::android::OK : ::android::NO_INIT;
}
::android::status_t StreamAlsaBase::drain(StreamDescriptor::DrainMode) {
if (!mIsInput) {
static constexpr float kMicrosPerSecond = MICROS_PER_SECOND;
const size_t delayUs = static_cast<size_t>(
std::roundf(mBufferSizeFrames * kMicrosPerSecond / mSampleRate));
usleep(delayUs);
}
return ::android::OK;
}
::android::status_t StreamAlsaBase::flush() {
return ::android::OK;
}
::android::status_t StreamAlsaBase::pause() {
return ::android::OK;
}
::android::status_t StreamAlsaBase::refinePosition(StreamDescriptor::Position* position) {
if (mAlsaDeviceProxies.empty()) {
LOG(WARNING) << __func__ << ": no opened devices";
return ::android::NO_INIT;
}
// Since the proxy can only count transferred frames since its creation,
// we override its counter value with ours and let it to correct for buffered frames.
alsa::resetTransferredFrames(mAlsaDeviceProxies[0], position->frames);
if (mIsInput) {
if (int ret = proxy_get_capture_position(mAlsaDeviceProxies[0].get(), &position->frames,
&position->timeNs);
ret != 0) {
LOG(WARNING) << __func__ << ": failed to retrieve capture position: " << ret;
return ::android::INVALID_OPERATION;
}
} else {
uint64_t hwFrames;
struct timespec timestamp;
if (int ret = proxy_get_presentation_position(mAlsaDeviceProxies[0].get(), &hwFrames,
&timestamp);
ret == 0) {
if (hwFrames > std::numeric_limits<int64_t>::max()) {
hwFrames -= std::numeric_limits<int64_t>::max();
}
position->frames = static_cast<int64_t>(hwFrames);
position->timeNs = audio_utils_ns_from_timespec(&timestamp);
} else {
LOG(WARNING) << __func__ << ": failed to retrieve presentation position: " << ret;
return ::android::INVALID_OPERATION;
}
}
return ::android::OK;
}
ndk::ScopedAStatus StreamAlsaBase::setGain(float gain) {
mGain = gain;
return ndk::ScopedAStatus::ok();
}
::android::status_t StreamAlsaBase::openDeviceProxies(std::vector<alsa::DeviceProxy>* proxies) {
for (const auto& device : getDeviceProfiles()) {
if ((device.direction == PCM_OUT && mIsInput) ||
(device.direction == PCM_IN && !mIsInput)) {
continue;
}
alsa::DeviceProxy proxy;
if (device.isExternal) {
// Always ask alsa configure as required since the configuration should be supported
// by the connected device. That is guaranteed by `setAudioPortConfig` and
// `setAudioPatch`.
proxy = alsa::openProxyForExternalDevice(
device, const_cast<struct pcm_config*>(&mConfig.value()),
true /*require_exact_match*/);
} else {
proxy = alsa::openProxyForAttachedDevice(
device, const_cast<struct pcm_config*>(&mConfig.value()), mBufferSizeFrames);
}
if (proxy.get() == nullptr) {
return ::android::NO_INIT;
}
proxies->push_back(std::move(proxy));
}
if (proxies->empty()) {
return ::android::NO_INIT;
}
return ::android::OK;
}
} // namespace aidl::android::hardware::audio::core
+236
View File
@@ -0,0 +1,236 @@
/*
* Copyright (C) 2026 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_StreamAlsaMonoPipe"
#include <Log.h>
#include <media/AidlConversionCppNdk.h>
#include "core-impl/StreamAlsaMonoPipe.h"
using aidl::android::hardware::audio::common::getChannelCount;
namespace aidl::android::hardware::audio::core {
StreamAlsaMonoPipe::StreamAlsaMonoPipe(StreamContext* context, const Metadata& metadata,
int readWriteRetries)
: StreamAlsaBase(context, metadata, readWriteRetries) {}
StreamAlsaMonoPipe::~StreamAlsaMonoPipe() {
cleanupWorker();
}
::android::NBAIO_Format StreamAlsaMonoPipe::getPipeFormat() const {
const audio_format_t audioFormat = VALUE_OR_FATAL(
aidl2legacy_AudioFormatDescription_audio_format_t(getContext().getFormat()));
const int channelCount = getChannelCount(getContext().getChannelLayout());
return ::android::Format_from_SR_C(getContext().getSampleRate(), channelCount, audioFormat);
}
::android::sp<::android::MonoPipe> StreamAlsaMonoPipe::makeSink(bool writeCanBlock) {
const ::android::NBAIO_Format format = getPipeFormat();
auto sink = ::android::sp<::android::MonoPipe>::make(mBufferSizeFrames, format, writeCanBlock);
const ::android::NBAIO_Format offers[1] = {format};
size_t numCounterOffers = 0;
ssize_t index = sink->negotiate(offers, 1, nullptr, numCounterOffers);
LOG_IF(FATAL, index != 0) << __func__ << ": Negotiation for the sink failed, index = " << index;
return sink;
}
::android::sp<::android::MonoPipeReader> StreamAlsaMonoPipe::makeSource(::android::MonoPipe* pipe) {
const ::android::NBAIO_Format format = getPipeFormat();
const ::android::NBAIO_Format offers[1] = {format};
auto source = ::android::sp<::android::MonoPipeReader>::make(pipe);
size_t numCounterOffers = 0;
ssize_t index = source->negotiate(offers, 1, nullptr, numCounterOffers);
LOG_IF(FATAL, index != 0) << __func__
<< ": Negotiation for the source failed, index = " << index;
return source;
}
::android::status_t StreamAlsaMonoPipe::standby() {
teardownIo();
return ::android::OK;
}
::android::status_t StreamAlsaMonoPipe::start() {
if (!mAlsaDeviceProxies.empty()) {
// This is a resume after a pause.
return ::android::OK;
}
decltype(mAlsaDeviceProxies) alsaDeviceProxies;
decltype(mSources) sources;
decltype(mSinks) sinks;
if (::android::status_t status = openDeviceProxies(&alsaDeviceProxies);
status != ::android::OK) {
return status;
}
for (size_t i = 0; i < alsaDeviceProxies.size(); ++i) {
auto sink = makeSink(mIsInput); // Do not block the writer when it is on our thread.
if (sink != nullptr) {
sinks.push_back(sink);
} else {
return ::android::NO_INIT;
}
if (auto source = makeSource(sink.get()); source != nullptr) {
sources.push_back(source);
} else {
return ::android::NO_INIT;
}
}
mAlsaDeviceProxies = std::move(alsaDeviceProxies);
mSources = std::move(sources);
mSinks = std::move(sinks);
mIoThreadIsRunning = true;
for (size_t i = 0; i < mAlsaDeviceProxies.size(); ++i) {
mIoThreads.emplace_back(
mIsInput ? &StreamAlsaMonoPipe::inputIoThread : &StreamAlsaMonoPipe::outputIoThread,
this, i);
}
return ::android::OK;
}
::android::status_t StreamAlsaMonoPipe::transfer(void* buffer, size_t frameCount,
size_t* actualFrameCount, int32_t* latencyMs) {
if (mAlsaDeviceProxies.empty()) {
LOG(FATAL) << __func__ << ": no opened devices";
return ::android::NO_INIT;
}
const size_t bytesToTransfer = frameCount * mFrameSizeBytes;
unsigned maxLatency = 0;
if (mIsInput) {
const size_t i = 0; // For the input case, only support a single device.
LOG(VERBOSE) << __func__ << ": reading from sink " << i;
ssize_t framesRead = mSources[i]->read(buffer, frameCount);
LOG_IF(FATAL, framesRead < 0) << "Error reading from the pipe: " << framesRead;
if (ssize_t framesMissing = static_cast<ssize_t>(frameCount) - framesRead;
framesMissing > 0) {
LOG(WARNING) << __func__ << ": incomplete data received, inserting " << framesMissing
<< " frames of silence";
memset(static_cast<char*>(buffer) + framesRead * mFrameSizeBytes, 0,
framesMissing * mFrameSizeBytes);
}
maxLatency = proxy_get_latency(mAlsaDeviceProxies[i].get());
} else {
alsa::applyGain(buffer, mGain, bytesToTransfer, mConfig.value().format, mConfig->channels);
for (size_t i = 0; i < mAlsaDeviceProxies.size(); ++i) {
LOG(VERBOSE) << __func__ << ": writing into sink " << i;
ssize_t framesWritten = mSinks[i]->write(buffer, frameCount);
LOG_IF(FATAL, framesWritten < 0) << "Error writing into the pipe: " << framesWritten;
if (ssize_t framesLost = static_cast<ssize_t>(frameCount) - framesWritten;
framesLost > 0) {
LOG(WARNING) << __func__ << ": sink " << i << " incomplete data sent, dropping "
<< framesLost << " frames";
}
maxLatency = std::max(maxLatency, proxy_get_latency(mAlsaDeviceProxies[i].get()));
}
}
*actualFrameCount = frameCount;
maxLatency = std::min(maxLatency, static_cast<unsigned>(std::numeric_limits<int32_t>::max()));
*latencyMs = maxLatency;
return ::android::OK;
}
void StreamAlsaMonoPipe::shutdown() {
teardownIo();
}
void StreamAlsaMonoPipe::inputIoThread(size_t idx) {
#if defined(__ANDROID__)
setWorkerThreadPriority(pthread_gettid_np(pthread_self()));
const std::string threadName = (std::string("in_") + std::to_string(idx)).substr(0, 15);
pthread_setname_np(pthread_self(), threadName.c_str());
#endif
const size_t bufferSize = mBufferSizeFrames * mFrameSizeBytes;
std::vector<char> buffer(bufferSize);
while (mIoThreadIsRunning) {
if (int ret = proxy_read_with_retries(mAlsaDeviceProxies[idx].get(), &buffer[0], bufferSize,
mReadWriteRetries);
ret == 0) {
size_t bufferFramesWritten = 0;
while (bufferFramesWritten < mBufferSizeFrames) {
if (!mIoThreadIsRunning) return;
ssize_t framesWrittenOrError =
mSinks[idx]->write(&buffer[0], mBufferSizeFrames - bufferFramesWritten);
if (framesWrittenOrError >= 0) {
bufferFramesWritten += framesWrittenOrError;
} else {
LOG(WARNING) << __func__ << "[" << idx
<< "]: Error while writing into the pipe: "
<< framesWrittenOrError;
}
}
} else {
// Errors when the stream is being stopped are expected.
LOG_IF(WARNING, mIoThreadIsRunning)
<< __func__ << "[" << idx << "]: Error reading from ALSA: " << ret;
}
}
}
void StreamAlsaMonoPipe::outputIoThread(size_t idx) {
#if defined(__ANDROID__)
setWorkerThreadPriority(pthread_gettid_np(pthread_self()));
const std::string threadName = (std::string("out_") + std::to_string(idx)).substr(0, 15);
pthread_setname_np(pthread_self(), threadName.c_str());
#endif
const size_t bufferSize = mBufferSizeFrames * mFrameSizeBytes;
std::vector<char> buffer(bufferSize);
while (mIoThreadIsRunning) {
ssize_t framesReadOrError = mSources[idx]->read(&buffer[0], mBufferSizeFrames);
if (framesReadOrError > 0) {
int ret = proxy_write_with_retries(mAlsaDeviceProxies[idx].get(), &buffer[0],
framesReadOrError * mFrameSizeBytes,
mReadWriteRetries);
// Errors when the stream is being stopped are expected.
LOG_IF(WARNING, ret != 0 && mIoThreadIsRunning)
<< __func__ << "[" << idx << "]: Error writing into ALSA: " << ret;
} else if (framesReadOrError == 0) {
// MonoPipeReader does not have a blocking read, while use of std::condition_variable
// requires use of a mutex. For now, just do a 1ms sleep. Consider using a different
// pipe / ring buffer mechanism.
if (mIoThreadIsRunning) usleep(1000);
} else {
LOG(WARNING) << __func__ << "[" << idx
<< "]: Error while reading from the pipe: " << framesReadOrError;
}
}
}
void StreamAlsaMonoPipe::teardownIo() {
mIoThreadIsRunning = false;
if (mIsInput) {
LOG(DEBUG) << __func__ << ": shutting down pipes";
for (auto& sink : mSinks) {
sink->shutdown(true);
}
}
LOG(DEBUG) << __func__ << ": stopping PCM streams";
for (const auto& proxy : mAlsaDeviceProxies) {
proxy_stop(proxy.get());
}
LOG(DEBUG) << __func__ << ": joining threads";
for (auto& thread : mIoThreads) {
if (thread.joinable()) thread.join();
}
mIoThreads.clear();
LOG(DEBUG) << __func__ << ": closing PCM devices";
mAlsaDeviceProxies.clear();
mSources.clear();
mSinks.clear();
}
} // namespace aidl::android::hardware::audio::core
@@ -18,14 +18,14 @@
#include <set>
#define LOG_TAG "AHAL_AlsaUtils"
#include <Log.h>
#include <Utils.h>
#include <aidl/android/media/audio/common/AudioFormatType.h>
#include <aidl/android/media/audio/common/PcmType.h>
#include <android-base/logging.h>
#include <audio_utils/primitives.h>
#include <cutils/compiler.h>
#include "Utils.h"
#include "UtilsAlsa.h"
#include "core-impl/utils.h"
using aidl::android::hardware::audio::common::getChannelCount;
@@ -127,6 +127,28 @@ const AudioChannelCountToMaskMap& getSupportedChannelIndexLayoutMap() {
}
#undef DEFINE_CHANNEL_INDEX_MASK
#define DEFINE_CHANNEL_ACN_FS_MASK(n) \
common::makeAcnAudioChannelLayout( \
AudioChannelLayout::Ambisonics::SourceLayout::FULL_SPHERE, \
AudioChannelLayout::Ambisonics::FULL_SPHERE_CHANNEL_COUNT_ORDER_##n)
#define DEFINE_CHANNEL_ACN_HZ_MASK(n) \
common::makeAcnAudioChannelLayout( \
AudioChannelLayout::Ambisonics::SourceLayout::HORIZONTAL, \
AudioChannelLayout::Ambisonics::HORIZONTAL_CHANNEL_COUNT_ORDER_##n)
const AudioChannelCountToMaskMap& getSupportedChannelAcnLayoutMap() {
static const std::set<AudioChannelLayout> supportedAcnChannelLayouts = {
DEFINE_CHANNEL_ACN_FS_MASK(0), DEFINE_CHANNEL_ACN_FS_MASK(1),
DEFINE_CHANNEL_ACN_HZ_MASK(1), DEFINE_CHANNEL_ACN_HZ_MASK(2),
DEFINE_CHANNEL_ACN_HZ_MASK(3),
};
static const AudioChannelCountToMaskMap acnLayouts =
make_ChannelCountToMaskMap(supportedAcnChannelLayouts);
return acnLayouts;
}
#undef DEFINE_CHANNEL_ACN_HZ_MASK
#undef DEFINE_CHANNEL_ACN_FS_MASK
AudioFormatDescription make_AudioFormatDescription(AudioFormatType type) {
AudioFormatDescription result;
@@ -269,6 +291,11 @@ AudioChannelLayout getChannelIndexMaskFromChannelCount(unsigned int channelCount
getInvalidChannelLayout());
}
AudioChannelLayout getChannelAcnMaskFromChannelCount(unsigned int channelCount) {
return findValueOrDefault(getSupportedChannelAcnLayoutMap(), channelCount,
getInvalidChannelLayout());
}
unsigned int getChannelCountFromChannelMask(const AudioChannelLayout& channelMask, bool isInput) {
switch (channelMask.getTag()) {
case AudioChannelLayout::Tag::layoutMask: {
@@ -281,6 +308,11 @@ unsigned int getChannelCountFromChannelMask(const AudioChannelLayout& channelMas
static_cast<unsigned>(getChannelCount(channelMask)),
0u /*defaultValue*/);
}
case AudioChannelLayout::Tag::acnMask: {
return findKeyOrDefault(getSupportedChannelAcnLayoutMap(),
static_cast<unsigned>(getChannelCount(channelMask)),
0u /*defaultValue*/);
}
case AudioChannelLayout::Tag::none:
case AudioChannelLayout::Tag::invalid:
case AudioChannelLayout::Tag::voiceMask:
@@ -289,7 +321,8 @@ unsigned int getChannelCountFromChannelMask(const AudioChannelLayout& channelMas
}
}
std::vector<AudioChannelLayout> getChannelMasksFromProfile(const alsa_device_profile* profile) {
std::vector<AudioChannelLayout> getChannelMasksFromProfile(const alsa_device_profile* profile,
bool addAcnMasks) {
const bool isInput = profile->direction == PCM_IN;
std::vector<AudioChannelLayout> channels;
for (size_t i = 0; i < AUDIO_PORT_MAX_CHANNEL_MASKS && profile->channel_counts[i] != 0; ++i) {
@@ -302,6 +335,12 @@ std::vector<AudioChannelLayout> getChannelMasksFromProfile(const alsa_device_pro
if (indexMask.getTag() == AudioChannelLayout::Tag::indexMask) {
channels.push_back(indexMask);
}
if (addAcnMasks) {
auto acnMask = alsa::getChannelAcnMaskFromChannelCount(profile->channel_counts[i]);
if (acnMask.getTag() == AudioChannelLayout::Tag::acnMask) {
channels.push_back(acnMask);
}
}
}
return channels;
}
@@ -65,10 +65,12 @@ void applyGain(void* buffer, float gain, size_t bytesToTransfer, enum pcm_format
unsigned int channelCount, int isInput);
::aidl::android::media::audio::common::AudioChannelLayout getChannelIndexMaskFromChannelCount(
unsigned int channelCount);
::aidl::android::media::audio::common::AudioChannelLayout getChannelAcnMaskFromChannelCount(
unsigned int channelCount);
unsigned int getChannelCountFromChannelMask(
const ::aidl::android::media::audio::common::AudioChannelLayout& channelMask, bool isInput);
std::vector<::aidl::android::media::audio::common::AudioChannelLayout> getChannelMasksFromProfile(
const alsa_device_profile* profile);
const alsa_device_profile* profile, bool addAcnMasks);
std::optional<DeviceProfile> getDeviceProfile(
const ::aidl::android::media::audio::common::AudioDevice& audioDevice, bool isInput);
std::optional<DeviceProfile> getDeviceProfile(
+7 -7
View File
@@ -1,39 +1,39 @@
<manifest version="1.0" type="device">
<hal format="aidl">
<name>android.hardware.audio.core</name>
<version>3</version>
<version>4</version>
<fqname>IModule/default</fqname>
</hal>
<hal format="aidl">
<name>android.hardware.audio.core</name>
<version>3</version>
<version>4</version>
<fqname>IModule/r_submix</fqname>
</hal>
<hal format="aidl">
<name>android.hardware.audio.core</name>
<version>3</version>
<version>4</version>
<fqname>IModule/bluetooth</fqname>
</hal>
<hal format="aidl">
<name>android.hardware.audio.core</name>
<version>3</version>
<version>4</version>
<fqname>IConfig/default</fqname>
</hal>
<!-- Uncomment when these modules present in the configuration
<hal format="aidl">
<name>android.hardware.audio.core</name>
<version>3</version>
<version>4</version>
<fqname>IModule/stub</fqname>
</hal>
-->
<hal format="aidl">
<name>android.hardware.audio.core</name>
<version>3</version>
<version>4</version>
<fqname>IModule/usb</fqname>
</hal>
<hal format="aidl">
<name>android.hardware.audio.effect</name>
<version>3</version>
<version>4</version>
<fqname>IFactory/default</fqname>
</hal>
</manifest>
File diff suppressed because it is too large Load Diff
+10 -296
View File
@@ -16,32 +16,13 @@
#define LOG_TAG "AHAL_ModuleBluetooth"
#include <android-base/logging.h>
#include <Log.h>
#include "BluetoothAudioSession.h"
#include "core-impl/DevicePortProxy.h"
#include "core-impl/ModuleBluetooth.h"
#include "core-impl/StreamBluetooth.h"
using aidl::android::hardware::audio::common::SinkMetadata;
using aidl::android::hardware::audio::common::SourceMetadata;
using aidl::android::hardware::bluetooth::audio::ChannelMode;
using aidl::android::hardware::bluetooth::audio::PcmConfiguration;
using aidl::android::media::audio::common::AudioChannelLayout;
using aidl::android::media::audio::common::AudioConfigBase;
using aidl::android::media::audio::common::AudioDeviceDescription;
using aidl::android::media::audio::common::AudioDeviceType;
using aidl::android::media::audio::common::AudioFormatDescription;
using aidl::android::media::audio::common::AudioFormatType;
using aidl::android::media::audio::common::AudioIoFlags;
using aidl::android::media::audio::common::AudioOffloadInfo;
using aidl::android::media::audio::common::AudioPort;
using aidl::android::media::audio::common::AudioPortConfig;
using aidl::android::media::audio::common::AudioPortExt;
using aidl::android::media::audio::common::AudioProfile;
using aidl::android::media::audio::common::Int;
using aidl::android::media::audio::common::MicrophoneInfo;
using aidl::android::media::audio::common::PcmType;
using android::bluetooth::audio::aidl::BluetoothAudioPortAidl;
using android::bluetooth::audio::aidl::BluetoothAudioPort;
using android::bluetooth::audio::aidl::BluetoothAudioPortAidlIn;
using android::bluetooth::audio::aidl::BluetoothAudioPortAidlOut;
@@ -51,292 +32,25 @@ extern "C" binder_status_t createIBluetoothAudioProviderFactory();
namespace aidl::android::hardware::audio::core {
namespace {
PcmType pcmTypeFromBitsPerSample(int8_t bitsPerSample) {
if (bitsPerSample == 8)
return PcmType::UINT_8_BIT;
else if (bitsPerSample == 16)
return PcmType::INT_16_BIT;
else if (bitsPerSample == 24)
return PcmType::INT_24_BIT;
else if (bitsPerSample == 32)
return PcmType::INT_32_BIT;
ALOGE("Unsupported bitsPerSample: %d", bitsPerSample);
return PcmType::DEFAULT;
}
AudioChannelLayout channelLayoutFromChannelMode(ChannelMode mode) {
if (mode == ChannelMode::MONO) {
return AudioChannelLayout::make<AudioChannelLayout::layoutMask>(
AudioChannelLayout::LAYOUT_MONO);
} else if (mode == ChannelMode::STEREO || mode == ChannelMode::DUALMONO) {
return AudioChannelLayout::make<AudioChannelLayout::layoutMask>(
AudioChannelLayout::LAYOUT_STEREO);
}
ALOGE("Unsupported channel mode: %s", toString(mode).c_str());
return AudioChannelLayout{};
}
} // namespace
ModuleBluetooth::ModuleBluetooth(std::unique_ptr<Module::Configuration>&& config)
: Module(Type::BLUETOOTH, std::move(config)) {
: ModuleBluetoothBase(std::move(config)) {
// TODO(b/312265159) bluetooth audio should be in its own process
// Remove this and the shared_libs when that happens
binder_status_t status = createIBluetoothAudioProviderFactory();
if (status != STATUS_OK) {
LOG(ERROR) << "Failed to create bluetooth audio provider factory. Status: "
LOG(ERROR) << __func__ << ": Failed to create bluetooth audio provider factory. Status: "
<< ::android::statusToString(status);
}
}
ndk::ScopedAStatus ModuleBluetooth::getBluetoothA2dp(
std::shared_ptr<IBluetoothA2dp>* _aidl_return) {
*_aidl_return = getBtA2dp().getInstance();
LOG(DEBUG) << __func__ << ": returning instance of IBluetoothA2dp: " << _aidl_return->get();
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus ModuleBluetooth::getBluetoothLe(std::shared_ptr<IBluetoothLe>* _aidl_return) {
*_aidl_return = getBtLe().getInstance();
LOG(DEBUG) << __func__ << ": returning instance of IBluetoothLe: " << _aidl_return->get();
return ndk::ScopedAStatus::ok();
}
ChildInterface<BluetoothA2dp>& ModuleBluetooth::getBtA2dp() {
if (!mBluetoothA2dp) {
auto handle = ndk::SharedRefBase::make<BluetoothA2dp>();
handle->registerHandler(std::bind(&ModuleBluetooth::bluetoothParametersUpdated, this));
mBluetoothA2dp = handle;
}
return mBluetoothA2dp;
}
ChildInterface<BluetoothLe>& ModuleBluetooth::getBtLe() {
if (!mBluetoothLe) {
auto handle = ndk::SharedRefBase::make<BluetoothLe>();
handle->registerHandler(std::bind(&ModuleBluetooth::bluetoothParametersUpdated, this));
mBluetoothLe = handle;
}
return mBluetoothLe;
}
ModuleBluetooth::BtProfileHandles ModuleBluetooth::getBtProfileManagerHandles() {
return std::make_tuple(std::weak_ptr<IBluetooth>(), getBtA2dp().getPtr(), getBtLe().getPtr());
}
ndk::ScopedAStatus ModuleBluetooth::getMicMute(bool* _aidl_return __unused) {
LOG(DEBUG) << __func__ << ": is not supported";
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
ndk::ScopedAStatus ModuleBluetooth::setMicMute(bool in_mute __unused) {
LOG(DEBUG) << __func__ << ": is not supported";
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
ndk::ScopedAStatus ModuleBluetooth::setAudioPortConfig(const AudioPortConfig& in_requested,
AudioPortConfig* out_suggested,
bool* _aidl_return) {
auto fillConfig = [this](const AudioPort& port, AudioPortConfig* config) {
if (port.ext.getTag() == AudioPortExt::device) {
CachedProxy proxy;
auto status = findOrCreateProxy(port, proxy);
if (status.isOk()) {
const auto& pcmConfig = proxy.pcmConfig;
LOG(DEBUG) << "setAudioPortConfig: suggesting port config from "
<< pcmConfig.toString();
const auto pcmType = pcmTypeFromBitsPerSample(pcmConfig.bitsPerSample);
const auto channelMask = channelLayoutFromChannelMode(pcmConfig.channelMode);
if (pcmType != PcmType::DEFAULT && channelMask != AudioChannelLayout{}) {
config->format =
AudioFormatDescription{.type = AudioFormatType::PCM, .pcm = pcmType};
config->channelMask = channelMask;
config->sampleRate = Int{.value = pcmConfig.sampleRateHz};
config->flags = port.flags;
config->ext = port.ext;
return true;
}
}
}
return generateDefaultPortConfig(port, config);
};
return Module::setAudioPortConfigImpl(in_requested, fillConfig, out_suggested, _aidl_return);
}
ndk::ScopedAStatus ModuleBluetooth::checkAudioPatchEndpointsMatch(
const std::vector<AudioPortConfig*>& sources, const std::vector<AudioPortConfig*>& sinks) {
// Both sources and sinks must be non-empty, this is guaranteed by 'setAudioPatch'.
const bool isInput = sources[0]->ext.getTag() == AudioPortExt::device;
const int32_t devicePortId = isInput ? sources[0]->portId : sinks[0]->portId;
const auto proxyIt = mProxies.find(devicePortId);
if (proxyIt == mProxies.end()) return ndk::ScopedAStatus::ok();
const auto& pcmConfig = proxyIt->second.pcmConfig;
const AudioPortConfig* mixPortConfig = isInput ? sinks[0] : sources[0];
if (!StreamBluetooth::checkConfigParams(
pcmConfig, AudioConfigBase{.sampleRate = mixPortConfig->sampleRate->value,
.channelMask = *(mixPortConfig->channelMask),
.format = *(mixPortConfig->format)})) {
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
if (int32_t handle = mixPortConfig->ext.get<AudioPortExt::mix>().handle; handle > 0) {
mConnections.insert(std::pair(handle, devicePortId));
}
return ndk::ScopedAStatus::ok();
}
void ModuleBluetooth::onExternalDeviceConnectionChanged(const AudioPort& audioPort,
bool connected) {
if (!connected) mProxies.erase(audioPort.id);
}
ndk::ScopedAStatus ModuleBluetooth::createInputStream(
StreamContext&& context, const SinkMetadata& sinkMetadata,
const std::vector<MicrophoneInfo>& microphones, std::shared_ptr<StreamIn>* result) {
CachedProxy proxy;
RETURN_STATUS_IF_ERROR(fetchAndCheckProxy(context, proxy));
return createStreamInstance<StreamInBluetooth>(result, std::move(context), sinkMetadata,
microphones, getBtProfileManagerHandles(),
proxy.ptr, proxy.pcmConfig);
}
ndk::ScopedAStatus ModuleBluetooth::createOutputStream(
StreamContext&& context, const SourceMetadata& sourceMetadata,
const std::optional<AudioOffloadInfo>& offloadInfo, std::shared_ptr<StreamOut>* result) {
CachedProxy proxy;
RETURN_STATUS_IF_ERROR(fetchAndCheckProxy(context, proxy));
return createStreamInstance<StreamOutBluetooth>(result, std::move(context), sourceMetadata,
offloadInfo, getBtProfileManagerHandles(),
proxy.ptr, proxy.pcmConfig);
}
ndk::ScopedAStatus ModuleBluetooth::populateConnectedDevicePort(AudioPort* audioPort,
int32_t nextPortId) {
if (audioPort->ext.getTag() != AudioPortExt::device) {
LOG(ERROR) << __func__ << ": not a device port: " << audioPort->toString();
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
if (!::aidl::android::hardware::bluetooth::audio::BluetoothAudioSession::IsAidlAvailable()) {
LOG(ERROR) << __func__ << ": IBluetoothAudioProviderFactory AIDL service not available";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
const auto& devicePort = audioPort->ext.get<AudioPortExt::device>();
const auto& description = devicePort.device.type;
// This method must return an error when the device can not be connected.
// Since A2DP/LE status events are sent asynchronously, it is more reliable
// to attempt connecting to the BT stack rather than judge by the A2DP/LE status.
if (description.connection != AudioDeviceDescription::CONNECTION_BT_A2DP &&
description.connection != AudioDeviceDescription::CONNECTION_BT_LE &&
!(description.connection == AudioDeviceDescription::CONNECTION_WIRELESS &&
description.type == AudioDeviceType::OUT_HEARING_AID)) {
LOG(ERROR) << __func__ << ": unsupported device type: " << audioPort->toString();
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
CachedProxy proxy;
RETURN_STATUS_IF_ERROR(createProxy(*audioPort, nextPortId, proxy));
// If the device is actually connected, it is configured by the BT stack.
// Provide the current configuration instead of all possible profiles.
const auto& pcmConfig = proxy.pcmConfig;
audioPort->profiles.clear();
audioPort->profiles.push_back(
AudioProfile{.format = AudioFormatDescription{.type = AudioFormatType::PCM,
.pcm = pcmTypeFromBitsPerSample(
pcmConfig.bitsPerSample)},
.channelMasks = std::vector<AudioChannelLayout>(
{channelLayoutFromChannelMode(pcmConfig.channelMode)}),
.sampleRates = std::vector<int>({pcmConfig.sampleRateHz})});
LOG(DEBUG) << __func__ << ": " << audioPort->toString();
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus ModuleBluetooth::onMasterMuteChanged(bool) {
LOG(DEBUG) << __func__ << ": is not supported";
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
ndk::ScopedAStatus ModuleBluetooth::onMasterVolumeChanged(float) {
LOG(DEBUG) << __func__ << ": is not supported";
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
int32_t ModuleBluetooth::getNominalLatencyMs(const AudioPortConfig& portConfig) {
const auto connectionsIt = mConnections.find(portConfig.ext.get<AudioPortExt::mix>().handle);
if (connectionsIt != mConnections.end()) {
const auto proxyIt = mProxies.find(connectionsIt->second);
if (proxyIt != mProxies.end()) {
auto proxy = proxyIt->second.ptr;
size_t dataIntervalUs = 0;
if (!proxy->getPreferredDataIntervalUs(dataIntervalUs)) {
LOG(WARNING) << __func__ << ": could not fetch preferred data interval";
}
const bool isInput = portConfig.flags->getTag() == AudioIoFlags::input;
return isInput ? StreamInBluetooth::getNominalLatencyMs(dataIntervalUs)
: StreamOutBluetooth::getNominalLatencyMs(dataIntervalUs);
}
}
LOG(ERROR) << __func__ << ": no connection or proxy found for " << portConfig.toString();
return Module::getNominalLatencyMs(portConfig);
}
ndk::ScopedAStatus ModuleBluetooth::createProxy(const AudioPort& audioPort, int32_t instancePortId,
CachedProxy& proxy) {
const bool isInput = audioPort.flags.getTag() == AudioIoFlags::input;
proxy.ptr = isInput ? std::shared_ptr<BluetoothAudioPortAidl>(
std::make_shared<BluetoothAudioPortAidlIn>())
: std::shared_ptr<BluetoothAudioPortAidl>(
std::make_shared<BluetoothAudioPortAidlOut>());
const auto& devicePort = audioPort.ext.get<AudioPortExt::device>();
const auto device = devicePort.device.type;
bool registrationSuccess = false;
for (int i = 0; i < kCreateProxyRetries && !registrationSuccess; ++i) {
registrationSuccess = proxy.ptr->registerPort(device);
usleep(kCreateProxyRetrySleepMs * 1000);
}
if (!registrationSuccess) {
LOG(ERROR) << __func__ << ": failed to register BT port for " << device.toString();
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
if (!proxy.ptr->loadAudioConfig(proxy.pcmConfig)) {
LOG(ERROR) << __func__ << ": state=" << proxy.ptr->getState()
<< ", failed to load audio config";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
mProxies.insert(std::pair(instancePortId, proxy));
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus ModuleBluetooth::fetchAndCheckProxy(const StreamContext& context,
CachedProxy& proxy) {
const auto connectionsIt = mConnections.find(context.getMixPortHandle());
if (connectionsIt != mConnections.end()) {
const auto proxyIt = mProxies.find(connectionsIt->second);
if (proxyIt != mProxies.end()) {
proxy = proxyIt->second;
mProxies.erase(proxyIt);
}
mConnections.erase(connectionsIt);
}
if (proxy.ptr != nullptr) {
if (!StreamBluetooth::checkConfigParams(
proxy.pcmConfig, AudioConfigBase{.sampleRate = context.getSampleRate(),
.channelMask = context.getChannelLayout(),
.format = context.getFormat()})) {
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
}
// Not having a proxy is OK, it may happen in VTS tests when streams are opened on unconnected
// mix ports.
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus ModuleBluetooth::findOrCreateProxy(const AudioPort& audioPort,
CachedProxy& proxy) {
if (auto proxyIt = mProxies.find(audioPort.id); proxyIt != mProxies.end()) {
proxy = proxyIt->second;
return ndk::ScopedAStatus::ok();
}
return createProxy(audioPort, audioPort.id, proxy);
std::shared_ptr<BluetoothAudioPort> ModuleBluetooth::createProxyInstance(bool isInput) {
return isInput ? std::shared_ptr<BluetoothAudioPort>(
std::make_shared<BluetoothAudioPortAidlIn>())
: std::shared_ptr<BluetoothAudioPort>(
std::make_shared<BluetoothAudioPortAidlOut>());
}
} // namespace aidl::android::hardware::audio::core
+414
View File
@@ -0,0 +1,414 @@
/*
* Copyright (C) 2025 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_ModuleBluetooth"
#include <Log.h>
#include "core-impl/ModuleBluetoothBase.h"
#include "core-impl/StreamBluetooth.h"
using aidl::android::hardware::audio::common::SinkMetadata;
using aidl::android::hardware::audio::common::SourceMetadata;
using aidl::android::hardware::bluetooth::audio::ChannelMode;
using aidl::android::hardware::bluetooth::audio::PcmConfiguration;
using aidl::android::media::audio::common::AudioChannelLayout;
using aidl::android::media::audio::common::AudioConfigBase;
using aidl::android::media::audio::common::AudioDeviceDescription;
using aidl::android::media::audio::common::AudioDeviceType;
using aidl::android::media::audio::common::AudioFormatDescription;
using aidl::android::media::audio::common::AudioFormatType;
using aidl::android::media::audio::common::AudioIoFlags;
using aidl::android::media::audio::common::AudioOffloadInfo;
using aidl::android::media::audio::common::AudioPort;
using aidl::android::media::audio::common::AudioPortConfig;
using aidl::android::media::audio::common::AudioPortExt;
using aidl::android::media::audio::common::AudioProfile;
using aidl::android::media::audio::common::Int;
using aidl::android::media::audio::common::MicrophoneInfo;
using aidl::android::media::audio::common::PcmType;
namespace aidl::android::hardware::audio::core {
namespace {
PcmType pcmTypeFromBitsPerSample(int8_t bitsPerSample) {
if (bitsPerSample == 8)
return PcmType::UINT_8_BIT;
else if (bitsPerSample == 16)
return PcmType::INT_16_BIT;
else if (bitsPerSample == 24)
return PcmType::INT_24_BIT;
else if (bitsPerSample == 32)
return PcmType::INT_32_BIT;
ALOGE("Unsupported bitsPerSample: %d", bitsPerSample);
return PcmType::DEFAULT;
}
AudioChannelLayout channelLayoutFromChannelMode(ChannelMode mode) {
if (mode == ChannelMode::MONO) {
return AudioChannelLayout::make<AudioChannelLayout::layoutMask>(
AudioChannelLayout::LAYOUT_MONO);
} else if (mode == ChannelMode::STEREO || mode == ChannelMode::DUALMONO) {
return AudioChannelLayout::make<AudioChannelLayout::layoutMask>(
AudioChannelLayout::LAYOUT_STEREO);
}
ALOGE("Unsupported channel mode: %s", toString(mode).c_str());
return AudioChannelLayout{};
}
} // namespace
ModuleBluetoothBase::ModuleBluetoothBase(std::unique_ptr<Module::Configuration>&& config)
: Module(Type::BLUETOOTH, std::move(config)) {}
ndk::ScopedAStatus ModuleBluetoothBase::getBluetoothA2dp(
std::shared_ptr<IBluetoothA2dp>* _aidl_return) {
*_aidl_return = getBtA2dp().getInstance();
LOG(DEBUG) << __func__ << ": returning instance of IBluetoothA2dp: " << _aidl_return->get();
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus ModuleBluetoothBase::getBluetoothLe(
std::shared_ptr<IBluetoothLe>* _aidl_return) {
*_aidl_return = getBtLe().getInstance();
LOG(DEBUG) << __func__ << ": returning instance of IBluetoothLe: " << _aidl_return->get();
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus ModuleBluetoothBase::getBluetooth(std::shared_ptr<IBluetooth>* _aidl_return) {
// Only provided when Bluetooth port is present.
const auto& ports = getConfig().ports;
bool hasSco = false;
for (const auto& port : ports) {
if (port.ext.getTag() == AudioPortExt::device &&
port.ext.get<AudioPortExt::device>().device.type.connection ==
AudioDeviceDescription::CONNECTION_BT_SCO) {
hasSco = true;
break;
}
}
if (hasSco) {
*_aidl_return = getBt().getInstance();
} else {
*_aidl_return = nullptr;
}
LOG(DEBUG) << __func__ << ": returning instance of IBluetooth: " << _aidl_return->get();
return ndk::ScopedAStatus::ok();
}
ChildInterface<BluetoothA2dp>& ModuleBluetoothBase::getBtA2dp() {
if (!mBluetoothA2dp) {
auto handle = ndk::SharedRefBase::make<BluetoothA2dp>();
handle->registerHandler(std::bind(&ModuleBluetoothBase::bluetoothParametersUpdated, this));
mBluetoothA2dp = handle;
}
return mBluetoothA2dp;
}
ChildInterface<BluetoothLe>& ModuleBluetoothBase::getBtLe() {
if (!mBluetoothLe) {
auto handle = ndk::SharedRefBase::make<BluetoothLe>();
handle->registerHandler(std::bind(&ModuleBluetoothBase::bluetoothParametersUpdated, this));
mBluetoothLe = handle;
}
return mBluetoothLe;
}
ChildInterface<Bluetooth>& ModuleBluetoothBase::getBt() {
if (!mBluetooth) {
auto handle = ndk::SharedRefBase::make<Bluetooth>();
handle->registerHandler(std::bind(&ModuleBluetoothBase::bluetoothParametersUpdated, this));
mBluetooth = handle;
}
return mBluetooth;
}
ModuleBluetoothBase::BtProfileHandles ModuleBluetoothBase::getBtProfileManagerHandles() {
return std::make_tuple(getBt().getPtr(), getBtA2dp().getPtr(), getBtLe().getPtr());
}
ndk::ScopedAStatus ModuleBluetoothBase::getMicMute(bool* _aidl_return __unused) {
LOG(DEBUG) << __func__ << ": is not supported";
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
ndk::ScopedAStatus ModuleBluetoothBase::setMicMute(bool in_mute __unused) {
LOG(DEBUG) << __func__ << ": is not supported";
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
ndk::ScopedAStatus ModuleBluetoothBase::setAudioPortConfig(const AudioPortConfig& in_requested,
AudioPortConfig* out_suggested,
bool* _aidl_return) {
// Since 'fillConfig' is only called when a new port config is created,
// handle the case of updating an existing port config separately.
int32_t portId = 0;
if (in_requested.id != 0) {
auto& configs = getConfig().portConfigs;
if (auto existing = findById<AudioPortConfig>(configs, in_requested.id);
existing != configs.end()) {
auto& ports = getConfig().ports;
auto portIt = findById<AudioPort>(ports, existing->portId);
if (portIt != ports.end() && portIt->ext.getTag() == AudioPortExt::Tag::device) {
portId = portIt->id;
}
}
}
auto fillConfig = [this](const AudioPort& port, AudioPortConfig* config) {
if (port.ext.getTag() == AudioPortExt::device) {
CachedProxy proxy;
auto status = findOrCreateProxy(port, proxy);
if (status.isOk()) {
const auto& pcmConfig = proxy.pcmConfig;
LOG(DEBUG) << "setAudioPortConfig: suggesting port config from "
<< pcmConfig.toString();
const auto pcmType = pcmTypeFromBitsPerSample(pcmConfig.bitsPerSample);
const auto channelMask = channelLayoutFromChannelMode(pcmConfig.channelMode);
if (pcmType != PcmType::DEFAULT && channelMask != AudioChannelLayout{}) {
config->format =
AudioFormatDescription{.type = AudioFormatType::PCM, .pcm = pcmType};
config->channelMask = channelMask;
config->sampleRate = Int{.value = pcmConfig.sampleRateHz};
config->flags = port.flags;
config->ext = port.ext;
return true;
}
}
}
return generateDefaultPortConfig(port, config);
};
RETURN_STATUS_IF_ERROR(
Module::setAudioPortConfigImpl(in_requested, fillConfig, out_suggested, _aidl_return));
if (portId != 0) {
auto& ports = getConfig().ports;
auto portIt = findById<AudioPort>(ports, portId);
if (portIt != ports.end() && portIt->ext.getTag() == AudioPortExt::Tag::device) {
CachedProxy proxy;
// Ignore error similar to 'fillConfig', this can happen in VTS tests when
// device connections are simulated.
findOrCreateProxy(*portIt, proxy);
}
}
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus ModuleBluetoothBase::supportsVariableLatency(bool* _aidl_return) {
LOG(DEBUG) << __func__ << ": " << getType();
*_aidl_return = true;
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus ModuleBluetoothBase::checkAudioPatchEndpointsMatch(
const std::vector<AudioPortConfig*>& sources, const std::vector<AudioPortConfig*>& sinks) {
// Both sources and sinks must be non-empty, this is guaranteed by 'setAudioPatch'.
const bool isInput = sources[0]->ext.getTag() == AudioPortExt::device;
const int32_t devicePortId = isInput ? sources[0]->portId : sinks[0]->portId;
const auto proxyIt = mProxies.find(devicePortId);
if (proxyIt == mProxies.end()) return ndk::ScopedAStatus::ok();
const auto& pcmConfig = proxyIt->second.pcmConfig;
const AudioPortConfig* mixPortConfig = isInput ? sinks[0] : sources[0];
if (!StreamBluetooth::checkConfigParams(
pcmConfig, AudioConfigBase{.sampleRate = mixPortConfig->sampleRate->value,
.channelMask = *(mixPortConfig->channelMask),
.format = *(mixPortConfig->format)})) {
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
if (int32_t handle = mixPortConfig->ext.get<AudioPortExt::mix>().handle; handle > 0) {
mConnections.insert(std::pair(handle, devicePortId));
}
return ndk::ScopedAStatus::ok();
}
void ModuleBluetoothBase::onExternalDeviceConnectionChanged(const AudioPort& audioPort,
bool connected) {
if (!connected) mProxies.erase(audioPort.id);
}
ndk::ScopedAStatus ModuleBluetoothBase::createInputStream(
StreamContext&& context, const SinkMetadata& sinkMetadata,
const std::vector<MicrophoneInfo>& microphones, std::shared_ptr<StreamIn>* result) {
CachedProxy proxy;
RETURN_STATUS_IF_ERROR(fetchAndCheckProxy(context, proxy));
return createStreamInstance<StreamInBluetooth>(result, std::move(context), sinkMetadata,
microphones, getBtProfileManagerHandles(),
proxy.ptr, proxy.pcmConfig);
}
ndk::ScopedAStatus ModuleBluetoothBase::createOutputStream(
StreamContext&& context, const SourceMetadata& sourceMetadata,
const std::optional<AudioOffloadInfo>& offloadInfo, std::shared_ptr<StreamOut>* result) {
CachedProxy proxy;
RETURN_STATUS_IF_ERROR(fetchAndCheckProxy(context, proxy));
return createStreamInstance<StreamOutBluetooth>(result, std::move(context), sourceMetadata,
offloadInfo, getBtProfileManagerHandles(),
proxy.ptr, proxy.pcmConfig);
}
ndk::ScopedAStatus ModuleBluetoothBase::populateConnectedDevicePort(AudioPort* audioPort,
int32_t nextPortId) {
if (audioPort->ext.getTag() != AudioPortExt::device) {
LOG(ERROR) << __func__ << ": not a device port: " << audioPort->toString();
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
const auto& devicePort = audioPort->ext.get<AudioPortExt::device>();
const auto& description = devicePort.device.type;
// This method must return an error when the device can not be connected.
// Since A2DP/LE status events are sent asynchronously, it is more reliable
// to attempt connecting to the BT stack rather than judge by the A2DP/LE status.
if (description.connection != AudioDeviceDescription::CONNECTION_BT_A2DP &&
description.connection != AudioDeviceDescription::CONNECTION_BT_LE &&
description.connection != AudioDeviceDescription::CONNECTION_BT_SCO &&
!(description.connection == AudioDeviceDescription::CONNECTION_WIRELESS &&
description.type == AudioDeviceType::OUT_HEARING_AID)) {
LOG(ERROR) << __func__ << ": unsupported device type: " << audioPort->toString();
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
CachedProxy proxy;
RETURN_STATUS_IF_ERROR(createProxy(*audioPort, nextPortId, proxy));
// If the device is actually connected, it is configured by the BT stack.
// Provide the current configuration instead of all possible profiles.
const auto& pcmConfig = proxy.pcmConfig;
audioPort->profiles.assign(
{AudioProfile{.format = AudioFormatDescription{.type = AudioFormatType::PCM,
.pcm = pcmTypeFromBitsPerSample(
pcmConfig.bitsPerSample)},
.channelMasks = std::vector<AudioChannelLayout>(
{channelLayoutFromChannelMode(pcmConfig.channelMode)}),
.sampleRates = std::vector<int>({pcmConfig.sampleRateHz})}});
LOG(DEBUG) << __func__ << ": " << audioPort->toString();
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus ModuleBluetoothBase::onMasterMuteChanged(bool) {
LOG(DEBUG) << __func__ << ": is not supported";
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
ndk::ScopedAStatus ModuleBluetoothBase::onMasterVolumeChanged(float) {
LOG(DEBUG) << __func__ << ": is not supported";
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
int32_t ModuleBluetoothBase::getNominalLatencyMs(const AudioPortConfig& portConfig) {
const auto connectionsIt = mConnections.find(portConfig.ext.get<AudioPortExt::mix>().handle);
if (connectionsIt != mConnections.end()) {
const auto proxyIt = mProxies.find(connectionsIt->second);
if (proxyIt != mProxies.end()) {
auto proxy = proxyIt->second.ptr;
size_t dataIntervalUs = 0;
if (!proxy->getPreferredDataIntervalUs(dataIntervalUs)) {
LOG(WARNING) << __func__ << ": could not fetch preferred data interval";
}
const bool isInput = portConfig.flags->getTag() == AudioIoFlags::input;
return isInput ? StreamInBluetooth::getNominalLatencyMs(dataIntervalUs)
: StreamOutBluetooth::getNominalLatencyMs(dataIntervalUs);
}
}
LOG(ERROR) << __func__ << ": no connection or proxy found for " << portConfig.toString();
return Module::getNominalLatencyMs(portConfig);
}
binder_status_t ModuleBluetoothBase::dump(int fd, const char** args, uint32_t numArgs) {
if (!::aidl::android::hardware::audio::common::hasArgument(
args, numArgs,
::aidl::android::hardware::audio::common::kDumpFromAudioServerArgument)) {
// Streams are dumped as part of audio flinger threads dump,
// no need for a separate dump since the module itself does not
// have anything interesting.
Module::dump(fd, args, numArgs);
}
return ::android::OK;
}
ndk::ScopedAStatus ModuleBluetoothBase::createProxy(const AudioPort& audioPort,
int32_t instancePortId, CachedProxy& proxy) {
proxy.ptr = createProxyInstance(audioPort.flags.getTag() == AudioIoFlags::input);
const auto& devicePort = audioPort.ext.get<AudioPortExt::device>();
const auto device = devicePort.device.type;
bool registrationSuccess = false;
for (int i = 0; i < kCreateProxyRetries && !registrationSuccess; ++i) {
registrationSuccess = proxy.ptr->registerPort(device);
usleep(kCreateProxyRetrySleepMs * 1000);
}
if (!registrationSuccess) {
LOG(ERROR) << __func__ << ": failed to register BT port for " << device.toString();
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
if (!proxy.ptr->loadAudioConfig(proxy.pcmConfig)) {
LOG(ERROR) << __func__ << ": state=" << proxy.ptr->getState()
<< ", failed to load audio config";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
mProxies.insert(std::pair(instancePortId, proxy));
mProxiesWeak[device] = proxy.ptr;
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus ModuleBluetoothBase::fetchAndCheckProxy(const StreamContext& context,
CachedProxy& proxy) {
const auto connectionsIt = mConnections.find(context.getMixPortHandle());
if (connectionsIt != mConnections.end()) {
const auto proxyIt = mProxies.find(connectionsIt->second);
if (proxyIt != mProxies.end()) {
proxy = proxyIt->second;
mProxies.erase(proxyIt);
}
mConnections.erase(connectionsIt);
}
if (proxy.ptr != nullptr) {
if (!StreamBluetooth::checkConfigParams(
proxy.pcmConfig, AudioConfigBase{.sampleRate = context.getSampleRate(),
.channelMask = context.getChannelLayout(),
.format = context.getFormat()})) {
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
}
// Not having a proxy is OK, it may happen in VTS tests when streams are opened on unconnected
// mix ports.
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus ModuleBluetoothBase::findOrCreateProxy(const AudioPort& audioPort,
CachedProxy& proxy) {
if (auto proxyIt = mProxies.find(audioPort.id); proxyIt != mProxies.end()) {
proxy = proxyIt->second;
return ndk::ScopedAStatus::ok();
}
if (audioPort.ext.getTag() == AudioPortExt::Tag::device) {
const auto& description = audioPort.ext.get<AudioPortExt::Tag::device>().device.type;
auto weakIt = mProxiesWeak.find(description);
if (weakIt != mProxiesWeak.end()) {
if (auto existingProxy = weakIt->second.lock()) {
proxy.ptr = existingProxy;
if (proxy.ptr->loadAudioConfig(proxy.pcmConfig)) {
mProxies.insert(std::pair(audioPort.id, proxy));
LOG(DEBUG) << __func__ << ": Reused existing proxy for "
<< description.toString();
return ndk::ScopedAStatus::ok();
} else {
LOG(ERROR) << __func__ << ": Failed to load config from reused proxy";
}
}
// If expired or failed to load config, remove from map and create new
mProxiesWeak.erase(weakIt);
}
}
return createProxy(audioPort, audioPort.id, proxy);
}
} // namespace aidl::android::hardware::audio::core
+174 -30
View File
@@ -14,11 +14,12 @@
* limitations under the License.
*/
#include <inttypes.h>
#include <algorithm>
#define LOG_TAG "AHAL_StreamBluetooth"
#include <Log.h>
#include <Utils.h>
#include <android-base/logging.h>
#include <audio_utils/clock.h>
#include "core-impl/StreamBluetooth.h"
@@ -27,6 +28,7 @@ using aidl::android::hardware::audio::common::SinkMetadata;
using aidl::android::hardware::audio::common::SourceMetadata;
using aidl::android::hardware::audio::core::VendorParameter;
using aidl::android::hardware::bluetooth::audio::ChannelMode;
using aidl::android::hardware::bluetooth::audio::LatencyMode;
using aidl::android::hardware::bluetooth::audio::PcmConfiguration;
using aidl::android::hardware::bluetooth::audio::PresentationPosition;
using aidl::android::media::audio::common::AudioChannelLayout;
@@ -35,12 +37,11 @@ using aidl::android::media::audio::common::AudioDevice;
using aidl::android::media::audio::common::AudioDeviceAddress;
using aidl::android::media::audio::common::AudioFormatDescription;
using aidl::android::media::audio::common::AudioFormatType;
using aidl::android::media::audio::common::AudioLatencyMode;
using aidl::android::media::audio::common::AudioOffloadInfo;
using aidl::android::media::audio::common::MicrophoneDynamicInfo;
using aidl::android::media::audio::common::MicrophoneInfo;
using android::bluetooth::audio::aidl::BluetoothAudioPortAidl;
using android::bluetooth::audio::aidl::BluetoothAudioPortAidlIn;
using android::bluetooth::audio::aidl::BluetoothAudioPortAidlOut;
using android::bluetooth::audio::aidl::BluetoothAudioPort;
using android::bluetooth::audio::aidl::BluetoothStreamState;
namespace aidl::android::hardware::audio::core {
@@ -50,21 +51,78 @@ constexpr int kBluetoothDefaultOutputBufferMs = 10;
// constexpr int kBluetoothSpatializerOutputBufferMs = 10;
constexpr int kBluetoothDefaultRemoteDelayMs = 200;
namespace {
LatencyMode audio2bt_LatencyMode(AudioLatencyMode mode) {
switch (mode) {
case AudioLatencyMode::FREE:
return LatencyMode::FREE;
case AudioLatencyMode::LOW:
return LatencyMode::LOW_LATENCY;
case AudioLatencyMode::DYNAMIC_SPATIAL_AUDIO_SOFTWARE:
return LatencyMode::DYNAMIC_SPATIAL_AUDIO_SOFTWARE;
case AudioLatencyMode::DYNAMIC_SPATIAL_AUDIO_HARDWARE:
return LatencyMode::DYNAMIC_SPATIAL_AUDIO_HARDWARE;
}
return LatencyMode::UNKNOWN;
}
std::optional<AudioLatencyMode> bt2audio_LatencyMode(LatencyMode mode) {
switch (mode) {
case LatencyMode::UNKNOWN:
return std::nullopt;
case LatencyMode::FREE:
return AudioLatencyMode::FREE;
case LatencyMode::LOW_LATENCY:
return AudioLatencyMode::LOW;
case LatencyMode::DYNAMIC_SPATIAL_AUDIO_SOFTWARE:
return AudioLatencyMode::DYNAMIC_SPATIAL_AUDIO_SOFTWARE;
case LatencyMode::DYNAMIC_SPATIAL_AUDIO_HARDWARE:
return AudioLatencyMode::DYNAMIC_SPATIAL_AUDIO_HARDWARE;
}
return std::nullopt;
}
std::vector<AudioLatencyMode> bt2audio_LatencyModes(const std::vector<LatencyMode>& modes) {
std::vector<AudioLatencyMode> result;
for (const auto m : modes) {
if (const auto alm = bt2audio_LatencyMode(m); alm.has_value()) {
result.push_back(*alm);
}
}
return result;
}
} // namespace
void PortCallbacksHandler::onRecommendedLatencyModeChanged(const std::vector<LatencyMode>& modes) {
mStreamCallback->onRecommendedLatencyModeChanged(bt2audio_LatencyModes(modes));
}
StreamBluetooth::StreamBluetooth(StreamContext* context, const Metadata& metadata,
ModuleBluetooth::BtProfileHandles&& btHandles,
const std::shared_ptr<BluetoothAudioPortAidl>& btDeviceProxy,
const std::shared_ptr<BluetoothAudioPort>& btDeviceProxy,
const PcmConfiguration& pcmConfig)
: StreamCommonImpl(context, metadata),
mFrameSizeBytes(getContext().getFrameSize()),
mIsInput(isInput(metadata)),
mBluetoothA2dp(std::move(std::get<ModuleBluetooth::BtInterface::BTA2DP>(btHandles))),
mBluetoothLe(std::move(std::get<ModuleBluetooth::BtInterface::BTLE>(btHandles))),
mBluetoothHfp(std::move(std::get<ModuleBluetooth::BtInterface::BTSCO>(btHandles))),
mPreferredDataIntervalUs(pcmConfig.dataIntervalUs != 0
? pcmConfig.dataIntervalUs
: (mIsInput ? kBluetoothDefaultInputBufferMs
: kBluetoothDefaultOutputBufferMs) *
1000),
mBtDeviceProxy(btDeviceProxy) {}
mCallbacksHandler(new PortCallbacksHandler(getContext().getOutEventCallback())),
mBtDeviceProxy(btDeviceProxy) {
if (mBtDeviceProxy != nullptr) {
mSessionTypeName = mBtDeviceProxy->getSessionNameForDebug();
if (mCallbacksHandler->hasCallback()) {
mBtDeviceProxy->setCallbacks(mCallbacksHandler);
}
}
}
StreamBluetooth::~StreamBluetooth() {
cleanupWorker();
@@ -97,15 +155,16 @@ StreamBluetooth::~StreamBluetooth() {
::android::status_t StreamBluetooth::transfer(void* buffer, size_t frameCount,
size_t* actualFrameCount, int32_t* latencyMs) {
std::lock_guard guard(mLock);
*actualFrameCount = 0;
*latencyMs = StreamDescriptor::LATENCY_UNKNOWN;
if (mBtDeviceProxy == nullptr || mBtDeviceProxy->getState() == BluetoothStreamState::DISABLED) {
// The BT session is turned down, silently ignore write.
if (!mEnabled) {
*actualFrameCount = frameCount;
*latencyMs = kBluetoothDefaultRemoteDelayMs;
usleep((float)(frameCount * 1000000) / (float)getContext().getSampleRate());
return ::android::OK;
}
if (!mBtDeviceProxy->start()) {
LOG(WARNING) << __func__ << ": state= " << mBtDeviceProxy->getState()
<< " failed to start, will retry";
*actualFrameCount = 0;
*latencyMs = StreamDescriptor::LATENCY_UNKNOWN;
if (mBtDeviceProxy == nullptr || !mBtDeviceProxy->start()) {
// The BT session is turned down.
return ::android::OK;
}
*latencyMs = 0;
@@ -113,17 +172,24 @@ StreamBluetooth::~StreamBluetooth() {
const size_t bytesTransferred = mIsInput ? mBtDeviceProxy->readData(buffer, bytesToTransfer)
: mBtDeviceProxy->writeData(buffer, bytesToTransfer);
*actualFrameCount = bytesTransferred / mFrameSizeBytes;
PresentationPosition presentation_position;
if (!mBtDeviceProxy->getPresentationPosition(presentation_position)) {
presentation_position.remoteDeviceAudioDelayNanos =
kBluetoothDefaultRemoteDelayMs * NANOS_PER_MILLISECOND;
LOG(WARNING) << __func__ << ": getPresentationPosition failed, latency info is unavailable";
const int64_t decimPresentationPosition =
getContext().getFrameCount() / getContext().getSampleRate();
if (decimPresentationPosition != mDecimPresentationPosition) {
if (!mBtDeviceProxy->getPresentationPosition(mActualPresentationPosition)) {
mActualPresentationPosition.remoteDeviceAudioDelayNanos =
kBluetoothDefaultRemoteDelayMs * NANOS_PER_MILLISECOND;
LOG(WARNING) << __func__
<< ": getPresentationPosition failed, latency info is unavailable";
}
mDecimPresentationPosition = decimPresentationPosition;
}
// TODO(b/317117580): incorporate logic from
// packages/modules/Bluetooth/system/audio_bluetooth_hw/stream_apis.cc
// out_calculate_feeding_delay_ms / in_calculate_starving_delay_ms
*latencyMs = std::max(*latencyMs, (int32_t)(presentation_position.remoteDeviceAudioDelayNanos /
NANOS_PER_MILLISECOND));
*latencyMs =
std::max(*latencyMs, (int32_t)(mActualPresentationPosition.remoteDeviceAudioDelayNanos /
NANOS_PER_MILLISECOND));
return ::android::OK;
}
@@ -151,8 +217,8 @@ bool StreamBluetooth::checkConfigParams(const PcmConfiguration& pcmConfig,
const int8_t bitsPerSample =
aidl::android::hardware::audio::common::getPcmSampleSizeInBytes(config.format.pcm) * 8;
if (bitsPerSample != pcmConfig.bitsPerSample) {
LOG(ERROR) << __func__ << ": bits per sample mismatch, stream value=" << bitsPerSample
<< ", BT HAL value=" << pcmConfig.bitsPerSample;
LOG(ERROR) << __func__ << ": bits per sample mismatch, stream value=" << (int)bitsPerSample
<< ", BT HAL value=" << (int)pcmConfig.bitsPerSample;
return false;
}
return true;
@@ -161,9 +227,15 @@ bool StreamBluetooth::checkConfigParams(const PcmConfiguration& pcmConfig,
ndk::ScopedAStatus StreamBluetooth::prepareToClose() {
std::lock_guard guard(mLock);
if (mBtDeviceProxy != nullptr) {
if (mBtDeviceProxy->getState() != BluetoothStreamState::DISABLED) {
mBtDeviceProxy->stop();
}
mBtDeviceProxy->stop();
}
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus StreamBluetooth::setConnectedDevices(const std::vector<AudioDevice>& devices) {
RETURN_STATUS_IF_ERROR(StreamCommonImpl::setConnectedDevices(devices));
if (devices.empty()) {
return ndk::ScopedAStatus::fromStatus(standby());
}
return ndk::ScopedAStatus::ok();
}
@@ -176,6 +248,10 @@ ndk::ScopedAStatus StreamBluetooth::prepareToClose() {
::android::status_t StreamBluetooth::start() {
std::lock_guard guard(mLock);
if (!mEnabled) {
return ::android::OK;
}
mDecimPresentationPosition = -1;
if (mBtDeviceProxy != nullptr) mBtDeviceProxy->start();
return ::android::OK;
}
@@ -207,8 +283,7 @@ ndk::ScopedAStatus StreamBluetooth::bluetoothParametersUpdated() {
if (mIsInput) {
return ndk::ScopedAStatus::ok();
}
auto applyParam = [](const std::shared_ptr<BluetoothAudioPortAidl>& proxy,
bool isEnabled) -> bool {
auto applyParam = [](const std::shared_ptr<BluetoothAudioPort>& proxy, bool isEnabled) -> bool {
if (!isEnabled) {
if (proxy->suspend()) return proxy->setState(BluetoothStreamState::DISABLED);
return false;
@@ -221,17 +296,67 @@ ndk::ScopedAStatus StreamBluetooth::bluetoothParametersUpdated() {
bool hasLeParam, enableLe;
auto btLe = mBluetoothLe.lock();
hasLeParam = btLe != nullptr && btLe->isEnabled(&enableLe).isOk();
bool hasScoParam;
Bluetooth::ScoConfig scoConfig;
auto btHfp = mBluetoothHfp.lock();
hasScoParam = btHfp != nullptr && btHfp->setScoConfig({}, &scoConfig).isOk();
std::lock_guard guard(mLock);
if (mBtDeviceProxy != nullptr) {
if ((hasA2dpParam && mBtDeviceProxy->isA2dp() && !applyParam(mBtDeviceProxy, enableA2dp)) ||
(hasLeParam && mBtDeviceProxy->isLeAudio() && !applyParam(mBtDeviceProxy, enableLe))) {
(hasLeParam && mBtDeviceProxy->isLeAudio() && !applyParam(mBtDeviceProxy, enableLe)) ||
(hasScoParam && mBtDeviceProxy->isHfp() &&
!applyParam(mBtDeviceProxy, scoConfig.isEnabled.value().value))) {
LOG(DEBUG) << __func__ << ": applyParam failed";
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
mEnabled = (mBtDeviceProxy->isA2dp() && enableA2dp) ||
(mBtDeviceProxy->isLeAudio() && enableLe) ||
(mBtDeviceProxy->isHfp() && scoConfig.isEnabled.value().value);
LOG(INFO) << __func__ << ": mEnabled: " << mEnabled;
}
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus StreamBluetooth::getRecommendedLatencyModes(
std::vector<AudioLatencyMode>* _aidl_return) {
LOG(DEBUG) << __func__;
std::vector<LatencyMode> modes;
std::lock_guard guard(mLock);
if (!mBtDeviceProxy || !mBtDeviceProxy->getRecommendedLatencyModes(&modes)) {
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
*_aidl_return = bt2audio_LatencyModes(modes);
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus StreamBluetooth::setLatencyMode(AudioLatencyMode in_mode) {
LOG(DEBUG) << __func__ << ": " << toString(in_mode);
std::lock_guard guard(mLock);
if (mBtDeviceProxy != nullptr) {
return mBtDeviceProxy->setLatencyMode(audio2bt_LatencyMode(in_mode))
? ndk::ScopedAStatus::ok()
: ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
void StreamBluetooth::dump(int fd, const char** args, uint32_t numArgs) {
const int indent = 4;
if (::aidl::android::hardware::audio::common::hasArgument(
args, numArgs,
::aidl::android::hardware::audio::common::kDumpFromAudioServerArgument)) {
// Just provide the frames count.
dprintf(fd, "%*sFrames transferred: %" PRId64 "\n", indent, "",
getContext().getFrameCount());
return;
}
dprintf(fd, "%*sI/O handle %d:\n", indent, "", getContext().getMixPortHandle());
dprintf(fd, "%*sFrames transferred: %" PRId64 "\n", indent + 2, "",
getContext().getFrameCount());
dprintf(fd, "%*sSession type: %s\n", indent + 2, "", mSessionTypeName.c_str());
dprintf(fd, "%*sSample rate: %d\n", indent + 2, "", getContext().getSampleRate());
}
// static
int32_t StreamInBluetooth::getNominalLatencyMs(size_t dataIntervalUs) {
if (dataIntervalUs == 0) dataIntervalUs = kBluetoothDefaultInputBufferMs * 1000LL;
@@ -241,7 +366,7 @@ int32_t StreamInBluetooth::getNominalLatencyMs(size_t dataIntervalUs) {
StreamInBluetooth::StreamInBluetooth(StreamContext&& context, const SinkMetadata& sinkMetadata,
const std::vector<MicrophoneInfo>& microphones,
ModuleBluetooth::BtProfileHandles&& btProfileHandles,
const std::shared_ptr<BluetoothAudioPortAidl>& btDeviceProxy,
const std::shared_ptr<BluetoothAudioPort>& btDeviceProxy,
const PcmConfiguration& pcmConfig)
: StreamIn(std::move(context), microphones),
StreamBluetooth(&mContextInstance, sinkMetadata, std::move(btProfileHandles), btDeviceProxy,
@@ -253,6 +378,11 @@ ndk::ScopedAStatus StreamInBluetooth::getActiveMicrophones(
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
binder_status_t StreamInBluetooth::dump(int fd, const char** args, uint32_t numArgs) {
StreamBluetooth::dump(fd, args, numArgs);
return ::android::OK;
}
// static
int32_t StreamOutBluetooth::getNominalLatencyMs(size_t dataIntervalUs) {
if (dataIntervalUs == 0) dataIntervalUs = kBluetoothDefaultOutputBufferMs * 1000LL;
@@ -263,10 +393,24 @@ StreamOutBluetooth::StreamOutBluetooth(StreamContext&& context,
const SourceMetadata& sourceMetadata,
const std::optional<AudioOffloadInfo>& offloadInfo,
ModuleBluetooth::BtProfileHandles&& btProfileHandles,
const std::shared_ptr<BluetoothAudioPortAidl>& btDeviceProxy,
const std::shared_ptr<BluetoothAudioPort>& btDeviceProxy,
const PcmConfiguration& pcmConfig)
: StreamOut(std::move(context), offloadInfo),
StreamBluetooth(&mContextInstance, sourceMetadata, std::move(btProfileHandles), btDeviceProxy,
pcmConfig) {}
ndk::ScopedAStatus StreamOutBluetooth::getRecommendedLatencyModes(
std::vector<AudioLatencyMode>* _aidl_return) {
return StreamBluetooth::getRecommendedLatencyModes(_aidl_return);
}
ndk::ScopedAStatus StreamOutBluetooth::setLatencyMode(AudioLatencyMode in_mode) {
return StreamBluetooth::setLatencyMode(in_mode);
}
binder_status_t StreamOutBluetooth::dump(int fd, const char** args, uint32_t numArgs) {
StreamBluetooth::dump(fd, args, numArgs);
return ::android::OK;
}
} // namespace aidl::android::hardware::audio::core
+44 -8
View File
@@ -1,13 +1,44 @@
// Signature format: 2.0
// Signature format: 6.0
// - style=java
package android.audio.policy.configuration {
public class AttachedDevices {
ctor public AttachedDevices();
method @Nullable public java.util.List<java.lang.String> getItem();
method @Nullable public java.util.List<String> getItem();
}
public enum AudioChannelMask {
method @NonNull public String getRawName();
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_0;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_0_HRZ;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_1;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_10;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_10_HRZ;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_11;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_11_HRZ;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_12;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_12_HRZ;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_13;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_13_HRZ;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_14;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_14_HRZ;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_1_HRZ;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_2;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_2_HRZ;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_3;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_3_HRZ;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_4;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_4_HRZ;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_5;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_5_HRZ;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_6;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_6_HRZ;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_7;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_7_HRZ;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_8;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_8_HRZ;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_9;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_ACN_ORDER_9_HRZ;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_INDEX_MASK_1;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_INDEX_MASK_10;
enum_constant public static final android.audio.policy.configuration.AudioChannelMask AUDIO_CHANNEL_INDEX_MASK_11;
@@ -91,6 +122,8 @@ package android.audio.policy.configuration {
enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET;
enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_IN_AUX_DIGITAL;
enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_IN_BACK_MIC;
enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_IN_BLE_CENTRAL;
enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_IN_BLE_CENTRAL_BROADCAST;
enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_IN_BLE_HEADSET;
enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_IN_BLUETOOTH_A2DP;
enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_IN_BLUETOOTH_BLE;
@@ -124,7 +157,9 @@ package android.audio.policy.configuration {
enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_OUT_AUX_DIGITAL;
enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_OUT_AUX_LINE;
enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_OUT_BLE_BROADCAST;
enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_OUT_BLE_CENTRAL;
enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_OUT_BLE_HEADSET;
enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_OUT_BLE_HEARING_AID;
enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_OUT_BLE_SPEAKER;
enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_OUT_BLUETOOTH_A2DP;
enum_constant public static final android.audio.policy.configuration.AudioDevice AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES;
@@ -200,6 +235,7 @@ package android.audio.policy.configuration {
enum_constant public static final android.audio.policy.configuration.AudioFormat AUDIO_FORMAT_APE;
enum_constant public static final android.audio.policy.configuration.AudioFormat AUDIO_FORMAT_APTX;
enum_constant public static final android.audio.policy.configuration.AudioFormat AUDIO_FORMAT_APTX_ADAPTIVE;
enum_constant public static final android.audio.policy.configuration.AudioFormat AUDIO_FORMAT_APTX_ADAPTIVE_PLUS;
enum_constant public static final android.audio.policy.configuration.AudioFormat AUDIO_FORMAT_APTX_ADAPTIVE_QLEA;
enum_constant public static final android.audio.policy.configuration.AudioFormat AUDIO_FORMAT_APTX_ADAPTIVE_R4;
enum_constant public static final android.audio.policy.configuration.AudioFormat AUDIO_FORMAT_APTX_HD;
@@ -373,7 +409,7 @@ package android.audio.policy.configuration {
public static class DevicePorts.DevicePort {
ctor public DevicePorts.DevicePort();
method @Nullable public String getAddress();
method @Nullable public java.util.List<java.lang.String> getEncodedFormats();
method @Nullable public java.util.List<String> getEncodedFormats();
method @Nullable public android.audio.policy.configuration.Gains getGains();
method @Nullable public java.util.List<android.audio.policy.configuration.Profile> getProfile();
method @Nullable public android.audio.policy.configuration.Role getRole();
@@ -381,7 +417,7 @@ package android.audio.policy.configuration {
method @Nullable public String getType();
method @Nullable public boolean get_default();
method public void setAddress(@Nullable String);
method public void setEncodedFormats(@Nullable java.util.List<java.lang.String>);
method public void setEncodedFormats(@Nullable java.util.List<String>);
method public void setGains(@Nullable android.audio.policy.configuration.Gains);
method public void setRole(@Nullable android.audio.policy.configuration.Role);
method public void setTagName(@Nullable String);
@@ -504,7 +540,7 @@ package android.audio.policy.configuration {
public class Reference {
ctor public Reference();
method @Nullable public String getName();
method @Nullable public java.util.List<java.lang.String> getPoint();
method @Nullable public java.util.List<String> getPoint();
method public void setName(@Nullable String);
}
@@ -537,9 +573,9 @@ package android.audio.policy.configuration {
public static class SurroundFormats.Format {
ctor public SurroundFormats.Format();
method @Nullable public String getName();
method @Nullable public java.util.List<java.lang.String> getSubformats();
method @Nullable public java.util.List<String> getSubformats();
method public void setName(@Nullable String);
method public void setSubformats(@Nullable java.util.List<java.lang.String>);
method public void setSubformats(@Nullable java.util.List<String>);
}
public class SurroundSound {
@@ -557,7 +593,7 @@ package android.audio.policy.configuration {
public class Volume {
ctor public Volume();
method @Nullable public android.audio.policy.configuration.DeviceCategory getDeviceCategory();
method @Nullable public java.util.List<java.lang.String> getPoint();
method @Nullable public java.util.List<String> getPoint();
method @Nullable public String getRef();
method @Nullable public android.audio.policy.configuration.AudioStreamType getStream();
method public void setDeviceCategory(@Nullable android.audio.policy.configuration.DeviceCategory);
+2 -1
View File
@@ -1 +1,2 @@
// Signature format: 2.0
// Signature format: 6.0
// - style=java
@@ -271,8 +271,10 @@
<xs:enumeration value="AUDIO_DEVICE_OUT_BLE_HEADSET"/>
<xs:enumeration value="AUDIO_DEVICE_OUT_BLE_SPEAKER"/>
<xs:enumeration value="AUDIO_DEVICE_OUT_BLE_BROADCAST"/>
<xs:enumeration value="AUDIO_DEVICE_OUT_BLE_HEARING_AID"/>
<xs:enumeration value="AUDIO_DEVICE_OUT_DEFAULT"/>
<xs:enumeration value="AUDIO_DEVICE_OUT_STUB"/>
<xs:enumeration value="AUDIO_DEVICE_OUT_BLE_CENTRAL"/>
<xs:enumeration value="AUDIO_DEVICE_IN_COMMUNICATION"/>
<xs:enumeration value="AUDIO_DEVICE_IN_AMBIENT"/>
@@ -306,6 +308,8 @@
<xs:enumeration value="AUDIO_DEVICE_IN_BLE_HEADSET"/>
<xs:enumeration value="AUDIO_DEVICE_IN_DEFAULT"/>
<xs:enumeration value="AUDIO_DEVICE_IN_STUB"/>
<xs:enumeration value="AUDIO_DEVICE_IN_BLE_CENTRAL"/>
<xs:enumeration value="AUDIO_DEVICE_IN_BLE_CENTRAL_BROADCAST"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="vendorExtension">
@@ -429,6 +433,7 @@
<xs:enumeration value="AUDIO_FORMAT_IAMF_BASE_ENHANCED_AAC"/>
<xs:enumeration value="AUDIO_FORMAT_IAMF_BASE_ENHANCED_PCM"/>
<xs:enumeration value="AUDIO_FORMAT_IAMF_BASE_ENHANCED_FLAC"/>
<xs:enumeration value="AUDIO_FORMAT_APTX_ADAPTIVE_PLUS"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="extendableAudioFormat">
@@ -524,7 +529,37 @@
<xs:enumeration value="AUDIO_CHANNEL_INDEX_MASK_22"/>
<xs:enumeration value="AUDIO_CHANNEL_INDEX_MASK_23"/>
<xs:enumeration value="AUDIO_CHANNEL_INDEX_MASK_24"/>
</xs:restriction>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_0"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_1"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_2"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_3"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_4"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_5"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_6"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_7"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_8"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_9"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_10"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_11"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_12"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_13"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_14"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_0_HRZ"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_1_HRZ"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_2_HRZ"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_3_HRZ"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_4_HRZ"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_5_HRZ"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_6_HRZ"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_7_HRZ"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_8_HRZ"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_9_HRZ"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_10_HRZ"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_11_HRZ"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_12_HRZ"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_13_HRZ"/>
<xs:enumeration value="AUDIO_CHANNEL_ACN_ORDER_14_HRZ"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="channelMasks">
<xs:list itemType="audioChannelMask" />
@@ -1,4 +1,5 @@
// Signature format: 2.0
// Signature format: 6.0
// - style=java
package android.audio.policy.capengine.configuration {
public class BitParameterBlockType {
@@ -1 +1,2 @@
// Signature format: 2.0
// Signature format: 6.0
// - style=java
@@ -1,4 +1,5 @@
// Signature format: 2.0
// Signature format: 6.0
// - style=java
package android.audio.policy.engine.configuration {
public class AttributesGroup {
@@ -203,8 +204,10 @@ package android.audio.policy.engine.configuration {
method @Nullable public java.util.List<android.audio.policy.engine.configuration.AttributesGroup> getAttributesGroup();
method @Nullable public int getId();
method @Nullable public String getName();
method @Nullable public int getZoneId();
method public void setId(@Nullable int);
method public void setName(@Nullable String);
method public void setZoneId(@Nullable int);
}
public enum SourceEnumType {
@@ -265,6 +268,8 @@ package android.audio.policy.engine.configuration {
enum_constant public static final android.audio.policy.engine.configuration.UsageEnumType AUDIO_USAGE_NOTIFICATION_COMMUNICATION_REQUEST;
enum_constant public static final android.audio.policy.engine.configuration.UsageEnumType AUDIO_USAGE_NOTIFICATION_EVENT;
enum_constant public static final android.audio.policy.engine.configuration.UsageEnumType AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE;
enum_constant public static final android.audio.policy.engine.configuration.UsageEnumType AUDIO_USAGE_NOTIFICATION_VIBRATION;
enum_constant public static final android.audio.policy.engine.configuration.UsageEnumType AUDIO_USAGE_RINGTONE_VIBRATION;
enum_constant public static final android.audio.policy.engine.configuration.UsageEnumType AUDIO_USAGE_SAFETY;
enum_constant public static final android.audio.policy.engine.configuration.UsageEnumType AUDIO_USAGE_SPEAKER_CLEANUP;
enum_constant public static final android.audio.policy.engine.configuration.UsageEnumType AUDIO_USAGE_UNKNOWN;
@@ -301,7 +306,7 @@ package android.audio.policy.engine.configuration {
public class Volume {
ctor public Volume();
method @Nullable public android.audio.policy.engine.configuration.DeviceCategory getDeviceCategory();
method @Nullable public java.util.List<java.lang.String> getPoint();
method @Nullable public java.util.List<String> getPoint();
method @Nullable public String getRef();
method public void setDeviceCategory(@Nullable android.audio.policy.engine.configuration.DeviceCategory);
method public void setRef(@Nullable String);
@@ -326,7 +331,7 @@ package android.audio.policy.engine.configuration {
public class VolumeRef {
ctor public VolumeRef();
method @Nullable public String getName();
method @Nullable public java.util.List<java.lang.String> getPoint();
method @Nullable public java.util.List<String> getPoint();
method public void setName(@Nullable String);
}
@@ -1 +1,2 @@
// Signature format: 2.0
// Signature format: 6.0
// - style=java
@@ -107,6 +107,7 @@
<xs:attribute name="name" type="xs:string" use="required"/>
<!-- Only needs to be specified for vendor strategies. -->
<xs:attribute name="id" type="xs:int" use="optional"/>
<xs:attribute name="zoneId" type="xs:int" use="optional"/>
</xs:complexType>
</xs:element>
</xs:sequence>
@@ -377,6 +378,8 @@
<xs:enumeration value="AUDIO_USAGE_VEHICLE_STATUS" />
<xs:enumeration value="AUDIO_USAGE_ANNOUNCEMENT" />
<xs:enumeration value="AUDIO_USAGE_SPEAKER_CLEANUP" />
<xs:enumeration value="AUDIO_USAGE_NOTIFICATION_VIBRATION" />
<xs:enumeration value="AUDIO_USAGE_RINGTONE_VIBRATION" />
</xs:restriction>
</xs:simpleType>
+13 -1
View File
@@ -18,8 +18,8 @@
#define LOG_TAG "AHAL_StreamSwitcher"
#include <Log.h>
#include <Utils.h>
#include <android-base/logging.h>
#include <error/expected_utils.h>
#include "core-impl/StreamStub.h"
@@ -135,6 +135,18 @@ ndk::ScopedAStatus StreamSwitcher::removeEffect(const std::shared_ptr<IEffect>&
return !mIsStubStream ? mStream->removeEffect(in_effect) : ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus StreamSwitcher::createMmapBuffer(MmapBufferDescriptor* _aidl_return) {
if (mStream == nullptr) {
LOG(ERROR) << __func__ << ": stream was closed";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
if (mIsStubStream) {
LOG(ERROR) << __func__ << ": the stream is not connected";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
return mStream->createMmapBuffer(_aidl_return);
}
ndk::ScopedAStatus StreamSwitcher::getStreamCommonCommon(
std::shared_ptr<IStreamCommon>* _aidl_return) {
if (!mCommon) {
+1
View File
@@ -124,6 +124,7 @@ class StreamSwitcher : virtual public StreamCommonInterface {
ndk::ScopedAStatus removeEffect(
const std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>& in_effect)
override;
ndk::ScopedAStatus createMmapBuffer(MmapBufferDescriptor* _aidl_return) override;
ndk::ScopedAStatus getStreamCommonCommon(std::shared_ptr<IStreamCommon>* _aidl_return) override;
ndk::ScopedAStatus updateMetadataCommon(const Metadata& metadata) override;
+1 -1
View File
@@ -31,7 +31,7 @@ class ParamChangeHandler {
std::function<ndk::ScopedAStatus()> mHandler = nullptr;
};
class Bluetooth : public BnBluetooth {
class Bluetooth : public BnBluetooth, public ParamChangeHandler {
public:
Bluetooth();
@@ -0,0 +1,184 @@
/*
* Copyright 2025 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.
*/
#pragma once
#include <memory>
#include <vector>
#include <android-base/stringprintf.h>
#include <aidl/android/hardware/audio/common/SinkMetadata.h>
#include <aidl/android/hardware/audio/common/SourceMetadata.h>
#include <aidl/android/hardware/bluetooth/audio/LatencyMode.h>
#include <aidl/android/hardware/bluetooth/audio/PcmConfiguration.h>
#include <aidl/android/hardware/bluetooth/audio/PresentationPosition.h>
#include <aidl/android/media/audio/common/AudioDeviceDescription.h>
namespace android::bluetooth::audio::aidl {
enum class BluetoothStreamState : uint8_t {
DISABLED = 0, // This stream is closing or Bluetooth profiles (A2DP/LE) is disabled
STANDBY,
STARTING,
STARTED,
SUSPENDING,
UNKNOWN,
};
inline std::ostream& operator<<(std::ostream& os, const BluetoothStreamState& state) {
switch (state) {
case BluetoothStreamState::DISABLED:
return os << "DISABLED";
case BluetoothStreamState::STANDBY:
return os << "STANDBY";
case BluetoothStreamState::STARTING:
return os << "STARTING";
case BluetoothStreamState::STARTED:
return os << "STARTED";
case BluetoothStreamState::SUSPENDING:
return os << "SUSPENDING";
case BluetoothStreamState::UNKNOWN:
return os << "UNKNOWN";
default:
return os << android::base::StringPrintf("%#hhx", state);
}
}
class BluetoothAudioPortCallbacks {
public:
virtual ~BluetoothAudioPortCallbacks() = default;
virtual void onRecommendedLatencyModeChanged(
const std::vector<::aidl::android::hardware::bluetooth::audio::LatencyMode>&) = 0;
};
/**
* Proxy for Bluetooth Audio HW Module to communicate with Bluetooth Audio
* Session Control. All methods are not thread safe, so users must acquire a
* lock. Note: currently, getState() of DevicePortProxy is only used for
* verbose logging, it is not locked, so the state may not be synchronized.
*/
class BluetoothAudioPort {
public:
virtual ~BluetoothAudioPort() = default;
/**
* Fetch output control / data path of BluetoothAudioPort and setup
* callbacks into BluetoothAudioProvider. If registerPort() returns false, the audio
* HAL must delete this BluetoothAudioPort and return EINVAL to caller
*/
virtual bool registerPort(
const ::aidl::android::media::audio::common::AudioDeviceDescription&) = 0;
/**
* Unregister this BluetoothAudioPort from BluetoothAudioSessionControl.
* Audio HAL must delete this BluetoothAudioPort after calling this.
*/
virtual void unregisterPort() = 0;
/**
* When the Audio framework / HAL tries to query audio config about format,
* channel mask and sample rate, it uses this function to fetch from the
* Bluetooth stack
*/
virtual bool loadAudioConfig(
::aidl::android::hardware::bluetooth::audio::PcmConfiguration&) = 0;
/**
* When the Audio framework / HAL wants to change the stream state, it invokes
* these 4 functions to control the Bluetooth stack (Audio Control Path).
* Note: standby(), start() and suspend() will return true when there are no errors.
* Called by Audio framework / HAL to change the state to stand by. When A2DP/LE profile is
* disabled, the port is first set to STANDBY by calling suspend and then mState is set to
* DISABLED. To reset the state back to STANDBY this method is called.
*/
virtual bool standby() = 0;
/**
* Called by Audio framework / HAL to start the stream. Starts the BT session stream with
* low latency when it is supported.
*/
virtual bool start() = 0;
/**
* Called by Audio framework / HAL to suspend the stream
*/
virtual bool suspend() = 0;
/**
* Called by Audio framework / HAL to stop the stream
*/
virtual void stop() = 0;
/**
* Called by the Audio framework / HAL to fetch information about audio frames
* presented to an external sink, or frames presented fror an internal sink
*/
virtual bool getPresentationPosition(
::aidl::android::hardware::bluetooth::audio::PresentationPosition&) const = 0;
/**
* Called by the Audio framework / HAL when the metadata of the stream's
* source has been changed.
*/
virtual bool updateSourceMetadata(
const ::aidl::android::hardware::audio::common::SourceMetadata&) const = 0;
/**
* Called by the Audio framework / HAL when the metadata of the stream's
* sink has been changed.
*/
virtual bool updateSinkMetadata(
const ::aidl::android::hardware::audio::common::SinkMetadata&) const = 0;
/**
* Return the current BluetoothStreamState
*/
virtual BluetoothStreamState getState() const = 0;
/**
* Set the current BluetoothStreamState
*/
virtual bool setState(BluetoothStreamState) = 0;
virtual bool isA2dp() const = 0;
virtual bool isLeAudio() const = 0;
virtual bool isHfp() const = 0;
virtual bool getPreferredDataIntervalUs(size_t&) const = 0;
virtual size_t writeData(const void*, size_t) const { return 0; }
virtual size_t readData(void*, size_t) const { return 0; }
virtual bool setLatencyMode(::aidl::android::hardware::bluetooth::audio::LatencyMode) {
return false;
}
virtual bool getRecommendedLatencyModes(
std::vector<::aidl::android::hardware::bluetooth::audio::LatencyMode>*) {
return false;
}
virtual void setCallbacks(const std::shared_ptr<BluetoothAudioPortCallbacks>&) = 0;
virtual std::string getSessionNameForDebug() const = 0;
};
} // namespace android::bluetooth::audio::aidl
+2
View File
@@ -26,4 +26,6 @@ std::unique_ptr<Module::Configuration> getConfiguration(Module::Type moduleType)
std::vector<aidl::android::media::audio::common::AudioProfile>
getStandard16And24BitPcmAudioProfiles();
const std::string kPortNameTelephonyTx = "telephony_tx";
} // namespace aidl::android::hardware::audio::core::internal
+46 -133
View File
@@ -20,150 +20,35 @@
#include <mutex>
#include <android-base/thread_annotations.h>
#include <audio_utils/resampler.h>
#include <aidl/android/hardware/audio/common/SinkMetadata.h>
#include <aidl/android/hardware/audio/common/SourceMetadata.h>
#include <aidl/android/hardware/bluetooth/audio/BluetoothAudioStatus.h>
#include <aidl/android/hardware/bluetooth/audio/PcmConfiguration.h>
#include <aidl/android/hardware/bluetooth/audio/PresentationPosition.h>
#include <aidl/android/hardware/bluetooth/audio/SessionType.h>
#include <aidl/android/media/audio/common/AudioDeviceDescription.h>
#include "BluetoothAudioPort.h"
namespace android::bluetooth::audio::aidl {
enum class BluetoothStreamState : uint8_t {
DISABLED = 0, // This stream is closing or Bluetooth profiles (A2DP/LE) is disabled
STANDBY,
STARTING,
STARTED,
SUSPENDING,
UNKNOWN,
};
std::ostream& operator<<(std::ostream& os, const BluetoothStreamState& state);
/**
* Proxy for Bluetooth Audio HW Module to communicate with Bluetooth Audio
* Session Control. All methods are not thread safe, so users must acquire a
* lock. Note: currently, getState() of DevicePortProxy is only used for
* verbose logging, it is not locked, so the state may not be synchronized.
*/
class BluetoothAudioPort {
public:
BluetoothAudioPort() = default;
virtual ~BluetoothAudioPort() = default;
/**
* Fetch output control / data path of BluetoothAudioPort and setup
* callbacks into BluetoothAudioProvider. If registerPort() returns false, the audio
* HAL must delete this BluetoothAudioPort and return EINVAL to caller
*/
virtual bool registerPort(
const ::aidl::android::media::audio::common::AudioDeviceDescription&) = 0;
/**
* Unregister this BluetoothAudioPort from BluetoothAudioSessionControl.
* Audio HAL must delete this BluetoothAudioPort after calling this.
*/
virtual void unregisterPort() = 0;
/**
* When the Audio framework / HAL tries to query audio config about format,
* channel mask and sample rate, it uses this function to fetch from the
* Bluetooth stack
*/
virtual bool loadAudioConfig(
::aidl::android::hardware::bluetooth::audio::PcmConfiguration&) = 0;
/**
* When the Audio framework / HAL wants to change the stream state, it invokes
* these 4 functions to control the Bluetooth stack (Audio Control Path).
* Note: standby(), start() and suspend() will return true when there are no errors.
* Called by Audio framework / HAL to change the state to stand by. When A2DP/LE profile is
* disabled, the port is first set to STANDBY by calling suspend and then mState is set to
* DISABLED. To reset the state back to STANDBY this method is called.
*/
virtual bool standby() = 0;
/**
* Called by Audio framework / HAL to start the stream
*/
virtual bool start() = 0;
/**
* Called by Audio framework / HAL to suspend the stream
*/
virtual bool suspend() = 0;
/**
* Called by Audio framework / HAL to stop the stream
*/
virtual void stop() = 0;
/**
* Called by the Audio framework / HAL to fetch information about audio frames
* presented to an external sink, or frames presented fror an internal sink
*/
virtual bool getPresentationPosition(
::aidl::android::hardware::bluetooth::audio::PresentationPosition&) const = 0;
/**
* Called by the Audio framework / HAL when the metadata of the stream's
* source has been changed.
*/
virtual bool updateSourceMetadata(
const ::aidl::android::hardware::audio::common::SourceMetadata&) const {
return false;
}
/**
* Called by the Audio framework / HAL when the metadata of the stream's
* sink has been changed.
*/
virtual bool updateSinkMetadata(
const ::aidl::android::hardware::audio::common::SinkMetadata&) const {
return false;
}
/**
* Return the current BluetoothStreamState
*/
virtual BluetoothStreamState getState() const = 0;
/**
* Set the current BluetoothStreamState
*/
virtual bool setState(BluetoothStreamState) = 0;
virtual bool isA2dp() const = 0;
virtual bool isLeAudio() const = 0;
virtual bool getPreferredDataIntervalUs(size_t&) const = 0;
virtual size_t writeData(const void*, size_t) const { return 0; }
virtual size_t readData(void*, size_t) const { return 0; }
};
class BluetoothSession;
class BluetoothAudioPortAidl : public BluetoothAudioPort {
public:
BluetoothAudioPortAidl();
explicit BluetoothAudioPortAidl(std::optional<bool> supportsLowLatency);
virtual ~BluetoothAudioPortAidl();
bool registerPort(const ::aidl::android::media::audio::common::AudioDeviceDescription&
description) override;
description) override EXCLUDES(mCvMutex);
void unregisterPort() override;
bool loadAudioConfig(
::aidl::android::hardware::bluetooth::audio::PcmConfiguration& audio_cfg) override;
bool standby() override;
bool start() override;
bool suspend() override;
void stop() override;
bool standby() override EXCLUDES(mCvMutex);
bool start() override EXCLUDES(mCvMutex);
bool suspend() override EXCLUDES(mCvMutex);
void stop() override EXCLUDES(mCvMutex);
bool getPresentationPosition(::aidl::android::hardware::bluetooth::audio::PresentationPosition&
presentation_position) const override;
@@ -176,26 +61,41 @@ class BluetoothAudioPortAidl : public BluetoothAudioPort {
/**
* Return the current BluetoothStreamState
* Note: This method is used for logging, does not lock, so value returned may not be latest
*/
BluetoothStreamState getState() const override NO_THREAD_SAFETY_ANALYSIS;
BluetoothStreamState getState() const override EXCLUDES(mCvMutex);
bool setState(BluetoothStreamState state) override;
bool setState(BluetoothStreamState state) override EXCLUDES(mCvMutex);
bool isA2dp() const override;
bool isHfp() const override;
bool isLeAudio() const override;
bool getPreferredDataIntervalUs(size_t& interval_us) const override;
bool getRecommendedLatencyModes(
std::vector<::aidl::android::hardware::bluetooth::audio::LatencyMode>* latencyModes)
override EXCLUDES(mCvMutex);
void setCallbacks(const std::shared_ptr<BluetoothAudioPortCallbacks>& callbacks) override
EXCLUDES(mCvMutex);
std::string getSessionNameForDebug() const override;
protected:
uint16_t mCookie;
BluetoothStreamState mState GUARDED_BY(mCvMutex);
::aidl::android::hardware::bluetooth::audio::SessionType mSessionType;
// WR to support Mono: True if fetching Stereo and mixing into Mono
bool mIsStereoToMono = false;
uint32_t mResampleRatio = 0;
using Resampler = std::unique_ptr<struct resampler_itfe, decltype(&release_resampler)>;
Resampler mResampler = {nullptr, nullptr};
std::shared_ptr<BluetoothAudioPortCallbacks> mCallbacks GUARDED_BY(mCvMutex);
std::optional<bool> mSupportsLowLatency GUARDED_BY(mCvMutex);
bool inUse() const;
BluetoothSession* getSession() const EXCLUDES(mCvMutex);
std::string debugMessage() const;
@@ -205,31 +105,44 @@ class BluetoothAudioPortAidl : public BluetoothAudioPort {
// conditional variable serves this purpose.
mutable std::mutex mCvMutex;
std::condition_variable mInternalCv GUARDED_BY(mCvMutex);
// do not call into BluetoothSession directly, use getSession() to ensure that 'mCvMutex'
// is not taken, to avoid deadlocks with callbacks.
std::unique_ptr<BluetoothSession> mSession;
bool getRecommendedLatencyModes(
std::vector<::aidl::android::hardware::bluetooth::audio::LatencyMode>* latency_modes,
std::optional<bool>* supports_low_latency);
// Check and initialize session type for |devices| If failed, this
// BluetoothAudioPortAidl is not initialized and must be deleted.
bool initSessionType(
bool initSession(
const ::aidl::android::media::audio::common::AudioDeviceDescription& description);
bool condWaitState(BluetoothStreamState state);
bool condWaitState(std::unique_lock<std::mutex>* lock) REQUIRES(mCvMutex);
void controlResultHandler(
uint16_t cookie,
const ::aidl::android::hardware::bluetooth::audio::BluetoothAudioStatus& status);
void sessionChangedHandler(uint16_t cookie);
const ::aidl::android::hardware::bluetooth::audio::BluetoothAudioStatus& status)
EXCLUDES(mCvMutex);
void lowLatencyAllowedHandler(uint16_t cookie, bool allowed) EXCLUDES(mCvMutex);
void sessionChangedHandler(uint16_t cookie) EXCLUDES(mCvMutex);
};
class BluetoothAudioPortAidlOut : public BluetoothAudioPortAidl {
public:
BluetoothAudioPortAidlOut() : BluetoothAudioPortAidl(std::nullopt /*supportsLowLatency*/) {}
bool loadAudioConfig(
::aidl::android::hardware::bluetooth::audio::PcmConfiguration& audio_cfg) override;
// The audio data path to the Bluetooth stack (Software encoding)
size_t writeData(const void* buffer, size_t bytes) const override;
bool setLatencyMode(
::aidl::android::hardware::bluetooth::audio::LatencyMode latency_mode) override;
};
class BluetoothAudioPortAidlIn : public BluetoothAudioPortAidl {
public:
BluetoothAudioPortAidlIn() : BluetoothAudioPortAidl(false /*supportsLowLatency*/) {}
// The audio data path from the Bluetooth stack (Software decoded)
size_t readData(void* buffer, size_t bytes) const override;
};
+7 -2
View File
@@ -142,6 +142,9 @@ class Module : public BnModule {
ndk::ScopedAStatus supportsVariableLatency(bool* _aidl_return) override;
ndk::ScopedAStatus getAAudioMixerBurstCount(int32_t* _aidl_return) override;
ndk::ScopedAStatus getAAudioHardwareBurstMinUsec(int32_t* _aidl_return) override;
ndk::ScopedAStatus getFlushFromFrameSupport(
const ::aidl::android::media::audio::common::AudioPortConfig& in_config,
::aidl::android::media::audio::common::FlushFromFrameSupport* _aidl_return) override;
// The maximum stream buffer size is 1 GiB = 2 ** 30 bytes;
static constexpr int32_t kMaximumStreamBufferSizeBytes = 1 << 30;
@@ -209,8 +212,9 @@ class Module : public BnModule {
virtual int32_t getNominalLatencyMs(
const ::aidl::android::media::audio::common::AudioPortConfig& portConfig);
virtual ndk::ScopedAStatus calculateBufferSizeFrames(
const ::aidl::android::media::audio::common::AudioFormatDescription &format,
int32_t latencyMs, int32_t sampleRateHz, int32_t *bufferSizeFrames);
const ::aidl::android::media::audio::common::AudioFormatDescription& format,
const ::aidl::android::media::audio::common::AudioIoFlags& flags, int32_t latencyMs,
int32_t sampleRateHz, int32_t* bufferSizeFrames);
virtual ndk::ScopedAStatus createMmapBuffer(
const ::aidl::android::media::audio::common::AudioPortConfig& portConfig,
int32_t bufferSizeFrames, int32_t frameSizeBytes, MmapBufferDescriptor* desc);
@@ -274,6 +278,7 @@ class Module : public BnModule {
bool setAudioPortConfigGain(
const ::aidl::android::media::audio::common::AudioPort& port,
const ::aidl::android::media::audio::common::AudioGainConfig& gainRequested);
ndk::ScopedAStatus validateMetadataAttributeTags(const std::vector<std::string>& tags);
};
std::ostream& operator<<(std::ostream& os, Module::Type t);
+3
View File
@@ -35,6 +35,9 @@ class ModuleAlsa : public Module {
ndk::ScopedAStatus populateConnectedDevicePort(
::aidl::android::media::audio::common::AudioPort* audioPort,
int32_t nextPortId) override;
virtual bool isDevicePortSupportAmbisonics(
const ::aidl::android::media::audio::common::AudioPort& audioPort);
};
} // namespace aidl::android::hardware::audio::core
+5 -69
View File
@@ -16,81 +16,17 @@
#pragma once
#include <map>
#include "core-impl/Bluetooth.h"
#include "core-impl/DevicePortProxy.h"
#include "core-impl/Module.h"
#include "core-impl/ModuleBluetoothBase.h"
namespace aidl::android::hardware::audio::core {
class ModuleBluetooth final : public Module {
class ModuleBluetooth final : public ModuleBluetoothBase {
public:
enum BtInterface : int { BTSCO, BTA2DP, BTLE };
typedef std::tuple<std::weak_ptr<IBluetooth>, std::weak_ptr<IBluetoothA2dp>,
std::weak_ptr<IBluetoothLe>>
BtProfileHandles;
ModuleBluetooth(std::unique_ptr<Configuration>&& config);
private:
struct CachedProxy {
std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPortAidl> ptr;
::aidl::android::hardware::bluetooth::audio::PcmConfiguration pcmConfig;
};
ChildInterface<BluetoothA2dp>& getBtA2dp();
ChildInterface<BluetoothLe>& getBtLe();
BtProfileHandles getBtProfileManagerHandles();
ndk::ScopedAStatus getBluetoothA2dp(std::shared_ptr<IBluetoothA2dp>* _aidl_return) override;
ndk::ScopedAStatus getBluetoothLe(std::shared_ptr<IBluetoothLe>* _aidl_return) override;
ndk::ScopedAStatus getMicMute(bool* _aidl_return) override;
ndk::ScopedAStatus setMicMute(bool in_mute) override;
ndk::ScopedAStatus setAudioPortConfig(
const ::aidl::android::media::audio::common::AudioPortConfig& in_requested,
::aidl::android::media::audio::common::AudioPortConfig* out_suggested,
bool* _aidl_return) override;
ndk::ScopedAStatus checkAudioPatchEndpointsMatch(
const std::vector<::aidl::android::media::audio::common::AudioPortConfig*>& sources,
const std::vector<::aidl::android::media::audio::common::AudioPortConfig*>& sinks)
override;
void onExternalDeviceConnectionChanged(
const ::aidl::android::media::audio::common::AudioPort& audioPort, bool connected);
ndk::ScopedAStatus createInputStream(
StreamContext&& context,
const ::aidl::android::hardware::audio::common::SinkMetadata& sinkMetadata,
const std::vector<::aidl::android::media::audio::common::MicrophoneInfo>& microphones,
std::shared_ptr<StreamIn>* result) override;
ndk::ScopedAStatus createOutputStream(
StreamContext&& context,
const ::aidl::android::hardware::audio::common::SourceMetadata& sourceMetadata,
const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
offloadInfo,
std::shared_ptr<StreamOut>* result) override;
ndk::ScopedAStatus populateConnectedDevicePort(
::aidl::android::media::audio::common::AudioPort* audioPort,
int32_t nextPortId) override;
ndk::ScopedAStatus onMasterMuteChanged(bool mute) override;
ndk::ScopedAStatus onMasterVolumeChanged(float volume) override;
int32_t getNominalLatencyMs(
const ::aidl::android::media::audio::common::AudioPortConfig& portConfig) override;
ndk::ScopedAStatus createProxy(
const ::aidl::android::media::audio::common::AudioPort& audioPort,
int32_t instancePortId, CachedProxy& proxy);
ndk::ScopedAStatus fetchAndCheckProxy(const StreamContext& context, CachedProxy& proxy);
ndk::ScopedAStatus findOrCreateProxy(
const ::aidl::android::media::audio::common::AudioPort& audioPort, CachedProxy& proxy);
static constexpr int kCreateProxyRetries = 5;
static constexpr int kCreateProxyRetrySleepMs = 75;
ChildInterface<BluetoothA2dp> mBluetoothA2dp;
ChildInterface<BluetoothLe> mBluetoothLe;
std::map<int32_t /*instantiated device port ID*/, CachedProxy> mProxies;
std::map<int32_t /*mix port handle*/, int32_t /*instantiated device port ID*/> mConnections;
protected:
std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPort> createProxyInstance(
bool isInput) override;
};
} // namespace aidl::android::hardware::audio::core
@@ -0,0 +1,111 @@
/*
* Copyright (C) 2025 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.
*/
#pragma once
#include <map>
#include <memory>
#include "core-impl/Bluetooth.h"
#include "core-impl/BluetoothAudioPort.h"
#include "core-impl/Module.h"
namespace aidl::android::hardware::audio::core {
class ModuleBluetoothBase : public Module {
public:
enum BtInterface : int { BTSCO, BTA2DP, BTLE };
typedef std::tuple<std::weak_ptr<IBluetooth>, std::weak_ptr<IBluetoothA2dp>,
std::weak_ptr<IBluetoothLe>>
BtProfileHandles;
ModuleBluetoothBase(std::unique_ptr<Configuration>&& config);
protected:
ndk::ScopedAStatus getBluetoothA2dp(std::shared_ptr<IBluetoothA2dp>* _aidl_return) override;
ndk::ScopedAStatus getBluetoothLe(std::shared_ptr<IBluetoothLe>* _aidl_return) override;
ndk::ScopedAStatus getBluetooth(std::shared_ptr<IBluetooth>* _aidl_return) override;
ndk::ScopedAStatus getMicMute(bool* _aidl_return) override;
ndk::ScopedAStatus setMicMute(bool in_mute) override;
ndk::ScopedAStatus setAudioPortConfig(
const ::aidl::android::media::audio::common::AudioPortConfig& in_requested,
::aidl::android::media::audio::common::AudioPortConfig* out_suggested,
bool* _aidl_return) override;
ndk::ScopedAStatus supportsVariableLatency(bool* _aidl_return) override;
ndk::ScopedAStatus checkAudioPatchEndpointsMatch(
const std::vector<::aidl::android::media::audio::common::AudioPortConfig*>& sources,
const std::vector<::aidl::android::media::audio::common::AudioPortConfig*>& sinks)
override;
void onExternalDeviceConnectionChanged(
const ::aidl::android::media::audio::common::AudioPort& audioPort, bool connected);
ndk::ScopedAStatus createInputStream(
StreamContext&& context,
const ::aidl::android::hardware::audio::common::SinkMetadata& sinkMetadata,
const std::vector<::aidl::android::media::audio::common::MicrophoneInfo>& microphones,
std::shared_ptr<StreamIn>* result) override;
ndk::ScopedAStatus createOutputStream(
StreamContext&& context,
const ::aidl::android::hardware::audio::common::SourceMetadata& sourceMetadata,
const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
offloadInfo,
std::shared_ptr<StreamOut>* result) override;
ndk::ScopedAStatus populateConnectedDevicePort(
::aidl::android::media::audio::common::AudioPort* audioPort,
int32_t nextPortId) override;
ndk::ScopedAStatus onMasterMuteChanged(bool mute) override;
ndk::ScopedAStatus onMasterVolumeChanged(float volume) override;
int32_t getNominalLatencyMs(
const ::aidl::android::media::audio::common::AudioPortConfig& portConfig) override;
binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
// Must be implemented by derived classes.
virtual std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPort>
createProxyInstance(bool isInput) = 0;
private:
struct CachedProxy {
std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPort> ptr;
::aidl::android::hardware::bluetooth::audio::PcmConfiguration pcmConfig;
};
ndk::ScopedAStatus createProxy(
const ::aidl::android::media::audio::common::AudioPort& audioPort,
int32_t instancePortId, CachedProxy& proxy);
ndk::ScopedAStatus fetchAndCheckProxy(const StreamContext& context, CachedProxy& proxy);
ndk::ScopedAStatus findOrCreateProxy(
const ::aidl::android::media::audio::common::AudioPort& audioPort, CachedProxy& proxy);
ChildInterface<BluetoothA2dp>& getBtA2dp();
ChildInterface<BluetoothLe>& getBtLe();
ChildInterface<Bluetooth>& getBt();
BtProfileHandles getBtProfileManagerHandles();
static constexpr int kCreateProxyRetries = 5;
static constexpr int kCreateProxyRetrySleepMs = 75;
ChildInterface<BluetoothA2dp> mBluetoothA2dp;
ChildInterface<BluetoothLe> mBluetoothLe;
ChildInterface<Bluetooth> mBluetooth;
std::map<int32_t /*instantiated device port ID*/, CachedProxy> mProxies;
std::map<int32_t /*mix port handle*/, int32_t /*instantiated device port ID*/> mConnections;
std::map<::aidl::android::media::audio::common::AudioDeviceDescription,
std::weak_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPort>>
mProxiesWeak;
};
} // namespace aidl::android::hardware::audio::core
+6 -1
View File
@@ -25,12 +25,17 @@ class ModulePrimary final : public Module {
ModulePrimary(std::unique_ptr<Configuration>&& config)
: Module(Type::DEFAULT, std::move(config)) {}
ndk::ScopedAStatus getFlushFromFrameSupport(
const ::aidl::android::media::audio::common::AudioPortConfig& in_config,
::aidl::android::media::audio::common::FlushFromFrameSupport* _aidl_return) override;
protected:
ndk::ScopedAStatus getTelephony(std::shared_ptr<ITelephony>* _aidl_return) override;
ndk::ScopedAStatus calculateBufferSizeFrames(
const ::aidl::android::media::audio::common::AudioFormatDescription& format,
int32_t latencyMs, int32_t sampleRateHz, int32_t* bufferSizeFrames) override;
const ::aidl::android::media::audio::common::AudioIoFlags& flags, int32_t latencyMs,
int32_t sampleRateHz, int32_t* bufferSizeFrames) override;
ndk::ScopedAStatus createInputStream(
StreamContext&& context,
const ::aidl::android::hardware::audio::common::SinkMetadata& sinkMetadata,
+29 -3
View File
@@ -25,6 +25,7 @@
#include <variant>
#include <StreamWorker.h>
#include <SynchronousTaskQueue.h>
#include <Utils.h>
#include <aidl/android/hardware/audio/common/SinkMetadata.h>
#include <aidl/android/hardware/audio/common/SourceMetadata.h>
@@ -37,6 +38,7 @@
#include <aidl/android/media/audio/common/AudioDevice.h>
#include <aidl/android/media/audio/common/AudioIoFlags.h>
#include <aidl/android/media/audio/common/AudioOffloadInfo.h>
#include <aidl/android/media/audio/common/FlushFromFrameAccuracy.h>
#include <aidl/android/media/audio/common/MicrophoneInfo.h>
#include <android-base/thread_annotations.h>
#include <error/expected_utils.h>
@@ -158,6 +160,9 @@ class StreamContext {
return mFlags.getTag() == ::aidl::android::media::audio::common::AudioIoFlags::input;
}
bool isMmap() const { return ::aidl::android::hardware::audio::common::hasMmapFlag(mFlags); }
bool isOffload() const {
return ::aidl::android::hardware::audio::common::hasNonblockingOffloadFlag(mFlags);
}
bool isValid() const;
// 'reset' is called on a Binder thread when closing the stream. Does not use
// locking because it only cleans MQ pointers which were also set on the Binder thread.
@@ -226,6 +231,12 @@ struct DriverInterface {
int32_t* /*latency*/) {
return ::android::OK;
}
// Implement 'flushFromFrame' for offload stream if it is supported.
virtual ::android::status_t flushFromFrame(
::aidl::android::media::audio::common::FlushFromFrameAccuracy /*accuracy*/,
int32_t /*position*/, int32_t* /*flushFromPosition*/) {
return ::android::INVALID_OPERATION;
}
virtual void shutdown() = 0; // This function is only called once.
};
@@ -260,6 +271,9 @@ class StreamWorkerCommonLogic : public ::android::hardware::audio::common::Strea
void populateReply(StreamDescriptor::Reply* reply, bool isConnected) const;
void populateReplyWrongState(StreamDescriptor::Reply* reply,
const StreamDescriptor::Command& command) const;
void populateReplyUnsupportedCommand(StreamDescriptor::Reply* reply,
const StreamDescriptor::Command& command) const;
void switchFromTransientState(StreamDescriptor::State state);
void switchToTransientState(StreamDescriptor::State state) {
mState = state;
mTransientStateStart = std::chrono::steady_clock::now();
@@ -349,6 +363,8 @@ class StreamOutWorkerLogic : public StreamWorkerCommonLogic {
void onClipStateChange(size_t clipFramesLeft, bool hasNextClip) override;
private:
void onBufferStateChangeImpl(size_t bufferFramesLeft);
void onClipStateChangeImpl(size_t clipFramesLeft, bool hasNextClip);
bool write(size_t clientSize, StreamDescriptor::Reply* reply);
bool writeMmap(StreamDescriptor::Reply* reply);
@@ -356,6 +372,9 @@ class StreamOutWorkerLogic : public StreamWorkerCommonLogic {
enum DrainState : int32_t { NONE, ALL, EN /*early notify*/, EN_SENT };
std::atomic<DrainState> mDrainState = DrainState::NONE;
::android::hardware::audio::common::SynchronousTaskQueue<
::android::hardware::audio::common::PostponedMethodCall<StreamOutWorkerLogic>>
mCallTasks;
};
using StreamOutWorker = StreamWorkerImpl<StreamOutWorkerLogic>;
@@ -386,6 +405,7 @@ struct StreamCommonInterface {
virtual ndk::ScopedAStatus removeEffect(
const std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>&
in_effect) = 0;
virtual ndk::ScopedAStatus createMmapBuffer(MmapBufferDescriptor* _aidl_return) = 0;
// Methods below are common for both 'IStreamIn' and 'IStreamOut'. Note that
// 'updateMetadata' in them uses an individual structure which is wrapped here.
// The 'Common' suffix is added to distinguish them from the methods from 'IStreamIn/Out'.
@@ -456,6 +476,11 @@ class StreamCommonDelegator : public BnStreamCommon {
return delegate != nullptr ? delegate->removeEffect(in_effect)
: ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
ndk::ScopedAStatus createMmapBuffer(MmapBufferDescriptor* _aidl_return) override {
auto delegate = mDelegate.lock();
return delegate != nullptr ? delegate->createMmapBuffer(_aidl_return)
: ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
// It is possible that on the client side the proxy for IStreamCommon will outlive
// the IStream* instance, and the server side IStream* instance will get destroyed
// while this IStreamCommon instance is still alive.
@@ -490,6 +515,7 @@ class StreamCommonImpl : virtual public StreamCommonInterface, virtual public Dr
ndk::ScopedAStatus removeEffect(
const std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>& in_effect)
override;
ndk::ScopedAStatus createMmapBuffer(MmapBufferDescriptor* _aidl_return) override;
ndk::ScopedAStatus getStreamCommonCommon(std::shared_ptr<IStreamCommon>* _aidl_return) override;
ndk::ScopedAStatus updateMetadataCommon(const Metadata& metadata) override;
@@ -588,9 +614,7 @@ class StreamOut : virtual public StreamCommonInterface, public BnStreamOut {
}
ndk::ScopedAStatus updateMetadata(
const ::aidl::android::hardware::audio::common::SourceMetadata& in_sourceMetadata)
override {
return updateMetadataCommon(in_sourceMetadata);
}
override;
ndk::ScopedAStatus updateOffloadMetadata(
const ::aidl::android::hardware::audio::common::AudioOffloadMetadata&
in_offloadMetadata) override;
@@ -619,6 +643,8 @@ class StreamOut : virtual public StreamCommonInterface, public BnStreamOut {
StreamOut(StreamContext&& context,
const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
offloadInfo);
ndk::ScopedAStatus validateMetadata(
const ::aidl::android::hardware::audio::common::SourceMetadata& sourceMetadata);
StreamContext mContextInstance;
const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo> mOffloadInfo;
+6 -52
View File
@@ -16,70 +16,24 @@
#pragma once
#include <atomic>
#include <optional>
#include <thread>
#include <vector>
#include <media/nbaio/MonoPipe.h>
#include <media/nbaio/MonoPipeReader.h>
#include "Stream.h"
#include "alsa/Utils.h"
#include "StreamAlsaBase.h"
namespace aidl::android::hardware::audio::core {
// This class is intended to be used as a base class for implementations
// that use TinyAlsa.
// This class does not define a complete stream implementation,
// and should never be used on its own. Derived classes are expected to
// provide necessary overrides for all interface methods omitted here.
class StreamAlsa : public StreamCommonImpl {
// Direct ALSA I/O path (no MonoPipe, no background I/O thread).
// Reads/writes directly to/from ALSA device proxies in the transfer() call.
// Used by StreamUsb.
class StreamAlsa : public StreamAlsaBase {
public:
StreamAlsa(StreamContext* context, const Metadata& metadata, int readWriteRetries);
~StreamAlsa();
// Methods of 'DriverInterface'.
::android::status_t init(DriverCallbackInterface* callback) override;
::android::status_t drain(StreamDescriptor::DrainMode) override;
::android::status_t flush() override;
::android::status_t pause() override;
::android::status_t standby() override;
::android::status_t start() override;
::android::status_t transfer(void* buffer, size_t frameCount, size_t* actualFrameCount,
int32_t* latencyMs) override;
::android::status_t refinePosition(StreamDescriptor::Position* position) override;
void shutdown() override;
ndk::ScopedAStatus setGain(float gain) override;
protected:
// Called from 'start' to initialize 'mAlsaDeviceProxies', the vector must be non-empty.
virtual std::vector<alsa::DeviceProfile> getDeviceProfiles() = 0;
const size_t mBufferSizeFrames;
const size_t mFrameSizeBytes;
const int mSampleRate;
const bool mIsInput;
const std::optional<struct pcm_config> mConfig;
const int mReadWriteRetries;
private:
::android::NBAIO_Format getPipeFormat() const;
::android::sp<::android::MonoPipe> makeSink(bool writeCanBlock);
::android::sp<::android::MonoPipeReader> makeSource(::android::MonoPipe* pipe);
void inputIoThread(size_t idx);
void outputIoThread(size_t idx);
void teardownIo();
std::atomic<float> mGain = 1.0;
// All fields below are only used on the worker thread.
std::vector<alsa::DeviceProxy> mAlsaDeviceProxies;
// Only 'libnbaio_mono' is vendor-accessible, thus no access to the multi-reader Pipe.
std::vector<::android::sp<::android::MonoPipe>> mSinks;
std::vector<::android::sp<::android::MonoPipeReader>> mSources;
std::vector<std::thread> mIoThreads;
std::atomic<bool> mIoThreadIsRunning = false; // used by all threads
};
} // namespace aidl::android::hardware::audio::core
} // namespace aidl::android::hardware::audio::core
+73
View File
@@ -0,0 +1,73 @@
/*
* Copyright (C) 2026 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.
*/
#pragma once
#include <atomic>
#include <optional>
#include <vector>
#include "Stream.h"
#include "alsa/UtilsAlsa.h"
namespace aidl::android::hardware::audio::core {
// Base class for implementations that use TinyAlsa.
// Contains code common to both the direct ALSA path (StreamAlsa) and the
// MonoPipe + I/O thread path (StreamAlsaMonoPipe).
// This class does not define a complete stream implementation and should never
// be used on its own. Derived classes must provide implementations for
// standby(), start(), transfer(), and shutdown().
class StreamAlsaBase : public StreamCommonImpl {
public:
StreamAlsaBase(StreamContext* context, const Metadata& metadata, int readWriteRetries);
~StreamAlsaBase();
// Methods of 'DriverInterface'.
::android::status_t init(DriverCallbackInterface* callback) override;
::android::status_t drain(StreamDescriptor::DrainMode) override;
::android::status_t flush() override;
::android::status_t pause() override;
::android::status_t standby() override = 0;
::android::status_t start() override = 0;
::android::status_t transfer(void* buffer, size_t frameCount, size_t* actualFrameCount,
int32_t* latencyMs) override = 0;
::android::status_t refinePosition(StreamDescriptor::Position* position) override;
void shutdown() override = 0;
ndk::ScopedAStatus setGain(float gain) override;
protected:
// Called from 'start' to initialize 'mAlsaDeviceProxies', the vector must be non-empty.
virtual std::vector<alsa::DeviceProfile> getDeviceProfiles() = 0;
// Helper used by subclass start() implementations to open ALSA device proxies.
// Returns ::android::NO_INIT if no proxies could be opened, ::android::OK otherwise.
::android::status_t openDeviceProxies(std::vector<alsa::DeviceProxy>* proxies);
const size_t mBufferSizeFrames;
const size_t mFrameSizeBytes;
const int mSampleRate;
const bool mIsInput;
const std::optional<struct pcm_config> mConfig;
const int mReadWriteRetries;
std::atomic<float> mGain = 1.0;
// Opened ALSA device proxies. Populated by start(), cleared by standby()/shutdown().
std::vector<alsa::DeviceProxy> mAlsaDeviceProxies;
};
} // namespace aidl::android::hardware::audio::core
@@ -0,0 +1,62 @@
/*
* Copyright (C) 2026 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.
*/
#pragma once
#include <atomic>
#include <thread>
#include <vector>
#include <media/nbaio/MonoPipe.h>
#include <media/nbaio/MonoPipeReader.h>
#include "StreamAlsaBase.h"
namespace aidl::android::hardware::audio::core {
// Extends StreamAlsaBase with a MonoPipe + dedicated I/O thread per device.
// The worker thread communicates with ALSA via a pipe, allowing the main
// transfer() call to be decoupled from the ALSA read/write latency.
// Used by StreamPrimary (and any other implementation that needs the pipe path).
class StreamAlsaMonoPipe : public StreamAlsaBase {
public:
StreamAlsaMonoPipe(StreamContext* context, const Metadata& metadata, int readWriteRetries);
~StreamAlsaMonoPipe();
// Methods of 'DriverInterface'.
::android::status_t standby() override;
::android::status_t start() override;
::android::status_t transfer(void* buffer, size_t frameCount, size_t* actualFrameCount,
int32_t* latencyMs) override;
void shutdown() override;
private:
::android::NBAIO_Format getPipeFormat() const;
::android::sp<::android::MonoPipe> makeSink(bool writeCanBlock);
::android::sp<::android::MonoPipeReader> makeSource(::android::MonoPipe* pipe);
void inputIoThread(size_t idx);
void outputIoThread(size_t idx);
void teardownIo();
// All fields below are only used on the worker thread.
// Only 'libnbaio_mono' is vendor-accessible, thus no access to the multi-reader Pipe.
std::vector<::android::sp<::android::MonoPipe>> mSinks;
std::vector<::android::sp<::android::MonoPipeReader>> mSources;
std::vector<std::thread> mIoThreads;
std::atomic<bool> mIoThreadIsRunning = false; // used by all threads
};
} // namespace aidl::android::hardware::audio::core
+50 -11
View File
@@ -29,18 +29,30 @@
namespace aidl::android::hardware::audio::core {
class PortCallbacksHandler : public ::android::bluetooth::audio::aidl::BluetoothAudioPortCallbacks {
public:
explicit PortCallbacksHandler(std::shared_ptr<IStreamOutEventCallback> streamCallback)
: mStreamCallback(streamCallback) {}
bool hasCallback() const { return mStreamCallback != nullptr; }
void onRecommendedLatencyModeChanged(
const std::vector<::aidl::android::hardware::bluetooth::audio::LatencyMode>& modes)
override;
private:
std::shared_ptr<IStreamOutEventCallback> mStreamCallback;
};
class StreamBluetooth : public StreamCommonImpl {
public:
static bool checkConfigParams(
const ::aidl::android::hardware::bluetooth::audio::PcmConfiguration& pcmConfig,
const ::aidl::android::media::audio::common::AudioConfigBase& config);
StreamBluetooth(
StreamContext* context, const Metadata& metadata,
ModuleBluetooth::BtProfileHandles&& btHandles,
const std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPortAidl>&
btDeviceProxy,
const ::aidl::android::hardware::bluetooth::audio::PcmConfiguration& pcmConfig);
StreamBluetooth(StreamContext* context, const Metadata& metadata,
ModuleBluetooth::BtProfileHandles&& btHandles,
const std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPort>&
btDeviceProxy,
const ::aidl::android::hardware::bluetooth::audio::PcmConfiguration& pcmConfig);
~StreamBluetooth();
// Methods of 'DriverInterface'.
@@ -55,20 +67,37 @@ class StreamBluetooth : public StreamCommonImpl {
void shutdown() override;
// Overridden methods of 'StreamCommonImpl', called on a Binder thread.
ndk::ScopedAStatus updateMetadataCommon(const Metadata& metadata) override;
ndk::ScopedAStatus prepareToClose() override;
ndk::ScopedAStatus bluetoothParametersUpdated() override;
ndk::ScopedAStatus prepareToClose() override;
ndk::ScopedAStatus setConnectedDevices(
const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices)
override;
ndk::ScopedAStatus updateMetadataCommon(const Metadata& metadata) override;
protected:
ndk::ScopedAStatus getRecommendedLatencyModes(
std::vector<::aidl::android::media::audio::common::AudioLatencyMode>* _aidl_return);
ndk::ScopedAStatus setLatencyMode(
::aidl::android::media::audio::common::AudioLatencyMode in_mode);
void dump(int fd, const char** args, uint32_t numArgs);
private:
const size_t mFrameSizeBytes;
const bool mIsInput;
const std::weak_ptr<IBluetoothA2dp> mBluetoothA2dp;
const std::weak_ptr<IBluetoothLe> mBluetoothLe;
const std::weak_ptr<IBluetooth> mBluetoothHfp;
const size_t mPreferredDataIntervalUs;
std::shared_ptr<PortCallbacksHandler> mCallbacksHandler;
std::string mSessionTypeName;
mutable std::mutex mLock;
// The lock is also used to serialize calls to the proxy.
std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPortAidl> mBtDeviceProxy
std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPort> mBtDeviceProxy
GUARDED_BY(mLock); // proxy may be null if the stream is not connected to a device
bool mEnabled GUARDED_BY(mLock) = true;
int64_t mDecimPresentationPosition;
::aidl::android::hardware::bluetooth::audio::PresentationPosition mActualPresentationPosition;
};
class StreamInBluetooth final : public StreamIn, public StreamBluetooth {
@@ -82,7 +111,7 @@ class StreamInBluetooth final : public StreamIn, public StreamBluetooth {
const ::aidl::android::hardware::audio::common::SinkMetadata& sinkMetadata,
const std::vector<::aidl::android::media::audio::common::MicrophoneInfo>& microphones,
ModuleBluetooth::BtProfileHandles&& btHandles,
const std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPortAidl>&
const std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPort>&
btDeviceProxy,
const ::aidl::android::hardware::bluetooth::audio::PcmConfiguration& pcmConfig);
@@ -91,6 +120,8 @@ class StreamInBluetooth final : public StreamIn, public StreamBluetooth {
ndk::ScopedAStatus getActiveMicrophones(
std::vector<::aidl::android::media::audio::common::MicrophoneDynamicInfo>* _aidl_return)
override;
binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
};
class StreamOutBluetooth final : public StreamOut, public StreamBluetooth {
@@ -105,12 +136,20 @@ class StreamOutBluetooth final : public StreamOut, public StreamBluetooth {
const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
offloadInfo,
ModuleBluetooth::BtProfileHandles&& btHandles,
const std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPortAidl>&
const std::shared_ptr<::android::bluetooth::audio::aidl::BluetoothAudioPort>&
btDeviceProxy,
const ::aidl::android::hardware::bluetooth::audio::PcmConfiguration& pcmConfig);
private:
void onClose(StreamDescriptor::State) override { defaultOnClose(); }
ndk::ScopedAStatus getRecommendedLatencyModes(
std::vector<::aidl::android::media::audio::common::AudioLatencyMode>* _aidl_return)
override;
ndk::ScopedAStatus setLatencyMode(
::aidl::android::media::audio::common::AudioLatencyMode in_mode) override;
binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
};
} // namespace aidl::android::hardware::audio::core
+3 -2
View File
@@ -65,7 +65,9 @@ class DriverMmapStubImpl : public DriverStubImpl {
explicit DriverMmapStubImpl(const StreamContext& context);
::android::status_t init(DriverCallbackInterface* callback) override;
::android::status_t drain(StreamDescriptor::DrainMode drainMode) override;
::android::status_t flush() override;
::android::status_t pause() override;
::android::status_t standby() override;
::android::status_t start() override;
::android::status_t transfer(void* buffer, size_t frameCount, size_t* actualFrameCount,
int32_t* latencyMs) override;
@@ -97,10 +99,9 @@ class StreamMmapStub : public StreamCommonImpl, public DriverMmapStubImpl {
std::vector<VendorParameter>* _aidl_return) override;
ndk::ScopedAStatus setVendorParameters(const std::vector<VendorParameter>& in_parameters,
bool in_async) override;
ndk::ScopedAStatus createMmapBuffer(MmapBufferDescriptor* _aidl_return) override;
private:
ndk::ScopedAStatus createMmapBuffer(MmapBufferDescriptor* desc);
ndk::ScopedFileDescriptor mSharedMemoryFd;
};
+89 -4
View File
@@ -28,17 +28,72 @@ namespace aidl::android::hardware::audio::core {
namespace offload {
class DspClipState {
public:
static constexpr int64_t kError = -1;
static constexpr size_t kClipCountLimit = 2;
bool add(int64_t frames) {
if (mFrames.size() < kClipCountLimit) {
mFrames.push_back(frames);
return true;
}
return false;
}
int64_t currentFrames() const { return !empty() ? mFrames[0] : kError; }
bool empty() const { return mFrames.empty(); }
bool hasNext() const { return mFrames.size() > 1; }
void erase() { mFrames.clear(); }
void eraseAllNext() {
if (!empty()) mFrames.resize(1);
}
int64_t removeCurrent() {
if (empty()) return kError;
const int64_t result = mFrames[0];
mFrames.erase(mFrames.begin());
return result;
}
int64_t trimCurrentFrames(int64_t frames) {
if (!empty()) {
if (mFrames[0] > frames) mFrames[0] = frames;
return mFrames[0];
}
return kError;
}
int64_t updateCurrentFrames(int64_t delta) {
return !empty() ? updateFrames(0, delta) : kError;
}
int64_t updateLastFrames(int64_t delta) {
return !empty() ? updateFrames(mFrames.size() - 1, delta) : kError;
}
std::string log() const { return ::android::internal::ToString(mFrames); }
private:
int64_t updateFrames(size_t index, int64_t delta) {
return delta >= 0 || mFrames[index] >= -delta ? mFrames[index] += delta : kError;
}
std::vector<int64_t> mFrames;
};
struct DspSimulatorState {
static constexpr int64_t kSkipBufferNotifyFrames = -1;
const std::string formatEncoding;
const ::aidl::android::media::audio::common::AudioFormatDescription format;
const int sampleRate;
const int64_t earlyNotifyFrames;
const int fallbackBitRatePerSecond; // Fallback from AudioOffloadInfo
DriverCallbackInterface* callback = nullptr; // set before starting DSP worker
std::mutex lock;
std::vector<int64_t> clipFramesLeft GUARDED_BY(lock);
DspClipState clips GUARDED_BY(lock);
int bitRatePerSecond GUARDED_BY(lock) = 0;
int64_t bufferFramesLeft GUARDED_BY(lock) = 0;
int64_t bufferNotifyFrames GUARDED_BY(lock) = kSkipBufferNotifyFrames;
StreamDescriptor::DrainMode draining GUARDED_BY(lock) =
StreamDescriptor::DrainMode::DRAIN_UNSPECIFIED;
int64_t mTotalFramesPlayed GUARDED_BY(lock) = 0;
int64_t mLastReportedFrames GUARDED_BY(lock) = 0;
bool isDrainCompleteClipStateChangeSent GUARDED_BY(lock) = false;
};
class DspSimulatorLogic : public ::android::hardware::audio::common::StreamLogic {
@@ -60,9 +115,23 @@ class DspSimulatorWorker
} // namespace offload
struct MpegFrameState {
bool clipEnded = false;
size_t bytesPending = 0;
// True if the frame contains actual audio samples.
// False if it is a metadata/ID3 tag.
bool isAudioFrame = false;
int frameSize = 0;
size_t totalFrameLengthBytes = 0;
};
class DriverOffloadStubImpl : public DriverStubImpl {
public:
explicit DriverOffloadStubImpl(const StreamContext& context);
explicit DriverOffloadStubImpl(
const StreamContext& context,
const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
offloadInfo);
::android::status_t init(DriverCallbackInterface* callback) override;
::android::status_t drain(StreamDescriptor::DrainMode drainMode) override;
::android::status_t flush() override;
@@ -70,22 +139,38 @@ class DriverOffloadStubImpl : public DriverStubImpl {
::android::status_t start() override;
::android::status_t transfer(void* buffer, size_t frameCount, size_t* actualFrameCount,
int32_t* latencyMs) override;
::android::status_t flushFromFrame(
::aidl::android::media::audio::common::FlushFromFrameAccuracy accuracy,
int32_t position, int32_t* flushFromPosition) override;
void shutdown() override;
::android::status_t refinePosition(StreamDescriptor::Position* position) override;
private:
::android::status_t startWorkerIfNeeded();
::android::status_t handleApeTransfer(void* buffer, size_t frameCount,
size_t* actualFrameCount);
::android::status_t handleMpegTransfer(void* buffer, size_t frameCount,
size_t* actualFrameCount);
::android::status_t handlePcmTransfer(void* buffer, size_t frameCount,
size_t* actualFrameCount);
::android::status_t (DriverOffloadStubImpl::*mTransferHandler)(void*, size_t, size_t*);
const int64_t mBufferNotifyFrames;
const int32_t mSafeMarginForFlushFromFrames;
const int32_t mSafeMarginForDrainMetadataFrames;
offload::DspSimulatorState mState;
offload::DspSimulatorWorker mDspWorker;
bool mDspWorkerStarted = false;
MpegFrameState mMpegFrameState;
};
class StreamOffloadStub : public StreamCommonImpl, public DriverOffloadStubImpl {
public:
static const std::set<std::string>& getSupportedEncodings();
StreamOffloadStub(StreamContext* context, const Metadata& metadata);
StreamOffloadStub(StreamContext* context, const Metadata& metadata,
const ::aidl::android::media::audio::common::AudioOffloadInfo& offloadInfo);
~StreamOffloadStub();
};
+2 -2
View File
@@ -23,12 +23,12 @@
#include <android-base/thread_annotations.h>
#include "DriverStubImpl.h"
#include "StreamAlsa.h"
#include "StreamAlsaMonoPipe.h"
#include "primary/PrimaryMixer.h"
namespace aidl::android::hardware::audio::core {
class StreamPrimary : public StreamAlsa {
class StreamPrimary : public StreamAlsaMonoPipe {
public:
StreamPrimary(StreamContext* context, const Metadata& metadata);
+22 -31
View File
@@ -16,19 +16,18 @@
#pragma once
#include <memory>
#include <mutex>
#include <vector>
#include "core-impl/Stream.h"
#include "deprecated/StreamSwitcher.h"
#include "r_submix/SubmixRoute.h"
namespace aidl::android::hardware::audio::core {
class StreamRemoteSubmix : public StreamCommonImpl {
public:
StreamRemoteSubmix(
StreamContext* context, const Metadata& metadata,
const ::aidl::android::media::audio::common::AudioDeviceAddress& deviceAddress);
StreamRemoteSubmix(StreamContext* context, const Metadata& metadata);
~StreamRemoteSubmix();
// Methods of 'DriverInterface'.
@@ -45,35 +44,39 @@ class StreamRemoteSubmix : public StreamCommonImpl {
// Overridden methods of 'StreamCommonImpl', called on a Binder thread.
ndk::ScopedAStatus prepareToClose() override;
ndk::ScopedAStatus setConnectedDevices(const ConnectedDevices& devices) override;
private:
long getDelayInUsForFrameCount(size_t frameCount);
size_t getStreamPipeSizeInFrames();
::android::status_t outWrite(void* buffer, size_t frameCount, size_t* actualFrameCount);
::android::status_t inRead(void* buffer, size_t frameCount, size_t* actualFrameCount);
long getDurationInUsForFrameCount(size_t frameCount) const;
std::shared_ptr<r_submix::SubmixRoute> prepareCurrentRoute(
const ::aidl::android::media::audio::common::AudioDeviceAddress& deviceAddress);
size_t getStreamPipeSizeInFrames(const std::shared_ptr<r_submix::SubmixRoute>& currentRoute);
::android::status_t outWrite(const std::shared_ptr<r_submix::SubmixRoute>& currentRoute,
void* buffer, size_t frameCount, size_t* actualFrameCount);
::android::status_t inRead(const std::shared_ptr<r_submix::SubmixRoute>& currentRoute,
void* buffer, size_t frameCount, size_t* actualFrameCount);
const ::aidl::android::media::audio::common::AudioDeviceAddress mDeviceAddress;
const bool mIsInput;
r_submix::AudioConfig mStreamConfig;
std::shared_ptr<r_submix::SubmixRoute> mCurrentRoute = nullptr;
const r_submix::AudioConfig mStreamConfig;
const int mReadAttemptSleepUs;
// Limit for the number of error log entries to avoid spamming the logs.
static constexpr int kMaxErrorLogs = 5;
// The duration of kMaxReadFailureAttempts * READ_ATTEMPT_SLEEP_MS must be strictly inferior
// to the duration of a record buffer at the current record sample rate (of the device, not of
// the recording itself). Here we have: 3 * 5ms = 15ms < 1024 frames * 1000 / 48000 = 21.333ms
static constexpr int kMaxReadFailureAttempts = 3;
// 5ms between two read attempts when pipe is empty
static constexpr int kReadAttemptSleepUs = 5000;
mutable std::mutex mLock;
std::shared_ptr<r_submix::SubmixRoute> mCurrentRoute GUARDED_BY(mLock);
// Used by the worker thread only.
int64_t mStartTimeNs = 0;
long mFramesSinceStart = 0;
int mReadErrorCount = 0;
int mReadFailureCount = 0;
int mWriteShutdownCount = 0;
bool mSkipNextTransfer = false;
};
class StreamInRemoteSubmix final : public StreamIn, public deprecated::StreamSwitcher {
class StreamInRemoteSubmix final : public StreamIn, public StreamRemoteSubmix {
public:
friend class ndk::SharedRefBase;
StreamInRemoteSubmix(
@@ -82,19 +85,13 @@ class StreamInRemoteSubmix final : public StreamIn, public deprecated::StreamSwi
const std::vector<::aidl::android::media::audio::common::MicrophoneInfo>& microphones);
private:
DeviceSwitchBehavior switchCurrentStream(
const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices)
override;
std::unique_ptr<deprecated::StreamCommonInterfaceEx> createNewStream(
const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices,
StreamContext* context, const Metadata& metadata) override;
void onClose(StreamDescriptor::State) override { defaultOnClose(); }
ndk::ScopedAStatus getActiveMicrophones(
std::vector<::aidl::android::media::audio::common::MicrophoneDynamicInfo>* _aidl_return)
override;
};
class StreamOutRemoteSubmix final : public StreamOut, public deprecated::StreamSwitcher {
class StreamOutRemoteSubmix final : public StreamOut, public StreamRemoteSubmix {
public:
friend class ndk::SharedRefBase;
StreamOutRemoteSubmix(
@@ -104,12 +101,6 @@ class StreamOutRemoteSubmix final : public StreamOut, public deprecated::StreamS
offloadInfo);
private:
DeviceSwitchBehavior switchCurrentStream(
const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices)
override;
std::unique_ptr<deprecated::StreamCommonInterfaceEx> createNewStream(
const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices,
StreamContext* context, const Metadata& metadata) override;
void onClose(StreamDescriptor::State) override { defaultOnClose(); }
};
+13 -1
View File
@@ -39,7 +39,7 @@ class StreamInStub final : public StreamIn, public StreamStub {
void onClose(StreamDescriptor::State) override { defaultOnClose(); }
};
class StreamOutStub final : public StreamOut, public StreamStub {
class StreamOutStub : public StreamOut, public StreamStub {
public:
friend class ndk::SharedRefBase;
StreamOutStub(StreamContext&& context,
@@ -51,4 +51,16 @@ class StreamOutStub final : public StreamOut, public StreamStub {
void onClose(StreamDescriptor::State) override { defaultOnClose(); }
};
class StreamOutTelephonyStub final : public StreamOutStub, public StreamOutHwVolumeHelper {
public:
StreamOutTelephonyStub(
StreamContext&& context,
const ::aidl::android::hardware::audio::common::SourceMetadata& sourceMetadata,
const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
offloadInfo);
ndk::ScopedAStatus getHwVolume(std::vector<float>* _aidl_return) override;
ndk::ScopedAStatus setHwVolume(const std::vector<float>& in_channelVolumes) override;
};
} // namespace aidl::android::hardware::audio::core
+6 -1
View File
@@ -88,6 +88,11 @@ class EffectImpl : public BnEffect, public EffectThread {
*/
void process() override;
/**
* Finish effect draining, and transit the state to State::IDLE.
*/
virtual void drainingComplete_l() EXCLUSIVE_LOCKS_REQUIRED(mImplMutex);
protected:
// current Hal version
int mVersion = 0;
@@ -114,7 +119,7 @@ class EffectImpl : public BnEffect, public EffectThread {
RetCode notifyEventFlag(uint32_t flag);
std::string getEffectNameWithVersion() {
return getEffectName() + "V" + std::to_string(mVersion);
return getEffectName() + " (aidl_v" + std::to_string(mVersion) + ")";
}
::android::hardware::EventFlag* mEventFlag;
+5 -3
View File
@@ -8,19 +8,17 @@ cc_library_shared {
relative_install_path: "soundfx",
vendor: true,
srcs: [
":effectCommonFileRpi",
"aidl/BundleContext.cpp",
"aidl/EffectBundleAidl.cpp",
],
local_include_dirs: ["aidl"],
static_libs: [
"libaudioeffectaidlcommon-rpi",
"libmusicbundle",
],
shared_libs: [
"android.hardware.audio.effect-V3-ndk",
"android.hardware.common-V2-ndk",
"android.hardware.common.fmq-V1-ndk",
"android.media.audio.common.types-V4-ndk",
"libaudio_aidl_conversion_common_ndk",
"libaudioaidlcommon",
"libaudioutils",
@@ -46,4 +44,8 @@ cc_library_shared {
"-Wextra",
"-Wthread-safety",
],
defaults: [
"latest_android_hardware_audio_effect_ndk_shared",
"latest_android_media_audio_common_types_ndk_shared",
],
}
+7 -3
View File
@@ -8,15 +8,15 @@ cc_library_shared {
relative_install_path: "soundfx",
vendor: true,
srcs: [
":effectCommonFileRpi",
"aidl/DownmixContext.cpp",
"aidl/EffectDownmix.cpp",
],
static_libs: [
"libaudioeffectaidlcommon-rpi",
],
shared_libs: [
"android.hardware.audio.effect-V3-ndk",
"android.hardware.common-V2-ndk",
"android.hardware.common.fmq-V1-ndk",
"android.media.audio.common.types-V4-ndk",
"libaudio_aidl_conversion_common_ndk",
"libaudioaidlcommon",
"libaudioutils",
@@ -41,4 +41,8 @@ cc_library_shared {
"-Wextra",
"-Wthread-safety",
],
defaults: [
"latest_android_hardware_audio_effect_ndk_shared",
"latest_android_media_audio_common_types_ndk_shared",
],
}
+5 -3
View File
@@ -8,7 +8,6 @@ cc_library_shared {
relative_install_path: "soundfx",
vendor: true,
srcs: [
":effectCommonFileRpi",
"aidl/DynamicsProcessing.cpp",
"aidl/DynamicsProcessingContext.cpp",
"dsp/DPBase.cpp",
@@ -16,12 +15,11 @@ cc_library_shared {
],
static_libs: [
"libaudioaidlranges",
"libaudioeffectaidlcommon-rpi",
],
shared_libs: [
"android.hardware.audio.effect-V3-ndk",
"android.hardware.common-V2-ndk",
"android.hardware.common.fmq-V1-ndk",
"android.media.audio.common.types-V4-ndk",
"libaudio_aidl_conversion_common_ndk",
"libaudioaidlcommon",
"libaudioutils",
@@ -46,4 +44,8 @@ cc_library_shared {
"-Wextra",
"-Wthread-safety",
],
defaults: [
"latest_android_hardware_audio_effect_ndk_shared",
"latest_android_media_audio_common_types_ndk_shared",
],
}
@@ -473,9 +473,8 @@ RetCode DynamicsProcessingContext::setDpChannelBand_l(const std::any& anyConfig,
case StageType::POSTEQ: {
dp_fx::DPEq* dp;
const auto& config = std::any_cast<DynamicsProcessing::EqBandConfig>(anyConfig);
RETURN_VALUE_IF(
nullptr == (dp = getEqWithType_l(type, config.channel)) || !dp->isEnabled(),
RetCode::ERROR_ILLEGAL_PARAMETER, "dpEqNotExist");
RETURN_VALUE_IF(nullptr == (dp = getEqWithType_l(type, config.channel)),
RetCode::ERROR_ILLEGAL_PARAMETER, "dpEqNotExist");
dp_fx::DPEqBand band;
band.init(config.enable, config.cutoffFrequencyHz, config.gainDb);
dp->setBand(config.band, band);
@@ -485,7 +484,7 @@ RetCode DynamicsProcessingContext::setDpChannelBand_l(const std::any& anyConfig,
case StageType::MBC: {
dp_fx::DPMbc* dp;
const auto& config = std::any_cast<DynamicsProcessing::MbcBandConfig>(anyConfig);
RETURN_VALUE_IF(nullptr == (dp = getMbc_l(config.channel)) || !dp->isEnabled(),
RETURN_VALUE_IF(nullptr == (dp = getMbc_l(config.channel)),
RetCode::ERROR_ILLEGAL_PARAMETER, "dpMbcNotExist");
dp_fx::DPMbcBand band;
band.init(config.enable, config.cutoffFrequencyHz, config.attackTimeMs,
+7 -3
View File
@@ -8,16 +8,16 @@ cc_library_shared {
relative_install_path: "soundfx",
vendor: true,
srcs: [
":effectCommonFileRpi",
"aidl/EffectLoudnessEnhancer.cpp",
"aidl/LoudnessEnhancerContext.cpp",
"dsp/core/dynamic_range_compression.cpp",
],
static_libs: [
"libaudioeffectaidlcommon-rpi",
],
shared_libs: [
"android.hardware.audio.effect-V3-ndk",
"android.hardware.common-V2-ndk",
"android.hardware.common.fmq-V1-ndk",
"android.media.audio.common.types-V4-ndk",
"libaudio_aidl_conversion_common_ndk",
"libaudioaidlcommon",
"libaudioutils",
@@ -42,4 +42,8 @@ cc_library_shared {
"-Wextra",
"-Wthread-safety",
],
defaults: [
"latest_android_hardware_audio_effect_ndk_shared",
"latest_android_media_audio_common_types_ndk_shared",
],
}
@@ -19,6 +19,8 @@
#include "dsp/core/dynamic_range_compression.h"
#include <system/audio.h>
#include <cmath>
namespace le_fx {
AdaptiveDynamicRangeCompression::AdaptiveDynamicRangeCompression() {
+5 -3
View File
@@ -8,19 +8,17 @@ cc_library_shared {
relative_install_path: "soundfx",
vendor: true,
srcs: [
":effectCommonFileRpi",
"aidl/EffectReverb.cpp",
"aidl/ReverbContext.cpp",
],
local_include_dirs: ["aidl"],
static_libs: [
"libaudioeffectaidlcommon-rpi",
"libreverb",
],
shared_libs: [
"android.hardware.audio.effect-V3-ndk",
"android.hardware.common-V2-ndk",
"android.hardware.common.fmq-V1-ndk",
"android.media.audio.common.types-V4-ndk",
"libaudio_aidl_conversion_common_ndk",
"libaudioaidlcommon",
"libaudioutils",
@@ -45,4 +43,8 @@ cc_library_shared {
"-Wextra",
"-Wthread-safety",
],
defaults: [
"latest_android_hardware_audio_effect_ndk_shared",
"latest_android_media_audio_common_types_ndk_shared",
],
}
+7 -3
View File
@@ -8,15 +8,15 @@ cc_library_shared {
relative_install_path: "soundfx",
vendor: true,
srcs: [
":effectCommonFileRpi",
"aidl/Visualizer.cpp",
"aidl/VisualizerContext.cpp",
],
static_libs: [
"libaudioeffectaidlcommon-rpi",
],
shared_libs: [
"android.hardware.audio.effect-V3-ndk",
"android.hardware.common-V2-ndk",
"android.hardware.common.fmq-V1-ndk",
"android.media.audio.common.types-V4-ndk",
"libaudio_aidl_conversion_common_ndk",
"libaudioaidlcommon",
"libaudioutils",
@@ -43,4 +43,8 @@ cc_library_shared {
"-Wextra",
"-Wthread-safety",
],
defaults: [
"latest_android_hardware_audio_effect_ndk_shared",
"latest_android_media_audio_common_types_ndk_shared",
],
}
+1 -1
View File
@@ -20,7 +20,7 @@
#include <vector>
#define LOG_TAG "AHAL_Main"
#include <android-base/logging.h>
#include <Log.h>
#include <android-base/properties.h>
#include <android/binder_ibinder_platform.h>
#include <android/binder_manager.h>
+14 -13
View File
@@ -19,8 +19,8 @@
#include <cstdio>
#include <Log.h>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/properties.h>
#include <audio_utils/clock.h>
@@ -44,7 +44,7 @@ using android::base::ReadFileToString;
namespace aidl::android::hardware::audio::core {
StreamPrimary::StreamPrimary(StreamContext* context, const Metadata& metadata)
: StreamAlsa(context, metadata, 3 /*readWriteRetries*/),
: StreamAlsaMonoPipe(context, metadata, 3 /*readWriteRetries*/),
mIsAsynchronous(!!getContext().getAsyncCallback()),
mStubDriver(getContext()) {
context->startStreamDataProcessor();
@@ -52,25 +52,26 @@ StreamPrimary::StreamPrimary(StreamContext* context, const Metadata& metadata)
::android::status_t StreamPrimary::init(DriverCallbackInterface* callback) {
RETURN_STATUS_IF_ERROR(mStubDriver.init(callback));
return StreamAlsa::init(callback);
return StreamAlsaMonoPipe::init(callback);
}
::android::status_t StreamPrimary::drain(StreamDescriptor::DrainMode mode) {
return isStubStreamOnWorker() ? mStubDriver.drain(mode) : StreamAlsa::drain(mode);
return isStubStreamOnWorker() ? mStubDriver.drain(mode) : StreamAlsaMonoPipe::drain(mode);
}
::android::status_t StreamPrimary::flush() {
RETURN_STATUS_IF_ERROR(isStubStreamOnWorker() ? mStubDriver.flush() : StreamAlsa::flush());
RETURN_STATUS_IF_ERROR(isStubStreamOnWorker() ? mStubDriver.flush()
: StreamAlsaMonoPipe::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();
return isStubStreamOnWorker() ? mStubDriver.pause() : StreamAlsaMonoPipe::pause();
}
::android::status_t StreamPrimary::standby() {
return isStubStreamOnWorker() ? mStubDriver.standby() : StreamAlsa::standby();
return isStubStreamOnWorker() ? mStubDriver.standby() : StreamAlsaMonoPipe::standby();
}
::android::status_t StreamPrimary::start() {
@@ -83,12 +84,12 @@ StreamPrimary::StreamPrimary(StreamContext* context, const Metadata& metadata)
mCurrAlsaDeviceId = mAlsaDeviceId;
}
if (shutdownAlsaStream) {
StreamAlsa::shutdown(); // Close currently opened ALSA devices.
StreamAlsaMonoPipe::shutdown(); // Close currently opened ALSA devices.
}
if (isStub) {
return mStubDriver.start();
}
RETURN_STATUS_IF_ERROR(StreamAlsa::start());
RETURN_STATUS_IF_ERROR(StreamAlsaMonoPipe::start());
mStartTimeNs = ::android::uptimeNanos();
mFramesSinceStart = 0;
mSkipNextTransfer = false;
@@ -104,7 +105,7 @@ StreamPrimary::StreamPrimary(StreamContext* context, const Metadata& metadata)
// 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));
StreamAlsaMonoPipe::transfer(buffer, frameCount, actualFrameCount, latencyMs));
} else {
LOG(DEBUG) << __func__ << ": skipping transfer (" << frameCount << " frames)";
*actualFrameCount = frameCount;
@@ -137,13 +138,13 @@ StreamPrimary::StreamPrimary(StreamContext* context, const Metadata& metadata)
const bool refinePosition =
GetBoolProperty("persist.vendor.audio.refine_position", true);
if (refinePosition) {
RETURN_STATUS_IF_ERROR(StreamAlsa::refinePosition(position));
RETURN_STATUS_IF_ERROR(StreamAlsaBase::refinePosition(position));
}
return ::android::OK;
}
void StreamPrimary::shutdown() {
StreamAlsa::shutdown();
StreamAlsaMonoPipe::shutdown();
mStubDriver.shutdown();
}
@@ -165,7 +166,7 @@ ndk::ScopedAStatus StreamPrimary::setConnectedDevices(const ConnectedDevices& de
streamDataProcessor->setAudioDevice(devices[0]);
}
}
return StreamAlsa::setConnectedDevices(devices);
return StreamAlsaMonoPipe::setConnectedDevices(devices);
}
std::vector<alsa::DeviceProfile> StreamPrimary::getDeviceProfiles() {
+4 -2
View File
@@ -19,7 +19,7 @@
#include <stdio.h>
#include <vector>
#include <android-base/logging.h>
#include <Log.h>
#include <error/expected_utils.h>
#include "SubmixRoute.h"
@@ -28,6 +28,7 @@
using aidl::android::hardware::audio::common::SinkMetadata;
using aidl::android::hardware::audio::common::SourceMetadata;
using aidl::android::media::audio::common::AudioChannelLayout;
using aidl::android::media::audio::common::AudioDeviceAddress;
using aidl::android::media::audio::common::AudioFormatType;
using aidl::android::media::audio::common::AudioIoFlags;
@@ -170,7 +171,8 @@ ndk::ScopedAStatus ModuleRemoteSubmix::onMasterVolumeChanged(float __unused) {
int32_t ModuleRemoteSubmix::getNominalLatencyMs(const AudioPortConfig&) {
// See the note on kDefaultPipePeriodCount.
static constexpr int32_t kMaxLatencyMs =
(r_submix::kDefaultPipeSizeInFrames * 1000) / r_submix::kDefaultSampleRateHz;
(r_submix::kDefaultPipeSizeInFrames * MILLIS_PER_SECOND) /
r_submix::kDefaultSampleRateHz;
static constexpr int32_t kMinLatencyMs = kMaxLatencyMs / r_submix::kDefaultPipePeriodCount;
return kMinLatencyMs;
}
+206 -163
View File
@@ -15,7 +15,7 @@
*/
#define LOG_TAG "AHAL_StreamRemoteSubmix"
#include <android-base/logging.h>
#include <Log.h>
#include <audio_utils/clock.h>
#include <error/Result.h>
#include <error/expected_utils.h>
@@ -26,53 +26,31 @@ using aidl::android::hardware::audio::common::SinkMetadata;
using aidl::android::hardware::audio::common::SourceMetadata;
using aidl::android::hardware::audio::core::r_submix::SubmixRoute;
using aidl::android::media::audio::common::AudioDeviceAddress;
using aidl::android::media::audio::common::AudioDeviceType;
using aidl::android::media::audio::common::AudioOffloadInfo;
using aidl::android::media::audio::common::MicrophoneDynamicInfo;
using aidl::android::media::audio::common::MicrophoneInfo;
using android::MonoPipe;
using android::MonoPipeReader;
using android::sp;
namespace aidl::android::hardware::audio::core {
using deprecated::InnerStreamWrapper;
using deprecated::StreamCommonInterfaceEx;
using deprecated::StreamSwitcher;
StreamRemoteSubmix::StreamRemoteSubmix(StreamContext* context, const Metadata& metadata,
const AudioDeviceAddress& deviceAddress)
StreamRemoteSubmix::StreamRemoteSubmix(StreamContext* context, const Metadata& metadata)
: StreamCommonImpl(context, metadata),
mDeviceAddress(deviceAddress),
mIsInput(isInput(metadata)) {
mStreamConfig.frameSize = context->getFrameSize();
mStreamConfig.format = context->getFormat();
mStreamConfig.channelLayout = context->getChannelLayout();
mStreamConfig.sampleRate = context->getSampleRate();
}
mIsInput(isInput(metadata)),
mStreamConfig{.sampleRate = context->getSampleRate(),
.format = context->getFormat(),
.channelLayout = context->getChannelLayout(),
.frameSize = context->getFrameSize(),
.frameCount = context->getBufferSizeInFrames()},
mReadAttemptSleepUs(getDurationInUsForFrameCount(r_submix::kReadAttemptSleepFrames)) {}
StreamRemoteSubmix::~StreamRemoteSubmix() {
cleanupWorker();
}
::android::status_t StreamRemoteSubmix::init(DriverCallbackInterface*) {
mCurrentRoute = SubmixRoute::findOrCreateRoute(mDeviceAddress, mStreamConfig);
if (mCurrentRoute == nullptr) {
return ::android::NO_INIT;
}
if (!mCurrentRoute->isStreamConfigValid(mIsInput, mStreamConfig)) {
LOG(ERROR) << __func__ << ": invalid stream config";
return ::android::NO_INIT;
}
sp<MonoPipe> sink = mCurrentRoute->getSink();
if (sink == nullptr) {
LOG(ERROR) << __func__ << ": nullptr sink when opening stream";
return ::android::NO_INIT;
}
if ((!mIsInput || mCurrentRoute->isStreamInOpen()) && sink->isShutdown()) {
LOG(DEBUG) << __func__ << ": Shut down sink when opening stream";
if (::android::OK != mCurrentRoute->resetPipe()) {
LOG(ERROR) << __func__ << ": reset pipe failed";
return ::android::NO_INIT;
}
}
mCurrentRoute->openStream(mIsInput);
return ::android::OK;
}
@@ -90,82 +68,111 @@ StreamRemoteSubmix::~StreamRemoteSubmix() {
}
::android::status_t StreamRemoteSubmix::standby() {
mCurrentRoute->standby(mIsInput);
std::lock_guard guard(mLock);
if (mCurrentRoute) mCurrentRoute->standby(mIsInput);
return ::android::OK;
}
::android::status_t StreamRemoteSubmix::start() {
mCurrentRoute->exitStandby(mIsInput);
{
std::lock_guard guard(mLock);
if (mCurrentRoute) mCurrentRoute->exitStandby(mIsInput);
}
mStartTimeNs = ::android::uptimeNanos();
mFramesSinceStart = 0;
return ::android::OK;
}
ndk::ScopedAStatus StreamRemoteSubmix::prepareToClose() {
if (!mIsInput) {
std::shared_ptr<SubmixRoute> route = SubmixRoute::findRoute(mDeviceAddress);
if (route != nullptr) {
sp<MonoPipe> sink = route->getSink();
if (sink == nullptr) {
ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
LOG(DEBUG) << __func__ << ": shutting down MonoPipe sink";
sink->shutdown(true);
// The client already considers this stream as closed, release the output end.
route->closeStream(mIsInput);
} else {
LOG(DEBUG) << __func__ << ": stream already closed.";
ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
}
return ndk::ScopedAStatus::ok();
}
// Remove references to the specified input and output streams. When the device no longer
// references input and output streams destroy the associated pipe.
void StreamRemoteSubmix::shutdown() {
mCurrentRoute->closeStream(mIsInput);
// If all stream instances are closed, we can remove route information for this port.
if (!mCurrentRoute->hasAtleastOneStreamOpen()) {
mCurrentRoute->releasePipe();
LOG(DEBUG) << __func__ << ": pipe destroyed";
SubmixRoute::removeRoute(mDeviceAddress);
std::shared_ptr<r_submix::SubmixRoute> currentRoute;
{
std::lock_guard guard(mLock);
mCurrentRoute.swap(currentRoute);
}
if (!currentRoute) {
LOG(DEBUG) << __func__ << ": no current route";
return;
}
currentRoute->closeStream(mIsInput);
// If all stream instances are closed, we can remove route information for this port.
if (!currentRoute->hasAtleastOneStreamOpen()) {
currentRoute->releasePipe();
LOG(DEBUG) << __func__ << ": pipe " << currentRoute->getDeviceAddress().toString()
<< " destroyed";
currentRoute->remove();
} else {
LOG(DEBUG) << __func__ << ": pipe " << currentRoute->getDeviceAddress().toString()
<< " status: " << currentRoute->dump();
}
mCurrentRoute.reset();
}
::android::status_t StreamRemoteSubmix::transfer(void* buffer, size_t frameCount,
size_t* actualFrameCount, int32_t* latencyMs) {
*latencyMs = getDelayInUsForFrameCount(getStreamPipeSizeInFrames()) / 1000;
std::shared_ptr<r_submix::SubmixRoute> currentRoute;
{
std::lock_guard guard(mLock);
currentRoute = mCurrentRoute;
}
*latencyMs = getDurationInUsForFrameCount(getStreamPipeSizeInFrames(currentRoute)) / 1000;
LOG(VERBOSE) << __func__ << ": Latency " << *latencyMs << "ms";
mCurrentRoute->exitStandby(mIsInput);
::android::status_t status = mIsInput ? inRead(buffer, frameCount, actualFrameCount)
: outWrite(buffer, frameCount, actualFrameCount);
if ((status != ::android::OK && mIsInput) ||
((status != ::android::OK && status != ::android::DEAD_OBJECT) && !mIsInput)) {
return status;
::android::status_t status = ::android::OK;
if (currentRoute) {
currentRoute->exitStandby(mIsInput);
if (!mSkipNextTransfer) {
status = mIsInput ? inRead(currentRoute, buffer, frameCount, actualFrameCount)
: outWrite(currentRoute, buffer, frameCount, actualFrameCount);
if ((status != ::android::OK && mIsInput) ||
((status != ::android::OK && status != ::android::DEAD_OBJECT) && !mIsInput)) {
return status;
}
} else {
LOG(VERBOSE) << __func__ << ": Skipping transfer";
if (mIsInput) memset(buffer, 0, mStreamConfig.frameSize * frameCount);
*actualFrameCount = frameCount;
}
} else {
LOG(WARNING) << __func__ << ": no current route";
if (mIsInput) {
memset(buffer, 0, mStreamConfig.frameSize * frameCount);
}
*actualFrameCount = frameCount;
}
mFramesSinceStart += *actualFrameCount;
if (!mIsInput && status != ::android::DEAD_OBJECT) return ::android::OK;
// Input streams always need to block, output streams need to block when there is no sink.
// When the sink exists, more sophisticated blocking algorithm is implemented by MonoPipe.
// If there is no route, always block, otherwise:
// - Input streams always need to block, output streams need to block when there is no sink.
// - When the sink exists, more sophisticated blocking algorithm is implemented by MonoPipe.
if (mSkipNextTransfer || (currentRoute && !mIsInput && status != ::android::DEAD_OBJECT)) {
mSkipNextTransfer = false;
return ::android::OK;
}
const long bufferDurationUs =
(*actualFrameCount) * MICROS_PER_SECOND / mContext.getSampleRate();
const auto totalDurationUs = (::android::uptimeNanos() - mStartTimeNs) / NANOS_PER_MICROSECOND;
const long totalOffsetUs =
mFramesSinceStart * MICROS_PER_SECOND / mContext.getSampleRate() - totalDurationUs;
const long totalOffsetUs = getDurationInUsForFrameCount(mFramesSinceStart) - totalDurationUs;
LOG(VERBOSE) << __func__ << ": totalOffsetUs " << totalOffsetUs;
if (totalOffsetUs > 0) {
const long sleepTimeUs = std::min(totalOffsetUs, bufferDurationUs);
const long sleepTimeUs = std::max(0L, std::min(totalOffsetUs, bufferDurationUs));
LOG(VERBOSE) << __func__ << ": sleeping for " << sleepTimeUs << " us";
usleep(sleepTimeUs);
} else if (totalOffsetUs <= -(bufferDurationUs / 2)) {
LOG(VERBOSE) << __func__ << ": skipping next transfer";
mSkipNextTransfer = true;
}
return ::android::OK;
}
::android::status_t StreamRemoteSubmix::refinePosition(StreamDescriptor::Position* position) {
sp<MonoPipeReader> source = mCurrentRoute->getSource();
std::shared_ptr<r_submix::SubmixRoute> currentRoute;
{
std::lock_guard guard(mLock);
currentRoute = mCurrentRoute;
}
if (!currentRoute) {
return ::android::OK;
}
sp<MonoPipeReader> source = currentRoute->getSource();
if (source == nullptr) {
return ::android::NO_INIT;
}
@@ -182,20 +189,23 @@ void StreamRemoteSubmix::shutdown() {
return ::android::OK;
}
long StreamRemoteSubmix::getDelayInUsForFrameCount(size_t frameCount) {
long StreamRemoteSubmix::getDurationInUsForFrameCount(size_t frameCount) const {
return frameCount * MICROS_PER_SECOND / mStreamConfig.sampleRate;
}
// Calculate the maximum size of the pipe buffer in frames for the specified stream.
size_t StreamRemoteSubmix::getStreamPipeSizeInFrames() {
auto pipeConfig = mCurrentRoute->getPipeConfig();
size_t StreamRemoteSubmix::getStreamPipeSizeInFrames(
const std::shared_ptr<r_submix::SubmixRoute>& currentRoute) {
if (!currentRoute) return r_submix::kDefaultPipeSizeInFrames;
auto pipeConfig = currentRoute->getPipeConfig();
const size_t maxFrameSize = std::max(mStreamConfig.frameSize, pipeConfig.frameSize);
return (pipeConfig.frameCount * pipeConfig.frameSize) / maxFrameSize;
}
::android::status_t StreamRemoteSubmix::outWrite(void* buffer, size_t frameCount,
size_t* actualFrameCount) {
sp<MonoPipe> sink = mCurrentRoute->getSink();
::android::status_t StreamRemoteSubmix::outWrite(
const std::shared_ptr<r_submix::SubmixRoute>& currentRoute, void* buffer, size_t frameCount,
size_t* actualFrameCount) {
sp<MonoPipe> sink = currentRoute->getSink();
if (sink != nullptr) {
if (sink->isShutdown()) {
sink.clear();
@@ -211,13 +221,13 @@ size_t StreamRemoteSubmix::getStreamPipeSizeInFrames() {
}
mWriteShutdownCount = 0;
LOG(VERBOSE) << __func__ << ": " << mDeviceAddress.toString() << ", " << frameCount
<< " frames";
LOG(VERBOSE) << __func__ << ": " << currentRoute->getDeviceAddress().toString() << ", "
<< frameCount << " frames";
const bool shouldBlockWrite = mCurrentRoute->shouldBlockWrite();
const bool shouldBlockWrite = currentRoute->shouldBlockWrite();
size_t availableToWrite = sink->availableToWrite();
// NOTE: sink has been checked above and sink and source life cycles are synchronized
sp<MonoPipeReader> source = mCurrentRoute->getSource();
sp<MonoPipeReader> source = currentRoute->getSource();
// If the write to the sink should be blocked, flush enough frames from the pipe to make space
// to write the most recent data.
if (!shouldBlockWrite && availableToWrite < frameCount) {
@@ -267,14 +277,22 @@ size_t StreamRemoteSubmix::getStreamPipeSizeInFrames() {
return ::android::OK;
}
::android::status_t StreamRemoteSubmix::inRead(void* buffer, size_t frameCount,
size_t* actualFrameCount) {
::android::status_t StreamRemoteSubmix::inRead(
const std::shared_ptr<r_submix::SubmixRoute>& currentRoute, void* buffer, size_t frameCount,
size_t* actualFrameCount) {
// Try to wait as long as possible for the audio duration, but leave some time for the call to
// 'transfer' to complete. 'mReadAttemptSleepUs' is a good constant for this purpose because it
// is by definition "strictly inferior" to the typical buffer duration.
const long durationUs =
std::max(0L, getDurationInUsForFrameCount(frameCount) - mReadAttemptSleepUs * 2);
const int64_t deadlineTimeNs = ::android::uptimeNanos() + durationUs * NANOS_PER_MICROSECOND;
// in any case, it is emulated that data for the entire buffer was available
memset(buffer, 0, mStreamConfig.frameSize * frameCount);
*actualFrameCount = frameCount;
// about to read from audio source
sp<MonoPipeReader> source = mCurrentRoute->getSource();
sp<MonoPipeReader> source = currentRoute->getSource();
if (source == nullptr) {
if (++mReadErrorCount < kMaxErrorLogs) {
LOG(ERROR) << __func__
@@ -284,7 +302,7 @@ size_t StreamRemoteSubmix::getStreamPipeSizeInFrames() {
return ::android::OK;
}
// get and hold the sink because 'MonoPipeReader' does not hold a strong pointer to it.
sp<MonoPipe> sink = mCurrentRoute->getSink();
sp<MonoPipe> sink = currentRoute->getSink();
if (sink == nullptr) {
if (++mReadErrorCount < kMaxErrorLogs) {
LOG(ERROR) << __func__
@@ -294,18 +312,13 @@ size_t StreamRemoteSubmix::getStreamPipeSizeInFrames() {
}
mReadErrorCount = 0;
LOG(VERBOSE) << __func__ << ": " << mDeviceAddress.toString() << ", " << frameCount
<< " frames";
LOG(VERBOSE) << __func__ << ": " << currentRoute->getDeviceAddress().toString() << ", "
<< frameCount << " frames";
// read the data from the pipe
char* buff = (char*)buffer;
size_t actuallyRead = 0;
long remainingFrames = frameCount;
// Try to wait as long as possible for the audio duration, but leave some time for the call to
// 'transfer' to complete. 'kReadAttemptSleepUs' is a good constant for this purpose because it
// is by definition "strictly inferior" to the typical buffer duration.
const long durationUs =
std::max(0L, getDelayInUsForFrameCount(frameCount) - kReadAttemptSleepUs);
const int64_t deadlineTimeNs = ::android::uptimeNanos() + durationUs * NANOS_PER_MICROSECOND;
while (remainingFrames > 0) {
ssize_t framesRead = source->read(buff, remainingFrames);
LOG(VERBOSE) << __func__ << ": frames read " << framesRead;
@@ -319,26 +332,112 @@ size_t StreamRemoteSubmix::getStreamPipeSizeInFrames() {
if (::android::uptimeNanos() >= deadlineTimeNs) break;
if (framesRead <= 0) {
LOG(VERBOSE) << __func__ << ": read returned " << framesRead
<< ", read failure, sleeping for " << kReadAttemptSleepUs << " us";
usleep(kReadAttemptSleepUs);
<< ", read failure, sleeping for " << mReadAttemptSleepUs << " us";
usleep(mReadAttemptSleepUs);
}
}
if (actuallyRead < frameCount) {
if (++mReadFailureCount < kMaxReadFailureAttempts) {
if (++mReadFailureCount < r_submix::kMaxReadFailureAttempts) {
LOG(WARNING) << __func__ << ": read " << actuallyRead << " vs. requested " << frameCount
<< " (not all errors will be logged)";
}
} else {
mReadFailureCount = 0;
}
mCurrentRoute->updateReadCounterFrames(*actualFrameCount);
currentRoute->updateReadCounterFrames(*actualFrameCount);
return ::android::OK;
}
std::shared_ptr<r_submix::SubmixRoute> StreamRemoteSubmix::prepareCurrentRoute(
const ::aidl::android::media::audio::common::AudioDeviceAddress& deviceAddress) {
if (deviceAddress == AudioDeviceAddress{}) {
return nullptr;
}
auto currentRoute = SubmixRoute::findOrCreateRoute(deviceAddress, mStreamConfig);
if (currentRoute == nullptr) return nullptr;
if (!currentRoute->isStreamConfigValid(mIsInput, mStreamConfig)) {
LOG(ERROR) << __func__ << ": invalid stream config";
return nullptr;
}
sp<MonoPipe> sink = currentRoute->getSink();
if (sink == nullptr) {
LOG(ERROR) << __func__ << ": nullptr sink when opening stream";
return nullptr;
}
if ((!mIsInput || currentRoute->isStreamInOpen()) && sink->isShutdown()) {
LOG(DEBUG) << __func__ << ": shut down sink when opening stream";
if (::android::OK != currentRoute->resetPipe()) {
LOG(ERROR) << __func__ << ": reset pipe failed";
return nullptr;
}
}
currentRoute->openStream(mIsInput);
return currentRoute;
}
ndk::ScopedAStatus StreamRemoteSubmix::prepareToClose() {
std::shared_ptr<r_submix::SubmixRoute> currentRoute;
{
std::lock_guard guard(mLock);
currentRoute = mCurrentRoute;
}
if (currentRoute != nullptr) {
if (!mIsInput) {
// The client already considers this stream as closed, release the output end.
currentRoute->closeStream(mIsInput);
}
} else {
LOG(DEBUG) << __func__ << ": stream already closed";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus StreamRemoteSubmix::setConnectedDevices(const ConnectedDevices& devices) {
LOG(DEBUG) << __func__ << ": ioHandle: " << mContext.getMixPortHandle()
<< ", devices: " << ::android::internal::ToString(devices);
if (devices.size() > 1) {
LOG(ERROR) << __func__ << ": Only single device supported, got " << devices.size();
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
AudioDeviceAddress newAddress;
if (!devices.empty()) {
if (auto deviceDesc = devices.front().type;
(mIsInput && deviceDesc.type != AudioDeviceType::IN_SUBMIX) ||
(!mIsInput && deviceDesc.type != AudioDeviceType::OUT_SUBMIX)) {
LOG(ERROR) << __func__ << ": Device type " << toString(deviceDesc.type)
<< " not supported";
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
newAddress = devices.front().address;
if (newAddress != AudioDeviceAddress{}) {
auto existingRoute = SubmixRoute::findRoute(newAddress);
if (existingRoute != nullptr) {
if (!existingRoute->isStreamConfigValid(mIsInput, mStreamConfig)) {
LOG(ERROR) << __func__ << ": invalid stream config";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
}
}
}
RETURN_STATUS_IF_ERROR(StreamCommonImpl::setConnectedDevices(devices));
auto newCurrentRoute = prepareCurrentRoute(newAddress);
if (newCurrentRoute) {
std::lock_guard guard(mLock);
mCurrentRoute = newCurrentRoute;
LOG(DEBUG) << __func__ << ": connected to " << newAddress.toString();
} else {
// Do not update `mCurrentRoute`, it will be cleaned up by the worker thread.
LOG(DEBUG) << __func__ << ": disconnected";
}
return ndk::ScopedAStatus::ok();
}
StreamInRemoteSubmix::StreamInRemoteSubmix(StreamContext&& context,
const SinkMetadata& sinkMetadata,
const std::vector<MicrophoneInfo>& microphones)
: StreamIn(std::move(context), microphones), StreamSwitcher(&mContextInstance, sinkMetadata) {}
: StreamIn(std::move(context), microphones),
StreamRemoteSubmix(&mContextInstance, sinkMetadata) {}
ndk::ScopedAStatus StreamInRemoteSubmix::getActiveMicrophones(
std::vector<MicrophoneDynamicInfo>* _aidl_return) {
@@ -347,66 +446,10 @@ ndk::ScopedAStatus StreamInRemoteSubmix::getActiveMicrophones(
return ndk::ScopedAStatus::ok();
}
StreamSwitcher::DeviceSwitchBehavior StreamInRemoteSubmix::switchCurrentStream(
const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices) {
// This implementation effectively postpones stream creation until
// receiving the first call to 'setConnectedDevices' with a non-empty list.
if (isStubStream()) {
if (devices.size() == 1) {
auto deviceDesc = devices.front().type;
if (deviceDesc.type ==
::aidl::android::media::audio::common::AudioDeviceType::IN_SUBMIX) {
return DeviceSwitchBehavior::CREATE_NEW_STREAM;
}
LOG(ERROR) << __func__ << ": Device type " << toString(deviceDesc.type)
<< " not supported";
} else {
LOG(ERROR) << __func__ << ": Only single device supported.";
}
return DeviceSwitchBehavior::UNSUPPORTED_DEVICES;
}
return DeviceSwitchBehavior::USE_CURRENT_STREAM;
}
std::unique_ptr<StreamCommonInterfaceEx> StreamInRemoteSubmix::createNewStream(
const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices,
StreamContext* context, const Metadata& metadata) {
return std::unique_ptr<StreamCommonInterfaceEx>(
new InnerStreamWrapper<StreamRemoteSubmix>(context, metadata, devices.front().address));
}
StreamOutRemoteSubmix::StreamOutRemoteSubmix(StreamContext&& context,
const SourceMetadata& sourceMetadata,
const std::optional<AudioOffloadInfo>& offloadInfo)
: StreamOut(std::move(context), offloadInfo),
StreamSwitcher(&mContextInstance, sourceMetadata) {}
StreamSwitcher::DeviceSwitchBehavior StreamOutRemoteSubmix::switchCurrentStream(
const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices) {
// This implementation effectively postpones stream creation until
// receiving the first call to 'setConnectedDevices' with a non-empty list.
if (isStubStream()) {
if (devices.size() == 1) {
auto deviceDesc = devices.front().type;
if (deviceDesc.type ==
::aidl::android::media::audio::common::AudioDeviceType::OUT_SUBMIX) {
return DeviceSwitchBehavior::CREATE_NEW_STREAM;
}
LOG(ERROR) << __func__ << ": Device type " << toString(deviceDesc.type)
<< " not supported";
} else {
LOG(ERROR) << __func__ << ": Only single device supported.";
}
return DeviceSwitchBehavior::UNSUPPORTED_DEVICES;
}
return DeviceSwitchBehavior::USE_CURRENT_STREAM;
}
std::unique_ptr<StreamCommonInterfaceEx> StreamOutRemoteSubmix::createNewStream(
const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices,
StreamContext* context, const Metadata& metadata) {
return std::unique_ptr<StreamCommonInterfaceEx>(
new InnerStreamWrapper<StreamRemoteSubmix>(context, metadata, devices.front().address));
}
StreamRemoteSubmix(&mContextInstance, sourceMetadata) {}
} // namespace aidl::android::hardware::audio::core
+13 -7
View File
@@ -17,7 +17,7 @@
#include <mutex>
#define LOG_TAG "AHAL_SubmixRoute"
#include <android-base/logging.h>
#include <Log.h>
#include <media/AidlConversionCppNdk.h>
#include <Utils.h>
@@ -25,7 +25,11 @@
#include "SubmixRoute.h"
using aidl::android::hardware::audio::common::getChannelCount;
using aidl::android::media::audio::common::AudioChannelLayout;
using aidl::android::media::audio::common::AudioDeviceAddress;
using android::MonoPipe;
using android::MonoPipeReader;
using android::sp;
namespace aidl::android::hardware::audio::core::r_submix {
@@ -45,7 +49,7 @@ std::shared_ptr<SubmixRoute> SubmixRoute::findOrCreateRoute(const AudioDeviceAdd
if (routeItr != routes->end()) {
return routeItr->second;
}
auto route = std::make_shared<SubmixRoute>();
auto route = std::make_shared<SubmixRoute>(deviceAddress);
if (::android::OK != route->createPipe(pipeConfig)) {
LOG(ERROR) << __func__ << ": create pipe failed";
return nullptr;
@@ -64,11 +68,6 @@ std::shared_ptr<SubmixRoute> SubmixRoute::findRoute(const AudioDeviceAddress& de
return nullptr;
}
// static
void SubmixRoute::removeRoute(const AudioDeviceAddress& deviceAddress) {
getRoutes()->erase(deviceAddress);
}
// static
std::string SubmixRoute::dumpRoutes() {
auto routes = getRoutes(true /*tryLock*/);
@@ -176,6 +175,9 @@ void SubmixRoute::closeStream(bool isInput) {
}
} else {
mStreamOutOpen = false;
if (mSink != nullptr) {
mSink->shutdown(true);
}
}
}
@@ -244,6 +246,10 @@ AudioConfig SubmixRoute::releasePipe() {
return mPipeConfig;
}
void SubmixRoute::remove() {
getRoutes()->erase(mDeviceAddress);
}
::android::status_t SubmixRoute::resetPipe() {
return createPipe(releasePipe());
}
+34 -22
View File
@@ -25,18 +25,11 @@
#include <media/nbaio/MonoPipe.h>
#include <media/nbaio/MonoPipeReader.h>
#include <Utils.h>
#include <aidl/android/media/audio/common/AudioChannelLayout.h>
#include <aidl/android/media/audio/common/AudioDeviceAddress.h>
#include <aidl/android/media/audio/common/AudioFormatDescription.h>
using aidl::android::media::audio::common::AudioChannelLayout;
using aidl::android::media::audio::common::AudioFormatDescription;
using aidl::android::media::audio::common::AudioFormatType;
using aidl::android::media::audio::common::PcmType;
using ::android::MonoPipe;
using ::android::MonoPipeReader;
using ::android::sp;
namespace aidl::android::hardware::audio::core::r_submix {
static constexpr int kDefaultSampleRateHz = 48000;
@@ -44,20 +37,34 @@ static constexpr int kDefaultSampleRateHz = 48000;
// read from the sink. The maximum latency of the device is the size of the MonoPipe's buffer
// the minimum latency is the MonoPipe buffer size divided by this value.
static constexpr int kDefaultPipePeriodCount = 4;
static constexpr int kDefaultPipePeriodSizeFrames = 1024;
// Size at the default sample rate
// NOTE: This value will be rounded up to the nearest power of 2 by MonoPipe.
static constexpr int kDefaultPipeSizeInFrames = 1024 * kDefaultPipePeriodCount;
static constexpr int kDefaultPipeSizeInFrames =
kDefaultPipePeriodSizeFrames * kDefaultPipePeriodCount;
// The duration of kMaxReadFailureAttempts * kReadAttemptSleepUs must be strictly inferior to the
// duration of a record buffer (kDefaultPipePeriodSizeFrames) at the current record sample rate (of
// the device, not of the recording itself). Here we have: 3 * 240 = 720 < 1024 frames
static constexpr int kMaxReadFailureAttempts = 3;
static constexpr int kReadAttemptSleepFrames = 240;
static_assert(kMaxReadFailureAttempts * kReadAttemptSleepFrames * 7 <
kDefaultPipePeriodSizeFrames * 5,
"Duration of attempts * sleep must be significantly less than the period size");
// Configuration of the audio stream.
struct AudioConfig {
int sampleRate = kDefaultSampleRateHz;
AudioFormatDescription format =
AudioFormatDescription{.type = AudioFormatType::PCM, .pcm = PcmType::INT_16_BIT};
AudioChannelLayout channelLayout =
AudioChannelLayout::make<AudioChannelLayout::Tag::layoutMask>(
AudioChannelLayout::LAYOUT_STEREO);
size_t frameSize;
size_t frameCount;
::aidl::android::media::audio::common::AudioFormatDescription format =
::aidl::android::media::audio::common::AudioFormatDescription{
.type = ::aidl::android::media::audio::common::AudioFormatType::PCM,
.pcm = ::aidl::android::media::audio::common::PcmType::INT_16_BIT};
::aidl::android::media::audio::common::AudioChannelLayout channelLayout =
::aidl::android::media::audio::common::AudioChannelLayout::make<
::aidl::android::media::audio::common::AudioChannelLayout::Tag::layoutMask>(
::aidl::android::media::audio::common::AudioChannelLayout::LAYOUT_STEREO);
size_t frameSize =
::aidl::android::hardware::audio::common::getFrameSizeInBytes(format, channelLayout);
size_t frameCount = 0;
};
class SubmixRoute {
@@ -67,10 +74,10 @@ class SubmixRoute {
const AudioConfig& pipeConfig);
static std::shared_ptr<SubmixRoute> findRoute(
const ::aidl::android::media::audio::common::AudioDeviceAddress& deviceAddress);
static void removeRoute(
const ::aidl::android::media::audio::common::AudioDeviceAddress& deviceAddress);
static std::string dumpRoutes();
SubmixRoute(const ::aidl::android::media::audio::common::AudioDeviceAddress& deviceAddress)
: mDeviceAddress(deviceAddress) {}
bool isStreamInOpen() {
std::lock_guard guard(mLock);
return mStreamInOpen;
@@ -91,11 +98,11 @@ class SubmixRoute {
std::lock_guard guard(mLock);
return mReadCounterFrames;
}
sp<MonoPipe> getSink() {
::android::sp<::android::MonoPipe> getSink() {
std::lock_guard guard(mLock);
return mSink;
}
sp<MonoPipeReader> getSource() {
::android::sp<::android::MonoPipeReader> getSource() {
std::lock_guard guard(mLock);
return mSource;
}
@@ -104,6 +111,9 @@ class SubmixRoute {
return mPipeConfig;
}
::aidl::android::media::audio::common::AudioDeviceAddress getDeviceAddress() const {
return mDeviceAddress;
}
bool isStreamConfigValid(bool isInput, const AudioConfig& streamConfig);
void closeStream(bool isInput);
::android::status_t createPipe(const AudioConfig& streamConfig);
@@ -112,6 +122,7 @@ class SubmixRoute {
int notifyReadError();
void openStream(bool isInput);
AudioConfig releasePipe();
void remove();
::android::status_t resetPipe();
bool shouldBlockWrite();
void standby(bool isInput);
@@ -138,6 +149,7 @@ class SubmixRoute {
bool isStreamConfigCompatible(const AudioConfig& streamConfig);
const ::aidl::android::media::audio::common::AudioDeviceAddress mDeviceAddress;
std::mutex mLock;
AudioConfig mPipeConfig GUARDED_BY(mLock);
bool mStreamInOpen GUARDED_BY(mLock) = false;
@@ -157,8 +169,8 @@ class SubmixRoute {
// A usecase example is one where the component capturing the audio is then sending it over
// Wifi for presentation on a remote Wifi Display device (e.g. a dongle attached to a TV, or a
// TV with Wifi Display capabilities), or to a wireless audio player.
sp<MonoPipe> mSink GUARDED_BY(mLock);
sp<MonoPipeReader> mSource GUARDED_BY(mLock);
::android::sp<::android::MonoPipe> mSink GUARDED_BY(mLock);
::android::sp<::android::MonoPipeReader> mSource GUARDED_BY(mLock);
};
} // namespace aidl::android::hardware::audio::core::r_submix
+1 -1
View File
@@ -15,7 +15,7 @@
*/
#define LOG_TAG "AHAL_OffloadStream"
#include <android-base/logging.h>
#include <Log.h>
#include "ApeHeader.h"
+1 -1
View File
@@ -18,7 +18,7 @@
#include <cstdlib>
#define LOG_TAG "AHAL_Stream"
#include <android-base/logging.h>
#include <Log.h>
#include <audio_utils/clock.h>
#include "core-impl/DriverStubImpl.h"
+1 -1
View File
@@ -17,8 +17,8 @@
#include <vector>
#define LOG_TAG "AHAL_ModuleStub"
#include <Log.h>
#include <Utils.h>
#include <android-base/logging.h>
#include "core-impl/Bluetooth.h"
#include "core-impl/ModuleStub.h"
+245
View File
@@ -0,0 +1,245 @@
/*
* Copyright (C) 2025 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 "MpegAudioHeader.h"
#define LOG_TAG "AHAL_OffloadStream"
#include <android-base/logging.h>
#include <log/log.h>
#include <climits>
namespace aidl::android::hardware::audio::core {
enum class Version { MPEG_2_5 = 0, RESERVED = 1, MPEG_2 = 2, MPEG_1 = 3 };
enum class Layer { RESERVED = 0, LAYER_3 = 1, LAYER_2 = 2, LAYER_1 = 3 };
// Bitrates in kilo bits per second
const int kBitrateTable[16][5] = {
// V1,L1, V1,L2, V1,L3, V2,L1, V2,L2&L3
{0, 0, 0, 0, 0}, // Invalid
{32, 32, 32, 32, 8}, {64, 48, 40, 48, 16}, {96, 56, 48, 56, 24},
{128, 64, 56, 64, 32}, {160, 80, 64, 80, 40}, {192, 96, 80, 96, 48},
{224, 112, 96, 112, 56}, {256, 128, 112, 128, 64}, {288, 160, 128, 144, 80},
{320, 192, 160, 160, 96}, {352, 224, 192, 176, 112}, {384, 256, 224, 192, 128},
{416, 320, 256, 224, 144}, {448, 384, 320, 256, 160}, {0, 0, 0, 0, 0} // Invalid
};
const int kSampleRateTable[4][3] = {
// MPEG1, MPEG2, MPEG2.5
{44100, 22050, 11025},
{48000, 24000, 12000},
{32000, 16000, 8000},
{0, 0, 0} // Invalid
};
constexpr int kLayer1SamplesPerMpegFrame = 384;
constexpr int kLayer2SamplesPerMpegFrame = 1152;
constexpr int kID3v2TagHeaderSize = 10;
constexpr int kID3v1TagSize = 128;
constexpr int kID3TagIdentifierSize = 3;
constexpr int kFrameHeaderSize = 4;
constexpr uint8_t kSyncByte = 0xFF;
constexpr uint8_t kId3v2VersionByteMax = 0xFF;
constexpr uint8_t kId3v2SizeByteMax = 0x80;
constexpr uint8_t kSyncByteMask = 0xE0;
constexpr int kPaddingBitShift = 1;
constexpr int kLayerDescShift = 1;
constexpr int kSampleRateIndexShift = 2;
constexpr int kVersionIdShift = 3;
constexpr int kBitrateIndexShift = 4;
constexpr int kChannelIdxShift = 6;
constexpr uint8_t kPaddingBitMask = 0x01;
constexpr uint8_t kLayerDescMask = 0x03;
constexpr uint8_t kSampleRateIndexMask = 0x03;
constexpr uint8_t kVersionIdMask = 0x03;
constexpr uint8_t kBitrateIndexMask = 0x0F;
constexpr uint8_t kChannelIdxMask = 0x03;
constexpr int kMonoChannelIdx = 3;
bool findID3v1Tag(const uint8_t** currBuff, const uint8_t* endBuff, MpegFrame& frame) {
frame.bytesPending = 0;
if ((*currBuff) + kID3TagIdentifierSize > endBuff) {
frame.bytesPending = (*currBuff + kID3TagIdentifierSize) - endBuff;
return false;
}
// ID3v1 header must start with TAG
if ((*currBuff) + kID3TagIdentifierSize <= endBuff) {
if (std::memcmp(*currBuff, "TAG", kID3TagIdentifierSize) == 0) {
frame.isID3v1 = true;
if ((*currBuff) + kID3v1TagSize > endBuff) {
frame.bytesPending = (*currBuff + kID3v1TagSize) - endBuff;
}
*currBuff = std::min(*currBuff + kID3v1TagSize, endBuff);
return true;
}
}
return false;
}
bool findID3v2Tag(const uint8_t** currBuff, const uint8_t* endBuff, MpegFrame& frame) {
frame.bytesPending = 0;
if ((*currBuff) + kID3v2TagHeaderSize > endBuff) {
frame.bytesPending = (*currBuff + kID3v2TagHeaderSize) - endBuff;
return false;
}
// An ID3v2 tag can be detected with the following pattern:
// I D 3 yy yy xx zz zz zz zz
// Where yy is less than $FF and zz is less than $80.
if (std::memcmp(*currBuff, "ID3", kID3TagIdentifierSize) == 0 &&
(*currBuff)[3] < kId3v2VersionByteMax /*yy*/ &&
(*currBuff)[4] < kId3v2VersionByteMax /*yy*/ && (*currBuff)[6] < kId3v2SizeByteMax /*zz*/ &&
(*currBuff)[7] < kId3v2SizeByteMax /*zz*/ && (*currBuff)[8] < kId3v2SizeByteMax /*zz*/ &&
(*currBuff)[9] < kId3v2SizeByteMax) /*zz*/ {
frame.isID3v2 = true;
size_t dataSize = ((*currBuff)[6] << 21) | ((*currBuff)[7] << 14) | ((*currBuff)[8] << 7) |
(*currBuff)[9];
size_t totalSize = dataSize + kID3v2TagHeaderSize;
if (*currBuff + totalSize > endBuff) {
frame.bytesPending = (*currBuff + totalSize) - endBuff;
}
*currBuff = std::min(*currBuff + totalSize, endBuff);
return true;
}
return false;
}
std::optional<int> getBitrate(Version version, Layer layer, size_t bitrateIndex) {
if (bitrateIndex >= std::size(kBitrateTable)) {
LOG(ERROR) << "Invalid bitrate index: " << bitrateIndex;
return std::nullopt;
}
int bitrateColumn = 0;
if (version == Version::MPEG_1) {
if (layer == Layer::LAYER_1)
bitrateColumn = 0;
else if (layer == Layer::LAYER_2)
bitrateColumn = 1;
else
bitrateColumn = 2;
} else { // MPEG_2 or MPEG_2_5
if (layer == Layer::LAYER_1)
bitrateColumn = 3;
else
bitrateColumn = 4;
}
const int bitrateInKbps = kBitrateTable[bitrateIndex][bitrateColumn];
if (bitrateInKbps == 0) {
LOG(WARNING) << "Invalid frame: bitrate is 0";
return std::nullopt;
}
return bitrateInKbps;
}
std::optional<int> getSampleRate(Version version, size_t sampleRateIndex) {
if (sampleRateIndex >= std::size(kSampleRateTable)) {
LOG(ERROR) << "Invalid sample rate index: " << sampleRateIndex;
return std::nullopt;
}
int versionColumn = 0;
if (version == Version::MPEG_1)
versionColumn = 0;
else if (version == Version::MPEG_2)
versionColumn = 1;
else
versionColumn = 2; // MPEG_2_5
const int sampleRate = kSampleRateTable[sampleRateIndex][versionColumn];
if (sampleRate == 0) {
LOG(WARNING) << "Invalid frame: sampling frequency is 0";
return std::nullopt;
}
return sampleRate;
}
std::optional<MpegFrame> findMpegFrame(const uint8_t** currBuff, const uint8_t* endBuff) {
MpegFrame frame;
bool id3TagFound =
findID3v2Tag(currBuff, endBuff, frame) || findID3v1Tag(currBuff, endBuff, frame);
if (id3TagFound && *currBuff == endBuff) {
return frame;
}
while ((*currBuff) + kFrameHeaderSize <= endBuff) {
// Sync word of 11 set bits
if ((*currBuff)[0] != kSyncByte || ((*currBuff)[1] & kSyncByteMask) != kSyncByteMask) {
(*currBuff)++;
continue;
}
const Version version =
static_cast<Version>((((*currBuff)[1] >> kVersionIdShift) & kVersionIdMask));
const Layer layer =
static_cast<Layer>(((*currBuff)[1] >> kLayerDescShift) & kLayerDescMask);
const size_t bitrateIndex = ((*currBuff)[2] >> kBitrateIndexShift) & kBitrateIndexMask;
const size_t sampleRateIndex =
((*currBuff)[2] >> kSampleRateIndexShift) & kSampleRateIndexMask;
if (version != Version::RESERVED && layer != Layer::RESERVED) {
// Get the bitrate and sample rate
auto bitrateOpt = getBitrate(version, layer, bitrateIndex);
auto sampleRateOpt = getSampleRate(version, sampleRateIndex);
if (bitrateOpt && sampleRateOpt) {
const int bitrateInKbps = bitrateOpt.value();
const int sampleRate = sampleRateOpt.value();
const int paddingBit = ((*currBuff)[2] >> kPaddingBitShift) & kPaddingBitMask;
const int channelIdx = ((*currBuff)[3] >> kChannelIdxShift) & kChannelIdxMask;
frame.sampleRate = sampleRate;
frame.bitRate = bitrateInKbps * 1000;
[[maybe_unused]] int channelCount = (channelIdx == kMonoChannelIdx) ? 1 : 2;
// Compute frameSize and frameLength
if (layer == Layer::LAYER_1) {
frame.frameSize = kLayer1SamplesPerMpegFrame;
// For Layer 1, frame length is calculated based on 4-byte "slots"
frame.frameLengthBytes = ((kLayer1SamplesPerMpegFrame / sizeof(uint32_t)) *
(bitrateInKbps * 1000 /* convert to bps */) /
(sampleRate * CHAR_BIT) +
paddingBit) *
sizeof(uint32_t);
} else {
frame.frameSize = kLayer2SamplesPerMpegFrame;
// For Layer 2 & 3, frame length is calculated directly in bytes.
frame.frameLengthBytes = (kLayer2SamplesPerMpegFrame *
(bitrateInKbps * 1000 /* convert to bps */) /
(sampleRate * CHAR_BIT)) +
paddingBit;
}
if (*currBuff + frame.frameLengthBytes > endBuff) {
// This buffer only has partial frame data.
// The second part of this frame will be available in the next call. Consume
// all the bytes in this call. The findMpegFrame call for the next frame
// will consume the second half of the frame.
frame.bytesPending = (*currBuff + frame.frameLengthBytes) - endBuff;
}
*currBuff = std::min(*currBuff + frame.frameLengthBytes, endBuff);
return frame;
}
}
(*currBuff)++;
}
return std::nullopt;
}
} // namespace aidl::android::hardware::audio::core
+54
View File
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2025 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 <cstdint>
#include <optional>
#include <ostream>
namespace aidl::android::hardware::audio::core {
/*
* This parser captures these fields to determine properties such as
* the MPEG version, layer, bitrate, sample rate, frame length and frame size.
*
* Reference - https://www.datavoyage.com/mpgscript/mpeghdr.htm
* - https://mutagen-specs.readthedocs.io/en/latest/id3/id3v2.2.html
*/
struct MpegFrame {
// The total length of the MPEG audio frame in bytes, including the header
int frameLengthBytes = 0;
// The number of audio samples in the frame
int frameSize = 0;
bool isID3v2 = false;
bool isID3v1 = false;
// Last few bytes of an ID3 tag or frame that couldn't be processed in a single chunk
size_t bytesPending = 0;
int sampleRate = 0;
int bitRate = 0;
};
inline std::ostream& operator<<(std::ostream& os, const MpegFrame& frame) {
os << "MpegFrame{len=" << frame.frameLengthBytes << ", size=" << frame.frameSize
<< ", isID3v2=" << (frame.isID3v2 ? "true" : "false")
<< ", isID3v1=" << (frame.isID3v1 ? "true" : "false") << ", pending=" << frame.bytesPending
<< ", sampleRate=" << frame.sampleRate << "}";
return os;
}
std::optional<MpegFrame> findMpegFrame(const uint8_t** currBuff, const uint8_t* endBuff);
} // namespace aidl::android::hardware::audio::core
+39 -9
View File
@@ -18,7 +18,7 @@
#include <cstdlib>
#define LOG_TAG "AHAL_MmapStream"
#include <android-base/logging.h>
#include <Log.h>
#include <audio_utils/clock.h>
#include <error/Result.h>
#include <utils/SystemClock.h>
@@ -105,12 +105,28 @@ DriverMmapStubImpl::DriverMmapStubImpl(const StreamContext& context)
return ::android::OK;
}
::android::status_t DriverMmapStubImpl::flush() {
RETURN_STATUS_IF_ERROR(DriverStubImpl::flush());
mDspWorker.pause();
if (mIsInput) {
RETURN_STATUS_IF_ERROR(standby());
}
return ::android::OK;
}
::android::status_t DriverMmapStubImpl::pause() {
RETURN_STATUS_IF_ERROR(DriverStubImpl::pause());
mDspWorker.pause();
return ::android::OK;
}
::android::status_t DriverMmapStubImpl::standby() {
RETURN_STATUS_IF_ERROR(DriverStubImpl::standby());
std::lock_guard l(mState.lock);
RETURN_STATUS_IF_ERROR(releaseSharedMemory());
return ::android::OK;
}
::android::status_t DriverMmapStubImpl::start() {
RETURN_STATUS_IF_ERROR(DriverStubImpl::start());
RETURN_STATUS_IF_ERROR(startWorkerIfNeeded());
@@ -118,7 +134,7 @@ DriverMmapStubImpl::DriverMmapStubImpl(const StreamContext& context)
return ::android::OK;
}
::android::status_t DriverMmapStubImpl::transfer(void*, size_t, size_t*, int32_t*) {
::android::status_t DriverMmapStubImpl::transfer(void*, size_t frameCount, size_t*, int32_t*) {
// Do not call into DriverStubImpl::transfer
if (!mIsInitialized) {
LOG(FATAL) << __func__ << ": must not happen for an uninitialized driver";
@@ -126,6 +142,10 @@ DriverMmapStubImpl::DriverMmapStubImpl(const StreamContext& context)
if (mIsStandby) {
LOG(FATAL) << __func__ << ": must not happen while in standby";
}
if (frameCount != 0) {
LOG(ERROR) << __func__ << ": burst value size must be 0 for MMAP";
return ::android::BAD_VALUE;
}
RETURN_STATUS_IF_ERROR(startWorkerIfNeeded());
mDspWorker.resume();
return ::android::OK;
@@ -194,7 +214,7 @@ void DriverMmapStubImpl::shutdown() {
std::lock_guard l(mState.lock);
*position = mState.mmapPos;
}
const size_t latencyFrames = mBufferSizeFrames / 2;
const size_t latencyFrames = mBufferSizeFrames / 4;
if (position->frames != StreamDescriptor::Position::UNKNOWN) {
position->frames += latencyFrames;
}
@@ -251,7 +271,17 @@ ndk::ScopedAStatus StreamMmapStub::setVendorParameters(
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus StreamMmapStub::createMmapBuffer(MmapBufferDescriptor* desc) {
ndk::ScopedAStatus StreamMmapStub::createMmapBuffer(MmapBufferDescriptor* _aidl_return) {
LOG(DEBUG) << __func__;
if (isClosed()) {
LOG(ERROR) << __func__ << ": stream was closed";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
if (getConnectedDevices().empty()) {
LOG(ERROR) << __func__ << ": stream is not connected";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
const size_t bufferSizeFrames = mContext.getBufferSizeInFrames();
const size_t bufferSizeBytes = static_cast<size_t>(bufferSizeFrames) * mContext.getFrameSize();
const std::string regionName =
@@ -266,11 +296,11 @@ ndk::ScopedAStatus StreamMmapStub::createMmapBuffer(MmapBufferDescriptor* desc)
if (initSharedMemory(mSharedMemoryFd.get()) != ::android::OK) {
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
desc->sharedMemory.fd = mSharedMemoryFd.dup();
desc->sharedMemory.size = bufferSizeBytes;
desc->burstSizeFrames = bufferSizeFrames / 2;
desc->flags = 0;
LOG(DEBUG) << __func__ << ": " << desc->toString();
_aidl_return->sharedMemory.fd = mSharedMemoryFd.dup();
_aidl_return->sharedMemory.size = bufferSizeBytes;
_aidl_return->burstSizeFrames = bufferSizeFrames / 4;
_aidl_return->flags = 1 << MmapBufferDescriptor::FLAG_INDEX_APPLICATION_SHAREABLE;
LOG(DEBUG) << __func__ << ": " << _aidl_return->toString();
return ndk::ScopedAStatus::ok();
}
+332 -47
View File
@@ -15,61 +15,101 @@
*/
#define LOG_TAG "AHAL_OffloadStream"
#include <android-base/logging.h>
#include <Log.h>
#include <audio_utils/clock.h>
#include <error/Result.h>
#include <utils/SystemClock.h>
#include "ApeHeader.h"
#include "MpegAudioHeader.h"
#include "core-impl/StreamOffloadStub.h"
using aidl::android::hardware::audio::common::SourceMetadata;
using aidl::android::media::audio::common::AudioDevice;
using aidl::android::media::audio::common::AudioFormatType;
using aidl::android::media::audio::common::AudioOffloadInfo;
using aidl::android::media::audio::common::FlushFromFrameAccuracy;
using aidl::android::media::audio::common::MicrophoneInfo;
namespace aidl::android::hardware::audio::core {
namespace offload {
static constexpr int32_t SAFE_FLUSH_FROM_MARGIN_IN_MS = 100;
static constexpr int32_t SAFE_DRAIN_METADATA_MARGIN_IN_MS = 10;
std::string DspSimulatorLogic::init() {
return "";
}
DspSimulatorLogic::Status DspSimulatorLogic::cycle() {
using DrainMode = StreamDescriptor::DrainMode;
std::vector<std::pair<int64_t, bool>> clipNotifies;
DrainMode emptyDrainNotify = DrainMode::DRAIN_UNSPECIFIED;
// Simulate playback.
const int64_t timeBeginNs = ::android::uptimeNanos();
usleep(1000);
const int64_t clipFramesPlayed =
(::android::uptimeNanos() - timeBeginNs) * mSharedState.sampleRate / NANOS_PER_SECOND;
const int64_t bufferFramesConsumed = clipFramesPlayed / 2; // assume 1:2 compression ratio
int64_t bufferFramesLeft = 0, bufferNotifyFrames = DspSimulatorState::kSkipBufferNotifyFrames;
int64_t bufferFramesConsumed = 0, bufferFramesLeft = 0,
bufferNotifyFrames = DspSimulatorState::kSkipBufferNotifyFrames;
bool isEmpty = false;
{
std::lock_guard l(mSharedState.lock);
int64_t bytesPerSecond = 0;
if (mSharedState.format.type == AudioFormatType::PCM) {
bytesPerSecond = mSharedState.sampleRate;
} else {
bytesPerSecond = mSharedState.bitRatePerSecond / 8;
if (bytesPerSecond <= 0) {
bytesPerSecond = mSharedState.fallbackBitRatePerSecond / 8;
}
if (bytesPerSecond <= 0) {
// Fallback to a 1:2 compression ratio
bytesPerSecond = mSharedState.sampleRate / 2LL;
}
}
bufferFramesConsumed = clipFramesPlayed * bytesPerSecond / mSharedState.sampleRate;
if (mSharedState.clips.empty()) {
isEmpty = true;
emptyDrainNotify = mSharedState.draining;
if (mSharedState.draining != DrainMode::DRAIN_EARLY_NOTIFY) {
mSharedState.draining = DrainMode::DRAIN_UNSPECIFIED;
}
}
mSharedState.bufferFramesLeft =
mSharedState.bufferFramesLeft > bufferFramesConsumed
? mSharedState.bufferFramesLeft - bufferFramesConsumed
: 0;
int64_t framesPlayed = clipFramesPlayed;
while (framesPlayed > 0 && !mSharedState.clipFramesLeft.empty()) {
LOG(VERBOSE) << __func__ << ": clips: "
<< ::android::internal::ToString(mSharedState.clipFramesLeft);
const bool hasNextClip = mSharedState.clipFramesLeft.size() > 1;
if (mSharedState.clipFramesLeft[0] > framesPlayed) {
mSharedState.clipFramesLeft[0] -= framesPlayed;
while (framesPlayed > 0 && !mSharedState.clips.empty()) {
LOG(VERBOSE) << __func__ << ": " << mSharedState.clips.log();
const bool hasNextClip = mSharedState.clips.hasNext();
if (mSharedState.clips.currentFrames() > framesPlayed) {
mSharedState.clips.updateCurrentFrames(-framesPlayed);
mSharedState.mTotalFramesPlayed += framesPlayed;
framesPlayed = 0;
if (mSharedState.clipFramesLeft[0] <= mSharedState.earlyNotifyFrames) {
clipNotifies.emplace_back(mSharedState.clipFramesLeft[0], hasNextClip);
if (auto clipFramesLeft = mSharedState.clips.currentFrames();
mSharedState.draining == DrainMode::DRAIN_EARLY_NOTIFY &&
clipFramesLeft <= mSharedState.earlyNotifyFrames) {
clipNotifies.emplace_back(clipFramesLeft, hasNextClip);
}
} else {
clipNotifies.emplace_back(0 /*clipFramesLeft*/, hasNextClip);
framesPlayed -= mSharedState.clipFramesLeft[0];
mSharedState.clipFramesLeft.erase(mSharedState.clipFramesLeft.begin());
if (!hasNextClip) {
// Since it's a simulation, the buffer consumption rate it not real,
// thus 'bufferFramesLeft' might still have something, need to erase it.
mSharedState.bufferFramesLeft = 0;
mSharedState.mTotalFramesPlayed += mSharedState.clips.currentFrames();
if (mSharedState.format.type == AudioFormatType::PCM) {
// There is not enough data to be full played, set `framesPlayed` to 0 so that
// it can exit the while loop.
framesPlayed = 0;
mSharedState.clips.trimCurrentFrames(0);
clipNotifies.emplace_back(0 /*clipFramesLeft*/, false /*hasNextClip*/);
} else {
clipNotifies.emplace_back(0 /*clipFramesLeft*/, hasNextClip);
framesPlayed -= mSharedState.clips.removeCurrent();
}
}
}
@@ -84,10 +124,31 @@ DspSimulatorLogic::Status DspSimulatorLogic::cycle() {
LOG(DEBUG) << __func__ << ": sending onBufferStateChange: " << bufferFramesLeft;
mSharedState.callback->onBufferStateChange(bufferFramesLeft);
}
for (const auto& notify : clipNotifies) {
LOG(DEBUG) << __func__ << ": sending onClipStateChange: " << notify.first << ", "
<< notify.second;
mSharedState.callback->onClipStateChange(notify.first, notify.second);
if (!clipNotifies.empty()) {
for (const auto& notify : clipNotifies) {
LOG(DEBUG) << __func__ << ": sending onClipStateChange: " << notify.first << ", "
<< notify.second;
mSharedState.callback->onClipStateChange(notify.first, notify.second);
}
} else if (emptyDrainNotify != DrainMode::DRAIN_UNSPECIFIED) {
bool alreadySent;
{
std::lock_guard l(mSharedState.lock);
alreadySent = mSharedState.isDrainCompleteClipStateChangeSent;
if (!alreadySent) mSharedState.isDrainCompleteClipStateChangeSent = true;
}
if (!alreadySent) {
LOG(DEBUG) << __func__ << ": sending onClipStateChange with no clips for "
<< toString(emptyDrainNotify);
// Regardless of the drain mode, the buffer is now empty, so we signal completion (0
// frames).
mSharedState.callback->onClipStateChange(0 /*clipFramesLeft*/, false /*hasNextClip*/);
std::lock_guard l(mSharedState.lock);
mSharedState.draining = DrainMode::DRAIN_UNSPECIFIED;
}
} else if (isEmpty) {
// Sleep when idle to avoid busy-waiting and reduce CPU usage.
usleep(10000);
}
return Status::CONTINUE;
}
@@ -96,19 +157,37 @@ DspSimulatorLogic::Status DspSimulatorLogic::cycle() {
using offload::DspSimulatorState;
DriverOffloadStubImpl::DriverOffloadStubImpl(const StreamContext& context)
DriverOffloadStubImpl::DriverOffloadStubImpl(const StreamContext& context,
const std::optional<AudioOffloadInfo>& offloadInfo)
: DriverStubImpl(context, 0 /*asyncSleepTimeUs*/),
mBufferNotifyFrames(static_cast<int64_t>(context.getBufferSizeInFrames()) / 2),
mState{context.getFormat().encoding, context.getSampleRate(),
250 /*earlyNotifyMs*/ * context.getSampleRate() / MILLIS_PER_SECOND},
mSafeMarginForFlushFromFrames(offload::SAFE_FLUSH_FROM_MARGIN_IN_MS *
context.getSampleRate() / MILLIS_PER_SECOND),
mSafeMarginForDrainMetadataFrames(offload::SAFE_DRAIN_METADATA_MARGIN_IN_MS *
context.getSampleRate() / MILLIS_PER_SECOND),
mState{context.getFormat(), context.getSampleRate(),
250 /*earlyNotifyMs*/ * context.getSampleRate() / MILLIS_PER_SECOND,
offloadInfo.has_value() ? offloadInfo->bitRatePerSecond : 0},
mDspWorker(mState) {
LOG_IF(FATAL, !mIsAsynchronous) << "The steam must be used in asynchronous mode";
if (mState.format.encoding == "audio/x-ape") {
mTransferHandler = &DriverOffloadStubImpl::handleApeTransfer;
} else if (mState.format.encoding == "audio/mpeg") {
mTransferHandler = &DriverOffloadStubImpl::handleMpegTransfer;
} else if (mState.format.type == AudioFormatType::PCM) {
mTransferHandler = &DriverOffloadStubImpl::handlePcmTransfer;
// For PCM offload, there is no way for the HAL to know where is the end of a clip.
// In that case, it assumes there is only one clip.
LOG_IF(FATAL, !mState.clips.add(0)) << "Cannot initialize for PCM offload";
}
}
::android::status_t DriverOffloadStubImpl::init(DriverCallbackInterface* callback) {
RETURN_STATUS_IF_ERROR(DriverStubImpl::init(callback));
if (!StreamOffloadStub::getSupportedEncodings().count(mState.formatEncoding)) {
LOG(ERROR) << __func__ << ": encoded format \"" << mState.formatEncoding
if (!StreamOffloadStub::getSupportedEncodings().count(mState.format.encoding) &&
mState.format.type != AudioFormatType::PCM) {
LOG(ERROR) << __func__ << ": encoded format \"" << mState.format.toString()
<< "\" is not supported";
return ::android::NO_INIT;
}
@@ -119,15 +198,15 @@ DriverOffloadStubImpl::DriverOffloadStubImpl(const StreamContext& context)
::android::status_t DriverOffloadStubImpl::drain(StreamDescriptor::DrainMode drainMode) {
RETURN_STATUS_IF_ERROR(DriverStubImpl::drain(drainMode));
std::lock_guard l(mState.lock);
if (!mState.clipFramesLeft.empty()) {
// Cut playback of the current clip.
mState.clipFramesLeft[0] = std::min(mState.earlyNotifyFrames * 2, mState.clipFramesLeft[0]);
if (!mState.clips.empty()) {
mState.clips.trimCurrentFrames(mState.earlyNotifyFrames * 2);
if (drainMode == StreamDescriptor::DrainMode::DRAIN_ALL) {
// Make sure there are no clips after the current one.
mState.clipFramesLeft.resize(1);
mState.clips.eraseAllNext();
}
}
mState.bufferNotifyFrames = DspSimulatorState::kSkipBufferNotifyFrames;
mState.draining = drainMode;
mState.isDrainCompleteClipStateChangeSent = false;
return ::android::OK;
}
@@ -136,9 +215,11 @@ DriverOffloadStubImpl::DriverOffloadStubImpl(const StreamContext& context)
mDspWorker.pause();
{
std::lock_guard l(mState.lock);
mState.clipFramesLeft.clear();
mState.clips.erase();
mState.bufferFramesLeft = 0;
mState.bufferNotifyFrames = DspSimulatorState::kSkipBufferNotifyFrames;
mState.draining = StreamDescriptor::DrainMode::DRAIN_UNSPECIFIED;
mState.isDrainCompleteClipStateChangeSent = false;
}
return ::android::OK;
}
@@ -156,15 +237,20 @@ DriverOffloadStubImpl::DriverOffloadStubImpl(const StreamContext& context)
::android::status_t DriverOffloadStubImpl::start() {
RETURN_STATUS_IF_ERROR(DriverStubImpl::start());
RETURN_STATUS_IF_ERROR(startWorkerIfNeeded());
bool hasClips; // Can be start after paused draining.
bool shouldResume;
{
std::lock_guard l(mState.lock);
hasClips = !mState.clipFramesLeft.empty();
LOG(DEBUG) << __func__
<< ": clipFramesLeft: " << ::android::internal::ToString(mState.clipFramesLeft);
bool hasClips = !mState.clips.empty();
bool isDraining = mState.draining != StreamDescriptor::DrainMode::DRAIN_UNSPECIFIED;
// Allow notifications if resuming into a drain with an empty buffer.
if (isDraining && !hasClips) {
mState.isDrainCompleteClipStateChangeSent = false;
}
LOG(DEBUG) << __func__ << ": " << mState.clips.log();
mState.bufferNotifyFrames = DspSimulatorState::kSkipBufferNotifyFrames;
shouldResume = hasClips || isDraining;
}
if (hasClips) {
if (shouldResume) {
mDspWorker.resume();
}
return ::android::OK;
@@ -175,6 +261,77 @@ DriverOffloadStubImpl::DriverOffloadStubImpl(const StreamContext& context)
RETURN_STATUS_IF_ERROR(
DriverStubImpl::transfer(buffer, frameCount, actualFrameCount, latencyMs));
RETURN_STATUS_IF_ERROR(startWorkerIfNeeded());
if (mTransferHandler != nullptr) {
const ::android::status_t status =
(this->*mTransferHandler)(buffer, frameCount, actualFrameCount);
if (status != ::android::OK) {
return status;
}
} else {
LOG(FATAL) << __func__ << ": unsupported format for offload: " << mState.format.toString();
*actualFrameCount = 0;
}
{
std::lock_guard l(mState.lock);
mState.bufferFramesLeft += *actualFrameCount;
mState.bufferNotifyFrames = mBufferNotifyFrames;
}
mDspWorker.resume();
return ::android::OK;
}
::android::status_t DriverOffloadStubImpl::flushFromFrame(FlushFromFrameAccuracy accuracy,
int32_t position,
int32_t* flushFromPosition) {
LOG(DEBUG) << __func__ << ": accuracy=" << toString(accuracy) << ", position=" << position;
if ((accuracy != FlushFromFrameAccuracy::BEST_EFFORT &&
accuracy != FlushFromFrameAccuracy::EXACT) ||
position < 0 || flushFromPosition == nullptr) {
*flushFromPosition = position;
LOG(ERROR) << __func__ << ": invalid parameters, accuracy= " << toString(accuracy)
<< ", position=" << position
<< ", suggestedPosition is null:" << (flushFromPosition == nullptr);
return ::android::BAD_VALUE;
}
if (mState.format.type != AudioFormatType::PCM) {
// Currently only support flushFromFrame for PCM offload.
LOG(ERROR) << __func__ << ": invalid as format is " << mState.format.toString();
return ::android::INVALID_OPERATION;
}
{
std::lock_guard l(mState.lock);
const int64_t requestedPosition = position + mState.mLastReportedFrames;
const int64_t safeFlushedPosition =
mState.mTotalFramesPlayed +
std::min((int64_t)mSafeMarginForFlushFromFrames, mState.clips.currentFrames());
const int64_t totalWrittenFrames = mState.mTotalFramesPlayed + mState.clips.currentFrames();
if (accuracy == FlushFromFrameAccuracy::BEST_EFFORT) {
if (requestedPosition < safeFlushedPosition) {
*flushFromPosition = safeFlushedPosition - mState.mLastReportedFrames;
mState.clips.trimCurrentFrames(safeFlushedPosition - mState.mTotalFramesPlayed);
} else if (requestedPosition > totalWrittenFrames) {
*flushFromPosition = totalWrittenFrames - mState.mLastReportedFrames;
} else {
*flushFromPosition = requestedPosition - mState.mLastReportedFrames;
mState.clips.trimCurrentFrames(requestedPosition - mState.mTotalFramesPlayed);
}
} else { // accuracy == FlushFromFrameAccuracy::EXACT
if (requestedPosition < safeFlushedPosition || requestedPosition > totalWrittenFrames) {
*flushFromPosition = safeFlushedPosition - mState.mLastReportedFrames;
return ::android::BAD_VALUE;
} else {
*flushFromPosition = requestedPosition - mState.mLastReportedFrames;
mState.clips.trimCurrentFrames(requestedPosition - mState.mTotalFramesPlayed);
}
}
}
return ::android::OK;
}
::android::status_t DriverOffloadStubImpl::handleApeTransfer(void* buffer, size_t frameCount,
size_t* actualFrameCount) {
// Scan the buffer for clip headers.
*actualFrameCount = frameCount;
while (buffer != nullptr && frameCount > 0) {
@@ -195,21 +352,141 @@ DriverOffloadStubImpl::DriverOffloadStubImpl(const StreamContext& context)
<< "sample rate: " << clipSampleRate;
if (clipSampleRate == mState.sampleRate) {
std::lock_guard l(mState.lock);
mState.clipFramesLeft.push_back(clipDurationFrames);
if (!mState.clips.add(clipDurationFrames)) {
LOG(ERROR) << __func__
<< ": not ready for the next clip, clips: " << mState.clips.log();
return ::android::INVALID_OPERATION;
}
} else {
LOG(ERROR) << __func__ << ": clip sample rate " << clipSampleRate
<< " does not match stream sample rate " << mState.sampleRate;
return ::android::BAD_VALUE;
}
} else {
frameCount = 0;
}
}
{
std::lock_guard l(mState.lock);
mState.bufferFramesLeft = *actualFrameCount;
mState.bufferNotifyFrames = mBufferNotifyFrames;
return ::android::OK;
}
::android::status_t DriverOffloadStubImpl::handleMpegTransfer(void* buffer, size_t frameCount,
size_t* actualFrameCount) {
const size_t bufferSizeBytes = frameCount * mFrameSizeBytes;
const uint8_t* currentPtr = static_cast<const uint8_t*>(buffer);
const uint8_t* const endPtr = currentPtr + bufferSizeBytes;
const uint8_t* const beginPtr = currentPtr;
// Track total audio payload bytes to distinguish from metadata.
size_t totalAudioBytes = 0;
if (mMpegFrameState.bytesPending > 0) {
size_t processBytes =
std::min(mMpegFrameState.bytesPending, static_cast<size_t>(endPtr - currentPtr));
currentPtr += processBytes;
mMpegFrameState.bytesPending -= processBytes;
if (mMpegFrameState.isAudioFrame) {
totalAudioBytes += processBytes;
// Incremental Timing: Update duration based on ratio of bytes processed to fix time
// jump.
if (mMpegFrameState.totalFrameLengthBytes > 0) {
std::lock_guard l(mState.lock);
const int64_t processedFrames = mMpegFrameState.frameSize * (int64_t)processBytes /
mMpegFrameState.totalFrameLengthBytes;
mState.clips.updateLastFrames(processedFrames);
LOG(DEBUG) << __func__ << ": added samples=" << processedFrames;
}
}
}
mDspWorker.resume();
while (currentPtr < endPtr) {
const uint8_t* const frameBeginning = currentPtr;
std::optional<MpegFrame> frameOpt = findMpegFrame(&currentPtr, endPtr);
if (!frameOpt.has_value()) {
// Could not find a header in the input buffer.
mMpegFrameState.bytesPending = 0;
break;
}
const MpegFrame& frame = frameOpt.value();
LOG(DEBUG) << __func__ << ": Found at offset " << frameBeginning - beginPtr << " " << frame;
mMpegFrameState.isAudioFrame = !(frame.isID3v2 || frame.isID3v1);
if (frame.isID3v1) {
mMpegFrameState.clipEnded = true;
} else {
if (frame.sampleRate != mState.sampleRate) {
LOG(ERROR) << __func__ << ": clip sample rate " << frame.sampleRate
<< " does not match stream sample rate " << mState.sampleRate;
return ::android::BAD_VALUE;
}
// Calculate physical bytes consumed in this iteration
size_t bytesInThisBuffer = currentPtr - frameBeginning;
if (mMpegFrameState.isAudioFrame) {
totalAudioBytes += bytesInThisBuffer;
}
std::lock_guard l(mState.lock);
if (frame.bitRate > 0) mState.bitRatePerSecond = frame.bitRate;
if (frame.isID3v2 || mState.clips.empty() || mMpegFrameState.clipEnded) {
if (!mState.clips.add(0)) {
LOG(ERROR) << __func__
<< ": not ready for the next clip, clips: " << mState.clips.log();
return ::android::INVALID_OPERATION;
}
mMpegFrameState.clipEnded = false;
}
// Proportional timing fix: satisfy PauseAsync by adding time for bytes delivered only.
if (mMpegFrameState.isAudioFrame && frame.frameLengthBytes > 0) {
const int64_t processedFrames =
frame.frameSize * (int64_t)bytesInThisBuffer / frame.frameLengthBytes;
mState.clips.updateLastFrames(processedFrames);
LOG(DEBUG) << __func__ << ": added samples=" << processedFrames;
}
}
if (currentPtr == endPtr) {
mMpegFrameState.bytesPending = frame.bytesPending;
mMpegFrameState.frameSize = frame.frameSize;
mMpegFrameState.totalFrameLengthBytes = frame.frameLengthBytes;
break;
}
}
*actualFrameCount = static_cast<size_t>(currentPtr - beginPtr) / mFrameSizeBytes;
// If totalAudioBytes > 0, we have mixed content, so we subtract metadata bytes.
// If totalAudioBytes == 0 (Header Only), we SKIP subtraction to keep buffer non-empty.
// The fallback bitrate in cycle() ensures it eventually drains.
if (*actualFrameCount > totalAudioBytes) {
size_t nonAudioBytes = *actualFrameCount - totalAudioBytes;
if (totalAudioBytes > 0) {
std::lock_guard l(mState.lock);
// Calculate the anticipated buffer size once the caller adds the frames.
int64_t projectedBufferLevel = mState.bufferFramesLeft + totalAudioBytes;
// Ensure the buffer level does not dip below mBufferNotifyFrames after subtraction.
// An instant drop triggers the worker thread to fire onTransferReady prematurely...
const int64_t safeThreshold = mBufferNotifyFrames + mSafeMarginForDrainMetadataFrames;
if (projectedBufferLevel < safeThreshold) {
// Subtract only enough to perfectly land on the safeThreshold
int64_t allowedSubtract =
(mState.bufferFramesLeft + *actualFrameCount) - safeThreshold;
if (allowedSubtract > 0) {
mState.bufferFramesLeft -= allowedSubtract;
}
} else {
// Safe to subtract all nonAudioBytes without triggering the race condition
mState.bufferFramesLeft -= nonAudioBytes;
}
}
}
return ::android::OK;
}
::android::status_t DriverOffloadStubImpl::handlePcmTransfer(void* /*buffer*/, size_t frameCount,
size_t* actualFrameCount) {
*actualFrameCount = frameCount;
std::lock_guard l(mState.lock);
mState.clips.updateLastFrames(frameCount);
return ::android::OK;
}
@@ -230,16 +507,24 @@ void DriverOffloadStubImpl::shutdown() {
return ::android::OK;
}
::android::status_t DriverOffloadStubImpl::refinePosition(StreamDescriptor::Position* position) {
std::lock_guard l(mState.lock);
position->frames = mState.mTotalFramesPlayed;
return ::android::OK;
}
// static
const std::set<std::string>& StreamOffloadStub::getSupportedEncodings() {
static const std::set<std::string> kSupportedEncodings = {
"audio/x-ape",
"audio/mpeg",
};
return kSupportedEncodings;
}
StreamOffloadStub::StreamOffloadStub(StreamContext* context, const Metadata& metadata)
: StreamCommonImpl(context, metadata), DriverOffloadStubImpl(getContext()) {}
StreamOffloadStub::StreamOffloadStub(StreamContext* context, const Metadata& metadata,
const AudioOffloadInfo& offloadInfo)
: StreamCommonImpl(context, metadata), DriverOffloadStubImpl(getContext(), offloadInfo) {}
StreamOffloadStub::~StreamOffloadStub() {
cleanupWorker();
@@ -249,6 +534,6 @@ StreamOutOffloadStub::StreamOutOffloadStub(StreamContext&& context,
const SourceMetadata& sourceMetadata,
const std::optional<AudioOffloadInfo>& offloadInfo)
: StreamOut(std::move(context), offloadInfo),
StreamOffloadStub(&mContextInstance, sourceMetadata) {}
StreamOffloadStub(&mContextInstance, sourceMetadata, offloadInfo.value()) {}
} // namespace aidl::android::hardware::audio::core
+16 -1
View File
@@ -15,7 +15,7 @@
*/
#define LOG_TAG "AHAL_Stream"
#include <android-base/logging.h>
#include <Log.h>
#include "core-impl/Module.h"
#include "core-impl/StreamStub.h"
@@ -43,4 +43,19 @@ StreamOutStub::StreamOutStub(StreamContext&& context, const SourceMetadata& sour
const std::optional<AudioOffloadInfo>& offloadInfo)
: StreamOut(std::move(context), offloadInfo), StreamStub(&mContextInstance, sourceMetadata) {}
StreamOutTelephonyStub::StreamOutTelephonyStub(StreamContext&& context,
const SourceMetadata& sourceMetadata,
const std::optional<AudioOffloadInfo>& offloadInfo)
: StreamOutStub(std::move(context), sourceMetadata, offloadInfo),
StreamOutHwVolumeHelper(&mContext) {}
ndk::ScopedAStatus StreamOutTelephonyStub::getHwVolume(std::vector<float>* _aidl_return) {
return getHwVolumeImpl(_aidl_return);
}
ndk::ScopedAStatus StreamOutTelephonyStub::setHwVolume(
const std::vector<float>& in_channelVolumes) {
return setHwVolumeImpl(in_channelVolumes);
}
} // namespace aidl::android::hardware::audio::core
+2 -2
View File
@@ -17,11 +17,11 @@
#include <vector>
#define LOG_TAG "AHAL_ModuleUsb"
#include <Log.h>
#include <Utils.h>
#include <android-base/logging.h>
#include "UsbAlsaMixerControl.h"
#include "alsa/Utils.h"
#include "alsa/UtilsAlsa.h"
#include "core-impl/ModuleUsb.h"
#include "core-impl/StreamUsb.h"
+1 -1
View File
@@ -17,7 +17,7 @@
#include <limits>
#define LOG_TAG "AHAL_StreamUsb"
#include <android-base/logging.h>
#include <Log.h>
#include <error/expected_utils.h>
#include "UsbAlsaMixerControl.h"
+1 -1
View File
@@ -15,7 +15,7 @@
*/
#define LOG_TAG "AHAL_UsbAlsaMixerControl"
#include <android-base/logging.h>
#include <Log.h>
#include <android/binder_status.h>
+1 -1
View File
@@ -26,7 +26,7 @@ $(call inherit-product, $(SRC_TARGET_DIR)/product/updatable_apex.mk)
OVERRIDE_PRODUCT_COMPRESSED_APEX := false
# API level
PRODUCT_SHIPPING_API_LEVEL := 36
PRODUCT_SHIPPING_API_LEVEL := 37
# Audio
PRODUCT_PACKAGES += \
+1 -1
View File
@@ -1,4 +1,4 @@
<compatibility-matrix version="1.0" type="framework" level="202504">
<compatibility-matrix version="1.0" type="framework" level="202604">
<hal format="aidl">
<name>android.hardware.media.c2</name>
<version>1</version>
+1 -1
View File
@@ -17,7 +17,7 @@ cc_binary {
"libhealthloop",
],
shared_libs: [
"android.hardware.health-V4-ndk",
"android.hardware.health-V5-ndk",
"libbase",
"libbinder_ndk",
"libcutils",
+3
View File
@@ -76,6 +76,9 @@ ndk::ScopedAStatus HealthImpl::getBatteryHealthData(BatteryHealthData* out) {
out->batterySerialNumber =
"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f";
out->batteryPartStatus = BatteryPartStatus::ORIGINAL;
out->batteryManufacturer = "Raspberry Pi";
out->batteryModelName = "Raspberry Pi fake battery";
out->batteryVoltageMinDesignUv = 5000000;
return ndk::ScopedAStatus::ok();
}
@@ -1,7 +1,7 @@
<manifest version="1.0" type="device">
<hal format="aidl">
<name>android.hardware.health</name>
<version>4</version>
<version>5</version>
<fqname>IHealth/default</fqname>
</hal>
</manifest>
+68 -26
View File
@@ -139,7 +139,7 @@ key 116 POWER
key 117 NUMPAD_EQUALS
# key 118 "KEY_KPPLUSMINUS"
key 119 BREAK
# key 120 (undefined)
key 120 RECENT_APPS
key 121 NUMPAD_COMMA
key 122 KANA
key 123 EISU
@@ -171,7 +171,7 @@ key 143 WAKEUP
# key 149 "KEY_PROG2"
key 150 EXPLORER
# key 151 "KEY_MSDOS"
key 152 POWER
key 152 LOCK
# key 153 "KEY_DIRECTION"
# key 154 "KEY_CYCLEWINDOWS"
key 155 ENVELOPE
@@ -200,20 +200,20 @@ key 177 PAGE_UP
key 178 PAGE_DOWN
key 179 NUMPAD_LEFT_PAREN
key 180 NUMPAD_RIGHT_PAREN
# key 181 "KEY_NEW"
key 181 NEW
# key 182 "KEY_REDO"
# key 183 F13
# key 184 F14
# key 185 F15
# key 186 F16
# key 187 F17
# key 188 F18
# key 189 F19
# key 190 F20
# key 191 F21
# key 192 F22
# key 193 F23
# key 194 F24
key 183 F13
key 184 F14
key 185 F15
key 186 F16
key 187 F17
key 188 F18
key 189 F19
key 190 F20
key 191 F21
key 192 F22
key 193 F23
key 194 F24
# key 195 (undefined)
# key 196 (undefined)
# key 197 (undefined)
@@ -223,13 +223,13 @@ key 200 MEDIA_PLAY
key 201 MEDIA_PAUSE
# key 202 "KEY_PROG3"
# key 203 "KEY_PROG4"
key 204 NOTIFICATION
key 204 NOTIFICATION
# key 205 "KEY_SUSPEND"
# key 206 "KEY_CLOSE"
key 206 CLOSE
key 207 MEDIA_PLAY
key 208 MEDIA_FAST_FORWARD
# key 209 "KEY_BASSBOOST"
# key 210 "KEY_PRINT"
key 210 PRINT
# key 211 "KEY_HP"
key 212 CAMERA
key 213 MUSIC
@@ -246,6 +246,10 @@ key 217 ASSIST
key 224 BRIGHTNESS_DOWN
key 225 BRIGHTNESS_UP
key 226 HEADSETHOOK
key 228 KEYBOARD_BACKLIGHT_TOGGLE
key 229 KEYBOARD_BACKLIGHT_DOWN
key 230 KEYBOARD_BACKLIGHT_UP
key 248 MUTE
key 256 BUTTON_1
key 257 BUTTON_2
@@ -299,6 +303,11 @@ key 317 BUTTON_THUMBL
key 318 BUTTON_THUMBR
key 329 STYLUS_BUTTON_TERTIARY
key 331 STYLUS_BUTTON_PRIMARY
key 332 STYLUS_BUTTON_SECONDARY
# key 352 "KEY_OK"
key 353 DPAD_CENTER
# key 354 "KEY_GOTO"
@@ -315,11 +324,11 @@ key 362 GUIDE
# key 365 "KEY_EPG"
key 366 DVR
# key 367 "KEY_MHP"
# key 368 "KEY_LANGUAGE"
key 368 LANGUAGE_SWITCH
# key 369 "KEY_TITLE"
key 370 CAPTIONS
# key 371 "KEY_ANGLE"
# key 372 "KEY_ZOOM"
key 372 FULLSCREEN
# key 373 "KEY_MODE"
# key 374 "KEY_KEYBOARD"
# key 375 "KEY_SCREEN"
@@ -363,6 +372,11 @@ key 405 LAST_CHANNEL
# key 413 "KEY_DIGITS"
# key 414 "KEY_TEEN"
# key 415 "KEY_TWEN"
# key 418 "KEY_ZOOM_IN"
key 418 ZOOM_IN
# key 419 "KEY_ZOOM_OUT"
key 419 ZOOM_OUT
key 528 FOCUS
key 429 CONTACTS
@@ -408,6 +422,15 @@ key 522 STAR
key 523 POUND
key 580 APP_SWITCH
key 582 VOICE_ASSIST
# Linux KEY_ASSISTANT
key 583 ASSIST
key 585 EMOJI_PICKER
key 586 DICTATE
key 591 DO_NOT_DISTURB
key 656 MACRO_1
key 657 MACRO_2
key 658 MACRO_3
key 659 MACRO_4
# CEC
key 352 ENTER
@@ -416,10 +439,29 @@ key 141 POWER
key 174 BACK
# Keys defined by HID usages
key usage 0x0c0067 WINDOW
key usage 0x0c006F BRIGHTNESS_UP
key usage 0x0c0070 BRIGHTNESS_DOWN
key usage 0x0c0173 MEDIA_AUDIO_TRACK
key usage 0x0c0065 SCREENSHOT FALLBACK_USAGE_MAPPING
key usage 0x0c0067 WINDOW FALLBACK_USAGE_MAPPING
key usage 0x0c006F BRIGHTNESS_UP FALLBACK_USAGE_MAPPING
key usage 0x0c0070 BRIGHTNESS_DOWN FALLBACK_USAGE_MAPPING
key usage 0x0c0079 KEYBOARD_BACKLIGHT_UP FALLBACK_USAGE_MAPPING
key usage 0x0c007A KEYBOARD_BACKLIGHT_DOWN FALLBACK_USAGE_MAPPING
key usage 0x0c007C KEYBOARD_BACKLIGHT_TOGGLE FALLBACK_USAGE_MAPPING
key usage 0x0c00D8 DICTATE FALLBACK_USAGE_MAPPING
key usage 0x0c00D9 EMOJI_PICKER FALLBACK_USAGE_MAPPING
key usage 0x0c0173 MEDIA_AUDIO_TRACK FALLBACK_USAGE_MAPPING
key usage 0x0c019C PROFILE_SWITCH FALLBACK_USAGE_MAPPING
key usage 0x0c019F SETTINGS FALLBACK_USAGE_MAPPING
key usage 0x0c01A2 ALL_APPS FALLBACK_USAGE_MAPPING
key usage 0x0c0201 NEW FALLBACK_USAGE_MAPPING
key usage 0x0c0203 CLOSE FALLBACK_USAGE_MAPPING
key usage 0x0c0208 PRINT FALLBACK_USAGE_MAPPING
key usage 0x0c0227 REFRESH FALLBACK_USAGE_MAPPING
key usage 0x0c0232 FULLSCREEN FALLBACK_USAGE_MAPPING
key usage 0x0c029D LANGUAGE_SWITCH FALLBACK_USAGE_MAPPING
key usage 0x0c029F RECENT_APPS FALLBACK_USAGE_MAPPING
key usage 0x0c02A2 ALL_APPS FALLBACK_USAGE_MAPPING
key usage 0x0d0044 STYLUS_BUTTON_PRIMARY FALLBACK_USAGE_MAPPING
key usage 0x0d005a STYLUS_BUTTON_SECONDARY FALLBACK_USAGE_MAPPING
# Joystick and game controller axes.
# Axes that are not mapped will be assigned generic axis numbers by the input subsystem.
@@ -432,8 +474,8 @@ axis 0x05 RZ
axis 0x06 THROTTLE
axis 0x07 RUDDER
axis 0x08 WHEEL
axis 0x09 GAS
axis 0x0a BRAKE
axis 0x09 RTRIGGER
axis 0x0a LTRIGGER
axis 0x10 HAT_X
axis 0x11 HAT_Y
+3
View File
@@ -426,6 +426,7 @@ key 582 VOICE_ASSIST
key 583 ASSIST
key 585 EMOJI_PICKER
key 586 DICTATE
key 590 ACCESSIBILITY
key 591 DO_NOT_DISTURB
key 656 MACRO_1
key 657 MACRO_2
@@ -439,6 +440,7 @@ key 141 POWER
key 174 BACK
# Keys defined by HID usages
key usage 0x0100AA ACCESSIBILITY FALLBACK_USAGE_MAPPING
key usage 0x0c0065 SCREENSHOT FALLBACK_USAGE_MAPPING
key usage 0x0c0067 WINDOW FALLBACK_USAGE_MAPPING
key usage 0x0c006F BRIGHTNESS_UP FALLBACK_USAGE_MAPPING
@@ -452,6 +454,7 @@ key usage 0x0c0173 MEDIA_AUDIO_TRACK FALLBACK_USAGE_MAPPING
key usage 0x0c019C PROFILE_SWITCH FALLBACK_USAGE_MAPPING
key usage 0x0c019F SETTINGS FALLBACK_USAGE_MAPPING
key usage 0x0c01A2 ALL_APPS FALLBACK_USAGE_MAPPING
key usage 0x0c01CD CONTEXTUAL_INSERT FALLBACK_USAGE_MAPPING
key usage 0x0c0201 NEW FALLBACK_USAGE_MAPPING
key usage 0x0c0203 CLOSE FALLBACK_USAGE_MAPPING
key usage 0x0c0208 PRINT FALLBACK_USAGE_MAPPING
+1 -1
View File
@@ -1 +1 @@
<manifest version="1.0" type="device" target-level="202504" />
<manifest version="1.0" type="device" target-level="202604" />
+1 -1
View File
@@ -25,7 +25,7 @@ for PARTITION in "boot" "system" "system_ext" "vendor"; do
fi
done
VERSION=RaspberryVanillaAOSP16
VERSION=RaspberryVanillaAOSP17
DATE=$(date +%Y%m%d)
TARGET=$(echo ${TARGET_PRODUCT} | sed 's/^aosp_//')
IMGNAME=${VERSION}-${DATE}-${TARGET}.img
+1 -1
View File
@@ -16,7 +16,7 @@ PRODUCT_CHARACTERISTICS := nosdcard
# Base config
$(call inherit-product, $(SRC_TARGET_DIR)/product/full_base.mk)
$(call enforce-product-packages-exist,com.android.ranging)
$(call enforce-product-packages-exist,com.android.ranging vendor_tracing_descriptors)
# Keylayout
PRODUCT_PACKAGES += \
+1 -1
View File
@@ -17,7 +17,7 @@ PRODUCT_CHARACTERISTICS := automotive,nosdcard
# Base config
$(call inherit-product, $(SRC_TARGET_DIR)/product/full_base.mk)
$(call inherit-product, packages/services/Car/car_product/build/car.mk)
$(call enforce-product-packages-exist,Bluetooth Keyguard Launcher2 OverviewApp RotaryIME RotaryPlayground com.android.ranging display_compat_config libnfc_ndef libvariablespeed pppd)
$(call enforce-product-packages-exist,Bluetooth Keyguard Launcher2 OverviewApp RotaryIME RotaryPlayground com.android.ranging libnfc_ndef libvariablespeed pppd vendor_tracing_descriptors)
# android.car
PRODUCT_PACKAGES += \
+1 -1
View File
@@ -15,7 +15,7 @@ PRODUCT_CHARACTERISTICS := tv
# Base config
$(call inherit-product, device/google/atv/products/atv_base.mk)
$(call enforce-product-packages-exist,com.android.ranging)
$(call enforce-product-packages-exist,com.android.ranging vendor_tracing_descriptors)
# Android TV
PRODUCT_PACKAGES += \