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