Coverage Report

Created: 2018-09-25 14:53

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