Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d5f56fd05 |
@@ -1 +1 @@
|
||||
Raspberry Vanilla AOSP 16 device configuration for Raspberry Pi 4.
|
||||
Raspberry Vanilla AOSP 17 device configuration for Raspberry Pi 4.
|
||||
|
||||
@@ -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
@@ -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",
|
||||
],
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
#define LOG_TAG "AHAL_Config"
|
||||
#include <android-base/logging.h>
|
||||
#include <Log.h>
|
||||
|
||||
#include <system/audio_config.h>
|
||||
|
||||
|
||||
+39
-1
@@ -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;
|
||||
}();
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
@@ -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,
|
||||
×tamp);
|
||||
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(×tamp);
|
||||
} 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
|
||||
@@ -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,
|
||||
×tamp);
|
||||
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(×tamp);
|
||||
} 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
|
||||
@@ -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(
|
||||
@@ -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>
|
||||
|
||||
+484
-208
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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(); }
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
#define LOG_TAG "AHAL_OffloadStream"
|
||||
#include <android-base/logging.h>
|
||||
#include <Log.h>
|
||||
|
||||
#include "ApeHeader.h"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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(¤tPtr, 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
#define LOG_TAG "AHAL_UsbAlsaMixerControl"
|
||||
#include <android-base/logging.h>
|
||||
#include <Log.h>
|
||||
|
||||
#include <android/binder_status.h>
|
||||
|
||||
|
||||
@@ -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,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
@@ -17,7 +17,7 @@ cc_binary {
|
||||
"libhealthloop",
|
||||
],
|
||||
shared_libs: [
|
||||
"android.hardware.health-V4-ndk",
|
||||
"android.hardware.health-V5-ndk",
|
||||
"libbase",
|
||||
"libbinder_ndk",
|
||||
"libcutils",
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -1 +1 @@
|
||||
<manifest version="1.0" type="device" target-level="202504" />
|
||||
<manifest version="1.0" type="device" target-level="202604" />
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 += \
|
||||
|
||||
Reference in New Issue
Block a user