blob: 5f187c5ea45f31f9b0b0c26253c7be2483fed03c [file] [log] [blame]
Mikhail Naganov10548292016-10-31 10:39:47 -07001/*
2 * Copyright (C) 2016 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#define LOG_TAG "StreamOutHAL"
Mikhail Naganov685f0e32016-12-16 17:18:08 -080018//#define LOG_NDEBUG 0
Mikhail Naganov10548292016-10-31 10:39:47 -070019
Yifan Hongf9d30342016-11-30 13:45:34 -080020#include <android/log.h>
Mikhail Naganovb29438e2016-12-22 09:21:34 -080021#include <hardware/audio.h>
22#include <mediautils/SchedulingPolicyService.h>
Mikhail Naganov10548292016-10-31 10:39:47 -070023
24#include "StreamOut.h"
25
26namespace android {
27namespace hardware {
28namespace audio {
29namespace V2_0 {
30namespace implementation {
31
Mikhail Naganovb29438e2016-12-22 09:21:34 -080032namespace {
33
34class WriteThread : public Thread {
35 public:
36 // WriteThread's lifespan never exceeds StreamOut's lifespan.
37 WriteThread(std::atomic<bool>* stop,
38 audio_stream_out_t* stream,
Mikhail Naganova468fa82017-01-31 13:56:02 -080039 StreamOut::CommandMQ* commandMQ,
Mikhail Naganovb29438e2016-12-22 09:21:34 -080040 StreamOut::DataMQ* dataMQ,
41 StreamOut::StatusMQ* statusMQ,
42 EventFlag* efGroup,
43 ThreadPriority threadPriority)
44 : Thread(false /*canCallJava*/),
45 mStop(stop),
46 mStream(stream),
Mikhail Naganova468fa82017-01-31 13:56:02 -080047 mCommandMQ(commandMQ),
Mikhail Naganovb29438e2016-12-22 09:21:34 -080048 mDataMQ(dataMQ),
49 mStatusMQ(statusMQ),
50 mEfGroup(efGroup),
51 mThreadPriority(threadPriority),
52 mBuffer(new uint8_t[dataMQ->getQuantumCount()]) {
53 }
54 virtual ~WriteThread() {}
55
56 status_t readyToRun() override;
57
58 private:
59 std::atomic<bool>* mStop;
60 audio_stream_out_t* mStream;
Mikhail Naganova468fa82017-01-31 13:56:02 -080061 StreamOut::CommandMQ* mCommandMQ;
Mikhail Naganovb29438e2016-12-22 09:21:34 -080062 StreamOut::DataMQ* mDataMQ;
63 StreamOut::StatusMQ* mStatusMQ;
64 EventFlag* mEfGroup;
65 ThreadPriority mThreadPriority;
66 std::unique_ptr<uint8_t[]> mBuffer;
Mikhail Naganova468fa82017-01-31 13:56:02 -080067 IStreamOut::WriteStatus mStatus;
Mikhail Naganovb29438e2016-12-22 09:21:34 -080068
69 bool threadLoop() override;
Mikhail Naganova468fa82017-01-31 13:56:02 -080070
71 void doGetLatency();
72 void doGetPresentationPosition();
73 void doWrite();
Mikhail Naganovb29438e2016-12-22 09:21:34 -080074};
75
76status_t WriteThread::readyToRun() {
77 if (mThreadPriority != ThreadPriority::NORMAL) {
78 int err = requestPriority(
79 getpid(), getTid(), static_cast<int>(mThreadPriority), true /*asynchronous*/);
80 ALOGW_IF(err, "failed to set priority %d for pid %d tid %d; error %d",
81 static_cast<int>(mThreadPriority), getpid(), getTid(), err);
82 }
83 return OK;
84}
85
Mikhail Naganova468fa82017-01-31 13:56:02 -080086void WriteThread::doWrite() {
87 const size_t availToRead = mDataMQ->availableToRead();
88 mStatus.retval = Result::OK;
89 mStatus.reply.written = 0;
90 if (mDataMQ->read(&mBuffer[0], availToRead)) {
91 ssize_t writeResult = mStream->write(mStream, &mBuffer[0], availToRead);
92 if (writeResult >= 0) {
93 mStatus.reply.written = writeResult;
94 } else {
95 mStatus.retval = Stream::analyzeStatus("write", writeResult);
96 }
97 }
98}
99
100void WriteThread::doGetPresentationPosition() {
101 mStatus.retval = StreamOut::getPresentationPositionImpl(
102 mStream,
103 &mStatus.reply.presentationPosition.frames,
104 &mStatus.reply.presentationPosition.timeStamp);
105}
106
107void WriteThread::doGetLatency() {
108 mStatus.retval = Result::OK;
109 mStatus.reply.latencyMs = mStream->get_latency(mStream);
110}
111
Mikhail Naganovb29438e2016-12-22 09:21:34 -0800112bool WriteThread::threadLoop() {
113 // This implementation doesn't return control back to the Thread until it decides to stop,
114 // as the Thread uses mutexes, and this can lead to priority inversion.
115 while(!std::atomic_load_explicit(mStop, std::memory_order_acquire)) {
116 // TODO: Remove manual event flag handling once blocking MQ is implemented. b/33815422
117 uint32_t efState = 0;
118 mEfGroup->wait(
119 static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY), &efState, NS_PER_SEC);
120 if (!(efState & static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY))) {
121 continue; // Nothing to do.
122 }
Mikhail Naganova468fa82017-01-31 13:56:02 -0800123 if (!mCommandMQ->read(&mStatus.replyTo)) {
124 continue; // Nothing to do.
Mikhail Naganovb29438e2016-12-22 09:21:34 -0800125 }
Mikhail Naganova468fa82017-01-31 13:56:02 -0800126 switch (mStatus.replyTo) {
127 case IStreamOut::WriteCommand::WRITE:
128 doWrite();
129 break;
130 case IStreamOut::WriteCommand::GET_PRESENTATION_POSITION:
131 doGetPresentationPosition();
132 break;
133 case IStreamOut::WriteCommand::GET_LATENCY:
134 doGetLatency();
135 break;
136 default:
137 ALOGE("Unknown write thread command code %d", mStatus.replyTo);
138 mStatus.retval = Result::NOT_SUPPORTED;
139 break;
140 }
141 if (!mStatusMQ->write(&mStatus)) {
142 ALOGE("status message queue write failed");
Mikhail Naganovb29438e2016-12-22 09:21:34 -0800143 }
144 mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL));
145 }
146
147 return false;
148}
149
150} // namespace
151
Mikhail Naganov10548292016-10-31 10:39:47 -0700152StreamOut::StreamOut(audio_hw_device_t* device, audio_stream_out_t* stream)
Mikhail Naganovb29438e2016-12-22 09:21:34 -0800153 : mIsClosed(false), mDevice(device), mStream(stream),
Eric Laurent7deb7da2016-12-15 19:15:45 -0800154 mStreamCommon(new Stream(&stream->common)),
Mikhail Naganovb29438e2016-12-22 09:21:34 -0800155 mStreamMmap(new StreamMmap<audio_stream_out_t>(stream)),
156 mEfGroup(nullptr), mStopWriteThread(false) {
Mikhail Naganov10548292016-10-31 10:39:47 -0700157}
158
159StreamOut::~StreamOut() {
Mikhail Naganovb29438e2016-12-22 09:21:34 -0800160 close();
Mikhail Naganov10548292016-10-31 10:39:47 -0700161 mStream = nullptr;
162 mDevice = nullptr;
163}
164
165// Methods from ::android::hardware::audio::V2_0::IStream follow.
166Return<uint64_t> StreamOut::getFrameSize() {
167 return audio_stream_out_frame_size(mStream);
168}
169
170Return<uint64_t> StreamOut::getFrameCount() {
171 return mStreamCommon->getFrameCount();
172}
173
174Return<uint64_t> StreamOut::getBufferSize() {
175 return mStreamCommon->getBufferSize();
176}
177
178Return<uint32_t> StreamOut::getSampleRate() {
179 return mStreamCommon->getSampleRate();
180}
181
182Return<void> StreamOut::getSupportedSampleRates(getSupportedSampleRates_cb _hidl_cb) {
183 return mStreamCommon->getSupportedSampleRates(_hidl_cb);
184}
185
186Return<Result> StreamOut::setSampleRate(uint32_t sampleRateHz) {
187 return mStreamCommon->setSampleRate(sampleRateHz);
188}
189
190Return<AudioChannelMask> StreamOut::getChannelMask() {
191 return mStreamCommon->getChannelMask();
192}
193
194Return<void> StreamOut::getSupportedChannelMasks(getSupportedChannelMasks_cb _hidl_cb) {
195 return mStreamCommon->getSupportedChannelMasks(_hidl_cb);
196}
197
198Return<Result> StreamOut::setChannelMask(AudioChannelMask mask) {
199 return mStreamCommon->setChannelMask(mask);
200}
201
202Return<AudioFormat> StreamOut::getFormat() {
203 return mStreamCommon->getFormat();
204}
205
206Return<void> StreamOut::getSupportedFormats(getSupportedFormats_cb _hidl_cb) {
207 return mStreamCommon->getSupportedFormats(_hidl_cb);
208}
209
210Return<Result> StreamOut::setFormat(AudioFormat format) {
211 return mStreamCommon->setFormat(format);
212}
213
214Return<void> StreamOut::getAudioProperties(getAudioProperties_cb _hidl_cb) {
215 return mStreamCommon->getAudioProperties(_hidl_cb);
216}
217
218Return<Result> StreamOut::addEffect(uint64_t effectId) {
219 return mStreamCommon->addEffect(effectId);
220}
221
222Return<Result> StreamOut::removeEffect(uint64_t effectId) {
223 return mStreamCommon->removeEffect(effectId);
224}
225
226Return<Result> StreamOut::standby() {
227 return mStreamCommon->standby();
228}
229
230Return<AudioDevice> StreamOut::getDevice() {
231 return mStreamCommon->getDevice();
232}
233
234Return<Result> StreamOut::setDevice(const DeviceAddress& address) {
235 return mStreamCommon->setDevice(address);
236}
237
238Return<Result> StreamOut::setConnectedState(const DeviceAddress& address, bool connected) {
239 return mStreamCommon->setConnectedState(address, connected);
240}
241
242Return<Result> StreamOut::setHwAvSync(uint32_t hwAvSync) {
243 return mStreamCommon->setHwAvSync(hwAvSync);
244}
245
246Return<void> StreamOut::getParameters(
247 const hidl_vec<hidl_string>& keys, getParameters_cb _hidl_cb) {
248 return mStreamCommon->getParameters(keys, _hidl_cb);
249}
250
251Return<Result> StreamOut::setParameters(const hidl_vec<ParameterValue>& parameters) {
252 return mStreamCommon->setParameters(parameters);
253}
254
Martijn Coenen70b9a152016-11-18 15:29:32 +0100255Return<void> StreamOut::debugDump(const hidl_handle& fd) {
Mikhail Naganov10548292016-10-31 10:39:47 -0700256 return mStreamCommon->debugDump(fd);
257}
258
Mikhail Naganovb29438e2016-12-22 09:21:34 -0800259Return<Result> StreamOut::close() {
260 if (mIsClosed) return Result::INVALID_STATE;
261 mIsClosed = true;
262 if (mWriteThread.get()) {
263 mStopWriteThread.store(true, std::memory_order_release);
264 status_t status = mWriteThread->requestExitAndWait();
265 ALOGE_IF(status, "write thread exit error: %s", strerror(-status));
266 }
267 if (mEfGroup) {
268 status_t status = EventFlag::deleteEventFlag(&mEfGroup);
269 ALOGE_IF(status, "write MQ event flag deletion error: %s", strerror(-status));
270 }
271 mCallback.clear();
272 mDevice->close_output_stream(mDevice, mStream);
273 return Result::OK;
274}
275
Mikhail Naganov10548292016-10-31 10:39:47 -0700276// Methods from ::android::hardware::audio::V2_0::IStreamOut follow.
277Return<uint32_t> StreamOut::getLatency() {
278 return mStream->get_latency(mStream);
279}
280
281Return<Result> StreamOut::setVolume(float left, float right) {
282 Result retval(Result::NOT_SUPPORTED);
283 if (mStream->set_volume != NULL) {
Eric Laurent7deb7da2016-12-15 19:15:45 -0800284 retval = Stream::analyzeStatus(
Mikhail Naganov10548292016-10-31 10:39:47 -0700285 "set_volume", mStream->set_volume(mStream, left, right));
286 }
287 return retval;
288}
289
Mikhail Naganovb29438e2016-12-22 09:21:34 -0800290Return<void> StreamOut::prepareForWriting(
291 uint32_t frameSize, uint32_t framesCount, ThreadPriority threadPriority,
292 prepareForWriting_cb _hidl_cb) {
293 status_t status;
294 // Create message queues.
295 if (mDataMQ) {
296 ALOGE("the client attempts to call prepareForWriting twice");
297 _hidl_cb(Result::INVALID_STATE,
Mikhail Naganova468fa82017-01-31 13:56:02 -0800298 CommandMQ::Descriptor(), DataMQ::Descriptor(), StatusMQ::Descriptor());
Mikhail Naganovb29438e2016-12-22 09:21:34 -0800299 return Void();
Mikhail Naganov10548292016-10-31 10:39:47 -0700300 }
Mikhail Naganova468fa82017-01-31 13:56:02 -0800301 std::unique_ptr<CommandMQ> tempCommandMQ(new CommandMQ(1));
Mikhail Naganovb29438e2016-12-22 09:21:34 -0800302 std::unique_ptr<DataMQ> tempDataMQ(
303 new DataMQ(frameSize * framesCount, true /* EventFlag */));
304 std::unique_ptr<StatusMQ> tempStatusMQ(new StatusMQ(1));
Mikhail Naganova468fa82017-01-31 13:56:02 -0800305 if (!tempCommandMQ->isValid() || !tempDataMQ->isValid() || !tempStatusMQ->isValid()) {
306 ALOGE_IF(!tempCommandMQ->isValid(), "command MQ is invalid");
Mikhail Naganovb29438e2016-12-22 09:21:34 -0800307 ALOGE_IF(!tempDataMQ->isValid(), "data MQ is invalid");
308 ALOGE_IF(!tempStatusMQ->isValid(), "status MQ is invalid");
309 _hidl_cb(Result::INVALID_ARGUMENTS,
Mikhail Naganova468fa82017-01-31 13:56:02 -0800310 CommandMQ::Descriptor(), DataMQ::Descriptor(), StatusMQ::Descriptor());
Mikhail Naganovb29438e2016-12-22 09:21:34 -0800311 return Void();
312 }
313 // TODO: Remove event flag management once blocking MQ is implemented. b/33815422
314 status = EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &mEfGroup);
315 if (status != OK || !mEfGroup) {
316 ALOGE("failed creating event flag for data MQ: %s", strerror(-status));
317 _hidl_cb(Result::INVALID_ARGUMENTS,
Mikhail Naganova468fa82017-01-31 13:56:02 -0800318 CommandMQ::Descriptor(), DataMQ::Descriptor(), StatusMQ::Descriptor());
Mikhail Naganovb29438e2016-12-22 09:21:34 -0800319 return Void();
320 }
321
322 // Create and launch the thread.
323 mWriteThread = new WriteThread(
324 &mStopWriteThread,
325 mStream,
Mikhail Naganova468fa82017-01-31 13:56:02 -0800326 tempCommandMQ.get(),
Mikhail Naganovb29438e2016-12-22 09:21:34 -0800327 tempDataMQ.get(),
328 tempStatusMQ.get(),
329 mEfGroup,
330 threadPriority);
331 status = mWriteThread->run("writer", PRIORITY_URGENT_AUDIO);
332 if (status != OK) {
333 ALOGW("failed to start writer thread: %s", strerror(-status));
334 _hidl_cb(Result::INVALID_ARGUMENTS,
Mikhail Naganova468fa82017-01-31 13:56:02 -0800335 CommandMQ::Descriptor(), DataMQ::Descriptor(), StatusMQ::Descriptor());
Mikhail Naganovb29438e2016-12-22 09:21:34 -0800336 return Void();
337 }
338
Mikhail Naganova468fa82017-01-31 13:56:02 -0800339 mCommandMQ = std::move(tempCommandMQ);
Mikhail Naganovb29438e2016-12-22 09:21:34 -0800340 mDataMQ = std::move(tempDataMQ);
341 mStatusMQ = std::move(tempStatusMQ);
Mikhail Naganova468fa82017-01-31 13:56:02 -0800342 _hidl_cb(Result::OK, *mCommandMQ->getDesc(), *mDataMQ->getDesc(), *mStatusMQ->getDesc());
Mikhail Naganov10548292016-10-31 10:39:47 -0700343 return Void();
344}
345
346Return<void> StreamOut::getRenderPosition(getRenderPosition_cb _hidl_cb) {
347 uint32_t halDspFrames;
Eric Laurent7deb7da2016-12-15 19:15:45 -0800348 Result retval = Stream::analyzeStatus(
Mikhail Naganov10548292016-10-31 10:39:47 -0700349 "get_render_position", mStream->get_render_position(mStream, &halDspFrames));
350 _hidl_cb(retval, halDspFrames);
351 return Void();
352}
353
354Return<void> StreamOut::getNextWriteTimestamp(getNextWriteTimestamp_cb _hidl_cb) {
355 Result retval(Result::NOT_SUPPORTED);
356 int64_t timestampUs = 0;
357 if (mStream->get_next_write_timestamp != NULL) {
Eric Laurent7deb7da2016-12-15 19:15:45 -0800358 retval = Stream::analyzeStatus(
Mikhail Naganov10548292016-10-31 10:39:47 -0700359 "get_next_write_timestamp",
360 mStream->get_next_write_timestamp(mStream, &timestampUs));
361 }
362 _hidl_cb(retval, timestampUs);
363 return Void();
364}
365
366Return<Result> StreamOut::setCallback(const sp<IStreamOutCallback>& callback) {
367 if (mStream->set_callback == NULL) return Result::NOT_SUPPORTED;
368 int result = mStream->set_callback(mStream, StreamOut::asyncCallback, this);
369 if (result == 0) {
370 mCallback = callback;
371 }
Eric Laurent7deb7da2016-12-15 19:15:45 -0800372 return Stream::analyzeStatus("set_callback", result);
Mikhail Naganov10548292016-10-31 10:39:47 -0700373}
374
Mikhail Naganov6e81e9b2016-11-16 16:30:17 -0800375Return<Result> StreamOut::clearCallback() {
376 if (mStream->set_callback == NULL) return Result::NOT_SUPPORTED;
377 mCallback.clear();
378 return Result::OK;
379}
380
Mikhail Naganov10548292016-10-31 10:39:47 -0700381// static
382int StreamOut::asyncCallback(stream_callback_event_t event, void*, void *cookie) {
383 wp<StreamOut> weakSelf(reinterpret_cast<StreamOut*>(cookie));
384 sp<StreamOut> self = weakSelf.promote();
Mikhail Naganov6e81e9b2016-11-16 16:30:17 -0800385 if (self == nullptr || self->mCallback == nullptr) return 0;
Mikhail Naganov10548292016-10-31 10:39:47 -0700386 ALOGV("asyncCallback() event %d", event);
387 switch (event) {
388 case STREAM_CBK_EVENT_WRITE_READY:
Mikhail Naganov6e81e9b2016-11-16 16:30:17 -0800389 self->mCallback->onWriteReady();
Mikhail Naganov10548292016-10-31 10:39:47 -0700390 break;
391 case STREAM_CBK_EVENT_DRAIN_READY:
Mikhail Naganov6e81e9b2016-11-16 16:30:17 -0800392 self->mCallback->onDrainReady();
Mikhail Naganov10548292016-10-31 10:39:47 -0700393 break;
394 case STREAM_CBK_EVENT_ERROR:
Mikhail Naganov6e81e9b2016-11-16 16:30:17 -0800395 self->mCallback->onError();
Mikhail Naganov10548292016-10-31 10:39:47 -0700396 break;
397 default:
398 ALOGW("asyncCallback() unknown event %d", event);
399 break;
400 }
401 return 0;
402}
403
404Return<void> StreamOut::supportsPauseAndResume(supportsPauseAndResume_cb _hidl_cb) {
405 _hidl_cb(mStream->pause != NULL, mStream->resume != NULL);
406 return Void();
407}
408
409Return<Result> StreamOut::pause() {
410 return mStream->pause != NULL ?
Eric Laurent7deb7da2016-12-15 19:15:45 -0800411 Stream::analyzeStatus("pause", mStream->pause(mStream)) :
Mikhail Naganov10548292016-10-31 10:39:47 -0700412 Result::NOT_SUPPORTED;
413}
414
415Return<Result> StreamOut::resume() {
416 return mStream->resume != NULL ?
Eric Laurent7deb7da2016-12-15 19:15:45 -0800417 Stream::analyzeStatus("resume", mStream->resume(mStream)) :
Mikhail Naganov10548292016-10-31 10:39:47 -0700418 Result::NOT_SUPPORTED;
419}
420
421Return<bool> StreamOut::supportsDrain() {
422 return mStream->drain != NULL;
423}
424
425Return<Result> StreamOut::drain(AudioDrain type) {
426 return mStream->drain != NULL ?
Eric Laurent7deb7da2016-12-15 19:15:45 -0800427 Stream::analyzeStatus(
Mikhail Naganov10548292016-10-31 10:39:47 -0700428 "drain", mStream->drain(mStream, static_cast<audio_drain_type_t>(type))) :
429 Result::NOT_SUPPORTED;
430}
431
432Return<Result> StreamOut::flush() {
433 return mStream->flush != NULL ?
Eric Laurent7deb7da2016-12-15 19:15:45 -0800434 Stream::analyzeStatus("flush", mStream->flush(mStream)) :
Mikhail Naganov10548292016-10-31 10:39:47 -0700435 Result::NOT_SUPPORTED;
436}
437
Mikhail Naganovee901e32017-01-12 09:28:52 -0800438// static
439Result StreamOut::getPresentationPositionImpl(
440 audio_stream_out_t *stream, uint64_t *frames, TimeSpec *timeStamp) {
Mikhail Naganov10548292016-10-31 10:39:47 -0700441 Result retval(Result::NOT_SUPPORTED);
Mikhail Naganovee901e32017-01-12 09:28:52 -0800442 if (stream->get_presentation_position == NULL) return retval;
443 struct timespec halTimeStamp;
444 retval = Stream::analyzeStatus(
445 "get_presentation_position",
446 stream->get_presentation_position(stream, frames, &halTimeStamp),
447 // Don't logspam on EINVAL--it's normal for get_presentation_position
448 // to return it sometimes.
449 EINVAL);
450 if (retval == Result::OK) {
451 timeStamp->tvSec = halTimeStamp.tv_sec;
452 timeStamp->tvNSec = halTimeStamp.tv_nsec;
453 }
454 return retval;
455}
456
457Return<void> StreamOut::getPresentationPosition(getPresentationPosition_cb _hidl_cb) {
Mikhail Naganov10548292016-10-31 10:39:47 -0700458 uint64_t frames = 0;
459 TimeSpec timeStamp = { 0, 0 };
Mikhail Naganovee901e32017-01-12 09:28:52 -0800460 Result retval = getPresentationPositionImpl(mStream, &frames, &timeStamp);
Mikhail Naganov10548292016-10-31 10:39:47 -0700461 _hidl_cb(retval, frames, timeStamp);
462 return Void();
463}
464
Eric Laurent7deb7da2016-12-15 19:15:45 -0800465Return<Result> StreamOut::start() {
466 return mStreamMmap->start();
467}
468
469Return<Result> StreamOut::stop() {
470 return mStreamMmap->stop();
471}
472
473Return<void> StreamOut::createMmapBuffer(int32_t minSizeFrames, createMmapBuffer_cb _hidl_cb) {
474 return mStreamMmap->createMmapBuffer(
475 minSizeFrames, audio_stream_out_frame_size(mStream), _hidl_cb);
476}
477
478Return<void> StreamOut::getMmapPosition(getMmapPosition_cb _hidl_cb) {
479 return mStreamMmap->getMmapPosition(_hidl_cb);
480}
481
Mikhail Naganov10548292016-10-31 10:39:47 -0700482} // namespace implementation
483} // namespace V2_0
484} // namespace audio
485} // namespace hardware
486} // namespace android