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/Monitor.h"
14 #include "mozilla/Mutex.h"
15 #include "mozilla/Sprintf.h"
16 #include <algorithm>
17 #include "mozilla/Telemetry.h"
18 #include "CubebUtils.h"
19 #include "nsPrintfCString.h"
20 #include "gfxPrefs.h"
21 #include "AudioConverter.h"
22 
23 namespace mozilla {
24 
25 #undef LOG
26 #undef LOGW
27 
28 LazyLogModule gAudioStreamLog("AudioStream");
29 // For simple logs
30 #define LOG(x, ...) MOZ_LOG(gAudioStreamLog, mozilla::LogLevel::Debug, ("%p " x, this, ##__VA_ARGS__))
31 #define LOGW(x, ...) MOZ_LOG(gAudioStreamLog, mozilla::LogLevel::Warning, ("%p " x, this, ##__VA_ARGS__))
32 
33 /**
34  * Keep a list of frames sent to the audio engine in each DataCallback along
35  * with the playback rate at the moment. Since the playback rate and number of
36  * underrun frames can vary in each callback. We need to keep the whole history
37  * in order to calculate the playback position of the audio engine correctly.
38  */
39 class FrameHistory {
40   struct Chunk {
41     uint32_t servicedFrames;
42     uint32_t totalFrames;
43     uint32_t rate;
44   };
45 
46   template <typename T>
FramesToUs(uint32_t frames,int rate)47   static T FramesToUs(uint32_t frames, int rate) {
48     return static_cast<T>(frames) * USECS_PER_S / rate;
49   }
50 public:
FrameHistory()51   FrameHistory()
52     : mBaseOffset(0), mBasePosition(0) {}
53 
Append(uint32_t aServiced,uint32_t aUnderrun,uint32_t aRate)54   void Append(uint32_t aServiced, uint32_t aUnderrun, uint32_t aRate) {
55     /* In most case where playback rate stays the same and we don't underrun
56      * frames, we are able to merge chunks to avoid lose of precision to add up
57      * in compressing chunks into |mBaseOffset| and |mBasePosition|.
58      */
59     if (!mChunks.IsEmpty()) {
60       Chunk& c = mChunks.LastElement();
61       // 2 chunks (c1 and c2) can be merged when rate is the same and
62       // adjacent frames are zero. That is, underrun frames in c1 are zero
63       // or serviced frames in c2 are zero.
64       if (c.rate == aRate &&
65           (c.servicedFrames == c.totalFrames ||
66            aServiced == 0)) {
67         c.servicedFrames += aServiced;
68         c.totalFrames += aServiced + aUnderrun;
69         return;
70       }
71     }
72     Chunk* p = mChunks.AppendElement();
73     p->servicedFrames = aServiced;
74     p->totalFrames = aServiced + aUnderrun;
75     p->rate = aRate;
76   }
77 
78   /**
79    * @param frames The playback position in frames of the audio engine.
80    * @return The playback position in microseconds of the audio engine,
81    *         adjusted by playback rate changes and underrun frames.
82    */
GetPosition(int64_t frames)83   int64_t GetPosition(int64_t frames) {
84     // playback position should not go backward.
85     MOZ_ASSERT(frames >= mBaseOffset);
86     while (true) {
87       if (mChunks.IsEmpty()) {
88         return mBasePosition;
89       }
90       const Chunk& c = mChunks[0];
91       if (frames <= mBaseOffset + c.totalFrames) {
92         uint32_t delta = frames - mBaseOffset;
93         delta = std::min(delta, c.servicedFrames);
94         return static_cast<int64_t>(mBasePosition) +
95                FramesToUs<int64_t>(delta, c.rate);
96       }
97       // Since the playback position of the audio engine will not go backward,
98       // we are able to compress chunks so that |mChunks| won't grow unlimitedly.
99       // Note that we lose precision in converting integers into floats and
100       // inaccuracy will accumulate over time. However, for a 24hr long,
101       // sample rate = 44.1k file, the error will be less than 1 microsecond
102       // after playing 24 hours. So we are fine with that.
103       mBaseOffset += c.totalFrames;
104       mBasePosition += FramesToUs<double>(c.servicedFrames, c.rate);
105       mChunks.RemoveElementAt(0);
106     }
107   }
108 private:
109   AutoTArray<Chunk, 7> mChunks;
110   int64_t mBaseOffset;
111   double mBasePosition;
112 };
113 
AudioStream(DataSource & aSource)114 AudioStream::AudioStream(DataSource& aSource)
115   : mMonitor("AudioStream")
116   , mChannels(0)
117   , mOutChannels(0)
118 #ifndef MOZ_SYSTEM_SOUNDTOUCH
119   , mTimeStretcher(nullptr)
120 #endif
121   , mDumpFile(nullptr)
122   , mState(INITIALIZED)
123   , mDataSource(aSource)
124 {
125 }
126 
~AudioStream()127 AudioStream::~AudioStream()
128 {
129   LOG("deleted, state %d", mState);
130   MOZ_ASSERT(mState == SHUTDOWN && !mCubebStream,
131              "Should've called Shutdown() before deleting an AudioStream");
132   if (mDumpFile) {
133     fclose(mDumpFile);
134   }
135 #ifndef MOZ_SYSTEM_SOUNDTOUCH
136   if (mTimeStretcher) {
137     soundtouch::destroySoundTouchObj(mTimeStretcher);
138   }
139 #endif
140 }
141 
142 size_t
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const143 AudioStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
144 {
145   size_t amount = aMallocSizeOf(this);
146 
147   // Possibly add in the future:
148   // - mTimeStretcher
149   // - mCubebStream
150 
151   return amount;
152 }
153 
EnsureTimeStretcherInitializedUnlocked()154 nsresult AudioStream::EnsureTimeStretcherInitializedUnlocked()
155 {
156   mMonitor.AssertCurrentThreadOwns();
157   if (!mTimeStretcher) {
158 #ifdef MOZ_SYSTEM_SOUNDTOUCH
159     mTimeStretcher = new soundtouch::SoundTouch();
160 #else
161     mTimeStretcher = soundtouch::createSoundTouchObj();
162 #endif
163     mTimeStretcher->setSampleRate(mAudioClock.GetInputRate());
164     mTimeStretcher->setChannels(mOutChannels);
165     mTimeStretcher->setPitch(1.0);
166   }
167   return NS_OK;
168 }
169 
SetPlaybackRate(double aPlaybackRate)170 nsresult AudioStream::SetPlaybackRate(double aPlaybackRate)
171 {
172   // MUST lock since the rate transposer is used from the cubeb callback,
173   // and rate changes can cause the buffer to be reallocated
174   MonitorAutoLock mon(mMonitor);
175 
176   NS_ASSERTION(aPlaybackRate > 0.0,
177                "Can't handle negative or null playbackrate in the AudioStream.");
178   // Avoid instantiating the resampler if we are not changing the playback rate.
179   // GetPreservesPitch/SetPreservesPitch don't need locking before calling
180   if (aPlaybackRate == mAudioClock.GetPlaybackRate()) {
181     return NS_OK;
182   }
183 
184   if (EnsureTimeStretcherInitializedUnlocked() != NS_OK) {
185     return NS_ERROR_FAILURE;
186   }
187 
188   mAudioClock.SetPlaybackRate(aPlaybackRate);
189 
190   if (mAudioClock.GetPreservesPitch()) {
191     mTimeStretcher->setTempo(aPlaybackRate);
192     mTimeStretcher->setRate(1.0f);
193   } else {
194     mTimeStretcher->setTempo(1.0f);
195     mTimeStretcher->setRate(aPlaybackRate);
196   }
197   return NS_OK;
198 }
199 
SetPreservesPitch(bool aPreservesPitch)200 nsresult AudioStream::SetPreservesPitch(bool aPreservesPitch)
201 {
202   // MUST lock since the rate transposer is used from the cubeb callback,
203   // and rate changes can cause the buffer to be reallocated
204   MonitorAutoLock mon(mMonitor);
205 
206   // Avoid instantiating the timestretcher instance if not needed.
207   if (aPreservesPitch == mAudioClock.GetPreservesPitch()) {
208     return NS_OK;
209   }
210 
211   if (EnsureTimeStretcherInitializedUnlocked() != NS_OK) {
212     return NS_ERROR_FAILURE;
213   }
214 
215   if (aPreservesPitch == true) {
216     mTimeStretcher->setTempo(mAudioClock.GetPlaybackRate());
217     mTimeStretcher->setRate(1.0f);
218   } else {
219     mTimeStretcher->setTempo(1.0f);
220     mTimeStretcher->setRate(mAudioClock.GetPlaybackRate());
221   }
222 
223   mAudioClock.SetPreservesPitch(aPreservesPitch);
224 
225   return NS_OK;
226 }
227 
SetUint16LE(uint8_t * aDest,uint16_t aValue)228 static void SetUint16LE(uint8_t* aDest, uint16_t aValue)
229 {
230   aDest[0] = aValue & 0xFF;
231   aDest[1] = aValue >> 8;
232 }
233 
SetUint32LE(uint8_t * aDest,uint32_t aValue)234 static void SetUint32LE(uint8_t* aDest, uint32_t aValue)
235 {
236   SetUint16LE(aDest, aValue & 0xFFFF);
237   SetUint16LE(aDest + 2, aValue >> 16);
238 }
239 
240 static FILE*
OpenDumpFile(uint32_t aChannels,uint32_t aRate)241 OpenDumpFile(uint32_t aChannels, uint32_t aRate)
242 {
243   /**
244    * When MOZ_DUMP_AUDIO is set in the environment (to anything),
245    * we'll drop a series of files in the current working directory named
246    * dumped-audio-<nnn>.wav, one per AudioStream created, containing
247    * the audio for the stream including any skips due to underruns.
248    */
249   static Atomic<int> gDumpedAudioCount(0);
250 
251   if (!getenv("MOZ_DUMP_AUDIO"))
252     return nullptr;
253   char buf[100];
254   SprintfLiteral(buf, "dumped-audio-%d.wav", ++gDumpedAudioCount);
255   FILE* f = fopen(buf, "wb");
256   if (!f)
257     return nullptr;
258 
259   uint8_t header[] = {
260     // RIFF header
261     0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45,
262     // fmt chunk. We always write 16-bit samples.
263     0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFF, 0xFF,
264     0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x10, 0x00,
265     // data chunk
266     0x64, 0x61, 0x74, 0x61, 0xFE, 0xFF, 0xFF, 0x7F
267   };
268   static const int CHANNEL_OFFSET = 22;
269   static const int SAMPLE_RATE_OFFSET = 24;
270   static const int BLOCK_ALIGN_OFFSET = 32;
271   SetUint16LE(header + CHANNEL_OFFSET, aChannels);
272   SetUint32LE(header + SAMPLE_RATE_OFFSET, aRate);
273   SetUint16LE(header + BLOCK_ALIGN_OFFSET, aChannels * 2);
274   fwrite(header, sizeof(header), 1, f);
275 
276   return f;
277 }
278 
279 template <typename T>
280 typename EnableIf<IsSame<T, int16_t>::value, void>::Type
WriteDumpFileHelper(T * aInput,size_t aSamples,FILE * aFile)281 WriteDumpFileHelper(T* aInput, size_t aSamples, FILE* aFile) {
282   fwrite(aInput, sizeof(T), aSamples, aFile);
283 }
284 
285 template <typename T>
286 typename EnableIf<IsSame<T, float>::value, void>::Type
WriteDumpFileHelper(T * aInput,size_t aSamples,FILE * aFile)287 WriteDumpFileHelper(T* aInput, size_t aSamples, FILE* aFile) {
288   AutoTArray<uint8_t, 1024*2> buf;
289   buf.SetLength(aSamples*2);
290   uint8_t* output = buf.Elements();
291   for (uint32_t i = 0; i < aSamples; ++i) {
292     SetUint16LE(output + i*2, int16_t(aInput[i]*32767.0f));
293   }
294   fwrite(output, 2, aSamples, aFile);
295   fflush(aFile);
296 }
297 
298 static void
WriteDumpFile(FILE * aDumpFile,AudioStream * aStream,uint32_t aFrames,void * aBuffer)299 WriteDumpFile(FILE* aDumpFile, AudioStream* aStream, uint32_t aFrames,
300               void* aBuffer)
301 {
302   if (!aDumpFile)
303     return;
304 
305   uint32_t samples = aStream->GetOutChannels()*aFrames;
306 
307   using SampleT = AudioSampleTraits<AUDIO_OUTPUT_FORMAT>::Type;
308   WriteDumpFileHelper(reinterpret_cast<SampleT*>(aBuffer), samples, aDumpFile);
309 }
310 
311 template <AudioSampleFormat N>
312 struct ToCubebFormat {
313   static const cubeb_sample_format value = CUBEB_SAMPLE_FLOAT32NE;
314 };
315 
316 template <>
317 struct ToCubebFormat<AUDIO_FORMAT_S16> {
318   static const cubeb_sample_format value = CUBEB_SAMPLE_S16NE;
319 };
320 
321 template <typename Function, typename... Args>
InvokeCubeb(Function aFunction,Args &&...aArgs)322 int AudioStream::InvokeCubeb(Function aFunction, Args&&... aArgs)
323 {
324   MonitorAutoUnlock mon(mMonitor);
325   return aFunction(mCubebStream.get(), Forward<Args>(aArgs)...);
326 }
327 
328 nsresult
Init(uint32_t aNumChannels,uint32_t aRate,const dom::AudioChannel aAudioChannel)329 AudioStream::Init(uint32_t aNumChannels, uint32_t aRate,
330                   const dom::AudioChannel aAudioChannel)
331 {
332   auto startTime = TimeStamp::Now();
333 
334   LOG("%s channels: %d, rate: %d", __FUNCTION__, aNumChannels, aRate);
335   mChannels = aNumChannels;
336   mOutChannels = aNumChannels;
337 
338   mDumpFile = OpenDumpFile(aNumChannels, aRate);
339 
340   cubeb_stream_params params;
341   params.rate = aRate;
342   params.channels = mOutChannels;
343 #if defined(__ANDROID__)
344 #if defined(MOZ_B2G)
345   params.stream_type = CubebUtils::ConvertChannelToCubebType(aAudioChannel);
346 #else
347   params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
348 #endif
349 
350   if (params.stream_type == CUBEB_STREAM_TYPE_MAX) {
351     return NS_ERROR_INVALID_ARG;
352   }
353 #endif
354 
355   params.format = ToCubebFormat<AUDIO_OUTPUT_FORMAT>::value;
356   mAudioClock.Init(aRate);
357 
358   cubeb* cubebContext = CubebUtils::GetCubebContext();
359   if (!cubebContext) {
360     NS_WARNING("Can't get cubeb context!");
361     CubebUtils::ReportCubebStreamInitFailure(true);
362     return NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR;
363   }
364 
365   return OpenCubeb(cubebContext, params, startTime, CubebUtils::GetFirstStream());
366 }
367 
368 nsresult
OpenCubeb(cubeb * aContext,cubeb_stream_params & aParams,TimeStamp aStartTime,bool aIsFirst)369 AudioStream::OpenCubeb(cubeb* aContext, cubeb_stream_params& aParams,
370                        TimeStamp aStartTime, bool aIsFirst)
371 {
372   MOZ_ASSERT(aContext);
373 
374   cubeb_stream* stream = nullptr;
375   /* Convert from milliseconds to frames. */
376   uint32_t latency_frames =
377     CubebUtils::GetCubebPlaybackLatencyInMilliseconds() * aParams.rate / 1000;
378   if (cubeb_stream_init(aContext, &stream, "AudioStream",
379                         nullptr, nullptr, nullptr, &aParams,
380                         latency_frames,
381                         DataCallback_S, StateCallback_S, this) == CUBEB_OK) {
382     mCubebStream.reset(stream);
383     CubebUtils::ReportCubebBackendUsed();
384   } else {
385     NS_WARNING(nsPrintfCString("AudioStream::OpenCubeb() %p failed to init cubeb", this).get());
386     CubebUtils::ReportCubebStreamInitFailure(aIsFirst);
387     return NS_ERROR_FAILURE;
388   }
389 
390   TimeDuration timeDelta = TimeStamp::Now() - aStartTime;
391   LOG("creation time %sfirst: %u ms", aIsFirst ? "" : "not ",
392       (uint32_t) timeDelta.ToMilliseconds());
393   Telemetry::Accumulate(aIsFirst ? Telemetry::AUDIOSTREAM_FIRST_OPEN_MS :
394       Telemetry::AUDIOSTREAM_LATER_OPEN_MS, timeDelta.ToMilliseconds());
395 
396   return NS_OK;
397 }
398 
399 void
SetVolume(double aVolume)400 AudioStream::SetVolume(double aVolume)
401 {
402   MOZ_ASSERT(aVolume >= 0.0 && aVolume <= 1.0, "Invalid volume");
403 
404   if (cubeb_stream_set_volume(mCubebStream.get(), aVolume * CubebUtils::GetVolumeScale()) != CUBEB_OK) {
405     NS_WARNING("Could not change volume on cubeb stream.");
406   }
407 }
408 
409 void
Start()410 AudioStream::Start()
411 {
412   MonitorAutoLock mon(mMonitor);
413   MOZ_ASSERT(mState == INITIALIZED);
414   mState = STARTED;
415   auto r = InvokeCubeb(cubeb_stream_start);
416   if (r != CUBEB_OK) {
417     mState = ERRORED;
418   }
419   LOG("started, state %s", mState == STARTED ? "STARTED" : mState == DRAINED ? "DRAINED" : "ERRORED");
420 }
421 
422 void
Pause()423 AudioStream::Pause()
424 {
425   MonitorAutoLock mon(mMonitor);
426   MOZ_ASSERT(mState != INITIALIZED, "Must be Start()ed.");
427   MOZ_ASSERT(mState != STOPPED, "Already Pause()ed.");
428   MOZ_ASSERT(mState != SHUTDOWN, "Already Shutdown()ed.");
429 
430   // Do nothing if we are already drained or errored.
431   if (mState == DRAINED || mState == ERRORED) {
432     return;
433   }
434 
435   if (InvokeCubeb(cubeb_stream_stop) != CUBEB_OK) {
436     mState = ERRORED;
437   } else if (mState != DRAINED && mState != ERRORED) {
438     // Don't transition to other states if we are already
439     // drained or errored.
440     mState = STOPPED;
441   }
442 }
443 
444 void
Resume()445 AudioStream::Resume()
446 {
447   MonitorAutoLock mon(mMonitor);
448   MOZ_ASSERT(mState != INITIALIZED, "Must be Start()ed.");
449   MOZ_ASSERT(mState != STARTED, "Already Start()ed.");
450   MOZ_ASSERT(mState != SHUTDOWN, "Already Shutdown()ed.");
451 
452   // Do nothing if we are already drained or errored.
453   if (mState == DRAINED || mState == ERRORED) {
454     return;
455   }
456 
457   if (InvokeCubeb(cubeb_stream_start) != CUBEB_OK) {
458     mState = ERRORED;
459   } else if (mState != DRAINED && mState != ERRORED) {
460     // Don't transition to other states if we are already
461     // drained or errored.
462     mState = STARTED;
463   }
464 }
465 
466 void
Shutdown()467 AudioStream::Shutdown()
468 {
469   MonitorAutoLock mon(mMonitor);
470   LOG("Shutdown, state %d", mState);
471 
472   if (mCubebStream) {
473     MonitorAutoUnlock mon(mMonitor);
474     // Force stop to put the cubeb stream in a stable state before deletion.
475     cubeb_stream_stop(mCubebStream.get());
476     // Must not try to shut down cubeb from within the lock!  wasapi may still
477     // call our callback after Pause()/stop()!?! Bug 996162
478     mCubebStream.reset();
479   }
480 
481   mState = SHUTDOWN;
482 }
483 
484 int64_t
GetPosition()485 AudioStream::GetPosition()
486 {
487   MonitorAutoLock mon(mMonitor);
488   int64_t frames = GetPositionInFramesUnlocked();
489   return frames >= 0 ? mAudioClock.GetPosition(frames) : -1;
490 }
491 
492 int64_t
GetPositionInFrames()493 AudioStream::GetPositionInFrames()
494 {
495   MonitorAutoLock mon(mMonitor);
496   int64_t frames = GetPositionInFramesUnlocked();
497   return frames >= 0 ? mAudioClock.GetPositionInFrames(frames) : -1;
498 }
499 
500 int64_t
GetPositionInFramesUnlocked()501 AudioStream::GetPositionInFramesUnlocked()
502 {
503   mMonitor.AssertCurrentThreadOwns();
504 
505   if (mState == ERRORED) {
506     return -1;
507   }
508 
509   uint64_t position = 0;
510   if (InvokeCubeb(cubeb_stream_get_position, &position) != CUBEB_OK) {
511     return -1;
512   }
513   return std::min<uint64_t>(position, INT64_MAX);
514 }
515 
516 bool
IsValidAudioFormat(Chunk * aChunk)517 AudioStream::IsValidAudioFormat(Chunk* aChunk)
518 {
519   if (aChunk->Rate() != mAudioClock.GetInputRate()) {
520     LOGW("mismatched sample %u, mInRate=%u", aChunk->Rate(), mAudioClock.GetInputRate());
521     return false;
522   }
523 
524   if (aChunk->Channels() > 8) {
525     return false;
526   }
527 
528   return true;
529 }
530 
531 void
GetUnprocessed(AudioBufferWriter & aWriter)532 AudioStream::GetUnprocessed(AudioBufferWriter& aWriter)
533 {
534   mMonitor.AssertCurrentThreadOwns();
535 
536   // Flush the timestretcher pipeline, if we were playing using a playback rate
537   // other than 1.0.
538   if (mTimeStretcher && mTimeStretcher->numSamples()) {
539     auto timeStretcher = mTimeStretcher;
540     aWriter.Write([timeStretcher] (AudioDataValue* aPtr, uint32_t aFrames) {
541       return timeStretcher->receiveSamples(aPtr, aFrames);
542     }, aWriter.Available());
543 
544     // TODO: There might be still unprocessed samples in the stretcher.
545     // We should either remove or flush them so they won't be in the output
546     // next time we switch a playback rate other than 1.0.
547     NS_WARNING_ASSERTION(
548       mTimeStretcher->numUnprocessedSamples() == 0, "no samples");
549   }
550 
551   while (aWriter.Available() > 0) {
552     UniquePtr<Chunk> c = mDataSource.PopFrames(aWriter.Available());
553     if (c->Frames() == 0) {
554       break;
555     }
556     MOZ_ASSERT(c->Frames() <= aWriter.Available());
557     if (IsValidAudioFormat(c.get())) {
558       aWriter.Write(c->Data(), c->Frames());
559     } else {
560       // Write silence if invalid format.
561       aWriter.WriteZeros(c->Frames());
562     }
563   }
564 }
565 
566 void
GetTimeStretched(AudioBufferWriter & aWriter)567 AudioStream::GetTimeStretched(AudioBufferWriter& aWriter)
568 {
569   mMonitor.AssertCurrentThreadOwns();
570 
571   // We need to call the non-locking version, because we already have the lock.
572   if (EnsureTimeStretcherInitializedUnlocked() != NS_OK) {
573     return;
574   }
575 
576   uint32_t toPopFrames =
577     ceil(aWriter.Available() * mAudioClock.GetPlaybackRate());
578 
579   while (mTimeStretcher->numSamples() < aWriter.Available()) {
580     UniquePtr<Chunk> c = mDataSource.PopFrames(toPopFrames);
581     if (c->Frames() == 0) {
582       break;
583     }
584     MOZ_ASSERT(c->Frames() <= toPopFrames);
585     if (IsValidAudioFormat(c.get())) {
586       mTimeStretcher->putSamples(c->Data(), c->Frames());
587     } else {
588       // Write silence if invalid format.
589       AutoTArray<AudioDataValue, 1000> buf;
590       buf.SetLength(mOutChannels * c->Frames());
591       memset(buf.Elements(), 0, buf.Length() * sizeof(AudioDataValue));
592       mTimeStretcher->putSamples(buf.Elements(), c->Frames());
593     }
594   }
595 
596   auto timeStretcher = mTimeStretcher;
597   aWriter.Write([timeStretcher] (AudioDataValue* aPtr, uint32_t aFrames) {
598     return timeStretcher->receiveSamples(aPtr, aFrames);
599   }, aWriter.Available());
600 }
601 
602 long
DataCallback(void * aBuffer,long aFrames)603 AudioStream::DataCallback(void* aBuffer, long aFrames)
604 {
605   MonitorAutoLock mon(mMonitor);
606   MOZ_ASSERT(mState != SHUTDOWN, "No data callback after shutdown");
607 
608   auto writer = AudioBufferWriter(
609     reinterpret_cast<AudioDataValue*>(aBuffer), mOutChannels, aFrames);
610 
611   if (!strcmp(cubeb_get_backend_id(CubebUtils::GetCubebContext()), "winmm")) {
612     // Don't consume audio data until Start() is called.
613     // Expected only with cubeb winmm backend.
614     if (mState == INITIALIZED) {
615       NS_WARNING("data callback fires before cubeb_stream_start() is called");
616       mAudioClock.UpdateFrameHistory(0, aFrames);
617       return writer.WriteZeros(aFrames);
618     }
619   } else {
620     MOZ_ASSERT(mState != INITIALIZED);
621   }
622 
623   // NOTE: wasapi (others?) can call us back *after* stop()/Shutdown() (mState == SHUTDOWN)
624   // Bug 996162
625 
626   if (mAudioClock.GetInputRate() == mAudioClock.GetOutputRate()) {
627     GetUnprocessed(writer);
628   } else {
629     GetTimeStretched(writer);
630   }
631 
632   // Always send audible frames first, and silent frames later.
633   // Otherwise it will break the assumption of FrameHistory.
634   if (!mDataSource.Ended()) {
635     mAudioClock.UpdateFrameHistory(aFrames - writer.Available(), writer.Available());
636     if (writer.Available() > 0) {
637       LOGW("lost %d frames", writer.Available());
638       writer.WriteZeros(writer.Available());
639     }
640   } else {
641     // No more new data in the data source. Don't send silent frames so the
642     // cubeb stream can start draining.
643     mAudioClock.UpdateFrameHistory(aFrames - writer.Available(), 0);
644   }
645 
646   WriteDumpFile(mDumpFile, this, aFrames, aBuffer);
647 
648   return aFrames - writer.Available();
649 }
650 
651 void
StateCallback(cubeb_state aState)652 AudioStream::StateCallback(cubeb_state aState)
653 {
654   MonitorAutoLock mon(mMonitor);
655   MOZ_ASSERT(mState != SHUTDOWN, "No state callback after shutdown");
656   LOG("StateCallback, mState=%d cubeb_state=%d", mState, aState);
657   if (aState == CUBEB_STATE_DRAINED) {
658     mState = DRAINED;
659     mDataSource.Drained();
660   } else if (aState == CUBEB_STATE_ERROR) {
661     LOG("StateCallback() state %d cubeb error", mState);
662     mState = ERRORED;
663   }
664 }
665 
AudioClock()666 AudioClock::AudioClock()
667 : mOutRate(0),
668   mInRate(0),
669   mPreservesPitch(true),
670   mFrameHistory(new FrameHistory())
671 {}
672 
Init(uint32_t aRate)673 void AudioClock::Init(uint32_t aRate)
674 {
675   mOutRate = aRate;
676   mInRate = aRate;
677 }
678 
UpdateFrameHistory(uint32_t aServiced,uint32_t aUnderrun)679 void AudioClock::UpdateFrameHistory(uint32_t aServiced, uint32_t aUnderrun)
680 {
681   mFrameHistory->Append(aServiced, aUnderrun, mOutRate);
682 }
683 
GetPositionInFrames(int64_t aFrames) const684 int64_t AudioClock::GetPositionInFrames(int64_t aFrames) const
685 {
686   CheckedInt64 v = UsecsToFrames(GetPosition(aFrames), mInRate);
687   return v.isValid() ? v.value() : -1;
688 }
689 
GetPosition(int64_t frames) const690 int64_t AudioClock::GetPosition(int64_t frames) const
691 {
692   return mFrameHistory->GetPosition(frames);
693 }
694 
SetPlaybackRate(double aPlaybackRate)695 void AudioClock::SetPlaybackRate(double aPlaybackRate)
696 {
697   mOutRate = static_cast<uint32_t>(mInRate / aPlaybackRate);
698 }
699 
GetPlaybackRate() const700 double AudioClock::GetPlaybackRate() const
701 {
702   return static_cast<double>(mInRate) / mOutRate;
703 }
704 
SetPreservesPitch(bool aPreservesPitch)705 void AudioClock::SetPreservesPitch(bool aPreservesPitch)
706 {
707   mPreservesPitch = aPreservesPitch;
708 }
709 
GetPreservesPitch() const710 bool AudioClock::GetPreservesPitch() const
711 {
712   return mPreservesPitch;
713 }
714 
715 } // namespace mozilla
716