1 // Copyright (C) 2021 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "StreamOutImpl.h"
16
17 #include <android-base/logging.h>
18 #include <inttypes.h>
19 #include <math.h>
20 #include <system/audio-hal-enums.h>
21 #include <time.h>
22 #include <utils/Log.h>
23
24 #include <cstring>
25
26 #include "AidlTypes.h"
27 #include "BusOutputStream.h"
28 #include "WriteThread.h"
29
30 using android::status_t;
31 using android::hardware::hidl_memory;
32
33 namespace audio_proxy::service {
34
35 namespace {
36
37 // 1GB
38 constexpr uint32_t kMaxBufferSize = 1 << 30;
39
40 constexpr int64_t kOneSecInNs = 1'000'000'000;
41
deleteEventFlag(EventFlag * obj)42 void deleteEventFlag(EventFlag* obj) {
43 if (!obj) {
44 return;
45 }
46
47 status_t status = EventFlag::deleteEventFlag(&obj);
48 if (status) {
49 LOG(ERROR) << "Write MQ event flag deletion error: " << strerror(-status);
50 }
51 }
52
estimatePlayedFramesSince(const TimeSpec & timestamp,uint32_t sampleRateHz)53 uint64_t estimatePlayedFramesSince(const TimeSpec& timestamp,
54 uint32_t sampleRateHz) {
55 timespec now = {0, 0};
56 clock_gettime(CLOCK_MONOTONIC, &now);
57 int64_t deltaSec = 0;
58 int64_t deltaNSec = 0;
59 if (now.tv_nsec >= timestamp.tvNSec) {
60 deltaSec = now.tv_sec - timestamp.tvSec;
61 deltaNSec = now.tv_nsec - timestamp.tvNSec;
62 } else {
63 deltaSec = now.tv_sec - timestamp.tvSec - 1;
64 deltaNSec = kOneSecInNs + now.tv_nsec - timestamp.tvNSec;
65 }
66
67 if (deltaSec < 0 || deltaNSec < 0) {
68 return 0;
69 }
70
71 return deltaSec * sampleRateHz + deltaNSec * sampleRateHz / kOneSecInNs;
72 }
73
74 } // namespace
75
StreamOutImpl(std::shared_ptr<BusOutputStream> stream,const StreamOutConfig & config)76 StreamOutImpl::StreamOutImpl(std::shared_ptr<BusOutputStream> stream,
77 const StreamOutConfig& config)
78 : mStream(std::move(stream)),
79 mConfig(config),
80 mBufferSizeBytes(mStream->getConfig().bufferSizeBytes),
81 mLatencyMs(mStream->getConfig().latencyMs),
82 mEventFlag(nullptr, deleteEventFlag) {}
83
~StreamOutImpl()84 StreamOutImpl::~StreamOutImpl() {
85 if (mWriteThread) {
86 mWriteThread->stop();
87 status_t status = mWriteThread->join();
88 if (status) {
89 LOG(ERROR) << "write thread exit error " << strerror(-status);
90 }
91 }
92
93 mEventFlag.reset();
94 }
95
getFrameSize()96 Return<uint64_t> StreamOutImpl::getFrameSize() {
97 return mStream->getFrameSize();
98 }
99
getFrameCount()100 Return<uint64_t> StreamOutImpl::getFrameCount() {
101 return mBufferSizeBytes / mStream->getFrameSize();
102 }
103
getBufferSize()104 Return<uint64_t> StreamOutImpl::getBufferSize() { return mBufferSizeBytes; }
105
106 #if MAJOR_VERSION >= 7
getSupportedProfiles(getSupportedProfiles_cb _hidl_cb)107 Return<void> StreamOutImpl::getSupportedProfiles(
108 getSupportedProfiles_cb _hidl_cb) {
109 // For devices with fixed configuration, this method can return NOT_SUPPORTED.
110 _hidl_cb(Result::NOT_SUPPORTED, {});
111 return Void();
112 }
113
getAudioProperties(getAudioProperties_cb _hidl_cb)114 Return<void> StreamOutImpl::getAudioProperties(getAudioProperties_cb _hidl_cb) {
115 _hidl_cb(Result::OK, mConfig);
116 return Void();
117 }
118
setAudioProperties(const AudioConfigBaseOptional & config)119 Return<Result> StreamOutImpl::setAudioProperties(
120 const AudioConfigBaseOptional& config) {
121 return Result::NOT_SUPPORTED;
122 }
123 #else
getSampleRate()124 Return<uint32_t> StreamOutImpl::getSampleRate() { return mConfig.sampleRateHz; }
125
getSupportedSampleRates(AudioFormat format,getSupportedSampleRates_cb _hidl_cb)126 Return<void> StreamOutImpl::getSupportedSampleRates(
127 AudioFormat format, getSupportedSampleRates_cb _hidl_cb) {
128 _hidl_cb(Result::NOT_SUPPORTED, {});
129 return Void();
130 }
131
getSupportedChannelMasks(AudioFormat format,getSupportedChannelMasks_cb _hidl_cb)132 Return<void> StreamOutImpl::getSupportedChannelMasks(
133 AudioFormat format, getSupportedChannelMasks_cb _hidl_cb) {
134 _hidl_cb(Result::NOT_SUPPORTED, {});
135 return Void();
136 }
137
setSampleRate(uint32_t sampleRateHz)138 Return<Result> StreamOutImpl::setSampleRate(uint32_t sampleRateHz) {
139 return Result::NOT_SUPPORTED;
140 }
141
getChannelMask()142 Return<hidl_bitfield<AudioChannelMask>> StreamOutImpl::getChannelMask() {
143 return mConfig.channelMask;
144 }
145
setChannelMask(hidl_bitfield<AudioChannelMask> mask)146 Return<Result> StreamOutImpl::setChannelMask(
147 hidl_bitfield<AudioChannelMask> mask) {
148 return Result::NOT_SUPPORTED;
149 }
150
getFormat()151 Return<AudioFormat> StreamOutImpl::getFormat() { return mConfig.format; }
152
getSupportedFormats(getSupportedFormats_cb _hidl_cb)153 Return<void> StreamOutImpl::getSupportedFormats(
154 getSupportedFormats_cb _hidl_cb) {
155 #if MAJOR_VERSION >= 6
156 _hidl_cb(Result::NOT_SUPPORTED, {});
157 #else
158 _hidl_cb({});
159 #endif
160 return Void();
161 }
162
setFormat(AudioFormat format)163 Return<Result> StreamOutImpl::setFormat(AudioFormat format) {
164 return Result::NOT_SUPPORTED;
165 }
166
getAudioProperties(getAudioProperties_cb _hidl_cb)167 Return<void> StreamOutImpl::getAudioProperties(getAudioProperties_cb _hidl_cb) {
168 _hidl_cb(mConfig.sampleRateHz, mConfig.channelMask, mConfig.format);
169 return Void();
170 }
171 #endif
172
173 // We don't support effects. So any effectId is invalid.
addEffect(uint64_t effectId)174 Return<Result> StreamOutImpl::addEffect(uint64_t effectId) {
175 return Result::INVALID_ARGUMENTS;
176 }
177
removeEffect(uint64_t effectId)178 Return<Result> StreamOutImpl::removeEffect(uint64_t effectId) {
179 return Result::INVALID_ARGUMENTS;
180 }
181
standby()182 Return<Result> StreamOutImpl::standby() {
183 bool success = mStream->standby();
184 if (!success) {
185 return Result::INVALID_STATE;
186 }
187
188 mTotalPlayedFramesSinceStandby = estimateTotalPlayedFrames();
189 return Result::OK;
190 }
191
getDevices(getDevices_cb _hidl_cb)192 Return<void> StreamOutImpl::getDevices(getDevices_cb _hidl_cb) {
193 _hidl_cb(Result::NOT_SUPPORTED, {});
194 return Void();
195 }
196
setDevices(const hidl_vec<DeviceAddress> & devices)197 Return<Result> StreamOutImpl::setDevices(
198 const hidl_vec<DeviceAddress>& devices) {
199 return Result::NOT_SUPPORTED;
200 }
201
getParameters(const hidl_vec<ParameterValue> & context,const hidl_vec<hidl_string> & keys,getParameters_cb _hidl_cb)202 Return<void> StreamOutImpl::getParameters(
203 const hidl_vec<ParameterValue>& context, const hidl_vec<hidl_string>& keys,
204 getParameters_cb _hidl_cb) {
205 _hidl_cb(keys.size() > 0 ? Result::NOT_SUPPORTED : Result::OK, {});
206 return Void();
207 }
208
setParameters(const hidl_vec<ParameterValue> & context,const hidl_vec<ParameterValue> & parameters)209 Return<Result> StreamOutImpl::setParameters(
210 const hidl_vec<ParameterValue>& context,
211 const hidl_vec<ParameterValue>& parameters) {
212 return Result::OK;
213 }
214
setHwAvSync(uint32_t hwAvSync)215 Return<Result> StreamOutImpl::setHwAvSync(uint32_t hwAvSync) {
216 return Result::NOT_SUPPORTED;
217 }
218
close()219 Return<Result> StreamOutImpl::close() {
220 if (!mStream) {
221 return Result::INVALID_STATE;
222 }
223
224 if (mWriteThread) {
225 mWriteThread->stop();
226 }
227
228 if (!mStream->close()) {
229 LOG(WARNING) << "Failed to close stream.";
230 }
231
232 mStream = nullptr;
233
234 return Result::OK;
235 }
236
getLatency()237 Return<uint32_t> StreamOutImpl::getLatency() { return mLatencyMs; }
238
setVolume(float left,float right)239 Return<Result> StreamOutImpl::setVolume(float left, float right) {
240 if (isnan(left) || left < 0.f || left > 1.f || isnan(right) || right < 0.f ||
241 right > 1.f) {
242 return Result::INVALID_ARGUMENTS;
243 }
244 return mStream->setVolume(left, right) ? Result::OK : Result::INVALID_STATE;
245 }
246
prepareForWriting(uint32_t frameSize,uint32_t framesCount,prepareForWriting_cb _hidl_cb)247 Return<void> StreamOutImpl::prepareForWriting(uint32_t frameSize,
248 uint32_t framesCount,
249 prepareForWriting_cb _hidl_cb) {
250 #if MAJOR_VERSION >= 7
251 int32_t threadInfo = 0;
252 #else
253 ThreadInfo threadInfo = {0, 0};
254 #endif
255
256 // Wrap the _hidl_cb to return an error
257 auto sendError = [&threadInfo, &_hidl_cb](Result result) -> Return<void> {
258 _hidl_cb(result, CommandMQ::Descriptor(), DataMQ::Descriptor(),
259 StatusMQ::Descriptor(), threadInfo);
260 return Void();
261 };
262
263 if (mDataMQ) {
264 LOG(ERROR) << "The client attempted to call prepareForWriting twice";
265 return sendError(Result::INVALID_STATE);
266 }
267
268 if (frameSize == 0 || framesCount == 0) {
269 LOG(ERROR) << "Invalid frameSize (" << frameSize << ") or framesCount ("
270 << framesCount << ")";
271 return sendError(Result::INVALID_ARGUMENTS);
272 }
273
274 if (frameSize > kMaxBufferSize / framesCount) {
275 LOG(ERROR) << "Buffer too big: " << frameSize << "*" << framesCount
276 << " bytes > MAX_BUFFER_SIZE (" << kMaxBufferSize << ")";
277 return sendError(Result::INVALID_ARGUMENTS);
278 }
279
280 auto commandMQ = std::make_unique<CommandMQ>(1);
281 if (!commandMQ->isValid()) {
282 LOG(ERROR) << "Command MQ is invalid";
283 return sendError(Result::INVALID_ARGUMENTS);
284 }
285
286 auto dataMQ =
287 std::make_unique<DataMQ>(frameSize * framesCount, true /* EventFlag */);
288 if (!dataMQ->isValid()) {
289 LOG(ERROR) << "Data MQ is invalid";
290 return sendError(Result::INVALID_ARGUMENTS);
291 }
292
293 auto statusMQ = std::make_unique<StatusMQ>(1);
294 if (!statusMQ->isValid()) {
295 LOG(ERROR) << "Status MQ is invalid";
296 return sendError(Result::INVALID_ARGUMENTS);
297 }
298
299 EventFlag* rawEventFlag = nullptr;
300 status_t status =
301 EventFlag::createEventFlag(dataMQ->getEventFlagWord(), &rawEventFlag);
302 std::unique_ptr<EventFlag, EventFlagDeleter> eventFlag(rawEventFlag,
303 deleteEventFlag);
304 if (status != ::android::OK || !eventFlag) {
305 LOG(ERROR) << "Failed creating event flag for data MQ: "
306 << strerror(-status);
307 return sendError(Result::INVALID_ARGUMENTS);
308 }
309
310 if (!mStream->prepareForWriting(frameSize, framesCount)) {
311 LOG(ERROR) << "Failed to prepare writing channel.";
312 return sendError(Result::INVALID_ARGUMENTS);
313 }
314
315 sp<WriteThread> writeThread =
316 sp<WriteThread>::make(mStream, commandMQ.get(), dataMQ.get(),
317 statusMQ.get(), eventFlag.get(), mLatencyMs);
318 status = writeThread->run("writer", ::android::PRIORITY_URGENT_AUDIO);
319 if (status != ::android::OK) {
320 LOG(ERROR) << "Failed to start writer thread: " << strerror(-status);
321 return sendError(Result::INVALID_ARGUMENTS);
322 }
323
324 mCommandMQ = std::move(commandMQ);
325 mDataMQ = std::move(dataMQ);
326 mStatusMQ = std::move(statusMQ);
327 mEventFlag = std::move(eventFlag);
328 mWriteThread = std::move(writeThread);
329
330 #if MAJOR_VERSION >= 7
331 threadInfo = mWriteThread->getTid();
332 #else
333 threadInfo.pid = getpid();
334 threadInfo.tid = mWriteThread->getTid();
335 #endif
336
337 _hidl_cb(Result::OK, *mCommandMQ->getDesc(), *mDataMQ->getDesc(),
338 *mStatusMQ->getDesc(), threadInfo);
339
340 return Void();
341 }
342
getRenderPosition(getRenderPosition_cb _hidl_cb)343 Return<void> StreamOutImpl::getRenderPosition(getRenderPosition_cb _hidl_cb) {
344 uint64_t totalPlayedFrames = estimateTotalPlayedFrames();
345 if (totalPlayedFrames == 0) {
346 _hidl_cb(Result::OK, 0);
347 return Void();
348 }
349
350 // getRenderPosition returns the number of frames played since the output has
351 // exited standby.
352 DCHECK_GE(totalPlayedFrames, mTotalPlayedFramesSinceStandby);
353 uint64_t position = totalPlayedFrames - mTotalPlayedFramesSinceStandby;
354
355 if (position > std::numeric_limits<uint32_t>::max()) {
356 _hidl_cb(Result::INVALID_STATE, 0);
357 return Void();
358 }
359
360 _hidl_cb(Result::OK, position);
361 return Void();
362 }
363
getNextWriteTimestamp(getNextWriteTimestamp_cb _hidl_cb)364 Return<void> StreamOutImpl::getNextWriteTimestamp(
365 getNextWriteTimestamp_cb _hidl_cb) {
366 _hidl_cb(Result::NOT_SUPPORTED, 0);
367 return Void();
368 }
369
setCallback(const sp<IStreamOutCallback> & callback)370 Return<Result> StreamOutImpl::setCallback(
371 const sp<IStreamOutCallback>& callback) {
372 return Result::NOT_SUPPORTED;
373 }
374
clearCallback()375 Return<Result> StreamOutImpl::clearCallback() { return Result::NOT_SUPPORTED; }
376
supportsPauseAndResume(supportsPauseAndResume_cb _hidl_cb)377 Return<void> StreamOutImpl::supportsPauseAndResume(
378 supportsPauseAndResume_cb _hidl_cb) {
379 _hidl_cb(true, true);
380 return Void();
381 }
382
383 // pause should not be called before starting the playback.
pause()384 Return<Result> StreamOutImpl::pause() {
385 if (!mWriteThread) {
386 return Result::INVALID_STATE;
387 }
388
389 if (!mStream->pause()) {
390 return Result::INVALID_STATE;
391 }
392
393 mIsPaused = true;
394 return Result::OK;
395 }
396
397 // Resume should onl be called after pause.
resume()398 Return<Result> StreamOutImpl::resume() {
399 if (!mIsPaused) {
400 return Result::INVALID_STATE;
401 }
402
403 if (!mStream->resume()) {
404 return Result::INVALID_STATE;
405 }
406
407 mIsPaused = false;
408 return Result::OK;
409 }
410
411 // Drain and flush should always succeed if supported.
supportsDrain()412 Return<bool> StreamOutImpl::supportsDrain() { return true; }
413
drain(AudioDrain type)414 Return<Result> StreamOutImpl::drain(AudioDrain type) {
415 if (!mStream->drain(static_cast<AidlAudioDrain>(type))) {
416 LOG(WARNING) << "Failed to drain the stream.";
417 }
418
419 return Result::OK;
420 }
421
flush()422 Return<Result> StreamOutImpl::flush() {
423 if (!mStream->flush()) {
424 LOG(WARNING) << "Failed to flush the stream.";
425 }
426
427 return Result::OK;
428 }
429
getPresentationPosition(getPresentationPosition_cb _hidl_cb)430 Return<void> StreamOutImpl::getPresentationPosition(
431 getPresentationPosition_cb _hidl_cb) {
432 if (!mWriteThread) {
433 _hidl_cb(Result::INVALID_STATE, 0, {});
434 return Void();
435 }
436
437 auto [frames, timestamp] = mWriteThread->getPresentationPosition();
438 _hidl_cb(Result::OK, frames, timestamp);
439 return Void();
440 }
441
start()442 Return<Result> StreamOutImpl::start() {
443 return mStream->start() ? Result::OK : Result::NOT_SUPPORTED;
444 }
445
stop()446 Return<Result> StreamOutImpl::stop() {
447 return mStream->stop() ? Result::OK : Result::NOT_SUPPORTED;
448 }
449
createMmapBuffer(int32_t minSizeFrames,createMmapBuffer_cb _hidl_cb)450 Return<void> StreamOutImpl::createMmapBuffer(int32_t minSizeFrames,
451 createMmapBuffer_cb _hidl_cb) {
452 MmapBufferInfo hidlInfo;
453 AidlMmapBufferInfo info = mStream->createMmapBuffer(minSizeFrames);
454 int sharedMemoryFd = info.sharedMemoryFd.get();
455 if (sharedMemoryFd == -1) {
456 _hidl_cb(Result::NOT_SUPPORTED, hidlInfo);
457 return Void();
458 }
459
460 native_handle_t* hidlHandle = nullptr;
461 hidlHandle = native_handle_create(1, 0);
462 hidlHandle->data[0] = sharedMemoryFd;
463
464 hidlInfo.sharedMemory =
465 hidl_memory("audio_proxy_mmap_buffer", hidlHandle,
466 mStream->getFrameSize() * info.bufferSizeFrames);
467 hidlInfo.bufferSizeFrames = info.bufferSizeFrames;
468 hidlInfo.burstSizeFrames = info.burstSizeFrames;
469 hidlInfo.flags = static_cast<hidl_bitfield<MmapBufferFlag>>(info.flags);
470 _hidl_cb(Result::OK, hidlInfo);
471 return Void();
472 }
473
getMmapPosition(getMmapPosition_cb _hidl_cb)474 Return<void> StreamOutImpl::getMmapPosition(getMmapPosition_cb _hidl_cb) {
475 MmapPosition hidlPosition;
476
477 AidlPresentationPosition position = mStream->getMmapPosition();
478 if (position.timestamp.tvSec == 0 && position.timestamp.tvNSec == 0) {
479 _hidl_cb(Result::NOT_SUPPORTED, hidlPosition);
480 return Void();
481 }
482
483 hidlPosition.timeNanoseconds =
484 position.timestamp.tvSec * kOneSecInNs + position.timestamp.tvNSec;
485 hidlPosition.positionFrames = position.frames;
486 _hidl_cb(Result::OK, hidlPosition);
487 return Void();
488 }
489
490 #if MAJOR_VERSION >= 7
updateSourceMetadata(const SourceMetadata & sourceMetadata)491 Return<Result> StreamOutImpl::updateSourceMetadata(
492 const SourceMetadata& sourceMetadata) {
493 return Result::NOT_SUPPORTED;
494 }
495 #else
updateSourceMetadata(const SourceMetadata & sourceMetadata)496 Return<void> StreamOutImpl::updateSourceMetadata(
497 const SourceMetadata& sourceMetadata) {
498 return Void();
499 }
500 #endif
501
selectPresentation(int32_t presentationId,int32_t programId)502 Return<Result> StreamOutImpl::selectPresentation(int32_t presentationId,
503 int32_t programId) {
504 return Result::NOT_SUPPORTED;
505 }
506
getOutputStream()507 std::shared_ptr<BusOutputStream> StreamOutImpl::getOutputStream() {
508 return mStream;
509 }
510
updateOutputStream(std::shared_ptr<BusOutputStream> stream)511 void StreamOutImpl::updateOutputStream(
512 std::shared_ptr<BusOutputStream> stream) {
513 DCHECK(stream);
514 DCHECK(mStream);
515 if (stream->getConfig() != mStream->getConfig()) {
516 LOG(ERROR) << "New stream's config doesn't match the old stream's config.";
517 return;
518 }
519
520 if (mWriteThread) {
521 if (!stream->prepareForWriting(mStream->getWritingFrameSize(),
522 mStream->getWritingFrameCount())) {
523 LOG(ERROR) << "Failed to prepare writing channel.";
524 return;
525 }
526
527 mWriteThread->updateOutputStream(stream);
528 }
529
530 mStream = std::move(stream);
531 }
532
estimateTotalPlayedFrames() const533 uint64_t StreamOutImpl::estimateTotalPlayedFrames() const {
534 if (!mWriteThread) {
535 return 0;
536 }
537
538 auto [frames, timestamp] = mWriteThread->getPresentationPosition();
539 if (frames == 0) {
540 return 0;
541 }
542
543 return frames + estimatePlayedFramesSince(timestamp, mConfig.sampleRateHz);
544 }
545
546 #if MAJOR_VERSION >= 6
setEventCallback(const sp<IStreamOutEventCallback> & callback)547 Return<Result> StreamOutImpl::setEventCallback(
548 const sp<IStreamOutEventCallback>& callback) {
549 return Result::NOT_SUPPORTED;
550 }
551
getDualMonoMode(getDualMonoMode_cb _hidl_cb)552 Return<void> StreamOutImpl::getDualMonoMode(getDualMonoMode_cb _hidl_cb) {
553 _hidl_cb(Result::NOT_SUPPORTED, DualMonoMode::OFF);
554 return Void();
555 }
556
setDualMonoMode(DualMonoMode mode)557 Return<Result> StreamOutImpl::setDualMonoMode(DualMonoMode mode) {
558 return Result::NOT_SUPPORTED;
559 }
560
getAudioDescriptionMixLevel(getAudioDescriptionMixLevel_cb _hidl_cb)561 Return<void> StreamOutImpl::getAudioDescriptionMixLevel(
562 getAudioDescriptionMixLevel_cb _hidl_cb) {
563 _hidl_cb(Result::NOT_SUPPORTED, 0.f);
564 return Void();
565 }
566
setAudioDescriptionMixLevel(float leveldB)567 Return<Result> StreamOutImpl::setAudioDescriptionMixLevel(float leveldB) {
568 return Result::NOT_SUPPORTED;
569 }
570
getPlaybackRateParameters(getPlaybackRateParameters_cb _hidl_cb)571 Return<void> StreamOutImpl::getPlaybackRateParameters(
572 getPlaybackRateParameters_cb _hidl_cb) {
573 _hidl_cb(Result::NOT_SUPPORTED, {});
574 return Void();
575 }
576
setPlaybackRateParameters(const PlaybackRate & playbackRate)577 Return<Result> StreamOutImpl::setPlaybackRateParameters(
578 const PlaybackRate& playbackRate) {
579 return Result::NOT_SUPPORTED;
580 }
581 #endif
582
583 #if MAJOR_VERSION == 7 && MINOR_VERSION == 1
setLatencyMode(android::hardware::audio::V7_1::LatencyMode mode)584 Return<Result> StreamOutImpl::setLatencyMode(
585 android::hardware::audio::V7_1::LatencyMode mode) {
586 return Result::NOT_SUPPORTED;
587 }
588
getRecommendedLatencyModes(getRecommendedLatencyModes_cb _hidl_cb)589 Return<void> StreamOutImpl::getRecommendedLatencyModes(
590 getRecommendedLatencyModes_cb _hidl_cb) {
591 _hidl_cb(Result::NOT_SUPPORTED, {});
592 return Void();
593 }
594
setLatencyModeCallback(const sp<android::hardware::audio::V7_1::IStreamOutLatencyModeCallback> & cb)595 Return<Result> StreamOutImpl::setLatencyModeCallback(
596 const sp<android::hardware::audio::V7_1::IStreamOutLatencyModeCallback>&
597 cb) {
598 return Result::NOT_SUPPORTED;
599 }
600 #endif
601
602 } // namespace audio_proxy::service
603