Merge "audio: Allow "dynamic" profiles for device ports" into main

This commit is contained in:
Mikhail Naganov 2023-10-11 17:32:22 +00:00 committed by Gerrit Code Review
commit e3dcd057d9
9 changed files with 335 additions and 150 deletions

View file

@ -105,15 +105,11 @@ static AudioPort createPort(int32_t id, const std::string& name, int32_t flags,
return port;
}
static AudioPortConfig createPortConfig(int32_t id, int32_t portId, PcmType pcmType, int32_t layout,
int32_t sampleRate, int32_t flags, bool isInput,
const AudioPortExt& ext) {
static AudioPortConfig createDynamicPortConfig(int32_t id, int32_t portId, int32_t flags,
bool isInput, const AudioPortExt& ext) {
AudioPortConfig config;
config.id = id;
config.portId = portId;
config.sampleRate = Int{.value = sampleRate};
config.channelMask = AudioChannelLayout::make<AudioChannelLayout::layoutMask>(layout);
config.format = AudioFormatDescription{.type = AudioFormatType::PCM, .pcm = pcmType};
config.gain = AudioGainConfig();
config.flags = isInput ? AudioIoFlags::make<AudioIoFlags::Tag::input>(flags)
: AudioIoFlags::make<AudioIoFlags::Tag::output>(flags);
@ -121,6 +117,16 @@ static AudioPortConfig createPortConfig(int32_t id, int32_t portId, PcmType pcmT
return config;
}
static AudioPortConfig createPortConfig(int32_t id, int32_t portId, PcmType pcmType, int32_t layout,
int32_t sampleRate, int32_t flags, bool isInput,
const AudioPortExt& ext) {
AudioPortConfig config = createDynamicPortConfig(id, portId, flags, isInput, ext);
config.sampleRate = Int{.value = sampleRate};
config.channelMask = AudioChannelLayout::make<AudioChannelLayout::layoutMask>(layout);
config.format = AudioFormatDescription{.type = AudioFormatType::PCM, .pcm = pcmType};
return config;
}
static AudioRoute createRoute(const std::vector<AudioPort>& sources, const AudioPort& sink) {
AudioRoute route;
route.sinkPortId = sink.id;
@ -147,8 +153,7 @@ static AudioRoute createRoute(const std::vector<AudioPort>& sources, const Audio
// * "primary output", PRIMARY, 1 max open, 1 max active stream
// - profile PCM 16-bit; MONO, STEREO; 8000, 11025, 16000, 32000, 44100, 48000
// * "primary input", 1 max open, 1 max active stream
// - profile PCM 16-bit; MONO, STEREO;
// 8000, 11025, 16000, 32000, 44100, 48000
// - profile PCM 16-bit; MONO, STEREO; 8000, 11025, 16000, 32000, 44100, 48000
// * "telephony_tx", 1 max open, 1 max active stream
// - profile PCM 16-bit; MONO, STEREO; 8000, 11025, 16000, 32000, 44100, 48000
// * "telephony_rx", 1 max open, 1 max active stream
@ -164,11 +169,11 @@ static AudioRoute createRoute(const std::vector<AudioPort>& sources, const Audio
// "FM Tuner" -> "fm_tuner"
//
// Initial port configs:
// * "Speaker" device port: PCM 16-bit; STEREO; 48000
// * "Built-In Mic" device port: PCM 16-bit; MONO; 48000
// * "Telephony Tx" device port: PCM 16-bit; MONO; 48000
// * "Telephony Rx" device port: PCM 16-bit; MONO; 48000
// * "FM Tuner" device port: PCM 16-bit; STEREO; 48000
// * "Speaker" device port: dynamic configuration
// * "Built-In Mic" device port: dynamic configuration
// * "Telephony Tx" device port: dynamic configuration
// * "Telephony Rx" device port: dynamic configuration
// * "FM Tuner" device port: dynamic configuration
//
std::unique_ptr<Configuration> getPrimaryConfiguration() {
static const Configuration configuration = []() {
@ -186,9 +191,8 @@ std::unique_ptr<Configuration> getPrimaryConfiguration() {
1 << AudioPortDeviceExt::FLAG_INDEX_DEFAULT_DEVICE));
c.ports.push_back(speakerOutDevice);
c.initialConfigs.push_back(
createPortConfig(speakerOutDevice.id, speakerOutDevice.id, PcmType::INT_16_BIT,
AudioChannelLayout::LAYOUT_STEREO, 48000, 0, false,
createDeviceExt(AudioDeviceType::OUT_SPEAKER, 0)));
createDynamicPortConfig(speakerOutDevice.id, speakerOutDevice.id, 0, false,
createDeviceExt(AudioDeviceType::OUT_SPEAKER, 0)));
AudioPort micInDevice =
createPort(c.nextPortId++, "Built-In Mic", 0, true,
@ -196,35 +200,31 @@ std::unique_ptr<Configuration> getPrimaryConfiguration() {
1 << AudioPortDeviceExt::FLAG_INDEX_DEFAULT_DEVICE));
c.ports.push_back(micInDevice);
c.initialConfigs.push_back(
createPortConfig(micInDevice.id, micInDevice.id, PcmType::INT_16_BIT,
AudioChannelLayout::LAYOUT_MONO, 48000, 0, true,
createDeviceExt(AudioDeviceType::IN_MICROPHONE, 0)));
createDynamicPortConfig(micInDevice.id, micInDevice.id, 0, true,
createDeviceExt(AudioDeviceType::IN_MICROPHONE, 0)));
AudioPort telephonyTxOutDevice =
createPort(c.nextPortId++, "Telephony Tx", 0, false,
createDeviceExt(AudioDeviceType::OUT_TELEPHONY_TX, 0));
c.ports.push_back(telephonyTxOutDevice);
c.initialConfigs.push_back(
createPortConfig(telephonyTxOutDevice.id, telephonyTxOutDevice.id,
PcmType::INT_16_BIT, AudioChannelLayout::LAYOUT_MONO, 48000, 0,
false, createDeviceExt(AudioDeviceType::OUT_TELEPHONY_TX, 0)));
createDynamicPortConfig(telephonyTxOutDevice.id, telephonyTxOutDevice.id, 0, false,
createDeviceExt(AudioDeviceType::OUT_TELEPHONY_TX, 0)));
AudioPort telephonyRxInDevice =
createPort(c.nextPortId++, "Telephony Rx", 0, true,
createDeviceExt(AudioDeviceType::IN_TELEPHONY_RX, 0));
c.ports.push_back(telephonyRxInDevice);
c.initialConfigs.push_back(
createPortConfig(telephonyRxInDevice.id, telephonyRxInDevice.id,
PcmType::INT_16_BIT, AudioChannelLayout::LAYOUT_MONO, 48000, 0,
true, createDeviceExt(AudioDeviceType::IN_TELEPHONY_RX, 0)));
createDynamicPortConfig(telephonyRxInDevice.id, telephonyRxInDevice.id, 0, true,
createDeviceExt(AudioDeviceType::IN_TELEPHONY_RX, 0)));
AudioPort fmTunerInDevice = createPort(c.nextPortId++, "FM Tuner", 0, true,
createDeviceExt(AudioDeviceType::IN_FM_TUNER, 0));
c.ports.push_back(fmTunerInDevice);
c.initialConfigs.push_back(
createPortConfig(fmTunerInDevice.id, fmTunerInDevice.id, PcmType::INT_16_BIT,
AudioChannelLayout::LAYOUT_STEREO, 48000, 0, true,
createDeviceExt(AudioDeviceType::IN_FM_TUNER, 0)));
createDynamicPortConfig(fmTunerInDevice.id, fmTunerInDevice.id, 0, true,
createDeviceExt(AudioDeviceType::IN_FM_TUNER, 0)));
// Mix ports
@ -287,13 +287,15 @@ std::unique_ptr<Configuration> getPrimaryConfiguration() {
// Note: When transitioning to loading of XML configs, either keep the configuration
// of the remote submix sources from this static configuration, or update the XML
// config to match it. There are two reasons for that:
// 1. The canonical r_submix configuration only lists 'STEREO' and '48000',
// config to match it. There are several reasons for that:
// 1. The "Remote Submix In" device is listed in the XML config as "attached",
// however in the AIDL scheme its device type has a "virtual" connection.
// 2. The canonical r_submix configuration only lists 'STEREO' and '48000',
// however the framework attempts to open streams for other sample rates
// as well. The legacy r_submix implementation allowed that, but libaudiohal@aidl
// will not find a mix port to use. Because of that, list all channel
// masks and sample rates that the legacy implementation allowed.
// 2. The legacy implementation had a hard limit on the number of routes (10),
// 3. The legacy implementation had a hard limit on the number of routes (10),
// and this is checked indirectly by AudioPlaybackCaptureTest#testPlaybackCaptureDoS
// CTS test. Instead of hardcoding the number of routes, we can use
// "maxOpen/ActiveStreamCount" to enforce a similar limit. However, the canonical
@ -331,15 +333,15 @@ std::unique_ptr<Configuration> getRSubmixConfiguration() {
createPort(c.nextPortId++, "Remote Submix Out", 0, false,
createDeviceExt(AudioDeviceType::OUT_SUBMIX, 0,
AudioDeviceDescription::CONNECTION_VIRTUAL));
rsubmixOutDevice.profiles = standardPcmAudioProfiles;
c.ports.push_back(rsubmixOutDevice);
c.connectedProfiles[rsubmixOutDevice.id] = standardPcmAudioProfiles;
AudioPort rsubmixInDevice =
createPort(c.nextPortId++, "Remote Submix In", 0, true,
createDeviceExt(AudioDeviceType::IN_SUBMIX, 0,
AudioDeviceDescription::CONNECTION_VIRTUAL));
rsubmixInDevice.profiles = standardPcmAudioProfiles;
c.ports.push_back(rsubmixInDevice);
c.connectedProfiles[rsubmixInDevice.id] = standardPcmAudioProfiles;
// Mix ports
@ -384,7 +386,7 @@ std::unique_ptr<Configuration> getRSubmixConfiguration() {
// * "usb_device output" -> "USB Headset Out"
// * "USB Device In", "USB Headset In" -> "usb_device input"
//
// Profiles for device port connected state:
// Profiles for device port connected state (when simulating connections):
// * "USB Device Out", "USB Headset Out":
// - profile PCM 16-bit; MONO, STEREO, INDEX_MASK_1, INDEX_MASK_2; 44100, 48000
// - profile PCM 24-bit; MONO, STEREO, INDEX_MASK_1, INDEX_MASK_2; 44100, 48000
@ -461,9 +463,9 @@ std::unique_ptr<Configuration> getUsbConfiguration() {
// * "Test In", IN_AFE_PROXY
// - no profiles specified
// * "Wired Headset", OUT_HEADSET
// - profile PCM 24-bit; STEREO; 48000
// - no profiles specified
// * "Wired Headset Mic", IN_HEADSET
// - profile PCM 24-bit; MONO; 48000
// - no profiles specified
//
// Mix ports:
// * "test output", 1 max open, 1 max active stream
@ -486,6 +488,10 @@ std::unique_ptr<Configuration> getUsbConfiguration() {
// * "Test Out" device port: PCM 24-bit; STEREO; 48000
// * "Test In" device port: PCM 24-bit; MONO; 48000
//
// Profiles for device port connected state (when simulating connections):
// * "Wired Headset": dynamic profiles
// * "Wired Headset Mic": dynamic profiles
//
std::unique_ptr<Configuration> getStubConfiguration() {
static const Configuration configuration = []() {
Configuration c;
@ -504,8 +510,6 @@ std::unique_ptr<Configuration> getStubConfiguration() {
createPort(c.nextPortId++, "Wired Headset", 0, false,
createDeviceExt(AudioDeviceType::OUT_HEADSET, 0,
AudioDeviceDescription::CONNECTION_ANALOG));
headsetOutDevice.profiles.push_back(
createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000}));
c.ports.push_back(headsetOutDevice);
AudioPort testInDevice = createPort(c.nextPortId++, "Test In", 0, true,
@ -520,8 +524,6 @@ std::unique_ptr<Configuration> getStubConfiguration() {
createPort(c.nextPortId++, "Wired Headset Mic", 0, true,
createDeviceExt(AudioDeviceType::IN_HEADSET, 0,
AudioDeviceDescription::CONNECTION_ANALOG));
headsetInDevice.profiles.push_back(
createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_MONO}, {48000}));
c.ports.push_back(headsetInDevice);
// Mix ports
@ -553,24 +555,24 @@ std::unique_ptr<Configuration> getStubConfiguration() {
{44100, 48000}));
c.ports.push_back(compressedOffloadOutMix);
AudioPort testInMIx =
AudioPort testInMix =
createPort(c.nextPortId++, "test input", 0, true, createPortMixExt(2, 2));
testInMIx.profiles.push_back(
testInMix.profiles.push_back(
createProfile(PcmType::INT_16_BIT,
{AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO,
AudioChannelLayout::LAYOUT_FRONT_BACK},
{8000, 11025, 16000, 22050, 32000, 44100, 48000}));
testInMIx.profiles.push_back(
testInMix.profiles.push_back(
createProfile(PcmType::INT_24_BIT,
{AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO,
AudioChannelLayout::LAYOUT_FRONT_BACK},
{8000, 11025, 16000, 22050, 32000, 44100, 48000}));
c.ports.push_back(testInMIx);
c.ports.push_back(testInMix);
c.routes.push_back(
createRoute({testOutMix, testFastOutMix, compressedOffloadOutMix}, testOutDevice));
c.routes.push_back(createRoute({testOutMix}, headsetOutDevice));
c.routes.push_back(createRoute({testInDevice, headsetInDevice}, testInMIx));
c.routes.push_back(createRoute({testInDevice, headsetInDevice}, testInMix));
c.portConfigs.insert(c.portConfigs.end(), c.initialConfigs.begin(), c.initialConfigs.end());
@ -603,11 +605,19 @@ std::unique_ptr<Configuration> getStubConfiguration() {
// "a2dp output" -> "BT A2DP Speaker"
// "hearing aid output" -> "BT Hearing Aid Out"
//
// 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
//
std::unique_ptr<Configuration> getBluetoothConfiguration() {
static const Configuration configuration = []() {
const std::vector<AudioProfile> standardPcmAudioProfiles = {
createProfile(PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_STEREO},
{44100, 48000, 88200, 96000})};
const std::vector<AudioProfile> hearingAidAudioProfiles = {createProfile(
PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {16000, 24000})};
Configuration c;
// Device ports
@ -645,8 +655,7 @@ std::unique_ptr<Configuration> getBluetoothConfiguration() {
createDeviceExt(AudioDeviceType::OUT_HEARING_AID, 0,
AudioDeviceDescription::CONNECTION_WIRELESS));
c.ports.push_back(btOutHearingAid);
c.connectedProfiles[btOutHearingAid.id] = std::vector<AudioProfile>(
{createProfile(PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {16000})});
c.connectedProfiles[btOutHearingAid.id] = hearingAidAudioProfiles;
// Mix ports
AudioPort btOutMix =
@ -655,8 +664,7 @@ std::unique_ptr<Configuration> getBluetoothConfiguration() {
AudioPort btHearingOutMix =
createPort(c.nextPortId++, "hearing aid output", 0, false, createPortMixExt(1, 1));
btHearingOutMix.profiles.push_back(createProfile(
PcmType::INT_16_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {16000, 24000}));
btHearingOutMix.profiles = hearingAidAudioProfiles;
c.ports.push_back(btHearingOutMix);
c.routes.push_back(createRoute({btOutMix}, btOutDevice));

View file

@ -65,32 +65,56 @@ namespace aidl::android::hardware::audio::core {
namespace {
inline bool hasDynamicChannelMasks(const std::vector<AudioChannelLayout>& channelMasks) {
return channelMasks.empty() ||
std::all_of(channelMasks.begin(), channelMasks.end(),
[](const auto& channelMask) { return channelMask == AudioChannelLayout{}; });
}
inline bool hasDynamicFormat(const AudioFormatDescription& format) {
return format == AudioFormatDescription{};
}
inline bool hasDynamicSampleRates(const std::vector<int32_t>& sampleRates) {
return sampleRates.empty() ||
std::all_of(sampleRates.begin(), sampleRates.end(),
[](const auto& sampleRate) { return sampleRate == 0; });
}
inline bool isDynamicProfile(const AudioProfile& profile) {
return hasDynamicFormat(profile.format) || hasDynamicChannelMasks(profile.channelMasks) ||
hasDynamicSampleRates(profile.sampleRates);
}
bool hasDynamicProfilesOnly(const std::vector<AudioProfile>& profiles) {
if (profiles.empty()) return true;
return std::all_of(profiles.begin(), profiles.end(), isDynamicProfile);
}
// Note: does not assign an ID to the config.
bool generateDefaultPortConfig(const AudioPort& port, AudioPortConfig* config) {
const bool allowDynamicConfig = port.ext.getTag() == AudioPortExt::device;
*config = {};
config->portId = port.id;
if (port.profiles.empty()) {
LOG(ERROR) << __func__ << ": port " << port.id << " has no profiles";
return false;
for (const auto& profile : port.profiles) {
if (isDynamicProfile(profile)) continue;
config->format = profile.format;
config->channelMask = *profile.channelMasks.begin();
config->sampleRate = Int{.value = *profile.sampleRates.begin()};
config->flags = port.flags;
config->ext = port.ext;
return true;
}
const auto& profile = port.profiles.begin();
config->format = profile->format;
if (profile->channelMasks.empty()) {
LOG(ERROR) << __func__ << ": the first profile in port " << port.id
<< " has no channel masks";
return false;
if (allowDynamicConfig) {
config->format = AudioFormatDescription{};
config->channelMask = AudioChannelLayout{};
config->sampleRate = Int{.value = 0};
config->flags = port.flags;
config->ext = port.ext;
return true;
}
config->channelMask = *profile->channelMasks.begin();
if (profile->sampleRates.empty()) {
LOG(ERROR) << __func__ << ": the first profile in port " << port.id
<< " has no sample rates";
return false;
}
Int sampleRate;
sampleRate.value = *profile->sampleRates.begin();
config->sampleRate = sampleRate;
config->flags = port.flags;
config->ext = port.ext;
return true;
LOG(ERROR) << __func__ << ": port " << port.id << " only has dynamic profiles";
return false;
}
bool findAudioProfile(const AudioPort& port, const AudioFormatDescription& format,
@ -314,6 +338,18 @@ std::unique_ptr<internal::Configuration> Module::initializeConfig() {
return config;
}
std::vector<AudioRoute*> Module::getAudioRoutesForAudioPortImpl(int32_t portId) {
std::vector<AudioRoute*> result;
auto& routes = getConfig().routes;
for (auto& r : routes) {
const auto& srcs = r.sourcePortIds;
if (r.sinkPortId == portId || std::find(srcs.begin(), srcs.end(), portId) != srcs.end()) {
result.push_back(&r);
}
}
return result;
}
internal::Configuration& Module::getConfig() {
if (!mConfig) {
mConfig = std::move(initializeConfig());
@ -321,6 +357,24 @@ internal::Configuration& Module::getConfig() {
return *mConfig;
}
std::set<int32_t> Module::getRoutableAudioPortIds(int32_t portId,
std::vector<AudioRoute*>* routes) {
std::vector<AudioRoute*> routesStorage;
if (routes == nullptr) {
routesStorage = getAudioRoutesForAudioPortImpl(portId);
routes = &routesStorage;
}
std::set<int32_t> result;
for (AudioRoute* r : *routes) {
if (r->sinkPortId == portId) {
result.insert(r->sourcePortIds.begin(), r->sourcePortIds.end());
} else {
result.insert(r->sinkPortId);
}
}
return result;
}
void Module::registerPatch(const AudioPatch& patch) {
auto& configs = getConfig().portConfigs;
auto do_insert = [&](const std::vector<int32_t>& portConfigIds) {
@ -510,7 +564,31 @@ ndk::ScopedAStatus Module::connectExternalDevice(const AudioPort& in_templateIdA
}
}
if (connectedPort.profiles.empty()) {
// Two main cases are considered with regard to the profiles of the connected device port:
//
// 1. If the template device port has dynamic profiles, and at least one routable mix
// port also has dynamic profiles, it means that after connecting the device, the
// connected device port must have profiles populated with actual capabilities of
// the connected device, and dynamic of routable mix ports will be filled
// according to these capabilities. An example of this case is connection of an
// HDMI or USB device. For USB handled by ADSP, there can be mix ports with static
// profiles, and one dedicated mix port for "hi-fi" playback. The latter is left with
// dynamic profiles so that they can be populated with actual capabilities of
// the connected device.
//
// 2. If the template device port has dynamic profiles, while all routable mix ports
// have static profiles, it means that after connecting the device, the connected
// device port can be left with dynamic profiles, and profiles of mix ports are
// left untouched. An example of this case is connection of an analog wired
// headset, it should be treated in the same way as a speaker.
//
// Yet another possible case is when both the template device port and all routable
// mix ports have static profiles. This is allowed and handled correctly, however, it
// is not very practical, since these profiles are likely duplicates of each other.
std::vector<AudioRoute*> routesToMixPorts = getAudioRoutesForAudioPortImpl(templateId);
std::set<int32_t> routableMixPortIds = getRoutableAudioPortIds(templateId, &routesToMixPorts);
if (hasDynamicProfilesOnly(connectedPort.profiles)) {
if (!mDebug.simulateDeviceConnections) {
RETURN_STATUS_IF_ERROR(populateConnectedDevicePort(&connectedPort));
} else {
@ -520,23 +598,22 @@ ndk::ScopedAStatus Module::connectExternalDevice(const AudioPort& in_templateIdA
connectedPort.profiles = connectedProfilesIt->second;
}
}
if (connectedPort.profiles.empty()) {
LOG(ERROR) << __func__
<< ": profiles of a connected port still empty after connecting external "
"device "
<< connectedPort.toString();
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
}
for (auto profile : connectedPort.profiles) {
if (profile.channelMasks.empty()) {
LOG(ERROR) << __func__ << ": the profile " << profile.name << " has no channel masks";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
if (profile.sampleRates.empty()) {
LOG(ERROR) << __func__ << ": the profile " << profile.name << " has no sample rates";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
if (hasDynamicProfilesOnly(connectedPort.profiles)) {
// Possible case 2. Check if all routable mix ports have static profiles.
if (auto dynamicMixPortIt = std::find_if(ports.begin(), ports.end(),
[&routableMixPortIds](const auto& p) {
return routableMixPortIds.count(p.id) >
0 &&
hasDynamicProfilesOnly(p.profiles);
});
dynamicMixPortIt != ports.end()) {
LOG(ERROR) << __func__
<< ": connected port only has dynamic profiles after connecting "
<< "external device " << connectedPort.toString() << ", and there exist "
<< "a routable mix port with dynamic profiles: "
<< dynamicMixPortIt->toString();
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
}
}
@ -548,44 +625,36 @@ ndk::ScopedAStatus Module::connectExternalDevice(const AudioPort& in_templateIdA
ports.push_back(connectedPort);
onExternalDeviceConnectionChanged(connectedPort, true /*connected*/);
std::vector<int32_t> routablePortIds;
// For routes where the template port is a source, add the connected port to sources,
// otherwise, create a new route by copying from the route for the template port.
std::vector<AudioRoute> newRoutes;
auto& routes = getConfig().routes;
for (auto& r : routes) {
if (r.sinkPortId == templateId) {
AudioRoute newRoute;
newRoute.sourcePortIds = r.sourcePortIds;
newRoute.sinkPortId = connectedPort.id;
newRoute.isExclusive = r.isExclusive;
newRoutes.push_back(std::move(newRoute));
routablePortIds.insert(routablePortIds.end(), r.sourcePortIds.begin(),
r.sourcePortIds.end());
for (AudioRoute* r : routesToMixPorts) {
if (r->sinkPortId == templateId) {
newRoutes.push_back(AudioRoute{.sourcePortIds = r->sourcePortIds,
.sinkPortId = connectedPort.id,
.isExclusive = r->isExclusive});
} else {
auto& srcs = r.sourcePortIds;
if (std::find(srcs.begin(), srcs.end(), templateId) != srcs.end()) {
srcs.push_back(connectedPort.id);
routablePortIds.push_back(r.sinkPortId);
}
r->sourcePortIds.push_back(connectedPort.id);
}
}
auto& routes = getConfig().routes;
routes.insert(routes.end(), newRoutes.begin(), newRoutes.end());
// Note: this is a simplistic approach assuming that a mix port can only be populated
// from a single device port. Implementing support for stuffing dynamic profiles with a superset
// of all profiles from all routable dynamic device ports would be more involved.
for (const auto mixPortId : routablePortIds) {
auto portsIt = findById<AudioPort>(ports, mixPortId);
if (portsIt != ports.end()) {
if (portsIt->profiles.empty()) {
portsIt->profiles = connectedPort.profiles;
connectedPortsIt->second.insert(portsIt->id);
if (!hasDynamicProfilesOnly(connectedPort.profiles) && !routableMixPortIds.empty()) {
// Note: this is a simplistic approach assuming that a mix port can only be populated
// from a single device port. Implementing support for stuffing dynamic profiles with
// a superset of all profiles from all routable dynamic device ports would be more involved.
for (auto& port : ports) {
if (routableMixPortIds.count(port.id) == 0) continue;
if (hasDynamicProfilesOnly(port.profiles)) {
port.profiles = connectedPort.profiles;
connectedPortsIt->second.insert(port.id);
} else {
// Check if profiles are non empty because they were populated by
// a previous connection. Otherwise, it means that they are not empty because
// the mix port has static profiles.
for (const auto cp : mConnectedDevicePorts) {
if (cp.second.count(portsIt->id) > 0) {
connectedPortsIt->second.insert(portsIt->id);
// Check if profiles are not all dynamic because they were populated by
// a previous connection. Otherwise, it means that they are actually static.
for (const auto& cp : mConnectedDevicePorts) {
if (cp.second.count(port.id) > 0) {
connectedPortsIt->second.insert(port.id);
break;
}
}
@ -705,13 +774,9 @@ ndk::ScopedAStatus Module::getAudioRoutesForAudioPort(int32_t in_portId,
LOG(ERROR) << __func__ << ": port id " << in_portId << " not found";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
auto& routes = getConfig().routes;
std::copy_if(routes.begin(), routes.end(), std::back_inserter(*_aidl_return),
[&](const auto& r) {
const auto& srcs = r.sourcePortIds;
return r.sinkPortId == in_portId ||
std::find(srcs.begin(), srcs.end(), in_portId) != srcs.end();
});
std::vector<AudioRoute*> routes = getAudioRoutesForAudioPortImpl(in_portId);
std::transform(routes.begin(), routes.end(), std::back_inserter(*_aidl_return),
[](auto rptr) { return *rptr; });
return ndk::ScopedAStatus::ok();
}
@ -925,13 +990,14 @@ ndk::ScopedAStatus Module::setAudioPortConfig(const AudioPortConfig& in_requeste
const int portId = existing != configs.end() ? existing->portId : in_requested.portId;
if (portId == 0) {
LOG(ERROR) << __func__ << ": input port config does not specify portId";
LOG(ERROR) << __func__ << ": requested port config does not specify portId";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
auto& ports = getConfig().ports;
auto portIt = findById<AudioPort>(ports, portId);
if (portIt == ports.end()) {
LOG(ERROR) << __func__ << ": input port config points to non-existent portId " << portId;
LOG(ERROR) << __func__ << ": requested port config points to non-existent portId "
<< portId;
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
if (existing != configs.end()) {
@ -949,6 +1015,10 @@ ndk::ScopedAStatus Module::setAudioPortConfig(const AudioPortConfig& in_requeste
// or a new generated config. Now attempt to update it according to the specified
// fields of 'in_requested'.
// Device ports with only dynamic profiles are used for devices that are connected via ADSP,
// which takes care of their actual configuration automatically.
const bool allowDynamicConfig = portIt->ext.getTag() == AudioPortExt::device &&
hasDynamicProfilesOnly(portIt->profiles);
bool requestedIsValid = true, requestedIsFullySpecified = true;
AudioIoFlags portFlags = portIt->flags;
@ -966,17 +1036,19 @@ ndk::ScopedAStatus Module::setAudioPortConfig(const AudioPortConfig& in_requeste
AudioProfile portProfile;
if (in_requested.format.has_value()) {
const auto& format = in_requested.format.value();
if (findAudioProfile(*portIt, format, &portProfile)) {
if ((format == AudioFormatDescription{} && allowDynamicConfig) ||
findAudioProfile(*portIt, format, &portProfile)) {
out_suggested->format = format;
} else {
LOG(WARNING) << __func__ << ": requested format " << format.toString()
<< " is not found in port's " << portId << " profiles";
<< " is not found in the profiles of port " << portId;
requestedIsValid = false;
}
} else {
requestedIsFullySpecified = false;
}
if (!findAudioProfile(*portIt, out_suggested->format.value(), &portProfile)) {
if (!(out_suggested->format.value() == AudioFormatDescription{} && allowDynamicConfig) &&
!findAudioProfile(*portIt, out_suggested->format.value(), &portProfile)) {
LOG(ERROR) << __func__ << ": port " << portId << " does not support format "
<< out_suggested->format.value().toString() << " anymore";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
@ -984,8 +1056,9 @@ ndk::ScopedAStatus Module::setAudioPortConfig(const AudioPortConfig& in_requeste
if (in_requested.channelMask.has_value()) {
const auto& channelMask = in_requested.channelMask.value();
if (find(portProfile.channelMasks.begin(), portProfile.channelMasks.end(), channelMask) !=
portProfile.channelMasks.end()) {
if ((channelMask == AudioChannelLayout{} && allowDynamicConfig) ||
find(portProfile.channelMasks.begin(), portProfile.channelMasks.end(), channelMask) !=
portProfile.channelMasks.end()) {
out_suggested->channelMask = channelMask;
} else {
LOG(WARNING) << __func__ << ": requested channel mask " << channelMask.toString()
@ -999,7 +1072,8 @@ ndk::ScopedAStatus Module::setAudioPortConfig(const AudioPortConfig& in_requeste
if (in_requested.sampleRate.has_value()) {
const auto& sampleRate = in_requested.sampleRate.value();
if (find(portProfile.sampleRates.begin(), portProfile.sampleRates.end(),
if ((sampleRate.value == 0 && allowDynamicConfig) ||
find(portProfile.sampleRates.begin(), portProfile.sampleRates.end(),
sampleRate.value) != portProfile.sampleRates.end()) {
out_suggested->sampleRate = sampleRate;
} else {
@ -1397,7 +1471,18 @@ bool Module::isMmapSupported() {
return mIsMmapSupported.value();
}
ndk::ScopedAStatus Module::populateConnectedDevicePort(AudioPort* audioPort __unused) {
ndk::ScopedAStatus Module::populateConnectedDevicePort(AudioPort* audioPort) {
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>();
if (!devicePort.device.type.connection.empty()) {
LOG(ERROR) << __func__
<< ": module implementation must override 'populateConnectedDevicePort' "
<< "to handle connection of external devices.";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
LOG(VERBOSE) << __func__ << ": do nothing and return ok";
return ndk::ScopedAStatus::ok();
}

View file

@ -18,15 +18,23 @@
#include <android-base/logging.h>
#include "BluetoothAudioSessionControl.h"
#include "core-impl/ModuleBluetooth.h"
#include "core-impl/StreamBluetooth.h"
namespace aidl::android::hardware::audio::core {
using aidl::android::hardware::audio::common::SinkMetadata;
using aidl::android::hardware::audio::common::SourceMetadata;
using aidl::android::media::audio::common::AudioOffloadInfo;
using aidl::android::media::audio::common::MicrophoneInfo;
using ::aidl::android::hardware::audio::common::SinkMetadata;
using ::aidl::android::hardware::audio::common::SourceMetadata;
using ::aidl::android::hardware::bluetooth::audio::BluetoothAudioSession;
using ::aidl::android::media::audio::common::AudioDeviceDescription;
using ::aidl::android::media::audio::common::AudioDeviceType;
using ::aidl::android::media::audio::common::AudioOffloadInfo;
using ::aidl::android::media::audio::common::AudioPort;
using ::aidl::android::media::audio::common::AudioPortExt;
using ::aidl::android::media::audio::common::MicrophoneInfo;
using ::android::bluetooth::audio::aidl::BluetoothAudioPortAidl;
using ::android::bluetooth::audio::aidl::BluetoothAudioPortAidlOut;
ndk::ScopedAStatus ModuleBluetooth::getBluetoothA2dp(
std::shared_ptr<IBluetoothA2dp>* _aidl_return) {
@ -80,6 +88,49 @@ ndk::ScopedAStatus ModuleBluetooth::createOutputStream(
offloadInfo, getBtProfileManagerHandles());
}
ndk::ScopedAStatus ModuleBluetooth::populateConnectedDevicePort(AudioPort* audioPort) {
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;
// Since the configuration of the BT module is static, there is nothing to populate here.
// However, this method must return an error when the device can not be connected,
// this is determined by the status of BT profiles.
if (description.connection == AudioDeviceDescription::CONNECTION_BT_A2DP) {
bool isA2dpEnabled = false;
if (!!mBluetoothA2dp) {
RETURN_STATUS_IF_ERROR(mBluetoothA2dp.getInstance()->isEnabled(&isA2dpEnabled));
}
return isA2dpEnabled ? ndk::ScopedAStatus::ok()
: ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
} else if (description.connection == AudioDeviceDescription::CONNECTION_BT_LE) {
bool isLeEnabled = false;
if (!!mBluetoothLe) {
RETURN_STATUS_IF_ERROR(mBluetoothLe.getInstance()->isEnabled(&isLeEnabled));
}
return isLeEnabled ? ndk::ScopedAStatus::ok()
: ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
} else if (description.connection == AudioDeviceDescription::CONNECTION_WIRELESS &&
description.type == AudioDeviceType::OUT_HEARING_AID) {
// Hearing aids can use a number of profiles, thus the only way to check
// connectivity is to try to talk to the BT HAL.
if (!BluetoothAudioSession::IsAidlAvailable()) {
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
std::shared_ptr<BluetoothAudioPortAidl> proxy = std::shared_ptr<BluetoothAudioPortAidl>(
std::make_shared<BluetoothAudioPortAidlOut>());
if (proxy->registerPort(description)) {
proxy->unregisterPort();
return ndk::ScopedAStatus::ok();
}
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
LOG(ERROR) << __func__ << ": unsupported device type: " << audioPort->toString();
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
ndk::ScopedAStatus ModuleBluetooth::onMasterMuteChanged(bool) {
LOG(DEBUG) << __func__ << ": is not supported";
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);

View file

@ -202,6 +202,7 @@ class Module : public BnModule {
std::set<int32_t> findConnectedPortConfigIds(int32_t portConfigId);
ndk::ScopedAStatus findPortIdForNewStream(
int32_t in_portConfigId, ::aidl::android::media::audio::common::AudioPort** port);
std::vector<AudioRoute*> getAudioRoutesForAudioPortImpl(int32_t portId);
virtual BtProfileHandles getBtProfileManagerHandles();
internal::Configuration& getConfig();
const ConnectedDevicePorts& getConnectedDevicePorts() const { return mConnectedDevicePorts; }
@ -209,6 +210,8 @@ class Module : public BnModule {
bool getMasterVolume() const { return mMasterVolume; }
bool getMicMute() const { return mMicMute; }
const Patches& getPatches() const { return mPatches; }
std::set<int32_t> getRoutableAudioPortIds(int32_t portId,
std::vector<AudioRoute*>* routes = nullptr);
const Streams& getStreams() const { return mStreams; }
Type getType() const { return mType; }
bool isMmapSupported();

View file

@ -44,6 +44,8 @@ class ModuleBluetooth final : public Module {
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) override;
ndk::ScopedAStatus onMasterMuteChanged(bool mute) override;
ndk::ScopedAStatus onMasterVolumeChanged(float volume) override;
@ -51,4 +53,4 @@ class ModuleBluetooth final : public Module {
ChildInterface<IBluetoothLe> mBluetoothLe;
};
} // namespace aidl::android::hardware::audio::core
} // namespace aidl::android::hardware::audio::core

View file

@ -59,23 +59,22 @@ ndk::ScopedAStatus ModuleRemoteSubmix::createOutputStream(
ndk::ScopedAStatus ModuleRemoteSubmix::populateConnectedDevicePort(AudioPort* audioPort) {
// Find the corresponding mix port and copy its profiles.
std::vector<AudioRoute> routes;
// At this moment, the port has the same ID as the template port, see connectExternalDevice.
RETURN_STATUS_IF_ERROR(getAudioRoutesForAudioPort(audioPort->id, &routes));
std::vector<AudioRoute*> routes = getAudioRoutesForAudioPortImpl(audioPort->id);
if (routes.empty()) {
LOG(ERROR) << __func__ << ": no routes found for the port " << audioPort->toString();
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
const auto& route = *routes.begin();
AudioPort mixPort;
if (route.sinkPortId == audioPort->id) {
if (route.sourcePortIds.empty()) {
LOG(ERROR) << __func__ << ": invalid route " << route.toString();
if (route->sinkPortId == audioPort->id) {
if (route->sourcePortIds.empty()) {
LOG(ERROR) << __func__ << ": invalid route " << route->toString();
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
RETURN_STATUS_IF_ERROR(getAudioPort(*route.sourcePortIds.begin(), &mixPort));
RETURN_STATUS_IF_ERROR(getAudioPort(*route->sourcePortIds.begin(), &mixPort));
} else {
RETURN_STATUS_IF_ERROR(getAudioPort(route.sinkPortId, &mixPort));
RETURN_STATUS_IF_ERROR(getAudioPort(route->sinkPortId, &mixPort));
}
audioPort->profiles = mixPort.profiles;
return ndk::ScopedAStatus::ok();

View file

@ -281,6 +281,22 @@ std::optional<AudioPort> ModuleConfig::getSourceMixPortForConnectedDevice() cons
return {};
}
std::vector<AudioPort> ModuleConfig::getRoutableMixPortsForDevicePort(const AudioPort& port) const {
std::set<int32_t> portIds;
for (const auto& route : mRoutes) {
if (port.id == route.sinkPortId) {
portIds.insert(route.sourcePortIds.begin(), route.sourcePortIds.end());
} else if (auto it = std::find(route.sourcePortIds.begin(), route.sourcePortIds.end(),
port.id);
it != route.sourcePortIds.end()) {
portIds.insert(route.sinkPortId);
}
}
const bool isInput = port.flags.getTag() == AudioIoFlags::input;
return findMixPorts(isInput, false /*connectedOnly*/, false /*singlePort*/,
[&portIds](const AudioPort& p) { return portIds.count(p.id) > 0; });
}
std::optional<ModuleConfig::SrcSinkPair> ModuleConfig::getNonRoutableSrcSinkPair(
bool isInput) const {
const auto mixPorts = getMixPorts(isInput, false /*connectedOnly*/);

View file

@ -103,6 +103,9 @@ class ModuleConfig {
std::optional<aidl::android::media::audio::common::AudioPort>
getSourceMixPortForConnectedDevice() const;
std::vector<aidl::android::media::audio::common::AudioPort> getRoutableMixPortsForDevicePort(
const aidl::android::media::audio::common::AudioPort& port) const;
std::optional<SrcSinkPair> getNonRoutableSrcSinkPair(bool isInput) const;
std::optional<SrcSinkPair> getRoutableSrcSinkPair(bool isInput) const;
std::vector<SrcSinkGroup> getRoutableSrcSinkGroups(bool isInput) const;

View file

@ -1501,8 +1501,25 @@ TEST_P(AudioCoreModule, GetAudioPortWithExternalDevices) {
<< "port ID " << connectedPortId;
EXPECT_EQ(portConnected.get(), connectedPort);
const auto& portProfiles = connectedPort.profiles;
EXPECT_NE(0UL, portProfiles.size())
<< "Connected port has no profiles: " << connectedPort.toString();
if (portProfiles.empty()) {
const auto routableMixPorts =
moduleConfig->getRoutableMixPortsForDevicePort(connectedPort);
bool hasMixPortWithStaticProfile = false;
for (const auto& mixPort : routableMixPorts) {
const auto& mixPortProfiles = mixPort.profiles;
if (!mixPortProfiles.empty() &&
!std::all_of(mixPortProfiles.begin(), mixPortProfiles.end(),
[](const auto& profile) {
return profile.format.type == AudioFormatType::DEFAULT;
})) {
hasMixPortWithStaticProfile = true;
break;
}
}
EXPECT_TRUE(hasMixPortWithStaticProfile)
<< "Connected port has no profiles and no routable mix ports with profiles: "
<< connectedPort.toString();
}
const auto dynamicProfileIt =
std::find_if(portProfiles.begin(), portProfiles.end(), [](const auto& profile) {
return profile.format.type == AudioFormatType::DEFAULT;
@ -1586,7 +1603,8 @@ TEST_P(AudioCoreModule, ResetAudioPortConfigToInitialValue) {
EXPECT_NE(portConfigsAfter.end(), afterIt)
<< " port config ID " << c.id << " was removed by reset";
if (afterIt != portConfigsAfter.end()) {
EXPECT_EQ(c, *afterIt);
EXPECT_TRUE(c == *afterIt)
<< "Expected: " << c.toString() << "; Actual: " << afterIt->toString();
}
}
}