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 // Use abort() instead of exception in SoundTouch.
34 #define ST_NO_EXCEPTION_HANDLING 1
35 #include "soundtouch/SoundTouchFactory.h"
43 LazyLogModule
gAudioStreamLog("AudioStream");
46 MOZ_LOG(gAudioStreamLog, mozilla::LogLevel::Debug, \
47 ("%p " x, this, ##__VA_ARGS__))
48 #define LOGW(x, ...) \
49 MOZ_LOG(gAudioStreamLog, mozilla::LogLevel::Warning, \
50 ("%p " x, this, ##__VA_ARGS__))
51 #define LOGE(x, ...) \
52 NS_DebugBreak(NS_DEBUG_WARNING, \
53 nsPrintfCString("%p " x, this, ##__VA_ARGS__).get(), nullptr, \
57 * Keep a list of frames sent to the audio engine in each DataCallback along
58 * with the playback rate at the moment. Since the playback rate and number of
59 * underrun frames can vary in each callback. We need to keep the whole history
60 * in order to calculate the playback position of the audio engine correctly.
64 uint32_t servicedFrames
;
70 static T
FramesToUs(uint32_t frames
, uint32_t rate
) {
71 return static_cast<T
>(frames
) * USECS_PER_S
/ rate
;
75 FrameHistory() : mBaseOffset(0), mBasePosition(0) {}
77 void Append(uint32_t aServiced
, uint32_t aUnderrun
, uint32_t aRate
) {
78 /* In most case where playback rate stays the same and we don't underrun
79 * frames, we are able to merge chunks to avoid lose of precision to add up
80 * in compressing chunks into |mBaseOffset| and |mBasePosition|.
82 if (!mChunks
.IsEmpty()) {
83 Chunk
& c
= mChunks
.LastElement();
84 // 2 chunks (c1 and c2) can be merged when rate is the same and
85 // adjacent frames are zero. That is, underrun frames in c1 are zero
86 // or serviced frames in c2 are zero.
87 if (c
.rate
== aRate
&&
88 (c
.servicedFrames
== c
.totalFrames
|| aServiced
== 0)) {
89 c
.servicedFrames
+= aServiced
;
90 c
.totalFrames
+= aServiced
+ aUnderrun
;
94 Chunk
* p
= mChunks
.AppendElement();
95 p
->servicedFrames
= aServiced
;
96 p
->totalFrames
= aServiced
+ aUnderrun
;
101 * @param frames The playback position in frames of the audio engine.
102 * @return The playback position in microseconds of the audio engine,
103 * adjusted by playback rate changes and underrun frames.
105 int64_t GetPosition(int64_t frames
) {
106 // playback position should not go backward.
107 MOZ_ASSERT(frames
>= mBaseOffset
);
109 if (mChunks
.IsEmpty()) {
110 return static_cast<int64_t>(mBasePosition
);
112 const Chunk
& c
= mChunks
[0];
113 if (frames
<= mBaseOffset
+ c
.totalFrames
) {
114 uint32_t delta
= frames
- mBaseOffset
;
115 delta
= std::min(delta
, c
.servicedFrames
);
116 return static_cast<int64_t>(mBasePosition
) +
117 FramesToUs
<int64_t>(delta
, c
.rate
);
119 // Since the playback position of the audio engine will not go backward,
120 // we are able to compress chunks so that |mChunks| won't grow
121 // unlimitedly. Note that we lose precision in converting integers into
122 // floats and inaccuracy will accumulate over time. However, for a 24hr
123 // long, sample rate = 44.1k file, the error will be less than 1
124 // microsecond after playing 24 hours. So we are fine with that.
125 mBaseOffset
+= c
.totalFrames
;
126 mBasePosition
+= FramesToUs
<double>(c
.servicedFrames
, c
.rate
);
127 mChunks
.RemoveElementAt(0);
132 AutoTArray
<Chunk
, 7> mChunks
;
134 double mBasePosition
;
137 AudioStream::AudioStream(DataSource
& aSource
, uint32_t aInRate
,
138 uint32_t aOutputChannels
,
139 AudioConfig::ChannelLayout::ChannelMap aChannelMap
)
140 : mTimeStretcher(nullptr),
141 mAudioClock(aInRate
),
142 mChannelMap(aChannelMap
),
143 mMonitor("AudioStream"),
144 mOutChannels(aOutputChannels
),
146 mDataSource(aSource
),
147 mAudioThreadId(ProfilerThreadId
{}),
148 mSandboxed(CubebUtils::SandboxEnabled()),
149 mPlaybackComplete(false),
151 mPreservesPitch(true),
152 mCallbacksStarted(false) {}
154 AudioStream::~AudioStream() {
155 LOG("deleted, state %d", mState
.load());
156 MOZ_ASSERT(mState
== SHUTDOWN
&& !mCubebStream
,
157 "Should've called ShutDown() before deleting an AudioStream");
160 size_t AudioStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const {
161 size_t amount
= aMallocSizeOf(this);
163 // Possibly add in the future:
170 nsresult
AudioStream::EnsureTimeStretcherInitialized() {
171 AssertIsOnAudioThread();
172 if (!mTimeStretcher
) {
173 mTimeStretcher
= soundtouch::createSoundTouchObj();
174 mTimeStretcher
->setSampleRate(mAudioClock
.GetInputRate());
175 mTimeStretcher
->setChannels(mOutChannels
);
176 mTimeStretcher
->setPitch(1.0);
178 // SoundTouch v2.1.2 uses automatic time-stretch settings with the following
180 // Tempo 0.5: 90ms sequence, 20ms seekwindow, 8ms overlap
181 // Tempo 2.0: 40ms sequence, 15ms seekwindow, 8ms overlap
182 // We are going to use a smaller 10ms sequence size to improve speech
183 // clarity, giving more resolution at high tempo and less reverb at low
184 // tempo. Maintain 15ms seekwindow and 8ms overlap for smoothness.
185 mTimeStretcher
->setSetting(
187 StaticPrefs::media_audio_playbackrate_soundtouch_sequence_ms());
188 mTimeStretcher
->setSetting(
189 SETTING_SEEKWINDOW_MS
,
190 StaticPrefs::media_audio_playbackrate_soundtouch_seekwindow_ms());
191 mTimeStretcher
->setSetting(
193 StaticPrefs::media_audio_playbackrate_soundtouch_overlap_ms());
198 nsresult
AudioStream::SetPlaybackRate(double aPlaybackRate
) {
199 TRACE("AudioStream::SetPlaybackRate");
202 "Can't handle negative or null playbackrate in the AudioStream.");
203 if (aPlaybackRate
== mPlaybackRate
) {
207 mPlaybackRate
= static_cast<float>(aPlaybackRate
);
212 nsresult
AudioStream::SetPreservesPitch(bool aPreservesPitch
) {
213 TRACE("AudioStream::SetPreservesPitch");
214 if (aPreservesPitch
== mPreservesPitch
) {
218 mPreservesPitch
= aPreservesPitch
;
223 template <typename Function
, typename
... Args
>
224 int AudioStream::InvokeCubeb(Function aFunction
, Args
&&... aArgs
) {
225 mMonitor
.AssertCurrentThreadOwns();
226 MonitorAutoUnlock
mon(mMonitor
);
227 return aFunction(mCubebStream
.get(), std::forward
<Args
>(aArgs
)...);
230 nsresult
AudioStream::Init(AudioDeviceInfo
* aSinkInfo
)
231 MOZ_NO_THREAD_SAFETY_ANALYSIS
{
232 auto startTime
= TimeStamp::Now();
233 TRACE("AudioStream::Init");
235 LOG("%s channels: %d, rate: %d", __FUNCTION__
, mOutChannels
,
236 mAudioClock
.GetInputRate());
238 mSinkInfo
= aSinkInfo
;
240 cubeb_stream_params params
;
241 params
.rate
= mAudioClock
.GetInputRate();
242 params
.channels
= mOutChannels
;
243 params
.layout
= static_cast<uint32_t>(mChannelMap
);
244 params
.format
= CubebUtils::ToCubebFormat
<AUDIO_OUTPUT_FORMAT
>::value
;
245 params
.prefs
= CubebUtils::GetDefaultStreamPrefs(CUBEB_DEVICE_TYPE_OUTPUT
);
247 // This is noop if MOZ_DUMP_AUDIO is not set.
248 mDumpFile
.Open("AudioStream", mOutChannels
, mAudioClock
.GetInputRate());
250 cubeb
* cubebContext
= CubebUtils::GetCubebContext();
252 LOGE("Can't get cubeb context!");
253 CubebUtils::ReportCubebStreamInitFailure(true);
254 return NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR
;
257 return OpenCubeb(cubebContext
, params
, startTime
,
258 CubebUtils::GetFirstStream());
261 nsresult
AudioStream::OpenCubeb(cubeb
* aContext
, cubeb_stream_params
& aParams
,
262 TimeStamp aStartTime
, bool aIsFirst
) {
263 TRACE("AudioStream::OpenCubeb");
264 MOZ_ASSERT(aContext
);
266 cubeb_stream
* stream
= nullptr;
267 /* Convert from milliseconds to frames. */
268 uint32_t latency_frames
=
269 CubebUtils::GetCubebPlaybackLatencyInMilliseconds() * aParams
.rate
/ 1000;
270 cubeb_devid deviceID
= nullptr;
271 if (mSinkInfo
&& mSinkInfo
->DeviceID()) {
272 deviceID
= mSinkInfo
->DeviceID();
274 if (CubebUtils::CubebStreamInit(aContext
, &stream
, "AudioStream", nullptr,
275 nullptr, deviceID
, &aParams
, latency_frames
,
276 DataCallback_S
, StateCallback_S
,
278 mCubebStream
.reset(stream
);
279 CubebUtils::ReportCubebBackendUsed();
281 LOGE("OpenCubeb() failed to init cubeb");
282 CubebUtils::ReportCubebStreamInitFailure(aIsFirst
);
283 return NS_ERROR_FAILURE
;
286 TimeDuration timeDelta
= TimeStamp::Now() - aStartTime
;
287 LOG("creation time %sfirst: %u ms", aIsFirst
? "" : "not ",
288 (uint32_t)timeDelta
.ToMilliseconds());
293 void AudioStream::SetVolume(double aVolume
) {
294 TRACE("AudioStream::SetVolume");
295 MOZ_ASSERT(aVolume
>= 0.0 && aVolume
<= 1.0, "Invalid volume");
297 MOZ_ASSERT(mState
!= SHUTDOWN
, "Don't set volume after shutdown.");
298 if (mState
== ERRORED
) {
302 MonitorAutoLock
mon(mMonitor
);
303 if (InvokeCubeb(cubeb_stream_set_volume
,
304 aVolume
* CubebUtils::GetVolumeScale()) != CUBEB_OK
) {
305 LOGE("Could not change volume on cubeb stream.");
309 void AudioStream::SetStreamName(const nsAString
& aStreamName
) {
310 TRACE("AudioStream::SetStreamName");
312 nsAutoCString aRawStreamName
;
313 nsresult rv
= NS_CopyUnicodeToNative(aStreamName
, aRawStreamName
);
315 if (NS_FAILED(rv
) || aStreamName
.IsEmpty()) {
319 MonitorAutoLock
mon(mMonitor
);
320 if (InvokeCubeb(cubeb_stream_set_name
, aRawStreamName
.get()) != CUBEB_OK
) {
321 LOGE("Could not set cubeb stream name.");
325 RefPtr
<MediaSink::EndedPromise
> AudioStream::Start() {
326 TRACE("AudioStream::Start");
327 MOZ_ASSERT(mState
== INITIALIZED
);
329 RefPtr
<MediaSink::EndedPromise
> promise
;
331 MonitorAutoLock
mon(mMonitor
);
332 // As cubeb might call audio stream's state callback very soon after we
333 // start cubeb, we have to create the promise beforehand in order to handle
334 // the case where we immediately get `drained`.
335 promise
= mEndedPromise
.Ensure(__func__
);
336 mPlaybackComplete
= false;
338 if (InvokeCubeb(cubeb_stream_start
) != CUBEB_OK
) {
340 mEndedPromise
.RejectIfExists(NS_ERROR_FAILURE
, __func__
);
343 LOG("started, state %s", mState
== STARTED
? "STARTED"
344 : mState
== DRAINED
? "DRAINED"
350 void AudioStream::Pause() {
351 TRACE("AudioStream::Pause");
352 MOZ_ASSERT(mState
!= INITIALIZED
, "Must be Start()ed.");
353 MOZ_ASSERT(mState
!= STOPPED
, "Already Pause()ed.");
354 MOZ_ASSERT(mState
!= SHUTDOWN
, "Already ShutDown()ed.");
356 // Do nothing if we are already drained or errored.
357 if (mState
== DRAINED
|| mState
== ERRORED
) {
361 MonitorAutoLock
mon(mMonitor
);
362 if (InvokeCubeb(cubeb_stream_stop
) != CUBEB_OK
) {
364 } else if (mState
!= DRAINED
&& mState
!= ERRORED
) {
365 // Don't transition to other states if we are already
366 // drained or errored.
371 void AudioStream::Resume() {
372 TRACE("AudioStream::Resume");
373 MOZ_ASSERT(mState
!= INITIALIZED
, "Must be Start()ed.");
374 MOZ_ASSERT(mState
!= STARTED
, "Already Start()ed.");
375 MOZ_ASSERT(mState
!= SHUTDOWN
, "Already ShutDown()ed.");
377 // Do nothing if we are already drained or errored.
378 if (mState
== DRAINED
|| mState
== ERRORED
) {
382 MonitorAutoLock
mon(mMonitor
);
383 if (InvokeCubeb(cubeb_stream_start
) != CUBEB_OK
) {
385 } else if (mState
!= DRAINED
&& mState
!= ERRORED
) {
386 // Don't transition to other states if we are already
387 // drained or errored.
392 void AudioStream::ShutDown() {
393 TRACE("AudioStream::ShutDown");
394 LOG("ShutDown, state %d", mState
.load());
396 MonitorAutoLock
mon(mMonitor
);
398 // Force stop to put the cubeb stream in a stable state before deletion.
399 InvokeCubeb(cubeb_stream_stop
);
400 // Must not try to shut down cubeb from within the lock! wasapi may still
401 // call our callback after Pause()/stop()!?! Bug 996162
402 cubeb_stream
* cubeb
= mCubebStream
.release();
403 MonitorAutoUnlock
unlock(mMonitor
);
404 cubeb_stream_destroy(cubeb
);
407 // After `cubeb_stream_stop` has been called, there is no audio thread
408 // anymore. We can delete the time stretcher.
409 if (mTimeStretcher
) {
410 soundtouch::destroySoundTouchObj(mTimeStretcher
);
411 mTimeStretcher
= nullptr;
415 mEndedPromise
.ResolveIfExists(true, __func__
);
418 int64_t AudioStream::GetPosition() {
419 TRACE("AudioStream::GetPosition");
421 MonitorAutoLock
mon(mMonitor
);
423 int64_t frames
= GetPositionInFramesUnlocked();
424 return frames
>= 0 ? mAudioClock
.GetPosition(frames
) : -1;
427 int64_t AudioStream::GetPositionInFrames() {
428 TRACE("AudioStream::GetPositionInFrames");
430 MonitorAutoLock
mon(mMonitor
);
432 int64_t frames
= GetPositionInFramesUnlocked();
434 return frames
>= 0 ? mAudioClock
.GetPositionInFrames(frames
) : -1;
437 int64_t AudioStream::GetPositionInFramesUnlocked() {
438 TRACE("AudioStream::GetPositionInFramesUnlocked");
440 mMonitor
.AssertCurrentThreadOwns();
443 if (mState
== ERRORED
) {
447 uint64_t position
= 0;
451 rv
= InvokeCubeb(cubeb_stream_get_position
, &position
);
453 rv
= cubeb_stream_get_position(mCubebStream
.get(), &position
);
456 if (rv
!= CUBEB_OK
) {
459 return static_cast<int64_t>(std::min
<uint64_t>(position
, INT64_MAX
));
462 bool AudioStream::IsValidAudioFormat(Chunk
* aChunk
) {
463 if (aChunk
->Rate() != mAudioClock
.GetInputRate()) {
464 LOGW("mismatched sample %u, mInRate=%u", aChunk
->Rate(),
465 mAudioClock
.GetInputRate());
469 return aChunk
->Channels() <= 8;
472 void AudioStream::GetUnprocessed(AudioBufferWriter
& aWriter
) {
473 TRACE("AudioStream::GetUnprocessed");
474 AssertIsOnAudioThread();
475 // Flush the timestretcher pipeline, if we were playing using a playback rate
477 if (mTimeStretcher
&& mTimeStretcher
->numSamples()) {
478 auto* timeStretcher
= mTimeStretcher
;
480 [timeStretcher
](AudioDataValue
* aPtr
, uint32_t aFrames
) {
481 return timeStretcher
->receiveSamples(aPtr
, aFrames
);
483 aWriter
.Available());
485 // TODO: There might be still unprocessed samples in the stretcher.
486 // We should either remove or flush them so they won't be in the output
487 // next time we switch a playback rate other than 1.0.
488 NS_WARNING_ASSERTION(mTimeStretcher
->numUnprocessedSamples() == 0,
490 } else if (mTimeStretcher
) {
491 // Don't need it anymore: playbackRate is 1.0, and the time stretcher has
493 soundtouch::destroySoundTouchObj(mTimeStretcher
);
494 mTimeStretcher
= nullptr;
497 while (aWriter
.Available() > 0) {
498 uint32_t count
= mDataSource
.PopFrames(aWriter
.Ptr(), aWriter
.Available(),
499 mAudioThreadChanged
);
503 aWriter
.Advance(count
);
507 void AudioStream::GetTimeStretched(AudioBufferWriter
& aWriter
) {
508 TRACE("AudioStream::GetTimeStretched");
509 AssertIsOnAudioThread();
510 if (EnsureTimeStretcherInitialized() != NS_OK
) {
514 uint32_t toPopFrames
=
515 ceil(aWriter
.Available() * mAudioClock
.GetPlaybackRate());
517 while (mTimeStretcher
->numSamples() < aWriter
.Available()) {
518 // pop into a temp buffer, and put into the stretcher.
519 AutoTArray
<AudioDataValue
, 1000> buf
;
520 auto size
= CheckedUint32(mOutChannels
) * toPopFrames
;
521 if (!size
.isValid()) {
522 // The overflow should not happen in normal case.
523 LOGW("Invalid member data: %d channels, %d frames", mOutChannels
,
527 buf
.SetLength(size
.value());
528 // ensure no variable channel count or something like that
530 mDataSource
.PopFrames(buf
.Elements(), toPopFrames
, mAudioThreadChanged
);
534 mTimeStretcher
->putSamples(buf
.Elements(), count
);
537 auto* timeStretcher
= mTimeStretcher
;
539 [timeStretcher
](AudioDataValue
* aPtr
, uint32_t aFrames
) {
540 return timeStretcher
->receiveSamples(aPtr
, aFrames
);
542 aWriter
.Available());
545 bool AudioStream::CheckThreadIdChanged() {
546 ProfilerThreadId id
= profiler_current_thread_id();
547 if (id
!= mAudioThreadId
) {
549 mAudioThreadChanged
= true;
552 mAudioThreadChanged
= false;
556 void AudioStream::AssertIsOnAudioThread() const {
557 // This can be called right after CheckThreadIdChanged, because the audio
558 // thread can change when not sandboxed.
559 MOZ_ASSERT(mAudioThreadId
.load() == profiler_current_thread_id());
562 void AudioStream::UpdatePlaybackRateIfNeeded() {
563 AssertIsOnAudioThread();
564 if (mAudioClock
.GetPreservesPitch() == mPreservesPitch
&&
565 mAudioClock
.GetPlaybackRate() == mPlaybackRate
) {
569 EnsureTimeStretcherInitialized();
571 mAudioClock
.SetPlaybackRate(mPlaybackRate
);
572 mAudioClock
.SetPreservesPitch(mPreservesPitch
);
574 if (mPreservesPitch
) {
575 mTimeStretcher
->setTempo(mPlaybackRate
);
576 mTimeStretcher
->setRate(1.0f
);
578 mTimeStretcher
->setTempo(1.0f
);
579 mTimeStretcher
->setRate(mPlaybackRate
);
583 long AudioStream::DataCallback(void* aBuffer
, long aFrames
) {
584 if (CheckThreadIdChanged() && !mSandboxed
) {
585 CallbackThreadRegistry::Get()->Register(mAudioThreadId
,
586 "NativeAudioCallback");
588 WebCore::DenormalDisabler disabler
;
589 if (!mCallbacksStarted
) {
590 mCallbacksStarted
= true;
593 TRACE_AUDIO_CALLBACK_BUDGET(aFrames
, mAudioClock
.GetInputRate());
594 TRACE("AudioStream::DataCallback");
595 MOZ_ASSERT(mState
!= SHUTDOWN
, "No data callback after shutdown");
597 if (SoftRealTimeLimitReached()) {
598 DemoteThreadFromRealTime();
601 UpdatePlaybackRateIfNeeded();
603 auto writer
= AudioBufferWriter(
604 Span
<AudioDataValue
>(reinterpret_cast<AudioDataValue
*>(aBuffer
),
605 mOutChannels
* aFrames
),
606 mOutChannels
, aFrames
);
608 if (mAudioClock
.GetInputRate() == mAudioClock
.GetOutputRate()) {
609 GetUnprocessed(writer
);
611 GetTimeStretched(writer
);
614 // Always send audible frames first, and silent frames later.
615 // Otherwise it will break the assumption of FrameHistory.
616 if (!mDataSource
.Ended()) {
618 MonitorAutoLock
mon(mMonitor
);
620 mAudioClock
.UpdateFrameHistory(aFrames
- writer
.Available(),
621 writer
.Available(), mAudioThreadChanged
);
622 if (writer
.Available() > 0) {
623 TRACE_COMMENT("AudioStream::DataCallback", "Underrun: %d frames missing",
625 LOGW("lost %d frames", writer
.Available());
626 writer
.WriteZeros(writer
.Available());
629 // No more new data in the data source, and the drain has completed. We
630 // don't need the time stretcher anymore at this point.
631 if (mTimeStretcher
&& writer
.Available()) {
632 soundtouch::destroySoundTouchObj(mTimeStretcher
);
633 mTimeStretcher
= nullptr;
636 MonitorAutoLock
mon(mMonitor
);
638 mAudioClock
.UpdateFrameHistory(aFrames
- writer
.Available(), 0,
639 mAudioThreadChanged
);
642 mDumpFile
.Write(static_cast<const AudioDataValue
*>(aBuffer
),
643 aFrames
* mOutChannels
);
645 if (!mSandboxed
&& writer
.Available() != 0) {
646 CallbackThreadRegistry::Get()->Unregister(mAudioThreadId
);
648 return aFrames
- writer
.Available();
651 void AudioStream::StateCallback(cubeb_state aState
) {
652 MOZ_ASSERT(mState
!= SHUTDOWN
, "No state callback after shutdown");
653 LOG("StateCallback, mState=%d cubeb_state=%d", mState
.load(), aState
);
655 MonitorAutoLock
mon(mMonitor
);
656 if (aState
== CUBEB_STATE_DRAINED
) {
659 mPlaybackComplete
= true;
660 mEndedPromise
.ResolveIfExists(true, __func__
);
661 } else if (aState
== CUBEB_STATE_ERROR
) {
662 LOGE("StateCallback() state %d cubeb error", mState
.load());
664 mPlaybackComplete
= true;
665 mEndedPromise
.RejectIfExists(NS_ERROR_FAILURE
, __func__
);
669 bool AudioStream::IsPlaybackCompleted() const { return mPlaybackComplete
; }
671 AudioClock::AudioClock(uint32_t aInRate
)
674 mPreservesPitch(true),
675 mFrameHistory(new FrameHistory()) {}
678 void AudioClock::UpdateFrameHistory(uint32_t aServiced
, uint32_t aUnderrun
,
679 bool aAudioThreadChanged
) {
681 if (aAudioThreadChanged
) {
682 mCallbackInfoQueue
.ResetThreadIds();
684 // Flush the local items, if any, and then attempt to enqueue the current
685 // item. This is only a fallback mechanism, under non-critical load this is
686 // just going to enqueue an item in the queue.
687 while (!mAudioThreadCallbackInfo
.IsEmpty()) {
688 CallbackInfo
& info
= mAudioThreadCallbackInfo
[0];
689 // If still full, keep it audio-thread side for now.
690 if (mCallbackInfoQueue
.Enqueue(info
) != 1) {
693 mAudioThreadCallbackInfo
.RemoveElementAt(0);
695 CallbackInfo
info(aServiced
, aUnderrun
, mOutRate
);
696 if (mCallbackInfoQueue
.Enqueue(info
) != 1) {
698 "mCallbackInfoQueue full, storing the values in the audio thread.");
699 mAudioThreadCallbackInfo
.AppendElement(info
);
702 MutexAutoLock
lock(mMutex
);
703 mFrameHistory
->Append(aServiced
, aUnderrun
, mOutRate
);
707 int64_t AudioClock::GetPositionInFrames(int64_t aFrames
) {
708 CheckedInt64 v
= UsecsToFrames(GetPosition(aFrames
), mInRate
);
709 return v
.isValid() ? v
.value() : -1;
712 int64_t AudioClock::GetPosition(int64_t frames
) {
714 // Dequeue all history info, and apply them before returning the position
715 // based on frame history.
717 while (mCallbackInfoQueue
.Dequeue(&info
, 1)) {
718 mFrameHistory
->Append(info
.mServiced
, info
.mUnderrun
, info
.mOutputRate
);
721 MutexAutoLock
lock(mMutex
);
723 return mFrameHistory
->GetPosition(frames
);
726 void AudioClock::SetPlaybackRate(double aPlaybackRate
) {
727 mOutRate
= static_cast<uint32_t>(mInRate
/ aPlaybackRate
);
730 double AudioClock::GetPlaybackRate() const {
731 return static_cast<double>(mInRate
) / mOutRate
;
734 void AudioClock::SetPreservesPitch(bool aPreservesPitch
) {
735 mPreservesPitch
= aPreservesPitch
;
738 bool AudioClock::GetPreservesPitch() const { return mPreservesPitch
; }
740 } // namespace mozilla