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 auto timestretcher
= MakeUnique
<RLBoxSoundTouch
>();
172 if (!timestretcher
|| !timestretcher
->Init()) {
173 return NS_ERROR_FAILURE
;
175 mTimeStretcher
= timestretcher
.release();
177 mTimeStretcher
->setSampleRate(mAudioClock
.GetInputRate());
178 mTimeStretcher
->setChannels(mOutChannels
);
179 mTimeStretcher
->setPitch(1.0);
181 // SoundTouch v2.1.2 uses automatic time-stretch settings with the following
183 // Tempo 0.5: 90ms sequence, 20ms seekwindow, 8ms overlap
184 // Tempo 2.0: 40ms sequence, 15ms seekwindow, 8ms overlap
185 // We are going to use a smaller 10ms sequence size to improve speech
186 // clarity, giving more resolution at high tempo and less reverb at low
187 // tempo. Maintain 15ms seekwindow and 8ms overlap for smoothness.
188 mTimeStretcher
->setSetting(
190 StaticPrefs::media_audio_playbackrate_soundtouch_sequence_ms());
191 mTimeStretcher
->setSetting(
192 SETTING_SEEKWINDOW_MS
,
193 StaticPrefs::media_audio_playbackrate_soundtouch_seekwindow_ms());
194 mTimeStretcher
->setSetting(
196 StaticPrefs::media_audio_playbackrate_soundtouch_overlap_ms());
201 nsresult
AudioStream::SetPlaybackRate(double aPlaybackRate
) {
202 TRACE_COMMENT("AudioStream::SetPlaybackRate", "%f", aPlaybackRate
);
205 "Can't handle negative or null playbackrate in the AudioStream.");
206 if (aPlaybackRate
== mPlaybackRate
) {
210 mPlaybackRate
= static_cast<float>(aPlaybackRate
);
215 nsresult
AudioStream::SetPreservesPitch(bool aPreservesPitch
) {
216 TRACE_COMMENT("AudioStream::SetPreservesPitch", "%d", aPreservesPitch
);
217 if (aPreservesPitch
== mPreservesPitch
) {
221 mPreservesPitch
= aPreservesPitch
;
226 template <typename Function
, typename
... Args
>
227 int AudioStream::InvokeCubeb(Function aFunction
, Args
&&... aArgs
) {
228 mMonitor
.AssertCurrentThreadOwns();
229 MonitorAutoUnlock
mon(mMonitor
);
230 return aFunction(mCubebStream
.get(), std::forward
<Args
>(aArgs
)...);
233 nsresult
AudioStream::Init(AudioDeviceInfo
* aSinkInfo
)
234 MOZ_NO_THREAD_SAFETY_ANALYSIS
{
235 auto startTime
= TimeStamp::Now();
236 TRACE("AudioStream::Init");
238 LOG("%s channels: %d, rate: %d", __FUNCTION__
, mOutChannels
,
239 mAudioClock
.GetInputRate());
241 mSinkInfo
= aSinkInfo
;
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
= CubebUtils::ToCubebFormat
<AUDIO_OUTPUT_FORMAT
>::value
;
248 params
.prefs
= CubebUtils::GetDefaultStreamPrefs(CUBEB_DEVICE_TYPE_OUTPUT
);
250 // This is noop if MOZ_DUMP_AUDIO is not set.
251 mDumpFile
.Open("AudioStream", mOutChannels
, mAudioClock
.GetInputRate());
253 RefPtr
<CubebUtils::CubebHandle
> handle
= CubebUtils::GetCubeb();
255 LOGE("Can't get cubeb context!");
256 CubebUtils::ReportCubebStreamInitFailure(true);
257 return NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR
;
261 return OpenCubeb(handle
->Context(), params
, startTime
,
262 CubebUtils::GetFirstStream());
265 nsresult
AudioStream::OpenCubeb(cubeb
* aContext
, cubeb_stream_params
& aParams
,
266 TimeStamp aStartTime
, bool aIsFirst
) {
267 TRACE("AudioStream::OpenCubeb");
268 MOZ_ASSERT(aContext
);
270 cubeb_stream
* stream
= nullptr;
271 /* Convert from milliseconds to frames. */
272 uint32_t latency_frames
=
273 CubebUtils::GetCubebPlaybackLatencyInMilliseconds() * aParams
.rate
/ 1000;
274 cubeb_devid deviceID
= nullptr;
275 if (mSinkInfo
&& mSinkInfo
->DeviceID()) {
276 deviceID
= mSinkInfo
->DeviceID();
278 if (CubebUtils::CubebStreamInit(aContext
, &stream
, "AudioStream", nullptr,
279 nullptr, deviceID
, &aParams
, latency_frames
,
280 DataCallback_S
, StateCallback_S
,
282 mCubebStream
.reset(stream
);
283 CubebUtils::ReportCubebBackendUsed();
285 LOGE("OpenCubeb() failed to init cubeb");
286 CubebUtils::ReportCubebStreamInitFailure(aIsFirst
);
287 return NS_ERROR_FAILURE
;
290 TimeDuration timeDelta
= TimeStamp::Now() - aStartTime
;
291 LOG("creation time %sfirst: %u ms", aIsFirst
? "" : "not ",
292 (uint32_t)timeDelta
.ToMilliseconds());
297 void AudioStream::SetVolume(double aVolume
) {
298 TRACE_COMMENT("AudioStream::SetVolume", "%f", aVolume
);
299 MOZ_ASSERT(aVolume
>= 0.0 && aVolume
<= 1.0, "Invalid volume");
301 MOZ_ASSERT(mState
!= SHUTDOWN
, "Don't set volume after shutdown.");
302 if (mState
== ERRORED
) {
306 MonitorAutoLock
mon(mMonitor
);
307 if (InvokeCubeb(cubeb_stream_set_volume
,
308 aVolume
* CubebUtils::GetVolumeScale()) != CUBEB_OK
) {
309 LOGE("Could not change volume on cubeb stream.");
313 void AudioStream::SetStreamName(const nsAString
& aStreamName
) {
314 TRACE("AudioStream::SetStreamName");
316 nsAutoCString aRawStreamName
;
317 nsresult rv
= NS_CopyUnicodeToNative(aStreamName
, aRawStreamName
);
319 if (NS_FAILED(rv
) || aStreamName
.IsEmpty()) {
323 MonitorAutoLock
mon(mMonitor
);
324 int r
= InvokeCubeb(cubeb_stream_set_name
, aRawStreamName
.get());
325 if (r
&& r
!= CUBEB_ERROR_NOT_SUPPORTED
) {
326 LOGE("Could not set cubeb stream name.");
330 RefPtr
<MediaSink::EndedPromise
> AudioStream::Start() {
331 TRACE("AudioStream::Start");
332 MOZ_ASSERT(mState
== INITIALIZED
);
334 RefPtr
<MediaSink::EndedPromise
> promise
;
336 MonitorAutoLock
mon(mMonitor
);
337 // As cubeb might call audio stream's state callback very soon after we
338 // start cubeb, we have to create the promise beforehand in order to handle
339 // the case where we immediately get `drained`.
340 promise
= mEndedPromise
.Ensure(__func__
);
341 mPlaybackComplete
= false;
343 if (InvokeCubeb(cubeb_stream_start
) != CUBEB_OK
) {
345 mEndedPromise
.RejectIfExists(NS_ERROR_FAILURE
, __func__
);
348 LOG("started, state %s", mState
== STARTED
? "STARTED"
349 : mState
== DRAINED
? "DRAINED"
355 void AudioStream::Pause() {
356 TRACE("AudioStream::Pause");
357 MOZ_ASSERT(mState
!= INITIALIZED
, "Must be Start()ed.");
358 MOZ_ASSERT(mState
!= STOPPED
, "Already Pause()ed.");
359 MOZ_ASSERT(mState
!= SHUTDOWN
, "Already ShutDown()ed.");
361 // Do nothing if we are already drained or errored.
362 if (mState
== DRAINED
|| mState
== ERRORED
) {
366 MonitorAutoLock
mon(mMonitor
);
367 if (InvokeCubeb(cubeb_stream_stop
) != CUBEB_OK
) {
369 } else if (mState
!= DRAINED
&& mState
!= ERRORED
) {
370 // Don't transition to other states if we are already
371 // drained or errored.
376 void AudioStream::Resume() {
377 TRACE("AudioStream::Resume");
378 MOZ_ASSERT(mState
!= INITIALIZED
, "Must be Start()ed.");
379 MOZ_ASSERT(mState
!= STARTED
, "Already Start()ed.");
380 MOZ_ASSERT(mState
!= SHUTDOWN
, "Already ShutDown()ed.");
382 // Do nothing if we are already drained or errored.
383 if (mState
== DRAINED
|| mState
== ERRORED
) {
387 MonitorAutoLock
mon(mMonitor
);
388 if (InvokeCubeb(cubeb_stream_start
) != CUBEB_OK
) {
390 } else if (mState
!= DRAINED
&& mState
!= ERRORED
) {
391 // Don't transition to other states if we are already
392 // drained or errored.
397 void AudioStream::ShutDown() {
398 TRACE("AudioStream::ShutDown");
399 LOG("ShutDown, state %d", mState
.load());
401 MonitorAutoLock
mon(mMonitor
);
403 // Force stop to put the cubeb stream in a stable state before deletion.
404 InvokeCubeb(cubeb_stream_stop
);
405 // Must not try to shut down cubeb from within the lock! wasapi may still
406 // call our callback after Pause()/stop()!?! Bug 996162
407 cubeb_stream
* cubeb
= mCubebStream
.release();
408 MonitorAutoUnlock
unlock(mMonitor
);
409 cubeb_stream_destroy(cubeb
);
412 // After `cubeb_stream_stop` has been called, there is no audio thread
413 // anymore. We can delete the time stretcher.
414 if (mTimeStretcher
) {
415 delete mTimeStretcher
;
416 mTimeStretcher
= nullptr;
420 mEndedPromise
.ResolveIfExists(true, __func__
);
423 int64_t AudioStream::GetPosition() {
424 TRACE("AudioStream::GetPosition");
426 MonitorAutoLock
mon(mMonitor
);
428 int64_t frames
= GetPositionInFramesUnlocked();
429 return frames
>= 0 ? mAudioClock
.GetPosition(frames
) : -1;
432 int64_t AudioStream::GetPositionInFrames() {
433 TRACE("AudioStream::GetPositionInFrames");
435 MonitorAutoLock
mon(mMonitor
);
437 int64_t frames
= GetPositionInFramesUnlocked();
439 return frames
>= 0 ? mAudioClock
.GetPositionInFrames(frames
) : -1;
442 int64_t AudioStream::GetPositionInFramesUnlocked() {
443 TRACE("AudioStream::GetPositionInFramesUnlocked");
445 mMonitor
.AssertCurrentThreadOwns();
448 if (mState
== ERRORED
) {
452 uint64_t position
= 0;
456 rv
= InvokeCubeb(cubeb_stream_get_position
, &position
);
458 rv
= cubeb_stream_get_position(mCubebStream
.get(), &position
);
461 if (rv
!= CUBEB_OK
) {
464 return static_cast<int64_t>(std::min
<uint64_t>(position
, INT64_MAX
));
467 bool AudioStream::IsValidAudioFormat(Chunk
* aChunk
) {
468 if (aChunk
->Rate() != mAudioClock
.GetInputRate()) {
469 LOGW("mismatched sample %u, mInRate=%u", aChunk
->Rate(),
470 mAudioClock
.GetInputRate());
474 return aChunk
->Channels() <= 8;
477 void AudioStream::GetUnprocessed(AudioBufferWriter
& aWriter
) {
478 TRACE("AudioStream::GetUnprocessed");
479 AssertIsOnAudioThread();
480 // Flush the timestretcher pipeline, if we were playing using a playback rate
482 if (mTimeStretcher
) {
483 // Get number of samples and based on this either receive samples or write
484 // silence. At worst, the attacker can supply weird sound samples or
485 // result in us writing silence.
486 auto numSamples
= mTimeStretcher
->numSamples().unverified_safe_because(
487 "We only use this to decide whether to receive samples or write "
490 RLBoxSoundTouch
* timeStretcher
= mTimeStretcher
;
492 [timeStretcher
](AudioDataValue
* aPtr
, uint32_t aFrames
) {
493 return timeStretcher
->receiveSamples(aPtr
, aFrames
);
495 aWriter
.Available());
497 // TODO: There might be still unprocessed samples in the stretcher.
498 // We should either remove or flush them so they won't be in the output
499 // next time we switch a playback rate other than 1.0.
500 mTimeStretcher
->numUnprocessedSamples().copy_and_verify([](auto samples
) {
501 NS_WARNING_ASSERTION(samples
== 0, "no samples");
504 // Don't need it anymore: playbackRate is 1.0, and the time stretcher has
506 delete mTimeStretcher
;
507 mTimeStretcher
= nullptr;
511 while (aWriter
.Available() > 0) {
512 uint32_t count
= mDataSource
.PopFrames(aWriter
.Ptr(), aWriter
.Available(),
513 mAudioThreadChanged
);
517 aWriter
.Advance(count
);
521 void AudioStream::GetTimeStretched(AudioBufferWriter
& aWriter
) {
522 TRACE("AudioStream::GetTimeStretched");
523 AssertIsOnAudioThread();
524 if (EnsureTimeStretcherInitialized() != NS_OK
) {
528 uint32_t toPopFrames
=
529 ceil(aWriter
.Available() * mAudioClock
.GetPlaybackRate());
531 // At each iteration, get number of samples and (based on this) write from
532 // the data source or silence. At worst, if the number of samples is a lie
533 // (i.e., under attacker control) we'll either not write anything or keep
534 // writing noise. This is safe because all the memory operations within the
535 // loop (and after) are checked.
536 while (mTimeStretcher
->numSamples().unverified_safe_because(
537 "Only used to decide whether to put samples.") <
538 aWriter
.Available()) {
539 // pop into a temp buffer, and put into the stretcher.
540 AutoTArray
<AudioDataValue
, 1000> buf
;
541 auto size
= CheckedUint32(mOutChannels
) * toPopFrames
;
542 if (!size
.isValid()) {
543 // The overflow should not happen in normal case.
544 LOGW("Invalid member data: %d channels, %d frames", mOutChannels
,
548 buf
.SetLength(size
.value());
549 // ensure no variable channel count or something like that
551 mDataSource
.PopFrames(buf
.Elements(), toPopFrames
, mAudioThreadChanged
);
555 mTimeStretcher
->putSamples(buf
.Elements(), count
);
558 auto* timeStretcher
= mTimeStretcher
;
560 [timeStretcher
](AudioDataValue
* aPtr
, uint32_t aFrames
) {
561 return timeStretcher
->receiveSamples(aPtr
, aFrames
);
563 aWriter
.Available());
566 bool AudioStream::CheckThreadIdChanged() {
567 ProfilerThreadId id
= profiler_current_thread_id();
568 if (id
!= mAudioThreadId
) {
570 mAudioThreadChanged
= true;
573 mAudioThreadChanged
= false;
577 void AudioStream::AssertIsOnAudioThread() const {
578 // This can be called right after CheckThreadIdChanged, because the audio
579 // thread can change when not sandboxed.
580 MOZ_ASSERT(mAudioThreadId
.load() == profiler_current_thread_id());
583 void AudioStream::UpdatePlaybackRateIfNeeded() {
584 AssertIsOnAudioThread();
585 if (mAudioClock
.GetPreservesPitch() == mPreservesPitch
&&
586 mAudioClock
.GetPlaybackRate() == mPlaybackRate
) {
590 EnsureTimeStretcherInitialized();
592 mAudioClock
.SetPlaybackRate(mPlaybackRate
);
593 mAudioClock
.SetPreservesPitch(mPreservesPitch
);
595 if (mPreservesPitch
) {
596 mTimeStretcher
->setTempo(mPlaybackRate
);
597 mTimeStretcher
->setRate(1.0f
);
599 mTimeStretcher
->setTempo(1.0f
);
600 mTimeStretcher
->setRate(mPlaybackRate
);
604 long AudioStream::DataCallback(void* aBuffer
, long aFrames
) {
605 if (CheckThreadIdChanged() && !mSandboxed
) {
606 CallbackThreadRegistry::Get()->Register(mAudioThreadId
,
607 "NativeAudioCallback");
609 WebCore::DenormalDisabler disabler
;
610 if (!mCallbacksStarted
) {
611 mCallbacksStarted
= true;
614 TRACE_AUDIO_CALLBACK_FRAME_COUNT("AudioStream real-time budget", aFrames
,
615 mAudioClock
.GetInputRate());
616 TRACE("AudioStream::DataCallback");
617 MOZ_ASSERT(mState
!= SHUTDOWN
, "No data callback after shutdown");
619 if (SoftRealTimeLimitReached()) {
620 DemoteThreadFromRealTime();
623 UpdatePlaybackRateIfNeeded();
625 auto writer
= AudioBufferWriter(
626 Span
<AudioDataValue
>(reinterpret_cast<AudioDataValue
*>(aBuffer
),
627 mOutChannels
* aFrames
),
628 mOutChannels
, aFrames
);
630 if (mAudioClock
.GetInputRate() == mAudioClock
.GetOutputRate()) {
631 GetUnprocessed(writer
);
633 GetTimeStretched(writer
);
636 // Always send audible frames first, and silent frames later.
637 // Otherwise it will break the assumption of FrameHistory.
638 if (!mDataSource
.Ended()) {
640 MonitorAutoLock
mon(mMonitor
);
642 mAudioClock
.UpdateFrameHistory(aFrames
- writer
.Available(),
643 writer
.Available(), mAudioThreadChanged
);
644 if (writer
.Available() > 0) {
645 TRACE_COMMENT("AudioStream::DataCallback", "Underrun: %d frames missing",
647 LOGW("lost %d frames", writer
.Available());
648 writer
.WriteZeros(writer
.Available());
651 // No more new data in the data source, and the drain has completed. We
652 // don't need the time stretcher anymore at this point.
653 if (mTimeStretcher
&& writer
.Available()) {
654 delete mTimeStretcher
;
655 mTimeStretcher
= nullptr;
658 MonitorAutoLock
mon(mMonitor
);
660 mAudioClock
.UpdateFrameHistory(aFrames
- writer
.Available(), 0,
661 mAudioThreadChanged
);
664 mDumpFile
.Write(static_cast<const AudioDataValue
*>(aBuffer
),
665 aFrames
* mOutChannels
);
667 if (!mSandboxed
&& writer
.Available() != 0) {
668 CallbackThreadRegistry::Get()->Unregister(mAudioThreadId
);
670 return aFrames
- writer
.Available();
673 void AudioStream::StateCallback(cubeb_state aState
) {
674 MOZ_ASSERT(mState
!= SHUTDOWN
, "No state callback after shutdown");
675 LOG("StateCallback, mState=%d cubeb_state=%d", mState
.load(), aState
);
677 MonitorAutoLock
mon(mMonitor
);
678 if (aState
== CUBEB_STATE_DRAINED
) {
681 mPlaybackComplete
= true;
682 mEndedPromise
.ResolveIfExists(true, __func__
);
683 } else if (aState
== CUBEB_STATE_ERROR
) {
684 LOGE("StateCallback() state %d cubeb error", mState
.load());
686 mPlaybackComplete
= true;
687 mEndedPromise
.RejectIfExists(NS_ERROR_FAILURE
, __func__
);
691 bool AudioStream::IsPlaybackCompleted() const { return mPlaybackComplete
; }
693 AudioClock::AudioClock(uint32_t aInRate
)
696 mPreservesPitch(true),
697 mFrameHistory(new FrameHistory()) {}
700 void AudioClock::UpdateFrameHistory(uint32_t aServiced
, uint32_t aUnderrun
,
701 bool aAudioThreadChanged
) {
703 if (aAudioThreadChanged
) {
704 mCallbackInfoQueue
.ResetProducerThreadId();
706 // Flush the local items, if any, and then attempt to enqueue the current
707 // item. This is only a fallback mechanism, under non-critical load this is
708 // just going to enqueue an item in the queue.
709 while (!mAudioThreadCallbackInfo
.IsEmpty()) {
710 CallbackInfo
& info
= mAudioThreadCallbackInfo
[0];
711 // If still full, keep it audio-thread side for now.
712 if (mCallbackInfoQueue
.Enqueue(info
) != 1) {
715 mAudioThreadCallbackInfo
.RemoveElementAt(0);
717 CallbackInfo
info(aServiced
, aUnderrun
, mOutRate
);
718 if (mCallbackInfoQueue
.Enqueue(info
) != 1) {
720 "mCallbackInfoQueue full, storing the values in the audio thread.");
721 mAudioThreadCallbackInfo
.AppendElement(info
);
724 MutexAutoLock
lock(mMutex
);
725 mFrameHistory
->Append(aServiced
, aUnderrun
, mOutRate
);
729 int64_t AudioClock::GetPositionInFrames(int64_t aFrames
) {
730 CheckedInt64 v
= UsecsToFrames(GetPosition(aFrames
), mInRate
);
731 return v
.isValid() ? v
.value() : -1;
734 int64_t AudioClock::GetPosition(int64_t frames
) {
736 // Dequeue all history info, and apply them before returning the position
737 // based on frame history.
739 while (mCallbackInfoQueue
.Dequeue(&info
, 1)) {
740 mFrameHistory
->Append(info
.mServiced
, info
.mUnderrun
, info
.mOutputRate
);
743 MutexAutoLock
lock(mMutex
);
745 return mFrameHistory
->GetPosition(frames
);
748 void AudioClock::SetPlaybackRate(double aPlaybackRate
) {
749 mOutRate
= static_cast<uint32_t>(mInRate
/ aPlaybackRate
);
752 double AudioClock::GetPlaybackRate() const {
753 return static_cast<double>(mInRate
) / mOutRate
;
756 void AudioClock::SetPreservesPitch(bool aPreservesPitch
) {
757 mPreservesPitch
= aPreservesPitch
;
760 bool AudioClock::GetPreservesPitch() const { return mPreservesPitch
; }
762 } // namespace mozilla