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/. */
9 #include "mozilla/Logging.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"
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"
26 # include "nsXULAppAPI.h"
29 #include "webaudio/blink/DenormalDisabler.h"
30 #include "CallbackThreadRegistry.h"
31 #include "mozilla/StaticPrefs_media.h"
33 #include "RLBoxSoundTouch.h"
41 LazyLogModule
gAudioStreamLog("AudioStream");
44 MOZ_LOG(gAudioStreamLog, mozilla::LogLevel::Debug, \
45 ("%p " x, this, ##__VA_ARGS__))
46 #define LOGW(x, ...) \
47 MOZ_LOG(gAudioStreamLog, mozilla::LogLevel::Warning, \
48 ("%p " x, this, ##__VA_ARGS__))
49 #define LOGE(x, ...) \
50 NS_DebugBreak(NS_DEBUG_WARNING, \
51 nsPrintfCString("%p " x, this, ##__VA_ARGS__).get(), nullptr, \
55 * Keep a list of frames sent to the audio engine in each DataCallback along
56 * with the playback rate at the moment. Since the playback rate and number of
57 * underrun frames can vary in each callback. We need to keep the whole history
58 * in order to calculate the playback position of the audio engine correctly.
62 uint32_t servicedFrames
;
68 static T
FramesToUs(uint32_t frames
, uint32_t rate
) {
69 return static_cast<T
>(frames
) * USECS_PER_S
/ rate
;
73 FrameHistory() : mBaseOffset(0), mBasePosition(0) {}
75 void Append(uint32_t aServiced
, uint32_t aUnderrun
, uint32_t aRate
) {
76 /* In most case where playback rate stays the same and we don't underrun
77 * frames, we are able to merge chunks to avoid lose of precision to add up
78 * in compressing chunks into |mBaseOffset| and |mBasePosition|.
80 if (!mChunks
.IsEmpty()) {
81 Chunk
& c
= mChunks
.LastElement();
82 // 2 chunks (c1 and c2) can be merged when rate is the same and
83 // adjacent frames are zero. That is, underrun frames in c1 are zero
84 // or serviced frames in c2 are zero.
85 if (c
.rate
== aRate
&&
86 (c
.servicedFrames
== c
.totalFrames
|| aServiced
== 0)) {
87 c
.servicedFrames
+= aServiced
;
88 c
.totalFrames
+= aServiced
+ aUnderrun
;
92 Chunk
* p
= mChunks
.AppendElement();
93 p
->servicedFrames
= aServiced
;
94 p
->totalFrames
= aServiced
+ aUnderrun
;
99 * @param frames The playback position in frames of the audio engine.
100 * @return The playback position in microseconds of the audio engine,
101 * adjusted by playback rate changes and underrun frames.
103 int64_t GetPosition(int64_t frames
) {
104 // playback position should not go backward.
105 MOZ_ASSERT(frames
>= mBaseOffset
);
107 if (mChunks
.IsEmpty()) {
108 return static_cast<int64_t>(mBasePosition
);
110 const Chunk
& c
= mChunks
[0];
111 if (frames
<= mBaseOffset
+ c
.totalFrames
) {
112 uint32_t delta
= frames
- mBaseOffset
;
113 delta
= std::min(delta
, c
.servicedFrames
);
114 return static_cast<int64_t>(mBasePosition
) +
115 FramesToUs
<int64_t>(delta
, c
.rate
);
117 // Since the playback position of the audio engine will not go backward,
118 // we are able to compress chunks so that |mChunks| won't grow
119 // unlimitedly. Note that we lose precision in converting integers into
120 // floats and inaccuracy will accumulate over time. However, for a 24hr
121 // long, sample rate = 44.1k file, the error will be less than 1
122 // microsecond after playing 24 hours. So we are fine with that.
123 mBaseOffset
+= c
.totalFrames
;
124 mBasePosition
+= FramesToUs
<double>(c
.servicedFrames
, c
.rate
);
125 mChunks
.RemoveElementAt(0);
130 AutoTArray
<Chunk
, 7> mChunks
;
132 double mBasePosition
;
135 AudioStream::AudioStream(DataSource
& aSource
, uint32_t aInRate
,
136 uint32_t aOutputChannels
,
137 AudioConfig::ChannelLayout::ChannelMap aChannelMap
)
138 : mTimeStretcher(nullptr),
139 mAudioClock(aInRate
),
140 mChannelMap(aChannelMap
),
141 mMonitor("AudioStream"),
142 mOutChannels(aOutputChannels
),
144 mDataSource(aSource
),
145 mAudioThreadId(ProfilerThreadId
{}),
146 mSandboxed(CubebUtils::SandboxEnabled()),
147 mPlaybackComplete(false),
149 mPreservesPitch(true),
150 mCallbacksStarted(false) {}
152 AudioStream::~AudioStream() {
153 LOG("deleted, state %d", mState
.load());
154 MOZ_ASSERT(mState
== SHUTDOWN
&& !mCubebStream
,
155 "Should've called ShutDown() before deleting an AudioStream");
158 size_t AudioStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
159 size_t amount
= aMallocSizeOf(this);
161 // Possibly add in the future:
168 nsresult
AudioStream::EnsureTimeStretcherInitialized() {
169 AssertIsOnAudioThread();
170 if (!mTimeStretcher
) {
171 mTimeStretcher
= new RLBoxSoundTouch();
172 mTimeStretcher
->setSampleRate(mAudioClock
.GetInputRate());
173 mTimeStretcher
->setChannels(mOutChannels
);
174 mTimeStretcher
->setPitch(1.0);
176 // SoundTouch v2.1.2 uses automatic time-stretch settings with the following
178 // Tempo 0.5: 90ms sequence, 20ms seekwindow, 8ms overlap
179 // Tempo 2.0: 40ms sequence, 15ms seekwindow, 8ms overlap
180 // We are going to use a smaller 10ms sequence size to improve speech
181 // clarity, giving more resolution at high tempo and less reverb at low
182 // tempo. Maintain 15ms seekwindow and 8ms overlap for smoothness.
183 mTimeStretcher
->setSetting(
185 StaticPrefs::media_audio_playbackrate_soundtouch_sequence_ms());
186 mTimeStretcher
->setSetting(
187 SETTING_SEEKWINDOW_MS
,
188 StaticPrefs::media_audio_playbackrate_soundtouch_seekwindow_ms());
189 mTimeStretcher
->setSetting(
191 StaticPrefs::media_audio_playbackrate_soundtouch_overlap_ms());
196 nsresult
AudioStream::SetPlaybackRate(double aPlaybackRate
) {
197 TRACE_COMMENT("AudioStream::SetPlaybackRate", "%f", aPlaybackRate
);
200 "Can't handle negative or null playbackrate in the AudioStream.");
201 if (aPlaybackRate
== mPlaybackRate
) {
205 mPlaybackRate
= static_cast<float>(aPlaybackRate
);
210 nsresult
AudioStream::SetPreservesPitch(bool aPreservesPitch
) {
211 TRACE_COMMENT("AudioStream::SetPreservesPitch", "%d", aPreservesPitch
);
212 if (aPreservesPitch
== mPreservesPitch
) {
216 mPreservesPitch
= aPreservesPitch
;
221 template <typename Function
, typename
... Args
>
222 int AudioStream::InvokeCubeb(Function aFunction
, Args
&&... aArgs
) {
223 mMonitor
.AssertCurrentThreadOwns();
224 MonitorAutoUnlock
mon(mMonitor
);
225 return aFunction(mCubebStream
.get(), std::forward
<Args
>(aArgs
)...);
228 nsresult
AudioStream::Init(AudioDeviceInfo
* aSinkInfo
)
229 MOZ_NO_THREAD_SAFETY_ANALYSIS
{
230 auto startTime
= TimeStamp::Now();
231 TRACE("AudioStream::Init");
233 LOG("%s channels: %d, rate: %d", __FUNCTION__
, mOutChannels
,
234 mAudioClock
.GetInputRate());
236 mSinkInfo
= aSinkInfo
;
238 cubeb_stream_params params
;
239 params
.rate
= mAudioClock
.GetInputRate();
240 params
.channels
= mOutChannels
;
241 params
.layout
= static_cast<uint32_t>(mChannelMap
);
242 params
.format
= CubebUtils::ToCubebFormat
<AUDIO_OUTPUT_FORMAT
>::value
;
243 params
.prefs
= CubebUtils::GetDefaultStreamPrefs(CUBEB_DEVICE_TYPE_OUTPUT
);
245 // This is noop if MOZ_DUMP_AUDIO is not set.
246 mDumpFile
.Open("AudioStream", mOutChannels
, mAudioClock
.GetInputRate());
248 RefPtr
<CubebUtils::CubebHandle
> handle
= CubebUtils::GetCubeb();
250 LOGE("Can't get cubeb context!");
251 CubebUtils::ReportCubebStreamInitFailure(true);
252 return NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR
;
256 return OpenCubeb(handle
->Context(), params
, startTime
,
257 CubebUtils::GetFirstStream());
260 nsresult
AudioStream::OpenCubeb(cubeb
* aContext
, cubeb_stream_params
& aParams
,
261 TimeStamp aStartTime
, bool aIsFirst
) {
262 TRACE("AudioStream::OpenCubeb");
263 MOZ_ASSERT(aContext
);
265 cubeb_stream
* stream
= nullptr;
266 /* Convert from milliseconds to frames. */
267 uint32_t latency_frames
=
268 CubebUtils::GetCubebPlaybackLatencyInMilliseconds() * aParams
.rate
/ 1000;
269 cubeb_devid deviceID
= nullptr;
270 if (mSinkInfo
&& mSinkInfo
->DeviceID()) {
271 deviceID
= mSinkInfo
->DeviceID();
273 if (CubebUtils::CubebStreamInit(aContext
, &stream
, "AudioStream", nullptr,
274 nullptr, deviceID
, &aParams
, latency_frames
,
275 DataCallback_S
, StateCallback_S
,
277 mCubebStream
.reset(stream
);
278 CubebUtils::ReportCubebBackendUsed();
280 LOGE("OpenCubeb() failed to init cubeb");
281 CubebUtils::ReportCubebStreamInitFailure(aIsFirst
);
282 return NS_ERROR_FAILURE
;
285 TimeDuration timeDelta
= TimeStamp::Now() - aStartTime
;
286 LOG("creation time %sfirst: %u ms", aIsFirst
? "" : "not ",
287 (uint32_t)timeDelta
.ToMilliseconds());
292 void AudioStream::SetVolume(double aVolume
) {
293 TRACE_COMMENT("AudioStream::SetVolume", "%f", aVolume
);
294 MOZ_ASSERT(aVolume
>= 0.0 && aVolume
<= 1.0, "Invalid volume");
296 MOZ_ASSERT(mState
!= SHUTDOWN
, "Don't set volume after shutdown.");
297 if (mState
== ERRORED
) {
301 MonitorAutoLock
mon(mMonitor
);
302 if (InvokeCubeb(cubeb_stream_set_volume
,
303 aVolume
* CubebUtils::GetVolumeScale()) != CUBEB_OK
) {
304 LOGE("Could not change volume on cubeb stream.");
308 void AudioStream::SetStreamName(const nsAString
& aStreamName
) {
309 TRACE("AudioStream::SetStreamName");
311 nsAutoCString aRawStreamName
;
312 nsresult rv
= NS_CopyUnicodeToNative(aStreamName
, aRawStreamName
);
314 if (NS_FAILED(rv
) || aStreamName
.IsEmpty()) {
318 MonitorAutoLock
mon(mMonitor
);
319 if (InvokeCubeb(cubeb_stream_set_name
, aRawStreamName
.get()) != CUBEB_OK
) {
320 LOGE("Could not set cubeb stream name.");
324 RefPtr
<MediaSink::EndedPromise
> AudioStream::Start() {
325 TRACE("AudioStream::Start");
326 MOZ_ASSERT(mState
== INITIALIZED
);
328 RefPtr
<MediaSink::EndedPromise
> promise
;
330 MonitorAutoLock
mon(mMonitor
);
331 // As cubeb might call audio stream's state callback very soon after we
332 // start cubeb, we have to create the promise beforehand in order to handle
333 // the case where we immediately get `drained`.
334 promise
= mEndedPromise
.Ensure(__func__
);
335 mPlaybackComplete
= false;
337 if (InvokeCubeb(cubeb_stream_start
) != CUBEB_OK
) {
339 mEndedPromise
.RejectIfExists(NS_ERROR_FAILURE
, __func__
);
342 LOG("started, state %s", mState
== STARTED
? "STARTED"
343 : mState
== DRAINED
? "DRAINED"
349 void AudioStream::Pause() {
350 TRACE("AudioStream::Pause");
351 MOZ_ASSERT(mState
!= INITIALIZED
, "Must be Start()ed.");
352 MOZ_ASSERT(mState
!= STOPPED
, "Already Pause()ed.");
353 MOZ_ASSERT(mState
!= SHUTDOWN
, "Already ShutDown()ed.");
355 // Do nothing if we are already drained or errored.
356 if (mState
== DRAINED
|| mState
== ERRORED
) {
360 MonitorAutoLock
mon(mMonitor
);
361 if (InvokeCubeb(cubeb_stream_stop
) != CUBEB_OK
) {
363 } else if (mState
!= DRAINED
&& mState
!= ERRORED
) {
364 // Don't transition to other states if we are already
365 // drained or errored.
370 void AudioStream::Resume() {
371 TRACE("AudioStream::Resume");
372 MOZ_ASSERT(mState
!= INITIALIZED
, "Must be Start()ed.");
373 MOZ_ASSERT(mState
!= STARTED
, "Already Start()ed.");
374 MOZ_ASSERT(mState
!= SHUTDOWN
, "Already ShutDown()ed.");
376 // Do nothing if we are already drained or errored.
377 if (mState
== DRAINED
|| mState
== ERRORED
) {
381 MonitorAutoLock
mon(mMonitor
);
382 if (InvokeCubeb(cubeb_stream_start
) != CUBEB_OK
) {
384 } else if (mState
!= DRAINED
&& mState
!= ERRORED
) {
385 // Don't transition to other states if we are already
386 // drained or errored.
391 void AudioStream::ShutDown() {
392 TRACE("AudioStream::ShutDown");
393 LOG("ShutDown, state %d", mState
.load());
395 MonitorAutoLock
mon(mMonitor
);
397 // Force stop to put the cubeb stream in a stable state before deletion.
398 InvokeCubeb(cubeb_stream_stop
);
399 // Must not try to shut down cubeb from within the lock! wasapi may still
400 // call our callback after Pause()/stop()!?! Bug 996162
401 cubeb_stream
* cubeb
= mCubebStream
.release();
402 MonitorAutoUnlock
unlock(mMonitor
);
403 cubeb_stream_destroy(cubeb
);
406 // After `cubeb_stream_stop` has been called, there is no audio thread
407 // anymore. We can delete the time stretcher.
408 if (mTimeStretcher
) {
409 delete mTimeStretcher
;
410 mTimeStretcher
= nullptr;
414 mEndedPromise
.ResolveIfExists(true, __func__
);
417 int64_t AudioStream::GetPosition() {
418 TRACE("AudioStream::GetPosition");
420 MonitorAutoLock
mon(mMonitor
);
422 int64_t frames
= GetPositionInFramesUnlocked();
423 return frames
>= 0 ? mAudioClock
.GetPosition(frames
) : -1;
426 int64_t AudioStream::GetPositionInFrames() {
427 TRACE("AudioStream::GetPositionInFrames");
429 MonitorAutoLock
mon(mMonitor
);
431 int64_t frames
= GetPositionInFramesUnlocked();
433 return frames
>= 0 ? mAudioClock
.GetPositionInFrames(frames
) : -1;
436 int64_t AudioStream::GetPositionInFramesUnlocked() {
437 TRACE("AudioStream::GetPositionInFramesUnlocked");
439 mMonitor
.AssertCurrentThreadOwns();
442 if (mState
== ERRORED
) {
446 uint64_t position
= 0;
450 rv
= InvokeCubeb(cubeb_stream_get_position
, &position
);
452 rv
= cubeb_stream_get_position(mCubebStream
.get(), &position
);
455 if (rv
!= CUBEB_OK
) {
458 return static_cast<int64_t>(std::min
<uint64_t>(position
, INT64_MAX
));
461 bool AudioStream::IsValidAudioFormat(Chunk
* aChunk
) {
462 if (aChunk
->Rate() != mAudioClock
.GetInputRate()) {
463 LOGW("mismatched sample %u, mInRate=%u", aChunk
->Rate(),
464 mAudioClock
.GetInputRate());
468 return aChunk
->Channels() <= 8;
471 void AudioStream::GetUnprocessed(AudioBufferWriter
& aWriter
) {
472 TRACE("AudioStream::GetUnprocessed");
473 AssertIsOnAudioThread();
474 // Flush the timestretcher pipeline, if we were playing using a playback rate
476 if (mTimeStretcher
) {
477 // Get number of samples and based on this either receive samples or write
478 // silence. At worst, the attacker can supply weird sound samples or
479 // result in us writing silence.
480 auto numSamples
= mTimeStretcher
->numSamples().unverified_safe_because(
481 "We only use this to decide whether to receive samples or write "
484 RLBoxSoundTouch
* timeStretcher
= mTimeStretcher
;
486 [timeStretcher
](AudioDataValue
* aPtr
, uint32_t aFrames
) {
487 return timeStretcher
->receiveSamples(aPtr
, aFrames
);
489 aWriter
.Available());
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 mTimeStretcher
->numUnprocessedSamples().copy_and_verify([](auto samples
) {
495 NS_WARNING_ASSERTION(samples
== 0, "no samples");
498 // Don't need it anymore: playbackRate is 1.0, and the time stretcher has
500 delete mTimeStretcher
;
501 mTimeStretcher
= nullptr;
505 while (aWriter
.Available() > 0) {
506 uint32_t count
= mDataSource
.PopFrames(aWriter
.Ptr(), aWriter
.Available(),
507 mAudioThreadChanged
);
511 aWriter
.Advance(count
);
515 void AudioStream::GetTimeStretched(AudioBufferWriter
& aWriter
) {
516 TRACE("AudioStream::GetTimeStretched");
517 AssertIsOnAudioThread();
518 if (EnsureTimeStretcherInitialized() != NS_OK
) {
522 uint32_t toPopFrames
=
523 ceil(aWriter
.Available() * mAudioClock
.GetPlaybackRate());
525 // At each iteration, get number of samples and (based on this) write from
526 // the data source or silence. At worst, if the number of samples is a lie
527 // (i.e., under attacker control) we'll either not write anything or keep
528 // writing noise. This is safe because all the memory operations within the
529 // loop (and after) are checked.
530 while (mTimeStretcher
->numSamples().unverified_safe_because(
531 "Only used to decide whether to put samples.") <
532 aWriter
.Available()) {
533 // pop into a temp buffer, and put into the stretcher.
534 AutoTArray
<AudioDataValue
, 1000> buf
;
535 auto size
= CheckedUint32(mOutChannels
) * toPopFrames
;
536 if (!size
.isValid()) {
537 // The overflow should not happen in normal case.
538 LOGW("Invalid member data: %d channels, %d frames", mOutChannels
,
542 buf
.SetLength(size
.value());
543 // ensure no variable channel count or something like that
545 mDataSource
.PopFrames(buf
.Elements(), toPopFrames
, mAudioThreadChanged
);
549 mTimeStretcher
->putSamples(buf
.Elements(), count
);
552 auto* timeStretcher
= mTimeStretcher
;
554 [timeStretcher
](AudioDataValue
* aPtr
, uint32_t aFrames
) {
555 return timeStretcher
->receiveSamples(aPtr
, aFrames
);
557 aWriter
.Available());
560 bool AudioStream::CheckThreadIdChanged() {
561 ProfilerThreadId id
= profiler_current_thread_id();
562 if (id
!= mAudioThreadId
) {
564 mAudioThreadChanged
= true;
567 mAudioThreadChanged
= false;
571 void AudioStream::AssertIsOnAudioThread() const {
572 // This can be called right after CheckThreadIdChanged, because the audio
573 // thread can change when not sandboxed.
574 MOZ_ASSERT(mAudioThreadId
.load() == profiler_current_thread_id());
577 void AudioStream::UpdatePlaybackRateIfNeeded() {
578 AssertIsOnAudioThread();
579 if (mAudioClock
.GetPreservesPitch() == mPreservesPitch
&&
580 mAudioClock
.GetPlaybackRate() == mPlaybackRate
) {
584 EnsureTimeStretcherInitialized();
586 mAudioClock
.SetPlaybackRate(mPlaybackRate
);
587 mAudioClock
.SetPreservesPitch(mPreservesPitch
);
589 if (mPreservesPitch
) {
590 mTimeStretcher
->setTempo(mPlaybackRate
);
591 mTimeStretcher
->setRate(1.0f
);
593 mTimeStretcher
->setTempo(1.0f
);
594 mTimeStretcher
->setRate(mPlaybackRate
);
598 long AudioStream::DataCallback(void* aBuffer
, long aFrames
) {
599 if (CheckThreadIdChanged() && !mSandboxed
) {
600 CallbackThreadRegistry::Get()->Register(mAudioThreadId
,
601 "NativeAudioCallback");
603 WebCore::DenormalDisabler disabler
;
604 if (!mCallbacksStarted
) {
605 mCallbacksStarted
= true;
608 TRACE_AUDIO_CALLBACK_BUDGET("AudioStream real-time budget", aFrames
,
609 mAudioClock
.GetInputRate());
610 TRACE("AudioStream::DataCallback");
611 MOZ_ASSERT(mState
!= SHUTDOWN
, "No data callback after shutdown");
613 if (SoftRealTimeLimitReached()) {
614 DemoteThreadFromRealTime();
617 UpdatePlaybackRateIfNeeded();
619 auto writer
= AudioBufferWriter(
620 Span
<AudioDataValue
>(reinterpret_cast<AudioDataValue
*>(aBuffer
),
621 mOutChannels
* aFrames
),
622 mOutChannels
, aFrames
);
624 if (mAudioClock
.GetInputRate() == mAudioClock
.GetOutputRate()) {
625 GetUnprocessed(writer
);
627 GetTimeStretched(writer
);
630 // Always send audible frames first, and silent frames later.
631 // Otherwise it will break the assumption of FrameHistory.
632 if (!mDataSource
.Ended()) {
634 MonitorAutoLock
mon(mMonitor
);
636 mAudioClock
.UpdateFrameHistory(aFrames
- writer
.Available(),
637 writer
.Available(), mAudioThreadChanged
);
638 if (writer
.Available() > 0) {
639 TRACE_COMMENT("AudioStream::DataCallback", "Underrun: %d frames missing",
641 LOGW("lost %d frames", writer
.Available());
642 writer
.WriteZeros(writer
.Available());
645 // No more new data in the data source, and the drain has completed. We
646 // don't need the time stretcher anymore at this point.
647 if (mTimeStretcher
&& writer
.Available()) {
648 delete mTimeStretcher
;
649 mTimeStretcher
= nullptr;
652 MonitorAutoLock
mon(mMonitor
);
654 mAudioClock
.UpdateFrameHistory(aFrames
- writer
.Available(), 0,
655 mAudioThreadChanged
);
658 mDumpFile
.Write(static_cast<const AudioDataValue
*>(aBuffer
),
659 aFrames
* mOutChannels
);
661 if (!mSandboxed
&& writer
.Available() != 0) {
662 CallbackThreadRegistry::Get()->Unregister(mAudioThreadId
);
664 return aFrames
- writer
.Available();
667 void AudioStream::StateCallback(cubeb_state aState
) {
668 MOZ_ASSERT(mState
!= SHUTDOWN
, "No state callback after shutdown");
669 LOG("StateCallback, mState=%d cubeb_state=%d", mState
.load(), aState
);
671 MonitorAutoLock
mon(mMonitor
);
672 if (aState
== CUBEB_STATE_DRAINED
) {
675 mPlaybackComplete
= true;
676 mEndedPromise
.ResolveIfExists(true, __func__
);
677 } else if (aState
== CUBEB_STATE_ERROR
) {
678 LOGE("StateCallback() state %d cubeb error", mState
.load());
680 mPlaybackComplete
= true;
681 mEndedPromise
.RejectIfExists(NS_ERROR_FAILURE
, __func__
);
685 bool AudioStream::IsPlaybackCompleted() const { return mPlaybackComplete
; }
687 AudioClock::AudioClock(uint32_t aInRate
)
690 mPreservesPitch(true),
691 mFrameHistory(new FrameHistory()) {}
694 void AudioClock::UpdateFrameHistory(uint32_t aServiced
, uint32_t aUnderrun
,
695 bool aAudioThreadChanged
) {
697 if (aAudioThreadChanged
) {
698 mCallbackInfoQueue
.ResetProducerThreadId();
700 // Flush the local items, if any, and then attempt to enqueue the current
701 // item. This is only a fallback mechanism, under non-critical load this is
702 // just going to enqueue an item in the queue.
703 while (!mAudioThreadCallbackInfo
.IsEmpty()) {
704 CallbackInfo
& info
= mAudioThreadCallbackInfo
[0];
705 // If still full, keep it audio-thread side for now.
706 if (mCallbackInfoQueue
.Enqueue(info
) != 1) {
709 mAudioThreadCallbackInfo
.RemoveElementAt(0);
711 CallbackInfo
info(aServiced
, aUnderrun
, mOutRate
);
712 if (mCallbackInfoQueue
.Enqueue(info
) != 1) {
714 "mCallbackInfoQueue full, storing the values in the audio thread.");
715 mAudioThreadCallbackInfo
.AppendElement(info
);
718 MutexAutoLock
lock(mMutex
);
719 mFrameHistory
->Append(aServiced
, aUnderrun
, mOutRate
);
723 int64_t AudioClock::GetPositionInFrames(int64_t aFrames
) {
724 CheckedInt64 v
= UsecsToFrames(GetPosition(aFrames
), mInRate
);
725 return v
.isValid() ? v
.value() : -1;
728 int64_t AudioClock::GetPosition(int64_t frames
) {
730 // Dequeue all history info, and apply them before returning the position
731 // based on frame history.
733 while (mCallbackInfoQueue
.Dequeue(&info
, 1)) {
734 mFrameHistory
->Append(info
.mServiced
, info
.mUnderrun
, info
.mOutputRate
);
737 MutexAutoLock
lock(mMutex
);
739 return mFrameHistory
->GetPosition(frames
);
742 void AudioClock::SetPlaybackRate(double aPlaybackRate
) {
743 mOutRate
= static_cast<uint32_t>(mInRate
/ aPlaybackRate
);
746 double AudioClock::GetPlaybackRate() const {
747 return static_cast<double>(mInRate
) / mOutRate
;
750 void AudioClock::SetPreservesPitch(bool aPreservesPitch
) {
751 mPreservesPitch
= aPreservesPitch
;
754 bool AudioClock::GetPreservesPitch() const { return mPreservesPitch
; }
756 } // namespace mozilla