blob: 1c6f90a021d890a140b6a003eb712f5deebe0519 [file] [log] [blame]
Mikhail Naganovdf5adfd2021-11-11 22:09:22 +00001/*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <algorithm>
18#include <set>
19
20#define LOG_TAG "AHAL_Module"
Mikhail Naganovdf5adfd2021-11-11 22:09:22 +000021#include <android-base/logging.h>
22
23#include <aidl/android/media/audio/common/AudioOutputFlags.h>
24
25#include "core-impl/Module.h"
26#include "core-impl/utils.h"
27
28using aidl::android::hardware::audio::common::SinkMetadata;
29using aidl::android::hardware::audio::common::SourceMetadata;
Mikhail Naganov6a4872d2022-06-15 21:39:04 +000030using aidl::android::media::audio::common::AudioChannelLayout;
Mikhail Naganovdf5adfd2021-11-11 22:09:22 +000031using aidl::android::media::audio::common::AudioFormatDescription;
Mikhail Naganov6a4872d2022-06-15 21:39:04 +000032using aidl::android::media::audio::common::AudioFormatType;
Mikhail Naganovdf5adfd2021-11-11 22:09:22 +000033using aidl::android::media::audio::common::AudioIoFlags;
34using aidl::android::media::audio::common::AudioOffloadInfo;
35using aidl::android::media::audio::common::AudioOutputFlags;
36using aidl::android::media::audio::common::AudioPort;
37using aidl::android::media::audio::common::AudioPortConfig;
38using aidl::android::media::audio::common::AudioPortExt;
39using aidl::android::media::audio::common::AudioProfile;
40using aidl::android::media::audio::common::Int;
Mikhail Naganov6a4872d2022-06-15 21:39:04 +000041using aidl::android::media::audio::common::PcmType;
Mikhail Naganovdf5adfd2021-11-11 22:09:22 +000042
43namespace aidl::android::hardware::audio::core {
44
45namespace {
46
47bool generateDefaultPortConfig(const AudioPort& port, AudioPortConfig* config) {
48 *config = {};
49 config->portId = port.id;
50 if (port.profiles.empty()) {
51 LOG(ERROR) << __func__ << ": port " << port.id << " has no profiles";
52 return false;
53 }
54 const auto& profile = port.profiles.begin();
55 config->format = profile->format;
56 if (profile->channelMasks.empty()) {
57 LOG(ERROR) << __func__ << ": the first profile in port " << port.id
58 << " has no channel masks";
59 return false;
60 }
61 config->channelMask = *profile->channelMasks.begin();
62 if (profile->sampleRates.empty()) {
63 LOG(ERROR) << __func__ << ": the first profile in port " << port.id
64 << " has no sample rates";
65 return false;
66 }
67 Int sampleRate;
68 sampleRate.value = *profile->sampleRates.begin();
69 config->sampleRate = sampleRate;
70 config->flags = port.flags;
71 config->ext = port.ext;
72 return true;
73}
74
Mikhail Naganov6a4872d2022-06-15 21:39:04 +000075constexpr size_t getPcmSampleSizeInBytes(PcmType pcm) {
76 switch (pcm) {
77 case PcmType::UINT_8_BIT:
78 return 1;
79 case PcmType::INT_16_BIT:
80 return 2;
81 case PcmType::INT_32_BIT:
82 return 4;
83 case PcmType::FIXED_Q_8_24:
84 return 4;
85 case PcmType::FLOAT_32_BIT:
86 return 4;
87 case PcmType::INT_24_BIT:
88 return 3;
89 }
90 return 0;
91}
92
93constexpr size_t getChannelCount(const AudioChannelLayout& layout) {
94 using Tag = AudioChannelLayout::Tag;
95 switch (layout.getTag()) {
96 case Tag::none:
97 return 0;
98 case Tag::invalid:
99 return 0;
100 case Tag::indexMask:
101 return __builtin_popcount(layout.get<Tag::indexMask>());
102 case Tag::layoutMask:
103 return __builtin_popcount(layout.get<Tag::layoutMask>());
104 case Tag::voiceMask:
105 return __builtin_popcount(layout.get<Tag::voiceMask>());
106 }
107 return 0;
108}
109
110size_t getFrameSizeInBytes(const AudioFormatDescription& format, const AudioChannelLayout& layout) {
111 if (format.type == AudioFormatType::PCM) {
112 return getPcmSampleSizeInBytes(format.pcm) * getChannelCount(layout);
113 }
114 // For non-PCM formats always use frame size of 1.
115 return 1;
116}
117
Mikhail Naganovdf5adfd2021-11-11 22:09:22 +0000118bool findAudioProfile(const AudioPort& port, const AudioFormatDescription& format,
119 AudioProfile* profile) {
120 if (auto profilesIt =
121 find_if(port.profiles.begin(), port.profiles.end(),
122 [&format](const auto& profile) { return profile.format == format; });
123 profilesIt != port.profiles.end()) {
124 *profile = *profilesIt;
125 return true;
126 }
127 return false;
128}
Mikhail Naganov00603d12022-05-02 22:52:13 +0000129
Mikhail Naganovdf5adfd2021-11-11 22:09:22 +0000130} // namespace
131
132void Module::cleanUpPatch(int32_t patchId) {
133 erase_all_values(mPatches, std::set<int32_t>{patchId});
134}
135
136void Module::cleanUpPatches(int32_t portConfigId) {
137 auto& patches = getConfig().patches;
138 if (patches.size() == 0) return;
139 auto range = mPatches.equal_range(portConfigId);
140 for (auto it = range.first; it != range.second; ++it) {
141 auto patchIt = findById<AudioPatch>(patches, it->second);
142 if (patchIt != patches.end()) {
143 erase_if(patchIt->sourcePortConfigIds,
144 [portConfigId](auto e) { return e == portConfigId; });
145 erase_if(patchIt->sinkPortConfigIds,
146 [portConfigId](auto e) { return e == portConfigId; });
147 }
148 }
149 std::set<int32_t> erasedPatches;
150 for (size_t i = patches.size() - 1; i != 0; --i) {
151 const auto& patch = patches[i];
152 if (patch.sourcePortConfigIds.empty() || patch.sinkPortConfigIds.empty()) {
153 erasedPatches.insert(patch.id);
154 patches.erase(patches.begin() + i);
155 }
156 }
157 erase_all_values(mPatches, erasedPatches);
158}
159
Mikhail Naganov6a4872d2022-06-15 21:39:04 +0000160ndk::ScopedAStatus Module::createStreamDescriptor(int32_t in_portConfigId,
161 int64_t in_bufferSizeFrames,
162 StreamDescriptor* out_descriptor) {
163 if (in_bufferSizeFrames <= 0) {
164 LOG(ERROR) << __func__ << ": non-positive buffer size " << in_bufferSizeFrames;
165 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
166 }
167 if (in_bufferSizeFrames < kMinimumStreamBufferSizeFrames) {
168 LOG(ERROR) << __func__ << ": insufficient buffer size " << in_bufferSizeFrames
169 << ", must be at least " << kMinimumStreamBufferSizeFrames;
170 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
171 }
172 auto& configs = getConfig().portConfigs;
173 auto portConfigIt = findById<AudioPortConfig>(configs, in_portConfigId);
174 // Since 'createStreamDescriptor' is an internal method, it is assumed that
175 // validity of the portConfigId has already been checked.
176 const size_t frameSize =
177 getFrameSizeInBytes(portConfigIt->format.value(), portConfigIt->channelMask.value());
178 if (frameSize == 0) {
179 LOG(ERROR) << __func__ << ": could not calculate frame size for port config "
180 << portConfigIt->toString();
181 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
182 }
183 LOG(DEBUG) << __func__ << ": frame size " << frameSize << " bytes";
184 if (frameSize > kMaximumStreamBufferSizeBytes / in_bufferSizeFrames) {
185 LOG(ERROR) << __func__ << ": buffer size " << in_bufferSizeFrames
186 << " frames is too large, maximum size is "
187 << kMaximumStreamBufferSizeBytes / frameSize;
188 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
189 }
190 (void)out_descriptor;
191 return ndk::ScopedAStatus::ok();
192}
193
194ndk::ScopedAStatus Module::findPortIdForNewStream(int32_t in_portConfigId, AudioPort** port) {
195 auto& configs = getConfig().portConfigs;
196 auto portConfigIt = findById<AudioPortConfig>(configs, in_portConfigId);
197 if (portConfigIt == configs.end()) {
198 LOG(ERROR) << __func__ << ": existing port config id " << in_portConfigId << " not found";
199 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
200 }
201 const int32_t portId = portConfigIt->portId;
202 // In our implementation, configs of mix ports always have unique IDs.
203 CHECK(portId != in_portConfigId);
204 auto& ports = getConfig().ports;
205 auto portIt = findById<AudioPort>(ports, portId);
206 if (portIt == ports.end()) {
207 LOG(ERROR) << __func__ << ": port id " << portId << " used by port config id "
208 << in_portConfigId << " not found";
209 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
210 }
211 if (mStreams.count(in_portConfigId) != 0) {
212 LOG(ERROR) << __func__ << ": port config id " << in_portConfigId
213 << " already has a stream opened on it";
214 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
215 }
216 if (portIt->ext.getTag() != AudioPortExt::Tag::mix) {
217 LOG(ERROR) << __func__ << ": port config id " << in_portConfigId
218 << " does not correspond to a mix port";
219 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
220 }
221 const int32_t maxOpenStreamCount = portIt->ext.get<AudioPortExt::Tag::mix>().maxOpenStreamCount;
222 if (maxOpenStreamCount != 0 && mStreams.count(portId) >= maxOpenStreamCount) {
223 LOG(ERROR) << __func__ << ": port id " << portId
224 << " has already reached maximum allowed opened stream count: "
225 << maxOpenStreamCount;
226 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
227 }
228 *port = &(*portIt);
229 return ndk::ScopedAStatus::ok();
230}
231
Mikhail Naganovdf5adfd2021-11-11 22:09:22 +0000232internal::Configuration& Module::getConfig() {
233 if (!mConfig) {
234 mConfig.reset(new internal::Configuration(internal::getNullPrimaryConfiguration()));
235 }
236 return *mConfig;
237}
238
239void Module::registerPatch(const AudioPatch& patch) {
240 auto& configs = getConfig().portConfigs;
241 auto do_insert = [&](const std::vector<int32_t>& portConfigIds) {
242 for (auto portConfigId : portConfigIds) {
243 auto configIt = findById<AudioPortConfig>(configs, portConfigId);
244 if (configIt != configs.end()) {
245 mPatches.insert(std::pair{portConfigId, patch.id});
246 if (configIt->portId != portConfigId) {
247 mPatches.insert(std::pair{configIt->portId, patch.id});
248 }
249 }
250 };
251 };
252 do_insert(patch.sourcePortConfigIds);
253 do_insert(patch.sinkPortConfigIds);
254}
255
Mikhail Naganov00603d12022-05-02 22:52:13 +0000256ndk::ScopedAStatus Module::setModuleDebug(
257 const ::aidl::android::hardware::audio::core::ModuleDebug& in_debug) {
258 LOG(DEBUG) << __func__ << ": old flags:" << mDebug.toString()
259 << ", new flags: " << in_debug.toString();
260 if (mDebug.simulateDeviceConnections != in_debug.simulateDeviceConnections &&
261 !mConnectedDevicePorts.empty()) {
262 LOG(ERROR) << __func__ << ": attempting to change device connections simulation "
263 << "while having external devices connected";
264 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
265 }
266 mDebug = in_debug;
267 return ndk::ScopedAStatus::ok();
268}
269
270ndk::ScopedAStatus Module::connectExternalDevice(const AudioPort& in_templateIdAndAdditionalData,
271 AudioPort* _aidl_return) {
272 const int32_t templateId = in_templateIdAndAdditionalData.id;
273 auto& ports = getConfig().ports;
274 AudioPort connectedPort;
275 { // Scope the template port so that we don't accidentally modify it.
276 auto templateIt = findById<AudioPort>(ports, templateId);
277 if (templateIt == ports.end()) {
278 LOG(ERROR) << __func__ << ": port id " << templateId << " not found";
279 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
280 }
281 if (templateIt->ext.getTag() != AudioPortExt::Tag::device) {
282 LOG(ERROR) << __func__ << ": port id " << templateId << " is not a device port";
283 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
284 }
285 if (!templateIt->profiles.empty()) {
286 LOG(ERROR) << __func__ << ": port id " << templateId
287 << " does not have dynamic profiles";
288 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
289 }
290 auto& templateDevicePort = templateIt->ext.get<AudioPortExt::Tag::device>();
291 if (templateDevicePort.device.type.connection.empty()) {
292 LOG(ERROR) << __func__ << ": port id " << templateId << " is permanently attached";
293 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
294 }
295 // Postpone id allocation until we ensure that there are no client errors.
296 connectedPort = *templateIt;
297 connectedPort.extraAudioDescriptors = in_templateIdAndAdditionalData.extraAudioDescriptors;
298 const auto& inputDevicePort =
299 in_templateIdAndAdditionalData.ext.get<AudioPortExt::Tag::device>();
300 auto& connectedDevicePort = connectedPort.ext.get<AudioPortExt::Tag::device>();
301 connectedDevicePort.device.address = inputDevicePort.device.address;
302 LOG(DEBUG) << __func__ << ": device port " << connectedPort.id << " device set to "
303 << connectedDevicePort.device.toString();
304 // Check if there is already a connected port with for the same external device.
305 for (auto connectedPortId : mConnectedDevicePorts) {
306 auto connectedPortIt = findById<AudioPort>(ports, connectedPortId);
307 if (connectedPortIt->ext.get<AudioPortExt::Tag::device>().device ==
308 connectedDevicePort.device) {
309 LOG(ERROR) << __func__ << ": device " << connectedDevicePort.device.toString()
310 << " is already connected at the device port id " << connectedPortId;
311 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
312 }
313 }
314 }
315
316 if (!mDebug.simulateDeviceConnections) {
317 // In a real HAL here we would attempt querying the profiles from the device.
318 LOG(ERROR) << __func__ << ": failed to query supported device profiles";
319 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
320 }
321
322 connectedPort.id = ++getConfig().nextPortId;
323 mConnectedDevicePorts.insert(connectedPort.id);
324 LOG(DEBUG) << __func__ << ": template port " << templateId << " external device connected, "
325 << "connected port ID " << connectedPort.id;
326 auto& connectedProfiles = getConfig().connectedProfiles;
327 if (auto connectedProfilesIt = connectedProfiles.find(templateId);
328 connectedProfilesIt != connectedProfiles.end()) {
329 connectedPort.profiles = connectedProfilesIt->second;
330 }
331 ports.push_back(connectedPort);
332 *_aidl_return = std::move(connectedPort);
333
334 std::vector<AudioRoute> newRoutes;
335 auto& routes = getConfig().routes;
336 for (auto& r : routes) {
337 if (r.sinkPortId == templateId) {
338 AudioRoute newRoute;
339 newRoute.sourcePortIds = r.sourcePortIds;
340 newRoute.sinkPortId = connectedPort.id;
341 newRoute.isExclusive = r.isExclusive;
342 newRoutes.push_back(std::move(newRoute));
343 } else {
344 auto& srcs = r.sourcePortIds;
345 if (std::find(srcs.begin(), srcs.end(), templateId) != srcs.end()) {
346 srcs.push_back(connectedPort.id);
347 }
348 }
349 }
350 routes.insert(routes.end(), newRoutes.begin(), newRoutes.end());
351
352 return ndk::ScopedAStatus::ok();
353}
354
355ndk::ScopedAStatus Module::disconnectExternalDevice(int32_t in_portId) {
356 auto& ports = getConfig().ports;
357 auto portIt = findById<AudioPort>(ports, in_portId);
358 if (portIt == ports.end()) {
359 LOG(ERROR) << __func__ << ": port id " << in_portId << " not found";
360 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
361 }
362 if (portIt->ext.getTag() != AudioPortExt::Tag::device) {
363 LOG(ERROR) << __func__ << ": port id " << in_portId << " is not a device port";
364 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
365 }
366 if (mConnectedDevicePorts.count(in_portId) == 0) {
367 LOG(ERROR) << __func__ << ": port id " << in_portId << " is not a connected device port";
368 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
369 }
370 auto& configs = getConfig().portConfigs;
371 auto& initials = getConfig().initialConfigs;
372 auto configIt = std::find_if(configs.begin(), configs.end(), [&](const auto& config) {
373 if (config.portId == in_portId) {
374 // Check if the configuration was provided by the client.
375 const auto& initialIt = findById<AudioPortConfig>(initials, config.id);
376 return initialIt == initials.end() || config != *initialIt;
377 }
378 return false;
379 });
380 if (configIt != configs.end()) {
381 LOG(ERROR) << __func__ << ": port id " << in_portId << " has a non-default config with id "
382 << configIt->id;
383 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
384 }
385 ports.erase(portIt);
386 mConnectedDevicePorts.erase(in_portId);
387 LOG(DEBUG) << __func__ << ": connected device port " << in_portId << " released";
388
389 auto& routes = getConfig().routes;
390 for (auto routesIt = routes.begin(); routesIt != routes.end();) {
391 if (routesIt->sinkPortId == in_portId) {
392 routesIt = routes.erase(routesIt);
393 } else {
394 // Note: the list of sourcePortIds can't become empty because there must
395 // be the id of the template port in the route.
396 erase_if(routesIt->sourcePortIds, [in_portId](auto src) { return src == in_portId; });
397 ++routesIt;
398 }
399 }
400
401 return ndk::ScopedAStatus::ok();
402}
403
Mikhail Naganovdf5adfd2021-11-11 22:09:22 +0000404ndk::ScopedAStatus Module::getAudioPatches(std::vector<AudioPatch>* _aidl_return) {
405 *_aidl_return = getConfig().patches;
406 LOG(DEBUG) << __func__ << ": returning " << _aidl_return->size() << " patches";
407 return ndk::ScopedAStatus::ok();
408}
409
410ndk::ScopedAStatus Module::getAudioPort(int32_t in_portId, AudioPort* _aidl_return) {
411 auto& ports = getConfig().ports;
412 auto portIt = findById<AudioPort>(ports, in_portId);
413 if (portIt != ports.end()) {
414 *_aidl_return = *portIt;
415 LOG(DEBUG) << __func__ << ": returning port by id " << in_portId;
416 return ndk::ScopedAStatus::ok();
417 }
418 LOG(ERROR) << __func__ << ": port id " << in_portId << " not found";
419 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
420}
421
422ndk::ScopedAStatus Module::getAudioPortConfigs(std::vector<AudioPortConfig>* _aidl_return) {
423 *_aidl_return = getConfig().portConfigs;
424 LOG(DEBUG) << __func__ << ": returning " << _aidl_return->size() << " port configs";
425 return ndk::ScopedAStatus::ok();
426}
427
428ndk::ScopedAStatus Module::getAudioPorts(std::vector<AudioPort>* _aidl_return) {
429 *_aidl_return = getConfig().ports;
430 LOG(DEBUG) << __func__ << ": returning " << _aidl_return->size() << " ports";
431 return ndk::ScopedAStatus::ok();
432}
433
434ndk::ScopedAStatus Module::getAudioRoutes(std::vector<AudioRoute>* _aidl_return) {
435 *_aidl_return = getConfig().routes;
436 LOG(DEBUG) << __func__ << ": returning " << _aidl_return->size() << " routes";
437 return ndk::ScopedAStatus::ok();
438}
439
Mikhail Naganov00603d12022-05-02 22:52:13 +0000440ndk::ScopedAStatus Module::getAudioRoutesForAudioPort(int32_t in_portId,
441 std::vector<AudioRoute>* _aidl_return) {
442 auto& ports = getConfig().ports;
443 if (auto portIt = findById<AudioPort>(ports, in_portId); portIt == ports.end()) {
444 LOG(ERROR) << __func__ << ": port id " << in_portId << " not found";
445 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
446 }
447 auto& routes = getConfig().routes;
448 std::copy_if(routes.begin(), routes.end(), std::back_inserter(*_aidl_return),
449 [&](const auto& r) {
450 const auto& srcs = r.sourcePortIds;
451 return r.sinkPortId == in_portId ||
452 std::find(srcs.begin(), srcs.end(), in_portId) != srcs.end();
453 });
454 return ndk::ScopedAStatus::ok();
455}
456
Mikhail Naganov6a4872d2022-06-15 21:39:04 +0000457ndk::ScopedAStatus Module::openInputStream(const OpenInputStreamArguments& in_args,
458 OpenInputStreamReturn* _aidl_return) {
459 LOG(DEBUG) << __func__ << ": port config id " << in_args.portConfigId << ", buffer size "
460 << in_args.bufferSizeFrames << " frames";
461 AudioPort* port = nullptr;
462 if (auto status = findPortIdForNewStream(in_args.portConfigId, &port); !status.isOk()) {
463 return status;
Mikhail Naganovdf5adfd2021-11-11 22:09:22 +0000464 }
Mikhail Naganov6a4872d2022-06-15 21:39:04 +0000465 if (port->flags.getTag() != AudioIoFlags::Tag::input) {
466 LOG(ERROR) << __func__ << ": port config id " << in_args.portConfigId
Mikhail Naganovdf5adfd2021-11-11 22:09:22 +0000467 << " does not correspond to an input mix port";
468 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
469 }
Mikhail Naganov6a4872d2022-06-15 21:39:04 +0000470 if (auto status = createStreamDescriptor(in_args.portConfigId, in_args.bufferSizeFrames,
471 &_aidl_return->desc);
472 !status.isOk()) {
473 return status;
Mikhail Naganovdf5adfd2021-11-11 22:09:22 +0000474 }
Mikhail Naganov6a4872d2022-06-15 21:39:04 +0000475 auto stream = ndk::SharedRefBase::make<StreamIn>(in_args.sinkMetadata);
476 mStreams.insert(port->id, in_args.portConfigId, StreamWrapper(stream));
477 _aidl_return->stream = std::move(stream);
Mikhail Naganovdf5adfd2021-11-11 22:09:22 +0000478 return ndk::ScopedAStatus::ok();
479}
480
Mikhail Naganov6a4872d2022-06-15 21:39:04 +0000481ndk::ScopedAStatus Module::openOutputStream(const OpenOutputStreamArguments& in_args,
482 OpenOutputStreamReturn* _aidl_return) {
483 LOG(DEBUG) << __func__ << ": port config id " << in_args.portConfigId << ", has offload info? "
484 << (in_args.offloadInfo.has_value()) << ", buffer size " << in_args.bufferSizeFrames
485 << " frames";
486 AudioPort* port = nullptr;
487 if (auto status = findPortIdForNewStream(in_args.portConfigId, &port); !status.isOk()) {
488 return status;
Mikhail Naganovdf5adfd2021-11-11 22:09:22 +0000489 }
Mikhail Naganov6a4872d2022-06-15 21:39:04 +0000490 if (port->flags.getTag() != AudioIoFlags::Tag::output) {
491 LOG(ERROR) << __func__ << ": port config id " << in_args.portConfigId
Mikhail Naganovdf5adfd2021-11-11 22:09:22 +0000492 << " does not correspond to an output mix port";
493 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
494 }
Mikhail Naganov6a4872d2022-06-15 21:39:04 +0000495 if ((port->flags.get<AudioIoFlags::Tag::output>() &
496 1 << static_cast<int32_t>(AudioOutputFlags::COMPRESS_OFFLOAD)) != 0 &&
497 !in_args.offloadInfo.has_value()) {
498 LOG(ERROR) << __func__ << ": port id " << port->id
Mikhail Naganov111e0ce2022-06-17 21:41:19 +0000499 << " has COMPRESS_OFFLOAD flag set, requires offload info";
500 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
501 }
Mikhail Naganov6a4872d2022-06-15 21:39:04 +0000502 if (auto status = createStreamDescriptor(in_args.portConfigId, in_args.bufferSizeFrames,
503 &_aidl_return->desc);
504 !status.isOk()) {
505 return status;
Mikhail Naganovdf5adfd2021-11-11 22:09:22 +0000506 }
Mikhail Naganov6a4872d2022-06-15 21:39:04 +0000507 auto stream = ndk::SharedRefBase::make<StreamOut>(in_args.sourceMetadata, in_args.offloadInfo);
508 mStreams.insert(port->id, in_args.portConfigId, StreamWrapper(stream));
509 _aidl_return->stream = std::move(stream);
Mikhail Naganovdf5adfd2021-11-11 22:09:22 +0000510 return ndk::ScopedAStatus::ok();
511}
512
513ndk::ScopedAStatus Module::setAudioPatch(const AudioPatch& in_requested, AudioPatch* _aidl_return) {
Mikhail Naganov16db9b72022-06-17 21:36:18 +0000514 LOG(DEBUG) << __func__ << ": requested patch " << in_requested.toString();
Mikhail Naganovdf5adfd2021-11-11 22:09:22 +0000515 if (in_requested.sourcePortConfigIds.empty()) {
516 LOG(ERROR) << __func__ << ": requested patch has empty sources list";
517 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
518 }
519 if (!all_unique<int32_t>(in_requested.sourcePortConfigIds)) {
520 LOG(ERROR) << __func__ << ": requested patch has duplicate ids in the sources list";
521 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
522 }
523 if (in_requested.sinkPortConfigIds.empty()) {
524 LOG(ERROR) << __func__ << ": requested patch has empty sinks list";
525 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
526 }
527 if (!all_unique<int32_t>(in_requested.sinkPortConfigIds)) {
528 LOG(ERROR) << __func__ << ": requested patch has duplicate ids in the sinks list";
529 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
530 }
531
532 auto& configs = getConfig().portConfigs;
533 std::vector<int32_t> missingIds;
534 auto sources =
535 selectByIds<AudioPortConfig>(configs, in_requested.sourcePortConfigIds, &missingIds);
536 if (!missingIds.empty()) {
537 LOG(ERROR) << __func__ << ": following source port config ids not found: "
538 << ::android::internal::ToString(missingIds);
539 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
540 }
541 auto sinks = selectByIds<AudioPortConfig>(configs, in_requested.sinkPortConfigIds, &missingIds);
542 if (!missingIds.empty()) {
543 LOG(ERROR) << __func__ << ": following sink port config ids not found: "
544 << ::android::internal::ToString(missingIds);
545 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
546 }
547 // bool indicates whether a non-exclusive route is available.
548 // If only an exclusive route is available, that means the patch can not be
549 // established if there is any other patch which currently uses the sink port.
550 std::map<int32_t, bool> allowedSinkPorts;
551 auto& routes = getConfig().routes;
552 for (auto src : sources) {
553 for (const auto& r : routes) {
554 const auto& srcs = r.sourcePortIds;
555 if (std::find(srcs.begin(), srcs.end(), src->portId) != srcs.end()) {
556 if (!allowedSinkPorts[r.sinkPortId]) { // prefer non-exclusive
557 allowedSinkPorts[r.sinkPortId] = !r.isExclusive;
558 }
559 }
560 }
561 }
562 for (auto sink : sinks) {
563 if (allowedSinkPorts.count(sink->portId) == 0) {
564 LOG(ERROR) << __func__ << ": there is no route to the sink port id " << sink->portId;
565 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
566 }
567 }
568
569 auto& patches = getConfig().patches;
570 auto existing = patches.end();
571 std::optional<decltype(mPatches)> patchesBackup;
572 if (in_requested.id != 0) {
573 existing = findById<AudioPatch>(patches, in_requested.id);
574 if (existing != patches.end()) {
575 patchesBackup = mPatches;
576 cleanUpPatch(existing->id);
577 } else {
578 LOG(ERROR) << __func__ << ": not found existing patch id " << in_requested.id;
579 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
580 }
581 }
582 // Validate the requested patch.
583 for (const auto& [sinkPortId, nonExclusive] : allowedSinkPorts) {
584 if (!nonExclusive && mPatches.count(sinkPortId) != 0) {
585 LOG(ERROR) << __func__ << ": sink port id " << sinkPortId
586 << "is exclusive and is already used by some other patch";
587 if (patchesBackup.has_value()) {
588 mPatches = std::move(*patchesBackup);
589 }
590 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
591 }
592 }
593 *_aidl_return = in_requested;
Mikhail Naganov6a4872d2022-06-15 21:39:04 +0000594 _aidl_return->minimumStreamBufferSizeFrames = kMinimumStreamBufferSizeFrames;
595 _aidl_return->latenciesMs.clear();
596 _aidl_return->latenciesMs.insert(_aidl_return->latenciesMs.end(),
597 _aidl_return->sinkPortConfigIds.size(), kLatencyMs);
Mikhail Naganovdf5adfd2021-11-11 22:09:22 +0000598 if (existing == patches.end()) {
599 _aidl_return->id = getConfig().nextPatchId++;
600 patches.push_back(*_aidl_return);
601 existing = patches.begin() + (patches.size() - 1);
602 } else {
603 *existing = *_aidl_return;
604 }
605 registerPatch(*existing);
606 LOG(DEBUG) << __func__ << ": created or updated patch id " << _aidl_return->id;
607 return ndk::ScopedAStatus::ok();
608}
609
610ndk::ScopedAStatus Module::setAudioPortConfig(const AudioPortConfig& in_requested,
611 AudioPortConfig* out_suggested, bool* _aidl_return) {
612 LOG(DEBUG) << __func__ << ": requested " << in_requested.toString();
613 auto& configs = getConfig().portConfigs;
614 auto existing = configs.end();
615 if (in_requested.id != 0) {
616 if (existing = findById<AudioPortConfig>(configs, in_requested.id);
617 existing == configs.end()) {
618 LOG(ERROR) << __func__ << ": existing port config id " << in_requested.id
619 << " not found";
620 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
621 }
622 }
623
624 const int portId = existing != configs.end() ? existing->portId : in_requested.portId;
625 if (portId == 0) {
626 LOG(ERROR) << __func__ << ": input port config does not specify portId";
627 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
628 }
629 auto& ports = getConfig().ports;
630 auto portIt = findById<AudioPort>(ports, portId);
631 if (portIt == ports.end()) {
632 LOG(ERROR) << __func__ << ": input port config points to non-existent portId " << portId;
633 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
634 }
635 if (existing != configs.end()) {
636 *out_suggested = *existing;
637 } else {
638 AudioPortConfig newConfig;
639 if (generateDefaultPortConfig(*portIt, &newConfig)) {
640 *out_suggested = newConfig;
641 } else {
642 LOG(ERROR) << __func__ << ": unable generate a default config for port " << portId;
643 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
644 }
645 }
646 // From this moment, 'out_suggested' is either an existing port config,
647 // or a new generated config. Now attempt to update it according to the specified
648 // fields of 'in_requested'.
649
650 bool requestedIsValid = true, requestedIsFullySpecified = true;
651
652 AudioIoFlags portFlags = portIt->flags;
653 if (in_requested.flags.has_value()) {
654 if (in_requested.flags.value() != portFlags) {
655 LOG(WARNING) << __func__ << ": requested flags "
656 << in_requested.flags.value().toString() << " do not match port's "
657 << portId << " flags " << portFlags.toString();
658 requestedIsValid = false;
659 }
660 } else {
661 requestedIsFullySpecified = false;
662 }
663
664 AudioProfile portProfile;
665 if (in_requested.format.has_value()) {
666 const auto& format = in_requested.format.value();
667 if (findAudioProfile(*portIt, format, &portProfile)) {
668 out_suggested->format = format;
669 } else {
670 LOG(WARNING) << __func__ << ": requested format " << format.toString()
671 << " is not found in port's " << portId << " profiles";
672 requestedIsValid = false;
673 }
674 } else {
675 requestedIsFullySpecified = false;
676 }
677 if (!findAudioProfile(*portIt, out_suggested->format.value(), &portProfile)) {
678 LOG(ERROR) << __func__ << ": port " << portId << " does not support format "
679 << out_suggested->format.value().toString() << " anymore";
680 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
681 }
682
683 if (in_requested.channelMask.has_value()) {
684 const auto& channelMask = in_requested.channelMask.value();
685 if (find(portProfile.channelMasks.begin(), portProfile.channelMasks.end(), channelMask) !=
686 portProfile.channelMasks.end()) {
687 out_suggested->channelMask = channelMask;
688 } else {
689 LOG(WARNING) << __func__ << ": requested channel mask " << channelMask.toString()
690 << " is not supported for the format " << portProfile.format.toString()
691 << " by the port " << portId;
692 requestedIsValid = false;
693 }
694 } else {
695 requestedIsFullySpecified = false;
696 }
697
698 if (in_requested.sampleRate.has_value()) {
699 const auto& sampleRate = in_requested.sampleRate.value();
700 if (find(portProfile.sampleRates.begin(), portProfile.sampleRates.end(),
701 sampleRate.value) != portProfile.sampleRates.end()) {
702 out_suggested->sampleRate = sampleRate;
703 } else {
704 LOG(WARNING) << __func__ << ": requested sample rate " << sampleRate.value
705 << " is not supported for the format " << portProfile.format.toString()
706 << " by the port " << portId;
707 requestedIsValid = false;
708 }
709 } else {
710 requestedIsFullySpecified = false;
711 }
712
713 if (in_requested.gain.has_value()) {
714 // Let's pretend that gain can always be applied.
715 out_suggested->gain = in_requested.gain.value();
716 }
717
718 if (existing == configs.end() && requestedIsValid && requestedIsFullySpecified) {
719 out_suggested->id = getConfig().nextPortId++;
720 configs.push_back(*out_suggested);
721 *_aidl_return = true;
722 LOG(DEBUG) << __func__ << ": created new port config " << out_suggested->toString();
723 } else if (existing != configs.end() && requestedIsValid) {
724 *existing = *out_suggested;
725 *_aidl_return = true;
726 LOG(DEBUG) << __func__ << ": updated port config " << out_suggested->toString();
727 } else {
728 LOG(DEBUG) << __func__ << ": not applied; existing config ? " << (existing != configs.end())
729 << "; requested is valid? " << requestedIsValid << ", fully specified? "
730 << requestedIsFullySpecified;
731 *_aidl_return = false;
732 }
733 return ndk::ScopedAStatus::ok();
734}
735
736ndk::ScopedAStatus Module::resetAudioPatch(int32_t in_patchId) {
737 auto& patches = getConfig().patches;
738 auto patchIt = findById<AudioPatch>(patches, in_patchId);
739 if (patchIt != patches.end()) {
740 cleanUpPatch(patchIt->id);
741 patches.erase(patchIt);
742 LOG(DEBUG) << __func__ << ": erased patch " << in_patchId;
743 return ndk::ScopedAStatus::ok();
744 }
745 LOG(ERROR) << __func__ << ": patch id " << in_patchId << " not found";
746 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
747}
748
749ndk::ScopedAStatus Module::resetAudioPortConfig(int32_t in_portConfigId) {
750 auto& configs = getConfig().portConfigs;
751 auto configIt = findById<AudioPortConfig>(configs, in_portConfigId);
752 if (configIt != configs.end()) {
753 if (mStreams.count(in_portConfigId) != 0) {
754 LOG(ERROR) << __func__ << ": port config id " << in_portConfigId
755 << " has a stream opened on it";
756 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
757 }
758 auto patchIt = mPatches.find(in_portConfigId);
759 if (patchIt != mPatches.end()) {
760 LOG(ERROR) << __func__ << ": port config id " << in_portConfigId
761 << " is used by the patch with id " << patchIt->second;
762 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
763 }
764 auto& initials = getConfig().initialConfigs;
765 auto initialIt = findById<AudioPortConfig>(initials, in_portConfigId);
766 if (initialIt == initials.end()) {
767 configs.erase(configIt);
768 LOG(DEBUG) << __func__ << ": erased port config " << in_portConfigId;
769 } else if (*configIt != *initialIt) {
770 *configIt = *initialIt;
771 LOG(DEBUG) << __func__ << ": reset port config " << in_portConfigId;
772 }
773 return ndk::ScopedAStatus::ok();
774 }
775 LOG(ERROR) << __func__ << ": port config id " << in_portConfigId << " not found";
776 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
777}
778
779} // namespace aidl::android::hardware::audio::core