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