1 /*
2  * Copyright 2021 HIMSA II K/S - www.himsa.com. Represented by EHIMA -
3  * www.ehima.com
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 #include "audio_hal_interface/le_audio_software_host.h"
19 
20 #include <bluetooth/log.h>
21 #include <errno.h>
22 #include <grp.h>
23 #include <sys/stat.h>
24 
25 #include "audio_hal_interface/le_audio_software.h"
26 #include "audio_hal_interface/le_audio_software_host_transport.h"
27 #include "bta/include/bta_le_audio_api.h"
28 #include "bta/le_audio/codec_manager.h"
29 #include "udrv/include/uipc.h"
30 
31 #define LEA_DATA_READ_POLL_MS 10
32 #define LEA_HOST_DATA_PATH "/var/run/bluetooth/audio/.lea_data"
33 // TODO(b/198260375): Make LEA data owner group configurable.
34 #define LEA_HOST_DATA_GROUP "bluetooth-audio"
35 
36 using namespace bluetooth;
37 
38 namespace {
39 
40 std::unique_ptr<tUIPC_STATE> lea_uipc = nullptr;
41 
lea_data_cb(tUIPC_CH_ID,tUIPC_EVENT event)42 void lea_data_cb(tUIPC_CH_ID, tUIPC_EVENT event) {
43   switch (event) {
44     case UIPC_OPEN_EVT:
45       log::info("UIPC_OPEN_EVT");
46       /*
47        * Read directly from media task from here on (keep callback for
48        * connection events.
49        */
50       UIPC_Ioctl(*lea_uipc, UIPC_CH_ID_AV_AUDIO, UIPC_REG_REMOVE_ACTIVE_READSET,
51                  NULL);
52       UIPC_Ioctl(*lea_uipc, UIPC_CH_ID_AV_AUDIO, UIPC_SET_READ_POLL_TMO,
53                  reinterpret_cast<void*>(LEA_DATA_READ_POLL_MS));
54       break;
55     case UIPC_CLOSE_EVT:
56       log::info("UIPC_CLOSE_EVT");
57       break;
58     default:
59       break;
60   }
61 }
62 
lea_data_path_open()63 static void lea_data_path_open() {
64   UIPC_Open(*lea_uipc, UIPC_CH_ID_AV_AUDIO, lea_data_cb, LEA_HOST_DATA_PATH);
65   struct group* grp = getgrnam(LEA_HOST_DATA_GROUP);
66   chmod(LEA_HOST_DATA_PATH, 0770);
67   if (grp) {
68     int res = chown(LEA_HOST_DATA_PATH, -1, grp->gr_gid);
69     if (res == -1) {
70       log::error("failed: {}", strerror(errno));
71     }
72   }
73 }
74 
75 }  // namespace
76 
77 namespace bluetooth {
78 namespace audio {
79 
80 namespace le_audio {
81 
82 // Invoked by audio server when it has audio data to stream.
HostStartRequest()83 bool HostStartRequest() {
84   if (!host::le_audio::LeAudioSinkTransport::instance) {
85     log::warn("instance is null");
86     return false;
87   }
88 
89   host::le_audio::LeAudioSinkTransport::stream_started =
90       btle_stream_started_status::IDLE;
91   host::le_audio::LeAudioSinkTransport::instance->ResetPresentationPosition();
92   return host::le_audio::LeAudioSinkTransport::instance->StartRequest();
93 }
94 
HostStopRequest()95 void HostStopRequest() {
96   if (!host::le_audio::LeAudioSinkTransport::instance) {
97     log::warn("instance is null");
98     return;
99   }
100 
101   host::le_audio::LeAudioSinkTransport::instance->StopRequest();
102 }
103 
GetHostPcmConfig()104 btle_pcm_parameters GetHostPcmConfig() {
105   if (!host::le_audio::LeAudioSinkTransport::instance) {
106     log::warn("instance is null");
107     return {};
108   }
109 
110   auto pcm_params = host::le_audio::LeAudioSinkTransport::instance
111                         ->LeAudioGetSelectedHalPcmConfig();
112 
113   btle_pcm_parameters pcm_config = {
114       .data_interval_us = pcm_params.data_interval_us,
115       .sample_rate = pcm_params.sample_rate,
116       .bits_per_sample = pcm_params.bits_per_sample,
117       .channels_count = pcm_params.channels_count,
118   };
119 
120   return pcm_config;
121 }
122 
123 // Invoked by audio server to request audio data streamed from the peer.
PeerStartRequest()124 bool PeerStartRequest() {
125   if (!host::le_audio::LeAudioSourceTransport::instance) {
126     log::warn("instance is null");
127     return false;
128   }
129 
130   host::le_audio::LeAudioSourceTransport::stream_started =
131       btle_stream_started_status::IDLE;
132   host::le_audio::LeAudioSourceTransport::instance->ResetPresentationPosition();
133   return host::le_audio::LeAudioSourceTransport::instance->StartRequest();
134 }
135 
PeerStopRequest()136 void PeerStopRequest() {
137   if (!host::le_audio::LeAudioSourceTransport::instance) {
138     log::warn("instance is null");
139     return;
140   }
141 
142   host::le_audio::LeAudioSourceTransport::instance->StopRequest();
143 }
144 
GetPeerPcmConfig()145 btle_pcm_parameters GetPeerPcmConfig() {
146   if (!host::le_audio::LeAudioSourceTransport::instance) {
147     log::warn("instance is null");
148     return {};
149   }
150 
151   auto pcm_params = host::le_audio::LeAudioSourceTransport::instance
152                         ->LeAudioGetSelectedHalPcmConfig();
153 
154   btle_pcm_parameters pcm_config = {
155       .data_interval_us = pcm_params.data_interval_us,
156       .sample_rate = pcm_params.sample_rate,
157       .bits_per_sample = pcm_params.bits_per_sample,
158       .channels_count = pcm_params.channels_count,
159   };
160 
161   return pcm_config;
162 }
163 
GetHostStreamStarted()164 btle_stream_started_status GetHostStreamStarted() {
165   return host::le_audio::LeAudioSinkTransport::stream_started;
166 }
167 
GetPeerStreamStarted()168 btle_stream_started_status GetPeerStreamStarted() {
169   return host::le_audio::LeAudioSourceTransport::stream_started;
170 }
171 
SourceMetadataChanged(const source_metadata_v7_t & metadata)172 void SourceMetadataChanged(const source_metadata_v7_t& metadata) {
173   if (host::le_audio::LeAudioSourceTransport::instance) {
174     host::le_audio::LeAudioSourceTransport::instance->SourceMetadataChanged(
175         metadata);
176   }
177 
178   if (host::le_audio::LeAudioSinkTransport::instance) {
179     host::le_audio::LeAudioSinkTransport::instance->SourceMetadataChanged(
180         metadata);
181   }
182 }
183 
SinkMetadataChanged(const sink_metadata_v7_t & metadata)184 void SinkMetadataChanged(const sink_metadata_v7_t& metadata) {
185   if (host::le_audio::LeAudioSourceTransport::instance) {
186     host::le_audio::LeAudioSourceTransport::instance->SinkMetadataChanged(
187         metadata);
188   }
189 
190   if (host::le_audio::LeAudioSinkTransport::instance) {
191     host::le_audio::LeAudioSinkTransport::instance->SinkMetadataChanged(
192         metadata);
193   }
194 }
195 
get_offload_capabilities()196 OffloadCapabilities get_offload_capabilities() {
197   return {
198       std::vector<
199           bluetooth::le_audio::set_configurations::AudioSetConfiguration>(0),
200       std::vector<
201           bluetooth::le_audio::set_configurations::AudioSetConfiguration>(0)};
202 }
203 
GetAidlInterfaceVersion()204 int GetAidlInterfaceVersion() { return 0; }
205 
Cleanup()206 void LeAudioClientInterface::Sink::Cleanup() {
207   log::info("");
208 
209   StopSession();
210 
211   delete host::le_audio::LeAudioSinkTransport::instance;
212   host::le_audio::LeAudioSinkTransport::instance = nullptr;
213 }
214 
SetPcmParameters(const PcmParameters & params)215 void LeAudioClientInterface::Sink::SetPcmParameters(
216     const PcmParameters& params) {
217   if (!host::le_audio::LeAudioSinkTransport::instance) {
218     log::warn("instance is null");
219     return;
220   }
221 
222   log::info(
223       "sample_rate={}, bits_per_sample={}, channels_count={}, "
224       "data_interval_us={}",
225       params.sample_rate, params.bits_per_sample, params.channels_count,
226       params.data_interval_us);
227 
228   host::le_audio::LeAudioSinkTransport::instance
229       ->LeAudioSetSelectedHalPcmConfig(
230           params.sample_rate, params.bits_per_sample, params.channels_count,
231           params.data_interval_us);
232 }
233 
SetRemoteDelay(uint16_t delay_report_ms)234 void LeAudioClientInterface::Sink::SetRemoteDelay(uint16_t delay_report_ms) {
235   if (!host::le_audio::LeAudioSinkTransport::instance) {
236     log::warn("instance is null");
237     return;
238   }
239 
240   log::info("delay_report_ms={} msec", delay_report_ms);
241 
242   host::le_audio::LeAudioSinkTransport::instance->SetRemoteDelay(
243       delay_report_ms);
244 }
245 
StartSession()246 void LeAudioClientInterface::Sink::StartSession() { log::info(""); }
247 
StopSession()248 void LeAudioClientInterface::Sink::StopSession() {
249   log::info("");
250 
251   if (host::le_audio::LeAudioSinkTransport::instance) {
252     host::le_audio::LeAudioSinkTransport::instance->ClearStartRequestState();
253   }
254 
255   host::le_audio::LeAudioSinkTransport::stream_started =
256       btle_stream_started_status::IDLE;
257 }
258 
ConfirmStreamingRequest()259 void LeAudioClientInterface::Sink::ConfirmStreamingRequest() {
260   if (!host::le_audio::LeAudioSinkTransport::instance) {
261     log::warn("instance is null");
262     return;
263   }
264 
265   log::info("");
266 
267   auto instance = host::le_audio::LeAudioSinkTransport::instance;
268   auto start_request_state = instance->GetStartRequestState();
269 
270   switch (start_request_state) {
271     case StartRequestState::IDLE:
272       log::warn(", no pending start stream request");
273       return;
274     case StartRequestState::PENDING_BEFORE_RESUME:
275       log::info("Response before sending PENDING to audio HAL");
276       instance->SetStartRequestState(StartRequestState::CONFIRMED);
277       lea_data_path_open();
278       return;
279     case StartRequestState::PENDING_AFTER_RESUME:
280       log::info("Response after sending PENDING to audio HAL");
281       instance->ClearStartRequestState();
282       lea_data_path_open();
283       host::le_audio::LeAudioSinkTransport::stream_started =
284           btle_stream_started_status::STARTED;
285       return;
286     case StartRequestState::CONFIRMED:
287     case StartRequestState::CANCELED:
288       log::error("Invalid state, start stream already confirmed");
289       return;
290   }
291 }
292 
ConfirmStreamingRequestV2()293 void LeAudioClientInterface::Sink::ConfirmStreamingRequestV2() {
294   ConfirmStreamingRequest();
295 }
296 
CancelStreamingRequest()297 void LeAudioClientInterface::Sink::CancelStreamingRequest() {
298   if (!host::le_audio::LeAudioSinkTransport::instance) {
299     log::warn("instance is null");
300     return;
301   }
302 
303   log::info("");
304 
305   auto instance = host::le_audio::LeAudioSinkTransport::instance;
306   auto start_request_state = instance->GetStartRequestState();
307 
308   switch (start_request_state) {
309     case StartRequestState::IDLE:
310       log::warn(", no pending start stream request");
311       return;
312     case StartRequestState::PENDING_BEFORE_RESUME:
313       log::info("Response before sending PENDING to audio HAL");
314       instance->SetStartRequestState(StartRequestState::CANCELED);
315       return;
316     case StartRequestState::PENDING_AFTER_RESUME:
317       log::info("Response after sending PENDING to audio HAL");
318       instance->ClearStartRequestState();
319       host::le_audio::LeAudioSinkTransport::stream_started =
320           btle_stream_started_status::CANCELED;
321       return;
322     case StartRequestState::CONFIRMED:
323     case StartRequestState::CANCELED:
324       log::error("Invalid state, start stream already confirmed");
325       break;
326   }
327 }
328 
CancelStreamingRequestV2()329 void LeAudioClientInterface::Sink::CancelStreamingRequestV2() {
330   CancelStreamingRequest();
331 }
332 
UpdateAudioConfigToHal(const::le_audio::offload_config & offload_config)333 void LeAudioClientInterface::Sink::UpdateAudioConfigToHal(
334     const ::le_audio::offload_config& offload_config) {}
335 
UpdateBroadcastAudioConfigToHal(::le_audio::broadcast_offload_config const & config)336 void LeAudioClientInterface::Sink::UpdateBroadcastAudioConfigToHal(
337     ::le_audio::broadcast_offload_config const& config) {}
338 
SuspendedForReconfiguration()339 void LeAudioClientInterface::Sink::SuspendedForReconfiguration() {
340   log::info("");
341   // TODO
342 }
343 
ReconfigurationComplete()344 void LeAudioClientInterface::Sink::ReconfigurationComplete() { log::info(""); }
345 
Read(uint8_t * p_buf,uint32_t len)346 size_t LeAudioClientInterface::Sink::Read(uint8_t* p_buf, uint32_t len) {
347   uint32_t bytes_read = 0;
348   bytes_read = UIPC_Read(*lea_uipc, UIPC_CH_ID_AV_AUDIO, p_buf, len);
349 
350   // TODO(b/317682986): grab meaningful statistics for logs and metrics
351   log::verbose("Read {} bytes", bytes_read);
352 
353   return bytes_read;
354 }
355 
356 std::optional<::bluetooth::le_audio::set_configurations::AudioSetConfiguration>
GetUnicastConfig(const::bluetooth::le_audio::CodecManager::UnicastConfigurationRequirements & requirements) const357 LeAudioClientInterface::Sink::GetUnicastConfig(
358     const ::bluetooth::le_audio::CodecManager::UnicastConfigurationRequirements&
359         requirements) const {
360   return std::nullopt;
361 }
362 
363 std::optional<::bluetooth::le_audio::broadcaster::BroadcastConfiguration>
GetBroadcastConfig(const std::vector<std::pair<::bluetooth::le_audio::types::LeAudioContextType,uint8_t>> & subgroup_quality,const std::optional<std::vector<::bluetooth::le_audio::types::acs_ac_record>> & pacs) const364 LeAudioClientInterface::Sink::GetBroadcastConfig(
365     const std::vector<
366         std::pair<::bluetooth::le_audio::types::LeAudioContextType, uint8_t>>&
367         subgroup_quality,
368     const std::optional<
369         std::vector<::bluetooth::le_audio::types::acs_ac_record>>& pacs) const {
370   return std::nullopt;
371 }
372 
Cleanup()373 void LeAudioClientInterface::Source::Cleanup() {
374   log::info("");
375 
376   StopSession();
377 
378   delete host::le_audio::LeAudioSourceTransport::instance;
379   host::le_audio::LeAudioSourceTransport::instance = nullptr;
380 }
381 
SetPcmParameters(const PcmParameters & params)382 void LeAudioClientInterface::Source::SetPcmParameters(
383     const PcmParameters& params) {
384   if (!host::le_audio::LeAudioSourceTransport::instance) {
385     log::warn("instance is null");
386     return;
387   }
388 
389   log::info(
390       "sample_rate={}, bits_per_sample={}, channels_count={}, "
391       "data_interval_us={}",
392       params.sample_rate, params.bits_per_sample, params.channels_count,
393       params.data_interval_us);
394 
395   host::le_audio::LeAudioSourceTransport::instance
396       ->LeAudioSetSelectedHalPcmConfig(
397           params.sample_rate, params.bits_per_sample, params.channels_count,
398           params.data_interval_us);
399 }
400 
SetRemoteDelay(uint16_t delay_report_ms)401 void LeAudioClientInterface::Source::SetRemoteDelay(uint16_t delay_report_ms) {
402   if (!host::le_audio::LeAudioSourceTransport::instance) {
403     log::warn("instance is null");
404     return;
405   }
406 
407   log::info("delay_report_ms={} msec", delay_report_ms);
408 
409   host::le_audio::LeAudioSourceTransport::instance->SetRemoteDelay(
410       delay_report_ms);
411 }
412 
StartSession()413 void LeAudioClientInterface::Source::StartSession() { log::info(""); }
414 
StopSession()415 void LeAudioClientInterface::Source::StopSession() {
416   log::info("");
417 
418   if (host::le_audio::LeAudioSourceTransport::instance) {
419     host::le_audio::LeAudioSourceTransport::instance->ClearStartRequestState();
420   }
421 
422   host::le_audio::LeAudioSourceTransport::stream_started =
423       btle_stream_started_status::IDLE;
424 }
425 
ConfirmStreamingRequest()426 void LeAudioClientInterface::Source::ConfirmStreamingRequest() {
427   if (!host::le_audio::LeAudioSourceTransport::instance) {
428     log::warn("instance is null");
429     return;
430   }
431 
432   log::info("");
433 
434   auto instance = host::le_audio::LeAudioSourceTransport::instance;
435   auto start_request_state = instance->GetStartRequestState();
436 
437   switch (start_request_state) {
438     case StartRequestState::IDLE:
439       log::warn(", no pending start stream request");
440       return;
441     case StartRequestState::PENDING_BEFORE_RESUME:
442       log::info("Response before sending PENDING to audio HAL");
443       instance->SetStartRequestState(StartRequestState::CONFIRMED);
444       lea_data_path_open();
445       return;
446     case StartRequestState::PENDING_AFTER_RESUME:
447       log::info("Response after sending PENDING to audio HAL");
448       instance->ClearStartRequestState();
449       lea_data_path_open();
450       host::le_audio::LeAudioSourceTransport::stream_started =
451           btle_stream_started_status::STARTED;
452       return;
453     case StartRequestState::CONFIRMED:
454     case StartRequestState::CANCELED:
455       log::error("Invalid state, start stream already confirmed");
456       return;
457   }
458 }
459 
ConfirmStreamingRequestV2()460 void LeAudioClientInterface::Source::ConfirmStreamingRequestV2() {
461   ConfirmStreamingRequest();
462 }
463 
CancelStreamingRequest()464 void LeAudioClientInterface::Source::CancelStreamingRequest() {
465   if (!host::le_audio::LeAudioSourceTransport::instance) {
466     log::warn("instance is null");
467     return;
468   }
469 
470   log::info("");
471 
472   auto instance = host::le_audio::LeAudioSourceTransport::instance;
473   auto start_request_state = instance->GetStartRequestState();
474 
475   switch (start_request_state) {
476     case StartRequestState::IDLE:
477       log::warn(", no pending start stream request");
478       return;
479     case StartRequestState::PENDING_BEFORE_RESUME:
480       log::info("Response before sending PENDING to audio HAL");
481       instance->SetStartRequestState(StartRequestState::CANCELED);
482       return;
483     case StartRequestState::PENDING_AFTER_RESUME:
484       log::info("Response after sending PENDING to audio HAL");
485       instance->ClearStartRequestState();
486       host::le_audio::LeAudioSourceTransport::stream_started =
487           btle_stream_started_status::CANCELED;
488       return;
489     case StartRequestState::CANCELED:
490     case StartRequestState::CONFIRMED:
491       log::error("Invalid state, start stream already confirmed");
492       break;
493   }
494 }
495 
CancelStreamingRequestV2()496 void LeAudioClientInterface::Source::CancelStreamingRequestV2() {
497   CancelStreamingRequest();
498 }
499 
UpdateAudioConfigToHal(const::le_audio::offload_config & offload_config)500 void LeAudioClientInterface::Source::UpdateAudioConfigToHal(
501     const ::le_audio::offload_config& offload_config) {}
502 
SuspendedForReconfiguration()503 void LeAudioClientInterface::Source::SuspendedForReconfiguration() {
504   log::info("");
505   // TODO
506 }
507 
ReconfigurationComplete()508 void LeAudioClientInterface::Source::ReconfigurationComplete() {
509   log::info("");
510 }
511 
Write(const uint8_t * p_buf,uint32_t len)512 size_t LeAudioClientInterface::Source::Write(const uint8_t* p_buf,
513                                              uint32_t len) {
514   bool ok = UIPC_Send(*lea_uipc, UIPC_CH_ID_AV_AUDIO, 0, p_buf, len);
515   return ok ? len : 0;
516 }
517 
GetSink(StreamCallbacks stream_cb,bluetooth::common::MessageLoopThread * message_loop,bool is_broadcasting_session_type)518 LeAudioClientInterface::Sink* LeAudioClientInterface::GetSink(
519     StreamCallbacks stream_cb,
520     bluetooth::common::MessageLoopThread* message_loop,
521     bool is_broadcasting_session_type) {
522   if (is_broadcasting_session_type &&
523       !LeAudioHalVerifier::SupportsLeAudioBroadcast()) {
524     log::warn("No support for broadcasting Le Audio");
525     return nullptr;
526   }
527 
528   Sink* sink = is_broadcasting_session_type ? broadcast_sink_ : unicast_sink_;
529   if (sink == nullptr) {
530     sink = new Sink(is_broadcasting_session_type);
531     (is_broadcasting_session_type ? broadcast_sink_ : unicast_sink_) = sink;
532   } else {
533     log::warn("Sink is already acquired");
534     return nullptr;
535   }
536 
537   host::le_audio::LeAudioSinkTransport::instance =
538       new host::le_audio::LeAudioSinkTransport(std::move(stream_cb));
539 
540   return sink;
541 }
542 
IsUnicastSinkAcquired()543 bool LeAudioClientInterface::IsUnicastSinkAcquired() {
544   return unicast_sink_ != nullptr;
545 }
546 
IsBroadcastSinkAcquired()547 bool LeAudioClientInterface::IsBroadcastSinkAcquired() {
548   return broadcast_sink_ != nullptr;
549 }
550 
ReleaseSink(LeAudioClientInterface::Sink * sink)551 bool LeAudioClientInterface::ReleaseSink(LeAudioClientInterface::Sink* sink) {
552   if (sink != unicast_sink_ && sink != broadcast_sink_) {
553     log::warn("Can't release not acquired sink");
554     return false;
555   }
556 
557   sink->Cleanup();
558 
559   if (sink == unicast_sink_) {
560     delete (unicast_sink_);
561     unicast_sink_ = nullptr;
562   } else if (sink == broadcast_sink_) {
563     delete (broadcast_sink_);
564     broadcast_sink_ = nullptr;
565   }
566 
567   return true;
568 }
569 
GetSource(StreamCallbacks stream_cb,bluetooth::common::MessageLoopThread * message_loop)570 LeAudioClientInterface::Source* LeAudioClientInterface::GetSource(
571     StreamCallbacks stream_cb,
572     bluetooth::common::MessageLoopThread* message_loop) {
573   if (source_ == nullptr) {
574     source_ = new Source();
575   } else {
576     log::warn("Source is already acquired");
577     return nullptr;
578   }
579 
580   log::info("");
581 
582   host::le_audio::LeAudioSourceTransport::instance =
583       new host::le_audio::LeAudioSourceTransport(std::move(stream_cb));
584 
585   return source_;
586 }
587 
IsSourceAcquired()588 bool LeAudioClientInterface::IsSourceAcquired() { return source_ != nullptr; }
589 
ReleaseSource(LeAudioClientInterface::Source * source)590 bool LeAudioClientInterface::ReleaseSource(
591     LeAudioClientInterface::Source* source) {
592   if (source != source_) {
593     log::warn("Can't release not acquired source");
594     return false;
595   }
596 
597   log::info("");
598 
599   if (host::le_audio::LeAudioSourceTransport::instance) source->Cleanup();
600 
601   delete (source_);
602   source_ = nullptr;
603 
604   return true;
605 }
606 
607 LeAudioClientInterface* LeAudioClientInterface::interface = nullptr;
608 
Get()609 LeAudioClientInterface* LeAudioClientInterface::Get() {
610   // TODO: check flag
611 
612   if (LeAudioClientInterface::interface == nullptr) {
613     lea_uipc = UIPC_Init();
614 
615     LeAudioClientInterface::interface = new LeAudioClientInterface();
616   }
617 
618   return LeAudioClientInterface::interface;
619 }
620 
SetAllowedDsaModes(DsaModes dsa_modes)621 void LeAudioClientInterface::SetAllowedDsaModes(DsaModes dsa_modes) { return; }
622 
623 }  // namespace le_audio
624 }  // namespace audio
625 }  // namespace bluetooth
626