1 /*
2 * Copyright 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 "AudioStreamRecord"
18 //#define LOG_NDEBUG 0
19 #include <utils/Log.h>
20
21 #include <stdint.h>
22
23 #include <aaudio/AAudio.h>
24 #include <audio_utils/primitives.h>
25 #include <media/AidlConversion.h>
26 #include <media/AudioRecord.h>
27 #include <utils/String16.h>
28
29 #include "core/AudioGlobal.h"
30 #include "legacy/AudioStreamLegacy.h"
31 #include "legacy/AudioStreamRecord.h"
32 #include "utility/AudioClock.h"
33 #include "utility/FixedBlockWriter.h"
34
35 using android::content::AttributionSourceState;
36
37 using namespace android;
38 using namespace aaudio;
39
AudioStreamRecord()40 AudioStreamRecord::AudioStreamRecord()
41 : AudioStreamLegacy()
42 , mFixedBlockWriter(*this)
43 {
44 }
45
~AudioStreamRecord()46 AudioStreamRecord::~AudioStreamRecord()
47 {
48 const aaudio_stream_state_t state = getState();
49 bool bad = !(state == AAUDIO_STREAM_STATE_UNINITIALIZED || state == AAUDIO_STREAM_STATE_CLOSED);
50 ALOGE_IF(bad, "stream not closed, in state %d", state);
51 }
52
open(const AudioStreamBuilder & builder)53 aaudio_result_t AudioStreamRecord::open(const AudioStreamBuilder& builder)
54 {
55 aaudio_result_t result = AAUDIO_OK;
56
57 result = AudioStream::open(builder);
58 if (result != AAUDIO_OK) {
59 return result;
60 }
61
62 // Try to create an AudioRecord
63
64 const aaudio_session_id_t requestedSessionId = builder.getSessionId();
65 const audio_session_t sessionId = AAudioConvert_aaudioToAndroidSessionId(requestedSessionId);
66
67 // TODO Support UNSPECIFIED in AudioRecord. For now, use stereo if unspecified.
68 int32_t samplesPerFrame = (getSamplesPerFrame() == AAUDIO_UNSPECIFIED)
69 ? 2 : getSamplesPerFrame();
70 audio_channel_mask_t channelMask = samplesPerFrame <= 2 ?
71 audio_channel_in_mask_from_count(samplesPerFrame) :
72 audio_channel_mask_for_index_assignment_from_count(samplesPerFrame);
73
74 size_t frameCount = (builder.getBufferCapacity() == AAUDIO_UNSPECIFIED) ? 0
75 : builder.getBufferCapacity();
76
77
78 audio_input_flags_t flags;
79 aaudio_performance_mode_t perfMode = getPerformanceMode();
80 switch (perfMode) {
81 case AAUDIO_PERFORMANCE_MODE_LOW_LATENCY:
82 // If the app asks for a sessionId then it means they want to use effects.
83 // So don't use RAW flag.
84 flags = (audio_input_flags_t) ((requestedSessionId == AAUDIO_SESSION_ID_NONE)
85 ? (AUDIO_INPUT_FLAG_FAST | AUDIO_INPUT_FLAG_RAW)
86 : (AUDIO_INPUT_FLAG_FAST));
87 break;
88
89 case AAUDIO_PERFORMANCE_MODE_POWER_SAVING:
90 case AAUDIO_PERFORMANCE_MODE_NONE:
91 default:
92 flags = AUDIO_INPUT_FLAG_NONE;
93 break;
94 }
95
96 const audio_format_t requestedFormat = getFormat();
97 // Preserve behavior of API 26
98 if (requestedFormat == AUDIO_FORMAT_DEFAULT) {
99 setFormat(AUDIO_FORMAT_PCM_FLOAT);
100 }
101
102 // Maybe change device format to get a FAST path.
103 // AudioRecord does not support FAST mode for FLOAT data.
104 // TODO AudioRecord should allow FLOAT data paths for FAST tracks.
105 // So IF the user asks for low latency FLOAT
106 // AND the sampleRate is likely to be compatible with FAST
107 // THEN request I16 and convert to FLOAT when passing to user.
108 // Note that hard coding 48000 Hz is not ideal because the sampleRate
109 // for a FAST path might not be 48000 Hz.
110 // It normally is but there is a chance that it is not.
111 // And there is no reliable way to know that in advance.
112 // Luckily the consequences of a wrong guess are minor.
113 // We just may not get a FAST track.
114 // But we wouldn't have anyway without this hack.
115 constexpr int32_t kMostLikelySampleRateForFast = 48000;
116 if (getFormat() == AUDIO_FORMAT_PCM_FLOAT
117 && perfMode == AAUDIO_PERFORMANCE_MODE_LOW_LATENCY
118 && (samplesPerFrame <= 2) // FAST only for mono and stereo
119 && (getSampleRate() == kMostLikelySampleRateForFast
120 || getSampleRate() == AAUDIO_UNSPECIFIED)) {
121 setDeviceFormat(AUDIO_FORMAT_PCM_16_BIT);
122 } else {
123 setDeviceFormat(getFormat());
124 }
125
126 // To avoid glitching, let AudioFlinger pick the optimal burst size.
127 uint32_t notificationFrames = 0;
128
129 // Setup the callback if there is one.
130 AudioRecord::callback_t callback = nullptr;
131 void *callbackData = nullptr;
132 AudioRecord::transfer_type streamTransferType = AudioRecord::transfer_type::TRANSFER_SYNC;
133 if (builder.getDataCallbackProc() != nullptr) {
134 streamTransferType = AudioRecord::transfer_type::TRANSFER_CALLBACK;
135 callback = getLegacyCallback();
136 callbackData = this;
137 }
138 mCallbackBufferSize = builder.getFramesPerDataCallback();
139
140 // Don't call mAudioRecord->setInputDevice() because it will be overwritten by set()!
141 audio_port_handle_t selectedDeviceId = (getDeviceId() == AAUDIO_UNSPECIFIED)
142 ? AUDIO_PORT_HANDLE_NONE
143 : getDeviceId();
144
145 const audio_content_type_t contentType =
146 AAudioConvert_contentTypeToInternal(builder.getContentType());
147 const audio_source_t source =
148 AAudioConvert_inputPresetToAudioSource(builder.getInputPreset());
149
150 const audio_flags_mask_t attrFlags =
151 AAudioConvert_privacySensitiveToAudioFlagsMask(builder.isPrivacySensitive());
152 const audio_attributes_t attributes = {
153 .content_type = contentType,
154 .usage = AUDIO_USAGE_UNKNOWN, // only used for output
155 .source = source,
156 .flags = attrFlags, // Different than the AUDIO_INPUT_FLAGS
157 .tags = ""
158 };
159
160 // TODO b/182392769: use attribution source util
161 AttributionSourceState attributionSource;
162 attributionSource.uid = VALUE_OR_FATAL(legacy2aidl_uid_t_int32_t(getuid()));
163 attributionSource.pid = VALUE_OR_FATAL(legacy2aidl_pid_t_int32_t(getpid()));
164 attributionSource.packageName = builder.getOpPackageName();
165 attributionSource.attributionTag = builder.getAttributionTag();
166 attributionSource.token = sp<BBinder>::make();
167
168 // ----------- open the AudioRecord ---------------------
169 // Might retry, but never more than once.
170 for (int i = 0; i < 2; i ++) {
171 const audio_format_t requestedInternalFormat = getDeviceFormat();
172
173 mAudioRecord = new AudioRecord(
174 attributionSource
175 );
176 mAudioRecord->set(
177 AUDIO_SOURCE_DEFAULT, // ignored because we pass attributes below
178 getSampleRate(),
179 requestedInternalFormat,
180 channelMask,
181 frameCount,
182 callback,
183 callbackData,
184 notificationFrames,
185 false /*threadCanCallJava*/,
186 sessionId,
187 streamTransferType,
188 flags,
189 AUDIO_UID_INVALID, // DEFAULT uid
190 -1, // DEFAULT pid
191 &attributes,
192 selectedDeviceId
193 );
194
195 // Set it here so it can be logged by the destructor if the open failed.
196 mAudioRecord->setCallerName(kCallerName);
197
198 // Did we get a valid track?
199 status_t status = mAudioRecord->initCheck();
200 if (status != OK) {
201 safeReleaseClose();
202 ALOGE("open(), initCheck() returned %d", status);
203 return AAudioConvert_androidToAAudioResult(status);
204 }
205
206 // Check to see if it was worth hacking the deviceFormat.
207 bool gotFastPath = (mAudioRecord->getFlags() & AUDIO_INPUT_FLAG_FAST)
208 == AUDIO_INPUT_FLAG_FAST;
209 if (getFormat() != getDeviceFormat() && !gotFastPath) {
210 // We tried to get a FAST path by switching the device format.
211 // But it didn't work. So we might as well reopen using the same
212 // format for device and for app.
213 ALOGD("%s() used a different device format but no FAST path, reopen", __func__);
214 mAudioRecord.clear();
215 setDeviceFormat(getFormat());
216 } else {
217 break; // Keep the one we just opened.
218 }
219 }
220
221 mMetricsId = std::string(AMEDIAMETRICS_KEY_PREFIX_AUDIO_RECORD)
222 + std::to_string(mAudioRecord->getPortId());
223 android::mediametrics::LogItem(mMetricsId)
224 .set(AMEDIAMETRICS_PROP_PERFORMANCEMODE,
225 AudioGlobal_convertPerformanceModeToText(builder.getPerformanceMode()))
226 .set(AMEDIAMETRICS_PROP_SHARINGMODE,
227 AudioGlobal_convertSharingModeToText(builder.getSharingMode()))
228 .set(AMEDIAMETRICS_PROP_ENCODINGCLIENT, toString(requestedFormat).c_str()).record();
229
230 // Get the actual values from the AudioRecord.
231 setSamplesPerFrame(mAudioRecord->channelCount());
232 setSampleRate(mAudioRecord->getSampleRate());
233 setBufferCapacity(getBufferCapacityFromDevice());
234 setFramesPerBurst(getFramesPerBurstFromDevice());
235
236 // We may need to pass the data through a block size adapter to guarantee constant size.
237 if (mCallbackBufferSize != AAUDIO_UNSPECIFIED) {
238 // The block adapter runs before the format conversion.
239 // So we need to use the device frame size.
240 mBlockAdapterBytesPerFrame = getBytesPerDeviceFrame();
241 int callbackSizeBytes = mBlockAdapterBytesPerFrame * mCallbackBufferSize;
242 mFixedBlockWriter.open(callbackSizeBytes);
243 mBlockAdapter = &mFixedBlockWriter;
244 } else {
245 mBlockAdapter = nullptr;
246 }
247
248 // Allocate format conversion buffer if needed.
249 if (getDeviceFormat() == AUDIO_FORMAT_PCM_16_BIT
250 && getFormat() == AUDIO_FORMAT_PCM_FLOAT) {
251
252 if (builder.getDataCallbackProc() != nullptr) {
253 // If we have a callback then we need to convert the data into an internal float
254 // array and then pass that entire array to the app.
255 mFormatConversionBufferSizeInFrames =
256 (mCallbackBufferSize != AAUDIO_UNSPECIFIED)
257 ? mCallbackBufferSize : getFramesPerBurst();
258 int32_t numSamples = mFormatConversionBufferSizeInFrames * getSamplesPerFrame();
259 mFormatConversionBufferFloat = std::make_unique<float[]>(numSamples);
260 } else {
261 // If we don't have a callback then we will read into an internal short array
262 // and then convert into the app float array in read().
263 mFormatConversionBufferSizeInFrames = getFramesPerBurst();
264 int32_t numSamples = mFormatConversionBufferSizeInFrames * getSamplesPerFrame();
265 mFormatConversionBufferI16 = std::make_unique<int16_t[]>(numSamples);
266 }
267 ALOGD("%s() setup I16>FLOAT conversion buffer with %d frames",
268 __func__, mFormatConversionBufferSizeInFrames);
269 }
270
271 // Update performance mode based on the actual stream.
272 // For example, if the sample rate does not match native then you won't get a FAST track.
273 audio_input_flags_t actualFlags = mAudioRecord->getFlags();
274 aaudio_performance_mode_t actualPerformanceMode = AAUDIO_PERFORMANCE_MODE_NONE;
275 // FIXME Some platforms do not advertise RAW mode for low latency inputs.
276 if ((actualFlags & (AUDIO_INPUT_FLAG_FAST))
277 == (AUDIO_INPUT_FLAG_FAST)) {
278 actualPerformanceMode = AAUDIO_PERFORMANCE_MODE_LOW_LATENCY;
279 }
280 setPerformanceMode(actualPerformanceMode);
281
282 setSharingMode(AAUDIO_SHARING_MODE_SHARED); // EXCLUSIVE mode not supported in legacy
283
284 // Log warning if we did not get what we asked for.
285 ALOGW_IF(actualFlags != flags,
286 "open() flags changed from 0x%08X to 0x%08X",
287 flags, actualFlags);
288 ALOGW_IF(actualPerformanceMode != perfMode,
289 "open() perfMode changed from %d to %d",
290 perfMode, actualPerformanceMode);
291
292 setState(AAUDIO_STREAM_STATE_OPEN);
293 setDeviceId(mAudioRecord->getRoutedDeviceId());
294
295 aaudio_session_id_t actualSessionId =
296 (requestedSessionId == AAUDIO_SESSION_ID_NONE)
297 ? AAUDIO_SESSION_ID_NONE
298 : (aaudio_session_id_t) mAudioRecord->getSessionId();
299 setSessionId(actualSessionId);
300
301 mAudioRecord->addAudioDeviceCallback(this);
302
303 return AAUDIO_OK;
304 }
305
release_l()306 aaudio_result_t AudioStreamRecord::release_l() {
307 // TODO add close() or release() to AudioFlinger's AudioRecord API.
308 // Then call it from here
309 if (getState() != AAUDIO_STREAM_STATE_CLOSING) {
310 mAudioRecord->removeAudioDeviceCallback(this);
311 logReleaseBufferState();
312 // Data callbacks may still be running!
313 return AudioStream::release_l();
314 } else {
315 return AAUDIO_OK; // already released
316 }
317 }
318
close_l()319 void AudioStreamRecord::close_l() {
320 // The callbacks are normally joined in the AudioRecord destructor.
321 // But if another object has a reference to the AudioRecord then
322 // it will not get deleted here.
323 // So we should join callbacks explicitly before returning.
324 // Unlock around the join to avoid deadlocks if the callback tries to lock.
325 // This can happen if the callback returns AAUDIO_CALLBACK_RESULT_STOP
326 mStreamLock.unlock();
327 mAudioRecord->stopAndJoinCallbacks();
328 mStreamLock.lock();
329
330 mAudioRecord.clear();
331 // Do not close mFixedBlockReader. It has a unique_ptr to its buffer
332 // so it will clean up by itself.
333 AudioStream::close_l();
334 }
335
maybeConvertDeviceData(const void * audioData,int32_t numFrames)336 const void * AudioStreamRecord::maybeConvertDeviceData(const void *audioData, int32_t numFrames) {
337 if (mFormatConversionBufferFloat.get() != nullptr) {
338 LOG_ALWAYS_FATAL_IF(numFrames > mFormatConversionBufferSizeInFrames,
339 "%s() conversion size %d too large for buffer %d",
340 __func__, numFrames, mFormatConversionBufferSizeInFrames);
341
342 int32_t numSamples = numFrames * getSamplesPerFrame();
343 // Only conversion supported is I16 to FLOAT
344 memcpy_to_float_from_i16(
345 mFormatConversionBufferFloat.get(),
346 (const int16_t *) audioData,
347 numSamples);
348 return mFormatConversionBufferFloat.get();
349 } else {
350 return audioData;
351 }
352 }
353
processCallback(int event,void * info)354 void AudioStreamRecord::processCallback(int event, void *info) {
355 switch (event) {
356 case AudioRecord::EVENT_MORE_DATA:
357 processCallbackCommon(AAUDIO_CALLBACK_OPERATION_PROCESS_DATA, info);
358 break;
359
360 // Stream got rerouted so we disconnect.
361 case AudioRecord::EVENT_NEW_IAUDIORECORD:
362 processCallbackCommon(AAUDIO_CALLBACK_OPERATION_DISCONNECTED, info);
363 break;
364
365 default:
366 break;
367 }
368 return;
369 }
370
requestStart_l()371 aaudio_result_t AudioStreamRecord::requestStart_l()
372 {
373 if (mAudioRecord.get() == nullptr) {
374 return AAUDIO_ERROR_INVALID_STATE;
375 }
376
377 // Enable callback before starting AudioRecord to avoid shutting
378 // down because of a race condition.
379 mCallbackEnabled.store(true);
380 aaudio_stream_state_t originalState = getState();
381 // Set before starting the callback so that we are in the correct state
382 // before updateStateMachine() can be called by the callback.
383 setState(AAUDIO_STREAM_STATE_STARTING);
384 mFramesWritten.reset32(); // service writes frames
385 mTimestampPosition.reset32();
386 status_t err = mAudioRecord->start(); // resets position to zero
387 if (err != OK) {
388 mCallbackEnabled.store(false);
389 setState(originalState);
390 return AAudioConvert_androidToAAudioResult(err);
391 }
392 return AAUDIO_OK;
393 }
394
requestStop_l()395 aaudio_result_t AudioStreamRecord::requestStop_l() {
396 if (mAudioRecord.get() == nullptr) {
397 return AAUDIO_ERROR_INVALID_STATE;
398 }
399 setState(AAUDIO_STREAM_STATE_STOPPING);
400 mFramesWritten.catchUpTo(getFramesRead());
401 mTimestampPosition.catchUpTo(getFramesRead());
402 mAudioRecord->stop();
403 mCallbackEnabled.store(false);
404 // Pass false to prevent errorCallback from being called after disconnect
405 // when app has already requested a stop().
406 return checkForDisconnectRequest(false);
407 }
408
updateStateMachine()409 aaudio_result_t AudioStreamRecord::updateStateMachine()
410 {
411 aaudio_result_t result = AAUDIO_OK;
412 aaudio_wrapping_frames_t position;
413 status_t err;
414 switch (getState()) {
415 // TODO add better state visibility to AudioRecord
416 case AAUDIO_STREAM_STATE_STARTING:
417 // When starting, the position will begin at zero and then go positive.
418 // The position can wrap but by that time the state will not be STARTING.
419 err = mAudioRecord->getPosition(&position);
420 if (err != OK) {
421 result = AAudioConvert_androidToAAudioResult(err);
422 } else if (position > 0) {
423 setState(AAUDIO_STREAM_STATE_STARTED);
424 }
425 break;
426 case AAUDIO_STREAM_STATE_STOPPING:
427 if (mAudioRecord->stopped()) {
428 setState(AAUDIO_STREAM_STATE_STOPPED);
429 }
430 break;
431 default:
432 break;
433 }
434 return result;
435 }
436
read(void * buffer,int32_t numFrames,int64_t timeoutNanoseconds)437 aaudio_result_t AudioStreamRecord::read(void *buffer,
438 int32_t numFrames,
439 int64_t timeoutNanoseconds)
440 {
441 int32_t bytesPerDeviceFrame = getBytesPerDeviceFrame();
442 int32_t numBytes;
443 // This will detect out of range values for numFrames.
444 aaudio_result_t result = AAudioConvert_framesToBytes(numFrames, bytesPerDeviceFrame, &numBytes);
445 if (result != AAUDIO_OK) {
446 return result;
447 }
448
449 if (getState() == AAUDIO_STREAM_STATE_DISCONNECTED) {
450 return AAUDIO_ERROR_DISCONNECTED;
451 }
452
453 // TODO add timeout to AudioRecord
454 bool blocking = (timeoutNanoseconds > 0);
455
456 ssize_t bytesActuallyRead = 0;
457 ssize_t totalBytesRead = 0;
458 if (mFormatConversionBufferI16.get() != nullptr) {
459 // Convert I16 data to float using an intermediate buffer.
460 float *floatBuffer = (float *) buffer;
461 int32_t framesLeft = numFrames;
462 // Perform conversion using multiple read()s if necessary.
463 while (framesLeft > 0) {
464 // Read into short internal buffer.
465 int32_t framesToRead = std::min(framesLeft, mFormatConversionBufferSizeInFrames);
466 size_t bytesToRead = framesToRead * bytesPerDeviceFrame;
467 bytesActuallyRead = mAudioRecord->read(mFormatConversionBufferI16.get(), bytesToRead, blocking);
468 if (bytesActuallyRead <= 0) {
469 break;
470 }
471 totalBytesRead += bytesActuallyRead;
472 int32_t framesToConvert = bytesActuallyRead / bytesPerDeviceFrame;
473 // Convert into app float buffer.
474 size_t numSamples = framesToConvert * getSamplesPerFrame();
475 memcpy_to_float_from_i16(
476 floatBuffer,
477 mFormatConversionBufferI16.get(),
478 numSamples);
479 floatBuffer += numSamples;
480 framesLeft -= framesToConvert;
481 }
482 } else {
483 bytesActuallyRead = mAudioRecord->read(buffer, numBytes, blocking);
484 totalBytesRead = bytesActuallyRead;
485 }
486 if (bytesActuallyRead == WOULD_BLOCK) {
487 return 0;
488 } else if (bytesActuallyRead < 0) {
489 // In this context, a DEAD_OBJECT is more likely to be a disconnect notification due to
490 // AudioRecord invalidation.
491 if (bytesActuallyRead == DEAD_OBJECT) {
492 setState(AAUDIO_STREAM_STATE_DISCONNECTED);
493 return AAUDIO_ERROR_DISCONNECTED;
494 }
495 return AAudioConvert_androidToAAudioResult(bytesActuallyRead);
496 }
497 int32_t framesRead = (int32_t)(totalBytesRead / bytesPerDeviceFrame);
498 incrementFramesRead(framesRead);
499
500 result = updateStateMachine();
501 if (result != AAUDIO_OK) {
502 return result;
503 }
504
505 return (aaudio_result_t) framesRead;
506 }
507
setBufferSize(int32_t requestedFrames)508 aaudio_result_t AudioStreamRecord::setBufferSize(int32_t requestedFrames)
509 {
510 return getBufferSize();
511 }
512
getBufferSize() const513 int32_t AudioStreamRecord::getBufferSize() const
514 {
515 return getBufferCapacity(); // TODO implement in AudioRecord?
516 }
517
getBufferCapacityFromDevice() const518 int32_t AudioStreamRecord::getBufferCapacityFromDevice() const
519 {
520 return static_cast<int32_t>(mAudioRecord->frameCount());
521 }
522
getXRunCount() const523 int32_t AudioStreamRecord::getXRunCount() const
524 {
525 return 0; // TODO implement when AudioRecord supports it
526 }
527
getFramesPerBurstFromDevice() const528 int32_t AudioStreamRecord::getFramesPerBurstFromDevice() const {
529 return static_cast<int32_t>(mAudioRecord->getNotificationPeriodInFrames());
530 }
531
getTimestamp(clockid_t clockId,int64_t * framePosition,int64_t * timeNanoseconds)532 aaudio_result_t AudioStreamRecord::getTimestamp(clockid_t clockId,
533 int64_t *framePosition,
534 int64_t *timeNanoseconds) {
535 ExtendedTimestamp extendedTimestamp;
536 if (getState() != AAUDIO_STREAM_STATE_STARTED) {
537 return AAUDIO_ERROR_INVALID_STATE;
538 }
539 status_t status = mAudioRecord->getTimestamp(&extendedTimestamp);
540 if (status == WOULD_BLOCK) {
541 return AAUDIO_ERROR_INVALID_STATE;
542 } else if (status != NO_ERROR) {
543 return AAudioConvert_androidToAAudioResult(status);
544 }
545 return getBestTimestamp(clockId, framePosition, timeNanoseconds, &extendedTimestamp);
546 }
547
getFramesWritten()548 int64_t AudioStreamRecord::getFramesWritten() {
549 aaudio_wrapping_frames_t position;
550 status_t result;
551 switch (getState()) {
552 case AAUDIO_STREAM_STATE_STARTING:
553 case AAUDIO_STREAM_STATE_STARTED:
554 result = mAudioRecord->getPosition(&position);
555 if (result == OK) {
556 mFramesWritten.update32(position);
557 }
558 break;
559 case AAUDIO_STREAM_STATE_STOPPING:
560 default:
561 break;
562 }
563 return AudioStreamLegacy::getFramesWritten();
564 }
565