1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include <stdio.h>
7 #include <math.h>
8 #include <string.h>
9 #include "mozilla/Logging.h"
10 #include "prdtoa.h"
11 #include "AudioStream.h"
12 #include "VideoUtils.h"
13 #include "mozilla/dom/AudioDeviceInfo.h"
14 #include "mozilla/Monitor.h"
15 #include "mozilla/Mutex.h"
16 #include "mozilla/Sprintf.h"
17 #include "mozilla/Unused.h"
18 #include <algorithm>
19 #include "mozilla/Telemetry.h"
20 #include "CubebUtils.h"
21 #include "nsNativeCharsetUtils.h"
22 #include "nsPrintfCString.h"
23 #include "AudioConverter.h"
24 #include "UnderrunHandler.h"
25 #if defined(XP_WIN)
26 # include "nsXULAppAPI.h"
27 #endif
28 #include "Tracing.h"
29 #include "webaudio/blink/DenormalDisabler.h"
30 #include "mozilla/StaticPrefs_media.h"
31
32 // Use abort() instead of exception in SoundTouch.
33 #define ST_NO_EXCEPTION_HANDLING 1
34 #include "soundtouch/SoundTouchFactory.h"
35
36 namespace mozilla {
37
38 #undef LOG
39 #undef LOGW
40 #undef LOGE
41
42 LazyLogModule gAudioStreamLog("AudioStream");
43 // For simple logs
44 #define LOG(x, ...) \
45 MOZ_LOG(gAudioStreamLog, mozilla::LogLevel::Debug, \
46 ("%p " x, this, ##__VA_ARGS__))
47 #define LOGW(x, ...) \
48 MOZ_LOG(gAudioStreamLog, mozilla::LogLevel::Warning, \
49 ("%p " x, this, ##__VA_ARGS__))
50 #define LOGE(x, ...) \
51 NS_DebugBreak(NS_DEBUG_WARNING, \
52 nsPrintfCString("%p " x, this, ##__VA_ARGS__).get(), nullptr, \
53 __FILE__, __LINE__)
54
55 /**
56 * Keep a list of frames sent to the audio engine in each DataCallback along
57 * with the playback rate at the moment. Since the playback rate and number of
58 * underrun frames can vary in each callback. We need to keep the whole history
59 * in order to calculate the playback position of the audio engine correctly.
60 */
61 class FrameHistory {
62 struct Chunk {
63 uint32_t servicedFrames;
64 uint32_t totalFrames;
65 uint32_t rate;
66 };
67
68 template <typename T>
FramesToUs(uint32_t frames,uint32_t rate)69 static T FramesToUs(uint32_t frames, uint32_t rate) {
70 return static_cast<T>(frames) * USECS_PER_S / rate;
71 }
72
73 public:
FrameHistory()74 FrameHistory() : mBaseOffset(0), mBasePosition(0) {}
75
Append(uint32_t aServiced,uint32_t aUnderrun,uint32_t aRate)76 void Append(uint32_t aServiced, uint32_t aUnderrun, uint32_t aRate) {
77 /* In most case where playback rate stays the same and we don't underrun
78 * frames, we are able to merge chunks to avoid lose of precision to add up
79 * in compressing chunks into |mBaseOffset| and |mBasePosition|.
80 */
81 if (!mChunks.IsEmpty()) {
82 Chunk& c = mChunks.LastElement();
83 // 2 chunks (c1 and c2) can be merged when rate is the same and
84 // adjacent frames are zero. That is, underrun frames in c1 are zero
85 // or serviced frames in c2 are zero.
86 if (c.rate == aRate &&
87 (c.servicedFrames == c.totalFrames || aServiced == 0)) {
88 c.servicedFrames += aServiced;
89 c.totalFrames += aServiced + aUnderrun;
90 return;
91 }
92 }
93 Chunk* p = mChunks.AppendElement();
94 p->servicedFrames = aServiced;
95 p->totalFrames = aServiced + aUnderrun;
96 p->rate = aRate;
97 }
98
99 /**
100 * @param frames The playback position in frames of the audio engine.
101 * @return The playback position in microseconds of the audio engine,
102 * adjusted by playback rate changes and underrun frames.
103 */
GetPosition(int64_t frames)104 int64_t GetPosition(int64_t frames) {
105 // playback position should not go backward.
106 MOZ_ASSERT(frames >= mBaseOffset);
107 while (true) {
108 if (mChunks.IsEmpty()) {
109 return static_cast<int64_t>(mBasePosition);
110 }
111 const Chunk& c = mChunks[0];
112 if (frames <= mBaseOffset + c.totalFrames) {
113 uint32_t delta = frames - mBaseOffset;
114 delta = std::min(delta, c.servicedFrames);
115 return static_cast<int64_t>(mBasePosition) +
116 FramesToUs<int64_t>(delta, c.rate);
117 }
118 // Since the playback position of the audio engine will not go backward,
119 // we are able to compress chunks so that |mChunks| won't grow
120 // unlimitedly. Note that we lose precision in converting integers into
121 // floats and inaccuracy will accumulate over time. However, for a 24hr
122 // long, sample rate = 44.1k file, the error will be less than 1
123 // microsecond after playing 24 hours. So we are fine with that.
124 mBaseOffset += c.totalFrames;
125 mBasePosition += FramesToUs<double>(c.servicedFrames, c.rate);
126 mChunks.RemoveElementAt(0);
127 }
128 }
129
130 private:
131 AutoTArray<Chunk, 7> mChunks;
132 int64_t mBaseOffset;
133 double mBasePosition;
134 };
135
AudioStream(DataSource & aSource,uint32_t aInRate,uint32_t aOutputChannels,AudioConfig::ChannelLayout::ChannelMap aChannelMap)136 AudioStream::AudioStream(DataSource& aSource, uint32_t aInRate,
137 uint32_t aOutputChannels,
138 AudioConfig::ChannelLayout::ChannelMap aChannelMap)
139 : mTimeStretcher(nullptr),
140 mMonitor("AudioStream"),
141 mOutChannels(aOutputChannels),
142 mChannelMap(aChannelMap),
143 mAudioClock(aInRate),
144 mState(INITIALIZED),
145 mDataSource(aSource),
146 mPlaybackComplete(false),
147 mPlaybackRate(1.0f),
148 mPreservesPitch(true) {}
149
~AudioStream()150 AudioStream::~AudioStream() {
151 LOG("deleted, state %d", mState);
152 MOZ_ASSERT(mState == SHUTDOWN && !mCubebStream,
153 "Should've called Shutdown() before deleting an AudioStream");
154 }
155
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const156 size_t AudioStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
157 size_t amount = aMallocSizeOf(this);
158
159 // Possibly add in the future:
160 // - mTimeStretcher
161 // - mCubebStream
162
163 return amount;
164 }
165
EnsureTimeStretcherInitialized()166 nsresult AudioStream::EnsureTimeStretcherInitialized() {
167 AssertIsOnAudioThread();
168 if (!mTimeStretcher) {
169 mTimeStretcher = soundtouch::createSoundTouchObj();
170 mTimeStretcher->setSampleRate(mAudioClock.GetInputRate());
171 mTimeStretcher->setChannels(mOutChannels);
172 mTimeStretcher->setPitch(1.0);
173
174 // SoundTouch v2.1.2 uses automatic time-stretch settings with the following
175 // values:
176 // Tempo 0.5: 90ms sequence, 20ms seekwindow, 8ms overlap
177 // Tempo 2.0: 40ms sequence, 15ms seekwindow, 8ms overlap
178 // We are going to use a smaller 10ms sequence size to improve speech
179 // clarity, giving more resolution at high tempo and less reverb at low
180 // tempo. Maintain 15ms seekwindow and 8ms overlap for smoothness.
181 mTimeStretcher->setSetting(
182 SETTING_SEQUENCE_MS,
183 StaticPrefs::media_audio_playbackrate_soundtouch_sequence_ms());
184 mTimeStretcher->setSetting(
185 SETTING_SEEKWINDOW_MS,
186 StaticPrefs::media_audio_playbackrate_soundtouch_seekwindow_ms());
187 mTimeStretcher->setSetting(
188 SETTING_OVERLAP_MS,
189 StaticPrefs::media_audio_playbackrate_soundtouch_overlap_ms());
190 }
191 return NS_OK;
192 }
193
SetPlaybackRate(double aPlaybackRate)194 nsresult AudioStream::SetPlaybackRate(double aPlaybackRate) {
195 TRACE("AudioStream::SetPlaybackRate");
196 NS_ASSERTION(
197 aPlaybackRate > 0.0,
198 "Can't handle negative or null playbackrate in the AudioStream.");
199 if (aPlaybackRate == mPlaybackRate) {
200 return NS_OK;
201 }
202
203 mPlaybackRate = static_cast<float>(aPlaybackRate);
204
205 return NS_OK;
206 }
207
SetPreservesPitch(bool aPreservesPitch)208 nsresult AudioStream::SetPreservesPitch(bool aPreservesPitch) {
209 TRACE("AudioStream::SetPreservesPitch");
210 if (aPreservesPitch == mPreservesPitch) {
211 return NS_OK;
212 }
213
214 mPreservesPitch = aPreservesPitch;
215
216 return NS_OK;
217 }
218
219 template <AudioSampleFormat N>
220 struct ToCubebFormat {
221 static const cubeb_sample_format value = CUBEB_SAMPLE_FLOAT32NE;
222 };
223
224 template <>
225 struct ToCubebFormat<AUDIO_FORMAT_S16> {
226 static const cubeb_sample_format value = CUBEB_SAMPLE_S16NE;
227 };
228
229 template <typename Function, typename... Args>
InvokeCubeb(Function aFunction,Args &&...aArgs)230 int AudioStream::InvokeCubeb(Function aFunction, Args&&... aArgs) {
231 MonitorAutoUnlock mon(mMonitor);
232 return aFunction(mCubebStream.get(), std::forward<Args>(aArgs)...);
233 }
234
Init(AudioDeviceInfo * aSinkInfo)235 nsresult AudioStream::Init(AudioDeviceInfo* aSinkInfo) {
236 auto startTime = TimeStamp::Now();
237 TRACE("AudioStream::Init");
238
239 LOG("%s channels: %d, rate: %d", __FUNCTION__, mOutChannels,
240 mAudioClock.GetInputRate());
241 mSinkInfo = aSinkInfo;
242
243 cubeb_stream_params params;
244 params.rate = mAudioClock.GetInputRate();
245 params.channels = mOutChannels;
246 params.layout = static_cast<uint32_t>(mChannelMap);
247 params.format = ToCubebFormat<AUDIO_OUTPUT_FORMAT>::value;
248 params.prefs = CubebUtils::GetDefaultStreamPrefs(CUBEB_DEVICE_TYPE_OUTPUT);
249
250 // This is noop if MOZ_DUMP_AUDIO is not set.
251 mDumpFile.Open("AudioStream", mOutChannels, mAudioClock.GetInputRate());
252
253 cubeb* cubebContext = CubebUtils::GetCubebContext();
254 if (!cubebContext) {
255 LOGE("Can't get cubeb context!");
256 CubebUtils::ReportCubebStreamInitFailure(true);
257 return NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR;
258 }
259
260 return OpenCubeb(cubebContext, params, startTime,
261 CubebUtils::GetFirstStream());
262 }
263
OpenCubeb(cubeb * aContext,cubeb_stream_params & aParams,TimeStamp aStartTime,bool aIsFirst)264 nsresult AudioStream::OpenCubeb(cubeb* aContext, cubeb_stream_params& aParams,
265 TimeStamp aStartTime, bool aIsFirst) {
266 TRACE("AudioStream::OpenCubeb");
267 MOZ_ASSERT(aContext);
268
269 cubeb_stream* stream = nullptr;
270 /* Convert from milliseconds to frames. */
271 uint32_t latency_frames =
272 CubebUtils::GetCubebPlaybackLatencyInMilliseconds() * aParams.rate / 1000;
273 cubeb_devid deviceID = nullptr;
274 if (mSinkInfo && mSinkInfo->DeviceID()) {
275 deviceID = mSinkInfo->DeviceID();
276 }
277 if (cubeb_stream_init(aContext, &stream, "AudioStream", nullptr, nullptr,
278 deviceID, &aParams, latency_frames, DataCallback_S,
279 StateCallback_S, this) == CUBEB_OK) {
280 mCubebStream.reset(stream);
281 CubebUtils::ReportCubebBackendUsed();
282 } else {
283 LOGE("OpenCubeb() failed to init cubeb");
284 CubebUtils::ReportCubebStreamInitFailure(aIsFirst);
285 return NS_ERROR_FAILURE;
286 }
287
288 TimeDuration timeDelta = TimeStamp::Now() - aStartTime;
289 LOG("creation time %sfirst: %u ms", aIsFirst ? "" : "not ",
290 (uint32_t)timeDelta.ToMilliseconds());
291
292 return NS_OK;
293 }
294
SetVolume(double aVolume)295 void AudioStream::SetVolume(double aVolume) {
296 TRACE("AudioStream::SetVolume");
297 MOZ_ASSERT(aVolume >= 0.0 && aVolume <= 1.0, "Invalid volume");
298
299 {
300 MonitorAutoLock mon(mMonitor);
301 MOZ_ASSERT(mState != SHUTDOWN, "Don't set volume after shutdown.");
302 if (mState == ERRORED) {
303 return;
304 }
305 }
306
307 if (cubeb_stream_set_volume(
308 mCubebStream.get(),
309 static_cast<float>(aVolume * CubebUtils::GetVolumeScale())) !=
310 CUBEB_OK) {
311 LOGE("Could not change volume on cubeb stream.");
312 }
313 }
314
SetStreamName(const nsAString & aStreamName)315 void AudioStream::SetStreamName(const nsAString& aStreamName) {
316 TRACE("AudioStream::SetStreamName");
317
318 nsAutoCString aRawStreamName;
319 nsresult rv = NS_CopyUnicodeToNative(aStreamName, aRawStreamName);
320
321 if (NS_FAILED(rv) || aStreamName.IsEmpty()) {
322 return;
323 }
324
325 if (cubeb_stream_set_name(mCubebStream.get(), aRawStreamName.get()) !=
326 CUBEB_OK) {
327 LOGE("Could not set cubeb stream name.");
328 }
329 }
330
331 Result<already_AddRefed<MediaSink::EndedPromise>, nsresult>
Start()332 AudioStream::Start() {
333 TRACE("AudioStream::Start");
334 MonitorAutoLock mon(mMonitor);
335 MOZ_ASSERT(mState == INITIALIZED);
336 mState = STARTED;
337
338 // As cubeb might call audio stream's state callback very soon after we start
339 // cubeb, we have to create the promise beforehand in order to handle the
340 // case where we immediately get `drained`.
341 RefPtr<MediaSink::EndedPromise> promise = mEndedPromise.Ensure(__func__);
342 mPlaybackComplete = false;
343
344 if (InvokeCubeb(cubeb_stream_start) != CUBEB_OK) {
345 mState = ERRORED;
346 }
347
348 LOG("started, state %s", mState == STARTED ? "STARTED"
349 : mState == DRAINED ? "DRAINED"
350 : "ERRORED");
351 if (mState == STARTED || mState == DRAINED) {
352 return promise.forget();
353 }
354 return Err(NS_ERROR_FAILURE);
355 }
356
Pause()357 void AudioStream::Pause() {
358 TRACE("AudioStream::Pause");
359 MonitorAutoLock mon(mMonitor);
360 MOZ_ASSERT(mState != INITIALIZED, "Must be Start()ed.");
361 MOZ_ASSERT(mState != STOPPED, "Already Pause()ed.");
362 MOZ_ASSERT(mState != SHUTDOWN, "Already Shutdown()ed.");
363
364 // Do nothing if we are already drained or errored.
365 if (mState == DRAINED || mState == ERRORED) {
366 return;
367 }
368
369 if (InvokeCubeb(cubeb_stream_stop) != CUBEB_OK) {
370 mState = ERRORED;
371 } else if (mState != DRAINED && mState != ERRORED) {
372 // Don't transition to other states if we are already
373 // drained or errored.
374 mState = STOPPED;
375 }
376 }
377
Resume()378 void AudioStream::Resume() {
379 TRACE("AudioStream::Resume");
380 MonitorAutoLock mon(mMonitor);
381 MOZ_ASSERT(mState != INITIALIZED, "Must be Start()ed.");
382 MOZ_ASSERT(mState != STARTED, "Already Start()ed.");
383 MOZ_ASSERT(mState != SHUTDOWN, "Already Shutdown()ed.");
384
385 // Do nothing if we are already drained or errored.
386 if (mState == DRAINED || mState == ERRORED) {
387 return;
388 }
389
390 if (InvokeCubeb(cubeb_stream_start) != CUBEB_OK) {
391 mState = ERRORED;
392 } else if (mState != DRAINED && mState != ERRORED) {
393 // Don't transition to other states if we are already
394 // drained or errored.
395 mState = STARTED;
396 }
397 }
398
Shutdown()399 void AudioStream::Shutdown() {
400 TRACE("AudioStream::Shutdown");
401 MonitorAutoLock mon(mMonitor);
402 LOG("Shutdown, state %d", mState);
403
404 if (mCubebStream) {
405 MonitorAutoUnlock mon(mMonitor);
406 // Force stop to put the cubeb stream in a stable state before deletion.
407 cubeb_stream_stop(mCubebStream.get());
408 // Must not try to shut down cubeb from within the lock! wasapi may still
409 // call our callback after Pause()/stop()!?! Bug 996162
410 mCubebStream.reset();
411 }
412
413 // After `cubeb_stream_stop` has been called, there is no audio thread
414 // anymore. We can delete the time stretcher.
415 if (mTimeStretcher) {
416 soundtouch::destroySoundTouchObj(mTimeStretcher);
417 mTimeStretcher = nullptr;
418 }
419
420 mState = SHUTDOWN;
421 mEndedPromise.ResolveIfExists(true, __func__);
422 }
423
GetPosition()424 int64_t AudioStream::GetPosition() {
425 TRACE("AudioStream::GetPosition");
426 #ifndef XP_MACOSX
427 MonitorAutoLock mon(mMonitor);
428 #endif
429 int64_t frames = GetPositionInFramesUnlocked();
430 return frames >= 0 ? mAudioClock.GetPosition(frames) : -1;
431 }
432
GetPositionInFrames()433 int64_t AudioStream::GetPositionInFrames() {
434 TRACE("AudioStream::GetPositionInFrames");
435 #ifndef XP_MACOSX
436 MonitorAutoLock mon(mMonitor);
437 #endif
438 int64_t frames = GetPositionInFramesUnlocked();
439
440 return frames >= 0 ? mAudioClock.GetPositionInFrames(frames) : -1;
441 }
442
GetPositionInFramesUnlocked()443 int64_t AudioStream::GetPositionInFramesUnlocked() {
444 TRACE("AudioStream::GetPositionInFramesUnlocked");
445 #ifndef XP_MACOSX
446 mMonitor.AssertCurrentThreadOwns();
447 #endif
448
449 if (mState == ERRORED) {
450 return -1;
451 }
452
453 uint64_t position = 0;
454 int rv;
455
456 #ifndef XP_MACOSX
457 rv = InvokeCubeb(cubeb_stream_get_position, &position);
458 #else
459 rv = cubeb_stream_get_position(mCubebStream.get(), &position);
460 #endif
461
462 if (rv != CUBEB_OK) {
463 return -1;
464 }
465 return static_cast<int64_t>(std::min<uint64_t>(position, INT64_MAX));
466 }
467
IsValidAudioFormat(Chunk * aChunk)468 bool AudioStream::IsValidAudioFormat(Chunk* aChunk) {
469 if (aChunk->Rate() != mAudioClock.GetInputRate()) {
470 LOGW("mismatched sample %u, mInRate=%u", aChunk->Rate(),
471 mAudioClock.GetInputRate());
472 return false;
473 }
474
475 return aChunk->Channels() <= 8;
476 }
477
GetUnprocessed(AudioBufferWriter & aWriter)478 void AudioStream::GetUnprocessed(AudioBufferWriter& aWriter) {
479 TRACE("AudioStream::GetUnprocessed");
480 AssertIsOnAudioThread();
481 // Flush the timestretcher pipeline, if we were playing using a playback rate
482 // other than 1.0.
483 if (mTimeStretcher && mTimeStretcher->numSamples()) {
484 auto* timeStretcher = mTimeStretcher;
485 aWriter.Write(
486 [timeStretcher](AudioDataValue* aPtr, uint32_t aFrames) {
487 return timeStretcher->receiveSamples(aPtr, aFrames);
488 },
489 aWriter.Available());
490
491 // TODO: There might be still unprocessed samples in the stretcher.
492 // We should either remove or flush them so they won't be in the output
493 // next time we switch a playback rate other than 1.0.
494 NS_WARNING_ASSERTION(mTimeStretcher->numUnprocessedSamples() == 0,
495 "no samples");
496 } else if (mTimeStretcher) {
497 // Don't need it anymore: playbackRate is 1.0, and the time stretcher has
498 // been flushed.
499 soundtouch::destroySoundTouchObj(mTimeStretcher);
500 mTimeStretcher = nullptr;
501 }
502
503 while (aWriter.Available() > 0) {
504 uint32_t count = mDataSource.PopFrames(aWriter.Ptr(), aWriter.Available(),
505 mAudioThreadChanged);
506 if (count == 0) {
507 break;
508 }
509 aWriter.Advance(count);
510 }
511 }
512
GetTimeStretched(AudioBufferWriter & aWriter)513 void AudioStream::GetTimeStretched(AudioBufferWriter& aWriter) {
514 TRACE("AudioStream::GetTimeStretched");
515 AssertIsOnAudioThread();
516 if (EnsureTimeStretcherInitialized() != NS_OK) {
517 return;
518 }
519
520 uint32_t toPopFrames =
521 ceil(aWriter.Available() * mAudioClock.GetPlaybackRate());
522
523 while (mTimeStretcher->numSamples() < aWriter.Available()) {
524 // pop into a temp buffer, and put into the stretcher.
525 AutoTArray<AudioDataValue, 1000> buf;
526 auto size = CheckedUint32(mOutChannels) * toPopFrames;
527 if (!size.isValid()) {
528 // The overflow should not happen in normal case.
529 LOGW("Invalid member data: %d channels, %d frames", mOutChannels,
530 toPopFrames);
531 return;
532 }
533 buf.SetLength(size.value());
534 // ensure no variable channel count or something like that
535 uint32_t count =
536 mDataSource.PopFrames(buf.Elements(), toPopFrames, mAudioThreadChanged);
537 if (count == 0) {
538 break;
539 }
540 mTimeStretcher->putSamples(buf.Elements(), count);
541 }
542
543 auto* timeStretcher = mTimeStretcher;
544 aWriter.Write(
545 [timeStretcher](AudioDataValue* aPtr, uint32_t aFrames) {
546 return timeStretcher->receiveSamples(aPtr, aFrames);
547 },
548 aWriter.Available());
549 }
550
AssertIsOnAudioThread() const551 void AudioStream::AssertIsOnAudioThread() const {
552 // This can be called right after CheckThreadIdChanged, because the audio
553 // thread can change when not sandboxed.
554 MOZ_ASSERT(mAudioThreadId.load() == profiler_current_thread_id());
555 }
556
UpdatePlaybackRateIfNeeded()557 void AudioStream::UpdatePlaybackRateIfNeeded() {
558 AssertIsOnAudioThread();
559 if (mAudioClock.GetPreservesPitch() == mPreservesPitch &&
560 mAudioClock.GetPlaybackRate() == mPlaybackRate) {
561 return;
562 }
563
564 EnsureTimeStretcherInitialized();
565
566 mAudioClock.SetPlaybackRate(mPlaybackRate);
567 mAudioClock.SetPreservesPitch(mPreservesPitch);
568
569 if (mPreservesPitch) {
570 mTimeStretcher->setTempo(mPlaybackRate);
571 mTimeStretcher->setRate(1.0f);
572 } else {
573 mTimeStretcher->setTempo(1.0f);
574 mTimeStretcher->setRate(mPlaybackRate);
575 }
576 }
577
DataCallback(void * aBuffer,long aFrames)578 long AudioStream::DataCallback(void* aBuffer, long aFrames) {
579 WebCore::DenormalDisabler disabler;
580
581 TRACE_AUDIO_CALLBACK_BUDGET(aFrames, mAudioClock.GetInputRate());
582 TRACE("AudioStream::DataCallback");
583 MOZ_ASSERT(mState != SHUTDOWN, "No data callback after shutdown");
584
585 if (SoftRealTimeLimitReached()) {
586 DemoteThreadFromRealTime();
587 }
588
589 UpdatePlaybackRateIfNeeded();
590
591 auto writer = AudioBufferWriter(
592 Span<AudioDataValue>(reinterpret_cast<AudioDataValue*>(aBuffer),
593 mOutChannels * aFrames),
594 mOutChannels, aFrames);
595
596 if (mAudioClock.GetInputRate() == mAudioClock.GetOutputRate()) {
597 GetUnprocessed(writer);
598 } else {
599 GetTimeStretched(writer);
600 }
601
602 // Always send audible frames first, and silent frames later.
603 // Otherwise it will break the assumption of FrameHistory.
604 if (!mDataSource.Ended()) {
605 #ifndef XP_MACOSX
606 MonitorAutoLock mon(mMonitor);
607 #endif
608 mAudioClock.UpdateFrameHistory(aFrames - writer.Available(),
609 writer.Available(), mAudioThreadChanged);
610 if (writer.Available() > 0) {
611 TRACE_COMMENT("AudioStream::DataCallback", "Underrun: %d frames missing",
612 writer.Available());
613 LOGW("lost %d frames", writer.Available());
614 writer.WriteZeros(writer.Available());
615 }
616 } else {
617 // No more new data in the data source, and the drain has completed. We
618 // don't need the time stretcher anymore at this point.
619 if (mTimeStretcher && writer.Available()) {
620 soundtouch::destroySoundTouchObj(mTimeStretcher);
621 mTimeStretcher = nullptr;
622 }
623 #ifndef XP_MACOSX
624 MonitorAutoLock mon(mMonitor);
625 #endif
626 mAudioClock.UpdateFrameHistory(aFrames - writer.Available(), 0,
627 mAudioThreadChanged);
628 }
629
630 mDumpFile.Write(static_cast<const AudioDataValue*>(aBuffer),
631 aFrames * mOutChannels);
632
633 return aFrames - writer.Available();
634 }
635
StateCallback(cubeb_state aState)636 void AudioStream::StateCallback(cubeb_state aState) {
637 MonitorAutoLock mon(mMonitor);
638 MOZ_ASSERT(mState != SHUTDOWN, "No state callback after shutdown");
639 LOG("StateCallback, mState=%d cubeb_state=%d", mState, aState);
640 if (aState == CUBEB_STATE_DRAINED) {
641 LOG("Drained");
642 mState = DRAINED;
643 mPlaybackComplete = true;
644 mEndedPromise.ResolveIfExists(true, __func__);
645 } else if (aState == CUBEB_STATE_ERROR) {
646 LOGE("StateCallback() state %d cubeb error", mState);
647 mState = ERRORED;
648 mPlaybackComplete = true;
649 mEndedPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
650 }
651 }
652
IsPlaybackCompleted() const653 bool AudioStream::IsPlaybackCompleted() const { return mPlaybackComplete; }
654
AudioClock(uint32_t aInRate)655 AudioClock::AudioClock(uint32_t aInRate)
656 : mOutRate(aInRate),
657 mInRate(aInRate),
658 mPreservesPitch(true),
659 mFrameHistory(new FrameHistory()) {}
660
UpdateFrameHistory(uint32_t aServiced,uint32_t aUnderrun,bool aAudioThreadChanged)661 void AudioClock::UpdateFrameHistory(uint32_t aServiced, uint32_t aUnderrun,
662 bool aAudioThreadChanged) {
663 #ifdef XP_MACOSX
664 if (aAudioThreadChanged) {
665 mCallbackInfoQueue.ResetThreadIds();
666 }
667 // Flush the local items, if any, and then attempt to enqueue the current
668 // item. This is only a fallback mechanism, under non-critical load this is
669 // just going to enqueue an item in the queue.
670 while (!mAudioThreadCallbackInfo.IsEmpty()) {
671 CallbackInfo& info = mAudioThreadCallbackInfo[0];
672 // If still full, keep it audio-thread side for now.
673 if (mCallbackInfoQueue.Enqueue(info) != 1) {
674 break;
675 }
676 mAudioThreadCallbackInfo.RemoveElementAt(0);
677 }
678 CallbackInfo info(aServiced, aUnderrun, mOutRate);
679 if (mCallbackInfoQueue.Enqueue(info) != 1) {
680 NS_WARNING(
681 "mCallbackInfoQueue full, storing the values in the audio thread.");
682 mAudioThreadCallbackInfo.AppendElement(info);
683 }
684 #else
685 mFrameHistory->Append(aServiced, aUnderrun, mOutRate);
686 #endif
687 }
688
GetPositionInFrames(int64_t aFrames)689 int64_t AudioClock::GetPositionInFrames(int64_t aFrames) {
690 CheckedInt64 v = UsecsToFrames(GetPosition(aFrames), mInRate);
691 return v.isValid() ? v.value() : -1;
692 }
693
GetPosition(int64_t frames)694 int64_t AudioClock::GetPosition(int64_t frames) {
695 #ifdef XP_MACOSX
696 // Dequeue all history info, and apply them before returning the position
697 // based on frame history.
698 CallbackInfo info;
699 while (mCallbackInfoQueue.Dequeue(&info, 1)) {
700 mFrameHistory->Append(info.mServiced, info.mUnderrun, info.mOutputRate);
701 }
702 #endif
703 return mFrameHistory->GetPosition(frames);
704 }
705
SetPlaybackRate(double aPlaybackRate)706 void AudioClock::SetPlaybackRate(double aPlaybackRate) {
707 mOutRate = static_cast<uint32_t>(mInRate / aPlaybackRate);
708 }
709
GetPlaybackRate() const710 double AudioClock::GetPlaybackRate() const {
711 return static_cast<double>(mInRate) / mOutRate;
712 }
713
SetPreservesPitch(bool aPreservesPitch)714 void AudioClock::SetPreservesPitch(bool aPreservesPitch) {
715 mPreservesPitch = aPreservesPitch;
716 }
717
GetPreservesPitch() const718 bool AudioClock::GetPreservesPitch() const { return mPreservesPitch; }
719
720 } // namespace mozilla
721