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