content/media/AudioStream.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/content/media/AudioStream.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1144 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +#include <stdio.h>
    1.10 +#include <math.h>
    1.11 +#include "prlog.h"
    1.12 +#include "prdtoa.h"
    1.13 +#include "AudioStream.h"
    1.14 +#include "VideoUtils.h"
    1.15 +#include "mozilla/Monitor.h"
    1.16 +#include "mozilla/Mutex.h"
    1.17 +#include <algorithm>
    1.18 +#include "mozilla/Preferences.h"
    1.19 +#include "soundtouch/SoundTouch.h"
    1.20 +#include "Latency.h"
    1.21 +
    1.22 +namespace mozilla {
    1.23 +
    1.24 +#ifdef LOG
    1.25 +#undef LOG
    1.26 +#endif
    1.27 +
    1.28 +#ifdef PR_LOGGING
    1.29 +PRLogModuleInfo* gAudioStreamLog = nullptr;
    1.30 +// For simple logs
    1.31 +#define LOG(x) PR_LOG(gAudioStreamLog, PR_LOG_DEBUG, x)
    1.32 +#else
    1.33 +#define LOG(x)
    1.34 +#endif
    1.35 +
    1.36 +/**
    1.37 + * When MOZ_DUMP_AUDIO is set in the environment (to anything),
    1.38 + * we'll drop a series of files in the current working directory named
    1.39 + * dumped-audio-<nnn>.wav, one per AudioStream created, containing
    1.40 + * the audio for the stream including any skips due to underruns.
    1.41 + */
    1.42 +static int gDumpedAudioCount = 0;
    1.43 +
    1.44 +#define PREF_VOLUME_SCALE "media.volume_scale"
    1.45 +#define PREF_CUBEB_LATENCY "media.cubeb_latency_ms"
    1.46 +
    1.47 +static const uint32_t CUBEB_NORMAL_LATENCY_MS = 100;
    1.48 +
    1.49 +StaticMutex AudioStream::sMutex;
    1.50 +cubeb* AudioStream::sCubebContext;
    1.51 +uint32_t AudioStream::sPreferredSampleRate;
    1.52 +double AudioStream::sVolumeScale;
    1.53 +uint32_t AudioStream::sCubebLatency;
    1.54 +bool AudioStream::sCubebLatencyPrefSet;
    1.55 +
    1.56 +/*static*/ void AudioStream::PrefChanged(const char* aPref, void* aClosure)
    1.57 +{
    1.58 +  if (strcmp(aPref, PREF_VOLUME_SCALE) == 0) {
    1.59 +    nsAdoptingString value = Preferences::GetString(aPref);
    1.60 +    StaticMutexAutoLock lock(sMutex);
    1.61 +    if (value.IsEmpty()) {
    1.62 +      sVolumeScale = 1.0;
    1.63 +    } else {
    1.64 +      NS_ConvertUTF16toUTF8 utf8(value);
    1.65 +      sVolumeScale = std::max<double>(0, PR_strtod(utf8.get(), nullptr));
    1.66 +    }
    1.67 +  } else if (strcmp(aPref, PREF_CUBEB_LATENCY) == 0) {
    1.68 +    // Arbitrary default stream latency of 100ms.  The higher this
    1.69 +    // value, the longer stream volume changes will take to become
    1.70 +    // audible.
    1.71 +    sCubebLatencyPrefSet = Preferences::HasUserValue(aPref);
    1.72 +    uint32_t value = Preferences::GetUint(aPref, CUBEB_NORMAL_LATENCY_MS);
    1.73 +    StaticMutexAutoLock lock(sMutex);
    1.74 +    sCubebLatency = std::min<uint32_t>(std::max<uint32_t>(value, 1), 1000);
    1.75 +  }
    1.76 +}
    1.77 +
    1.78 +/*static*/ double AudioStream::GetVolumeScale()
    1.79 +{
    1.80 +  StaticMutexAutoLock lock(sMutex);
    1.81 +  return sVolumeScale;
    1.82 +}
    1.83 +
    1.84 +/*static*/ cubeb* AudioStream::GetCubebContext()
    1.85 +{
    1.86 +  StaticMutexAutoLock lock(sMutex);
    1.87 +  return GetCubebContextUnlocked();
    1.88 +}
    1.89 +
    1.90 +/*static*/ void AudioStream::InitPreferredSampleRate()
    1.91 +{
    1.92 +  StaticMutexAutoLock lock(sMutex);
    1.93 +  if (sPreferredSampleRate == 0 &&
    1.94 +      cubeb_get_preferred_sample_rate(GetCubebContextUnlocked(),
    1.95 +                                      &sPreferredSampleRate) != CUBEB_OK) {
    1.96 +    sPreferredSampleRate = 44100;
    1.97 +  }
    1.98 +}
    1.99 +
   1.100 +/*static*/ cubeb* AudioStream::GetCubebContextUnlocked()
   1.101 +{
   1.102 +  sMutex.AssertCurrentThreadOwns();
   1.103 +  if (sCubebContext ||
   1.104 +      cubeb_init(&sCubebContext, "AudioStream") == CUBEB_OK) {
   1.105 +    return sCubebContext;
   1.106 +  }
   1.107 +  NS_WARNING("cubeb_init failed");
   1.108 +  return nullptr;
   1.109 +}
   1.110 +
   1.111 +/*static*/ uint32_t AudioStream::GetCubebLatency()
   1.112 +{
   1.113 +  StaticMutexAutoLock lock(sMutex);
   1.114 +  return sCubebLatency;
   1.115 +}
   1.116 +
   1.117 +/*static*/ bool AudioStream::CubebLatencyPrefSet()
   1.118 +{
   1.119 +  StaticMutexAutoLock lock(sMutex);
   1.120 +  return sCubebLatencyPrefSet;
   1.121 +}
   1.122 +
   1.123 +#if defined(__ANDROID__) && defined(MOZ_B2G)
   1.124 +static cubeb_stream_type ConvertChannelToCubebType(dom::AudioChannel aChannel)
   1.125 +{
   1.126 +  switch(aChannel) {
   1.127 +    case dom::AudioChannel::Normal:
   1.128 +      return CUBEB_STREAM_TYPE_SYSTEM;
   1.129 +    case dom::AudioChannel::Content:
   1.130 +      return CUBEB_STREAM_TYPE_MUSIC;
   1.131 +    case dom::AudioChannel::Notification:
   1.132 +      return CUBEB_STREAM_TYPE_NOTIFICATION;
   1.133 +    case dom::AudioChannel::Alarm:
   1.134 +      return CUBEB_STREAM_TYPE_ALARM;
   1.135 +    case dom::AudioChannel::Telephony:
   1.136 +      return CUBEB_STREAM_TYPE_VOICE_CALL;
   1.137 +    case dom::AudioChannel::Ringer:
   1.138 +      return CUBEB_STREAM_TYPE_RING;
   1.139 +    // Currently Android openSLES library doesn't support FORCE_AUDIBLE yet.
   1.140 +    case dom::AudioChannel::Publicnotification:
   1.141 +    default:
   1.142 +      NS_ERROR("The value of AudioChannel is invalid");
   1.143 +      return CUBEB_STREAM_TYPE_MAX;
   1.144 +  }
   1.145 +}
   1.146 +#endif
   1.147 +
   1.148 +AudioStream::AudioStream()
   1.149 +  : mMonitor("AudioStream")
   1.150 +  , mInRate(0)
   1.151 +  , mOutRate(0)
   1.152 +  , mChannels(0)
   1.153 +  , mOutChannels(0)
   1.154 +  , mWritten(0)
   1.155 +  , mAudioClock(MOZ_THIS_IN_INITIALIZER_LIST())
   1.156 +  , mLatencyRequest(HighLatency)
   1.157 +  , mReadPoint(0)
   1.158 +  , mLostFrames(0)
   1.159 +  , mDumpFile(nullptr)
   1.160 +  , mVolume(1.0)
   1.161 +  , mBytesPerFrame(0)
   1.162 +  , mState(INITIALIZED)
   1.163 +  , mNeedsStart(false)
   1.164 +{
   1.165 +  // keep a ref in case we shut down later than nsLayoutStatics
   1.166 +  mLatencyLog = AsyncLatencyLogger::Get(true);
   1.167 +}
   1.168 +
   1.169 +AudioStream::~AudioStream()
   1.170 +{
   1.171 +  LOG(("AudioStream: delete %p, state %d", this, mState));
   1.172 +  Shutdown();
   1.173 +  if (mDumpFile) {
   1.174 +    fclose(mDumpFile);
   1.175 +  }
   1.176 +}
   1.177 +
   1.178 +size_t
   1.179 +AudioStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
   1.180 +{
   1.181 +  size_t amount = aMallocSizeOf(this);
   1.182 +
   1.183 +  // Possibly add in the future:
   1.184 +  // - mTimeStretcher
   1.185 +  // - mLatencyLog
   1.186 +  // - mCubebStream
   1.187 +
   1.188 +  amount += mInserts.SizeOfExcludingThis(aMallocSizeOf);
   1.189 +  amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf);
   1.190 +
   1.191 +  return amount;
   1.192 +}
   1.193 +
   1.194 +/*static*/ void AudioStream::InitLibrary()
   1.195 +{
   1.196 +#ifdef PR_LOGGING
   1.197 +  gAudioStreamLog = PR_NewLogModule("AudioStream");
   1.198 +#endif
   1.199 +  PrefChanged(PREF_VOLUME_SCALE, nullptr);
   1.200 +  Preferences::RegisterCallback(PrefChanged, PREF_VOLUME_SCALE);
   1.201 +  PrefChanged(PREF_CUBEB_LATENCY, nullptr);
   1.202 +  Preferences::RegisterCallback(PrefChanged, PREF_CUBEB_LATENCY);
   1.203 +}
   1.204 +
   1.205 +/*static*/ void AudioStream::ShutdownLibrary()
   1.206 +{
   1.207 +  Preferences::UnregisterCallback(PrefChanged, PREF_VOLUME_SCALE);
   1.208 +  Preferences::UnregisterCallback(PrefChanged, PREF_CUBEB_LATENCY);
   1.209 +
   1.210 +  StaticMutexAutoLock lock(sMutex);
   1.211 +  if (sCubebContext) {
   1.212 +    cubeb_destroy(sCubebContext);
   1.213 +    sCubebContext = nullptr;
   1.214 +  }
   1.215 +}
   1.216 +
   1.217 +nsresult AudioStream::EnsureTimeStretcherInitializedUnlocked()
   1.218 +{
   1.219 +  mMonitor.AssertCurrentThreadOwns();
   1.220 +  if (!mTimeStretcher) {
   1.221 +    mTimeStretcher = new soundtouch::SoundTouch();
   1.222 +    mTimeStretcher->setSampleRate(mInRate);
   1.223 +    mTimeStretcher->setChannels(mOutChannels);
   1.224 +    mTimeStretcher->setPitch(1.0);
   1.225 +  }
   1.226 +  return NS_OK;
   1.227 +}
   1.228 +
   1.229 +nsresult AudioStream::SetPlaybackRate(double aPlaybackRate)
   1.230 +{
   1.231 +  NS_ASSERTION(aPlaybackRate > 0.0,
   1.232 +               "Can't handle negative or null playbackrate in the AudioStream.");
   1.233 +  // Avoid instantiating the resampler if we are not changing the playback rate.
   1.234 +  // GetPreservesPitch/SetPreservesPitch don't need locking before calling
   1.235 +  if (aPlaybackRate == mAudioClock.GetPlaybackRate()) {
   1.236 +    return NS_OK;
   1.237 +  }
   1.238 +
   1.239 +  // MUST lock since the rate transposer is used from the cubeb callback,
   1.240 +  // and rate changes can cause the buffer to be reallocated
   1.241 +  MonitorAutoLock mon(mMonitor);
   1.242 +  if (EnsureTimeStretcherInitializedUnlocked() != NS_OK) {
   1.243 +    return NS_ERROR_FAILURE;
   1.244 +  }
   1.245 +
   1.246 +  mAudioClock.SetPlaybackRateUnlocked(aPlaybackRate);
   1.247 +  mOutRate = mInRate / aPlaybackRate;
   1.248 +
   1.249 +  if (mAudioClock.GetPreservesPitch()) {
   1.250 +    mTimeStretcher->setTempo(aPlaybackRate);
   1.251 +    mTimeStretcher->setRate(1.0f);
   1.252 +  } else {
   1.253 +    mTimeStretcher->setTempo(1.0f);
   1.254 +    mTimeStretcher->setRate(aPlaybackRate);
   1.255 +  }
   1.256 +  return NS_OK;
   1.257 +}
   1.258 +
   1.259 +nsresult AudioStream::SetPreservesPitch(bool aPreservesPitch)
   1.260 +{
   1.261 +  // Avoid instantiating the timestretcher instance if not needed.
   1.262 +  if (aPreservesPitch == mAudioClock.GetPreservesPitch()) {
   1.263 +    return NS_OK;
   1.264 +  }
   1.265 +
   1.266 +  // MUST lock since the rate transposer is used from the cubeb callback,
   1.267 +  // and rate changes can cause the buffer to be reallocated
   1.268 +  MonitorAutoLock mon(mMonitor);
   1.269 +  if (EnsureTimeStretcherInitializedUnlocked() != NS_OK) {
   1.270 +    return NS_ERROR_FAILURE;
   1.271 +  }
   1.272 +
   1.273 +  if (aPreservesPitch == true) {
   1.274 +    mTimeStretcher->setTempo(mAudioClock.GetPlaybackRate());
   1.275 +    mTimeStretcher->setRate(1.0f);
   1.276 +  } else {
   1.277 +    mTimeStretcher->setTempo(1.0f);
   1.278 +    mTimeStretcher->setRate(mAudioClock.GetPlaybackRate());
   1.279 +  }
   1.280 +
   1.281 +  mAudioClock.SetPreservesPitch(aPreservesPitch);
   1.282 +
   1.283 +  return NS_OK;
   1.284 +}
   1.285 +
   1.286 +int64_t AudioStream::GetWritten()
   1.287 +{
   1.288 +  return mWritten;
   1.289 +}
   1.290 +
   1.291 +/*static*/ int AudioStream::MaxNumberOfChannels()
   1.292 +{
   1.293 +  cubeb* cubebContext = GetCubebContext();
   1.294 +  uint32_t maxNumberOfChannels;
   1.295 +  if (cubebContext &&
   1.296 +      cubeb_get_max_channel_count(cubebContext,
   1.297 +                                  &maxNumberOfChannels) == CUBEB_OK) {
   1.298 +    return static_cast<int>(maxNumberOfChannels);
   1.299 +  }
   1.300 +
   1.301 +  return 0;
   1.302 +}
   1.303 +
   1.304 +/*static*/ int AudioStream::PreferredSampleRate()
   1.305 +{
   1.306 +  MOZ_ASSERT(sPreferredSampleRate,
   1.307 +             "sPreferredSampleRate has not been initialized!");
   1.308 +  return sPreferredSampleRate;
   1.309 +}
   1.310 +
   1.311 +static void SetUint16LE(uint8_t* aDest, uint16_t aValue)
   1.312 +{
   1.313 +  aDest[0] = aValue & 0xFF;
   1.314 +  aDest[1] = aValue >> 8;
   1.315 +}
   1.316 +
   1.317 +static void SetUint32LE(uint8_t* aDest, uint32_t aValue)
   1.318 +{
   1.319 +  SetUint16LE(aDest, aValue & 0xFFFF);
   1.320 +  SetUint16LE(aDest + 2, aValue >> 16);
   1.321 +}
   1.322 +
   1.323 +static FILE*
   1.324 +OpenDumpFile(AudioStream* aStream)
   1.325 +{
   1.326 +  if (!getenv("MOZ_DUMP_AUDIO"))
   1.327 +    return nullptr;
   1.328 +  char buf[100];
   1.329 +  sprintf(buf, "dumped-audio-%d.wav", gDumpedAudioCount);
   1.330 +  FILE* f = fopen(buf, "wb");
   1.331 +  if (!f)
   1.332 +    return nullptr;
   1.333 +  ++gDumpedAudioCount;
   1.334 +
   1.335 +  uint8_t header[] = {
   1.336 +    // RIFF header
   1.337 +    0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45,
   1.338 +    // fmt chunk. We always write 16-bit samples.
   1.339 +    0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFF, 0xFF,
   1.340 +    0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x10, 0x00,
   1.341 +    // data chunk
   1.342 +    0x64, 0x61, 0x74, 0x61, 0xFE, 0xFF, 0xFF, 0x7F
   1.343 +  };
   1.344 +  static const int CHANNEL_OFFSET = 22;
   1.345 +  static const int SAMPLE_RATE_OFFSET = 24;
   1.346 +  static const int BLOCK_ALIGN_OFFSET = 32;
   1.347 +  SetUint16LE(header + CHANNEL_OFFSET, aStream->GetChannels());
   1.348 +  SetUint32LE(header + SAMPLE_RATE_OFFSET, aStream->GetRate());
   1.349 +  SetUint16LE(header + BLOCK_ALIGN_OFFSET, aStream->GetChannels()*2);
   1.350 +  fwrite(header, sizeof(header), 1, f);
   1.351 +
   1.352 +  return f;
   1.353 +}
   1.354 +
   1.355 +static void
   1.356 +WriteDumpFile(FILE* aDumpFile, AudioStream* aStream, uint32_t aFrames,
   1.357 +              void* aBuffer)
   1.358 +{
   1.359 +  if (!aDumpFile)
   1.360 +    return;
   1.361 +
   1.362 +  uint32_t samples = aStream->GetOutChannels()*aFrames;
   1.363 +  if (AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_S16) {
   1.364 +    fwrite(aBuffer, 2, samples, aDumpFile);
   1.365 +    return;
   1.366 +  }
   1.367 +
   1.368 +  NS_ASSERTION(AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_FLOAT32, "bad format");
   1.369 +  nsAutoTArray<uint8_t, 1024*2> buf;
   1.370 +  buf.SetLength(samples*2);
   1.371 +  float* input = static_cast<float*>(aBuffer);
   1.372 +  uint8_t* output = buf.Elements();
   1.373 +  for (uint32_t i = 0; i < samples; ++i) {
   1.374 +    SetUint16LE(output + i*2, int16_t(input[i]*32767.0f));
   1.375 +  }
   1.376 +  fwrite(output, 2, samples, aDumpFile);
   1.377 +  fflush(aDumpFile);
   1.378 +}
   1.379 +
   1.380 +// NOTE: this must not block a LowLatency stream for any significant amount
   1.381 +// of time, or it will block the entirety of MSG
   1.382 +nsresult
   1.383 +AudioStream::Init(int32_t aNumChannels, int32_t aRate,
   1.384 +                  const dom::AudioChannel aAudioChannel,
   1.385 +                  LatencyRequest aLatencyRequest)
   1.386 +{
   1.387 +  if (!GetCubebContext() || aNumChannels < 0 || aRate < 0) {
   1.388 +    return NS_ERROR_FAILURE;
   1.389 +  }
   1.390 +
   1.391 +  PR_LOG(gAudioStreamLog, PR_LOG_DEBUG,
   1.392 +    ("%s  channels: %d, rate: %d for %p", __FUNCTION__, aNumChannels, aRate, this));
   1.393 +  mInRate = mOutRate = aRate;
   1.394 +  mChannels = aNumChannels;
   1.395 +  mOutChannels = (aNumChannels > 2) ? 2 : aNumChannels;
   1.396 +  mLatencyRequest = aLatencyRequest;
   1.397 +
   1.398 +  mDumpFile = OpenDumpFile(this);
   1.399 +
   1.400 +  cubeb_stream_params params;
   1.401 +  params.rate = aRate;
   1.402 +  params.channels = mOutChannels;
   1.403 +#if defined(__ANDROID__)
   1.404 +#if defined(MOZ_B2G)
   1.405 +  params.stream_type = ConvertChannelToCubebType(aAudioChannel);
   1.406 +#else
   1.407 +  params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
   1.408 +#endif
   1.409 +
   1.410 +  if (params.stream_type == CUBEB_STREAM_TYPE_MAX) {
   1.411 +    return NS_ERROR_INVALID_ARG;
   1.412 +  }
   1.413 +#endif
   1.414 +  if (AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_S16) {
   1.415 +    params.format = CUBEB_SAMPLE_S16NE;
   1.416 +  } else {
   1.417 +    params.format = CUBEB_SAMPLE_FLOAT32NE;
   1.418 +  }
   1.419 +  mBytesPerFrame = sizeof(AudioDataValue) * mOutChannels;
   1.420 +
   1.421 +  mAudioClock.Init();
   1.422 +
   1.423 +  // Size mBuffer for one second of audio.  This value is arbitrary, and was
   1.424 +  // selected based on the observed behaviour of the existing AudioStream
   1.425 +  // implementations.
   1.426 +  uint32_t bufferLimit = FramesToBytes(aRate);
   1.427 +  NS_ABORT_IF_FALSE(bufferLimit % mBytesPerFrame == 0, "Must buffer complete frames");
   1.428 +  mBuffer.SetCapacity(bufferLimit);
   1.429 +
   1.430 +  if (aLatencyRequest == LowLatency) {
   1.431 +    // Don't block this thread to initialize a cubeb stream.
   1.432 +    // When this is done, it will start callbacks from Cubeb.  Those will
   1.433 +    // cause us to move from INITIALIZED to RUNNING.  Until then, we
   1.434 +    // can't access any cubeb functions.
   1.435 +    // Use a RefPtr to avoid leaks if Dispatch fails
   1.436 +    RefPtr<AudioInitTask> init = new AudioInitTask(this, aLatencyRequest, params);
   1.437 +    init->Dispatch();
   1.438 +    return NS_OK;
   1.439 +  }
   1.440 +  // High latency - open synchronously
   1.441 +  nsresult rv = OpenCubeb(params, aLatencyRequest);
   1.442 +  // See if we need to start() the stream, since we must do that from this
   1.443 +  // thread for now (cubeb API issue)
   1.444 +  CheckForStart();
   1.445 +  return rv;
   1.446 +}
   1.447 +
   1.448 +// This code used to live inside AudioStream::Init(), but on Mac (others?)
   1.449 +// it has been known to take 300-800 (or even 8500) ms to execute(!)
   1.450 +nsresult
   1.451 +AudioStream::OpenCubeb(cubeb_stream_params &aParams,
   1.452 +                       LatencyRequest aLatencyRequest)
   1.453 +{
   1.454 +  cubeb* cubebContext = GetCubebContext();
   1.455 +  if (!cubebContext) {
   1.456 +    MonitorAutoLock mon(mMonitor);
   1.457 +    mState = AudioStream::ERRORED;
   1.458 +    return NS_ERROR_FAILURE;
   1.459 +  }
   1.460 +
   1.461 +  // If the latency pref is set, use it. Otherwise, if this stream is intended
   1.462 +  // for low latency playback, try to get the lowest latency possible.
   1.463 +  // Otherwise, for normal streams, use 100ms.
   1.464 +  uint32_t latency;
   1.465 +  if (aLatencyRequest == LowLatency && !CubebLatencyPrefSet()) {
   1.466 +    if (cubeb_get_min_latency(cubebContext, aParams, &latency) != CUBEB_OK) {
   1.467 +      latency = GetCubebLatency();
   1.468 +    }
   1.469 +  } else {
   1.470 +    latency = GetCubebLatency();
   1.471 +  }
   1.472 +
   1.473 +  {
   1.474 +    cubeb_stream* stream;
   1.475 +    if (cubeb_stream_init(cubebContext, &stream, "AudioStream", aParams,
   1.476 +                          latency, DataCallback_S, StateCallback_S, this) == CUBEB_OK) {
   1.477 +      MonitorAutoLock mon(mMonitor);
   1.478 +      mCubebStream.own(stream);
   1.479 +      // Make sure we weren't shut down while in flight!
   1.480 +      if (mState == SHUTDOWN) {
   1.481 +        mCubebStream.reset();
   1.482 +        LOG(("AudioStream::OpenCubeb() %p Shutdown while opening cubeb", this));
   1.483 +        return NS_ERROR_FAILURE;
   1.484 +      }
   1.485 +
   1.486 +      // We can't cubeb_stream_start() the thread from a transient thread due to
   1.487 +      // cubeb API requirements (init can be called from another thread, but
   1.488 +      // not start/stop/destroy/etc)
   1.489 +    } else {
   1.490 +      MonitorAutoLock mon(mMonitor);
   1.491 +      mState = ERRORED;
   1.492 +      LOG(("AudioStream::OpenCubeb() %p failed to init cubeb", this));
   1.493 +      return NS_ERROR_FAILURE;
   1.494 +    }
   1.495 +  }
   1.496 +
   1.497 +  return NS_OK;
   1.498 +}
   1.499 +
   1.500 +void
   1.501 +AudioStream::CheckForStart()
   1.502 +{
   1.503 +  if (mState == INITIALIZED) {
   1.504 +    // Start the stream right away when low latency has been requested. This means
   1.505 +    // that the DataCallback will feed silence to cubeb, until the first frames
   1.506 +    // are written to this AudioStream.  Also start if a start has been queued.
   1.507 +    if (mLatencyRequest == LowLatency || mNeedsStart) {
   1.508 +      StartUnlocked(); // mState = STARTED or ERRORED
   1.509 +      mNeedsStart = false;
   1.510 +      PR_LOG(gAudioStreamLog, PR_LOG_WARNING,
   1.511 +             ("Started waiting %s-latency stream",
   1.512 +              mLatencyRequest == LowLatency ? "low" : "high"));
   1.513 +    } else {
   1.514 +      // high latency, not full - OR Pause() was called before we got here
   1.515 +      PR_LOG(gAudioStreamLog, PR_LOG_DEBUG,
   1.516 +             ("Not starting waiting %s-latency stream",
   1.517 +              mLatencyRequest == LowLatency ? "low" : "high"));
   1.518 +    }
   1.519 +  }
   1.520 +}
   1.521 +
   1.522 +NS_IMETHODIMP
   1.523 +AudioInitTask::Run()
   1.524 +{
   1.525 +  MOZ_ASSERT(mThread);
   1.526 +  if (NS_IsMainThread()) {
   1.527 +    mThread->Shutdown(); // can't Shutdown from the thread itself, darn
   1.528 +    // Don't null out mThread!
   1.529 +    // See bug 999104.  We must hold a ref to the thread across Dispatch()
   1.530 +    // since the internal mThread ref could be released while processing
   1.531 +    // the Dispatch(), and Dispatch/PutEvent itself doesn't hold a ref; it
   1.532 +    // assumes the caller does.
   1.533 +    return NS_OK;
   1.534 +  }
   1.535 +
   1.536 +  nsresult rv = mAudioStream->OpenCubeb(mParams, mLatencyRequest);
   1.537 +
   1.538 +  // and now kill this thread
   1.539 +  NS_DispatchToMainThread(this);
   1.540 +  return rv;
   1.541 +}
   1.542 +
   1.543 +// aTime is the time in ms the samples were inserted into MediaStreamGraph
   1.544 +nsresult
   1.545 +AudioStream::Write(const AudioDataValue* aBuf, uint32_t aFrames, TimeStamp *aTime)
   1.546 +{
   1.547 +  MonitorAutoLock mon(mMonitor);
   1.548 +  if (mState == ERRORED) {
   1.549 +    return NS_ERROR_FAILURE;
   1.550 +  }
   1.551 +  NS_ASSERTION(mState == INITIALIZED || mState == STARTED || mState == RUNNING,
   1.552 +    "Stream write in unexpected state.");
   1.553 +
   1.554 +  // See if we need to start() the stream, since we must do that from this thread
   1.555 +  CheckForStart();
   1.556 +
   1.557 +  // Downmix to Stereo.
   1.558 +  if (mChannels > 2 && mChannels <= 8) {
   1.559 +    DownmixAudioToStereo(const_cast<AudioDataValue*> (aBuf), mChannels, aFrames);
   1.560 +  }
   1.561 +  else if (mChannels > 8) {
   1.562 +    return NS_ERROR_FAILURE;
   1.563 +  }
   1.564 +
   1.565 +  const uint8_t* src = reinterpret_cast<const uint8_t*>(aBuf);
   1.566 +  uint32_t bytesToCopy = FramesToBytes(aFrames);
   1.567 +
   1.568 +  // XXX this will need to change if we want to enable this on-the-fly!
   1.569 +  if (PR_LOG_TEST(GetLatencyLog(), PR_LOG_DEBUG)) {
   1.570 +    // Record the position and time this data was inserted
   1.571 +    int64_t timeMs;
   1.572 +    if (aTime && !aTime->IsNull()) {
   1.573 +      if (mStartTime.IsNull()) {
   1.574 +        AsyncLatencyLogger::Get(true)->GetStartTime(mStartTime);
   1.575 +      }
   1.576 +      timeMs = (*aTime - mStartTime).ToMilliseconds();
   1.577 +    } else {
   1.578 +      timeMs = 0;
   1.579 +    }
   1.580 +    struct Inserts insert = { timeMs, aFrames};
   1.581 +    mInserts.AppendElement(insert);
   1.582 +  }
   1.583 +
   1.584 +  while (bytesToCopy > 0) {
   1.585 +    uint32_t available = std::min(bytesToCopy, mBuffer.Available());
   1.586 +    NS_ABORT_IF_FALSE(available % mBytesPerFrame == 0,
   1.587 +        "Must copy complete frames.");
   1.588 +
   1.589 +    mBuffer.AppendElements(src, available);
   1.590 +    src += available;
   1.591 +    bytesToCopy -= available;
   1.592 +
   1.593 +    if (bytesToCopy > 0) {
   1.594 +      // Careful - the CubebInit thread may not have gotten to STARTED yet
   1.595 +      if ((mState == INITIALIZED || mState == STARTED) && mLatencyRequest == LowLatency) {
   1.596 +        // don't ever block MediaStreamGraph low-latency streams
   1.597 +        uint32_t remains = 0; // we presume the buffer is full
   1.598 +        if (mBuffer.Length() > bytesToCopy) {
   1.599 +          remains = mBuffer.Length() - bytesToCopy; // Free up just enough space
   1.600 +        }
   1.601 +        // account for dropping samples
   1.602 +        PR_LOG(gAudioStreamLog, PR_LOG_WARNING, ("Stream %p dropping %u bytes (%u frames)in Write()",
   1.603 +            this, mBuffer.Length() - remains, BytesToFrames(mBuffer.Length() - remains)));
   1.604 +        mReadPoint += BytesToFrames(mBuffer.Length() - remains);
   1.605 +        mBuffer.ContractTo(remains);
   1.606 +      } else { // RUNNING or high latency
   1.607 +        // If we are not playing, but our buffer is full, start playing to make
   1.608 +        // room for soon-to-be-decoded data.
   1.609 +        if (mState != STARTED && mState != RUNNING) {
   1.610 +          PR_LOG(gAudioStreamLog, PR_LOG_WARNING, ("Starting stream %p in Write (%u waiting)",
   1.611 +                                                 this, bytesToCopy));
   1.612 +          StartUnlocked();
   1.613 +          if (mState == ERRORED) {
   1.614 +            return NS_ERROR_FAILURE;
   1.615 +          }
   1.616 +        }
   1.617 +        PR_LOG(gAudioStreamLog, PR_LOG_WARNING, ("Stream %p waiting in Write() (%u waiting)",
   1.618 +                                                 this, bytesToCopy));
   1.619 +        mon.Wait();
   1.620 +      }
   1.621 +    }
   1.622 +  }
   1.623 +
   1.624 +  mWritten += aFrames;
   1.625 +  return NS_OK;
   1.626 +}
   1.627 +
   1.628 +uint32_t
   1.629 +AudioStream::Available()
   1.630 +{
   1.631 +  MonitorAutoLock mon(mMonitor);
   1.632 +  NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Buffer invariant violated.");
   1.633 +  return BytesToFrames(mBuffer.Available());
   1.634 +}
   1.635 +
   1.636 +void
   1.637 +AudioStream::SetVolume(double aVolume)
   1.638 +{
   1.639 +  MonitorAutoLock mon(mMonitor);
   1.640 +  NS_ABORT_IF_FALSE(aVolume >= 0.0 && aVolume <= 1.0, "Invalid volume");
   1.641 +  mVolume = aVolume;
   1.642 +}
   1.643 +
   1.644 +void
   1.645 +AudioStream::Drain()
   1.646 +{
   1.647 +  MonitorAutoLock mon(mMonitor);
   1.648 +  LOG(("AudioStream::Drain() for %p, state %d, avail %u", this, mState, mBuffer.Available()));
   1.649 +  if (mState != STARTED && mState != RUNNING) {
   1.650 +    NS_ASSERTION(mState == ERRORED || mBuffer.Available() == 0, "Draining without full buffer of unplayed audio");
   1.651 +    return;
   1.652 +  }
   1.653 +  mState = DRAINING;
   1.654 +  while (mState == DRAINING) {
   1.655 +    mon.Wait();
   1.656 +  }
   1.657 +}
   1.658 +
   1.659 +void
   1.660 +AudioStream::Start()
   1.661 +{
   1.662 +  MonitorAutoLock mon(mMonitor);
   1.663 +  StartUnlocked();
   1.664 +}
   1.665 +
   1.666 +void
   1.667 +AudioStream::StartUnlocked()
   1.668 +{
   1.669 +  mMonitor.AssertCurrentThreadOwns();
   1.670 +  if (!mCubebStream) {
   1.671 +    mNeedsStart = true;
   1.672 +    return;
   1.673 +  }
   1.674 +  MonitorAutoUnlock mon(mMonitor);
   1.675 +  if (mState == INITIALIZED) {
   1.676 +    int r = cubeb_stream_start(mCubebStream);
   1.677 +    mState = r == CUBEB_OK ? STARTED : ERRORED;
   1.678 +    LOG(("AudioStream: started %p, state %s", this, mState == STARTED ? "STARTED" : "ERRORED"));
   1.679 +  }
   1.680 +}
   1.681 +
   1.682 +void
   1.683 +AudioStream::Pause()
   1.684 +{
   1.685 +  MonitorAutoLock mon(mMonitor);
   1.686 +  if (!mCubebStream || (mState != STARTED && mState != RUNNING)) {
   1.687 +    mNeedsStart = false;
   1.688 +    mState = STOPPED; // which also tells async OpenCubeb not to start, just init
   1.689 +    return;
   1.690 +  }
   1.691 +
   1.692 +  int r;
   1.693 +  {
   1.694 +    MonitorAutoUnlock mon(mMonitor);
   1.695 +    r = cubeb_stream_stop(mCubebStream);
   1.696 +  }
   1.697 +  if (mState != ERRORED && r == CUBEB_OK) {
   1.698 +    mState = STOPPED;
   1.699 +  }
   1.700 +}
   1.701 +
   1.702 +void
   1.703 +AudioStream::Resume()
   1.704 +{
   1.705 +  MonitorAutoLock mon(mMonitor);
   1.706 +  if (!mCubebStream || mState != STOPPED) {
   1.707 +    return;
   1.708 +  }
   1.709 +
   1.710 +  int r;
   1.711 +  {
   1.712 +    MonitorAutoUnlock mon(mMonitor);
   1.713 +    r = cubeb_stream_start(mCubebStream);
   1.714 +  }
   1.715 +  if (mState != ERRORED && r == CUBEB_OK) {
   1.716 +    mState = STARTED;
   1.717 +  }
   1.718 +}
   1.719 +
   1.720 +void
   1.721 +AudioStream::Shutdown()
   1.722 +{
   1.723 +  LOG(("AudioStream: Shutdown %p, state %d", this, mState));
   1.724 +  {
   1.725 +    MonitorAutoLock mon(mMonitor);
   1.726 +    if (mState == STARTED || mState == RUNNING) {
   1.727 +      MonitorAutoUnlock mon(mMonitor);
   1.728 +      Pause();
   1.729 +    }
   1.730 +    MOZ_ASSERT(mState != STARTED && mState != RUNNING); // paranoia
   1.731 +    mState = SHUTDOWN;
   1.732 +  }
   1.733 +  // Must not try to shut down cubeb from within the lock!  wasapi may still
   1.734 +  // call our callback after Pause()/stop()!?! Bug 996162
   1.735 +  if (mCubebStream) {
   1.736 +    mCubebStream.reset();
   1.737 +  }
   1.738 +}
   1.739 +
   1.740 +int64_t
   1.741 +AudioStream::GetPosition()
   1.742 +{
   1.743 +  MonitorAutoLock mon(mMonitor);
   1.744 +  return mAudioClock.GetPositionUnlocked();
   1.745 +}
   1.746 +
   1.747 +// This function is miscompiled by PGO with MSVC 2010.  See bug 768333.
   1.748 +#ifdef _MSC_VER
   1.749 +#pragma optimize("", off)
   1.750 +#endif
   1.751 +int64_t
   1.752 +AudioStream::GetPositionInFrames()
   1.753 +{
   1.754 +  return mAudioClock.GetPositionInFrames();
   1.755 +}
   1.756 +#ifdef _MSC_VER
   1.757 +#pragma optimize("", on)
   1.758 +#endif
   1.759 +
   1.760 +int64_t
   1.761 +AudioStream::GetPositionInFramesInternal()
   1.762 +{
   1.763 +  MonitorAutoLock mon(mMonitor);
   1.764 +  return GetPositionInFramesUnlocked();
   1.765 +}
   1.766 +
   1.767 +int64_t
   1.768 +AudioStream::GetPositionInFramesUnlocked()
   1.769 +{
   1.770 +  mMonitor.AssertCurrentThreadOwns();
   1.771 +
   1.772 +  if (!mCubebStream || mState == ERRORED) {
   1.773 +    return -1;
   1.774 +  }
   1.775 +
   1.776 +  uint64_t position = 0;
   1.777 +  {
   1.778 +    MonitorAutoUnlock mon(mMonitor);
   1.779 +    if (cubeb_stream_get_position(mCubebStream, &position) != CUBEB_OK) {
   1.780 +      return -1;
   1.781 +    }
   1.782 +  }
   1.783 +
   1.784 +  // Adjust the reported position by the number of silent frames written
   1.785 +  // during stream underruns.
   1.786 +  uint64_t adjustedPosition = 0;
   1.787 +  if (position >= mLostFrames) {
   1.788 +    adjustedPosition = position - mLostFrames;
   1.789 +  }
   1.790 +  return std::min<uint64_t>(adjustedPosition, INT64_MAX);
   1.791 +}
   1.792 +
   1.793 +int64_t
   1.794 +AudioStream::GetLatencyInFrames()
   1.795 +{
   1.796 +  uint32_t latency;
   1.797 +  if (cubeb_stream_get_latency(mCubebStream, &latency)) {
   1.798 +    NS_WARNING("Could not get cubeb latency.");
   1.799 +    return 0;
   1.800 +  }
   1.801 +  return static_cast<int64_t>(latency);
   1.802 +}
   1.803 +
   1.804 +bool
   1.805 +AudioStream::IsPaused()
   1.806 +{
   1.807 +  MonitorAutoLock mon(mMonitor);
   1.808 +  return mState == STOPPED;
   1.809 +}
   1.810 +
   1.811 +void
   1.812 +AudioStream::GetBufferInsertTime(int64_t &aTimeMs)
   1.813 +{
   1.814 +  if (mInserts.Length() > 0) {
   1.815 +    // Find the right block, but don't leave the array empty
   1.816 +    while (mInserts.Length() > 1 && mReadPoint >= mInserts[0].mFrames) {
   1.817 +      mReadPoint -= mInserts[0].mFrames;
   1.818 +      mInserts.RemoveElementAt(0);
   1.819 +    }
   1.820 +    // offset for amount already read
   1.821 +    // XXX Note: could misreport if we couldn't find a block in the right timeframe
   1.822 +    aTimeMs = mInserts[0].mTimeMs + ((mReadPoint * 1000) / mOutRate);
   1.823 +  } else {
   1.824 +    aTimeMs = INT64_MAX;
   1.825 +  }
   1.826 +}
   1.827 +
   1.828 +long
   1.829 +AudioStream::GetUnprocessed(void* aBuffer, long aFrames, int64_t &aTimeMs)
   1.830 +{
   1.831 +  uint8_t* wpos = reinterpret_cast<uint8_t*>(aBuffer);
   1.832 +
   1.833 +  // Flush the timestretcher pipeline, if we were playing using a playback rate
   1.834 +  // other than 1.0.
   1.835 +  uint32_t flushedFrames = 0;
   1.836 +  if (mTimeStretcher && mTimeStretcher->numSamples()) {
   1.837 +    flushedFrames = mTimeStretcher->receiveSamples(reinterpret_cast<AudioDataValue*>(wpos), aFrames);
   1.838 +    wpos += FramesToBytes(flushedFrames);
   1.839 +  }
   1.840 +  uint32_t toPopBytes = FramesToBytes(aFrames - flushedFrames);
   1.841 +  uint32_t available = std::min(toPopBytes, mBuffer.Length());
   1.842 +
   1.843 +  void* input[2];
   1.844 +  uint32_t input_size[2];
   1.845 +  mBuffer.PopElements(available, &input[0], &input_size[0], &input[1], &input_size[1]);
   1.846 +  memcpy(wpos, input[0], input_size[0]);
   1.847 +  wpos += input_size[0];
   1.848 +  memcpy(wpos, input[1], input_size[1]);
   1.849 +
   1.850 +  // First time block now has our first returned sample
   1.851 +  mReadPoint += BytesToFrames(available);
   1.852 +  GetBufferInsertTime(aTimeMs);
   1.853 +
   1.854 +  return BytesToFrames(available) + flushedFrames;
   1.855 +}
   1.856 +
   1.857 +// Get unprocessed samples, and pad the beginning of the buffer with silence if
   1.858 +// there is not enough data.
   1.859 +long
   1.860 +AudioStream::GetUnprocessedWithSilencePadding(void* aBuffer, long aFrames, int64_t& aTimeMs)
   1.861 +{
   1.862 +  uint32_t toPopBytes = FramesToBytes(aFrames);
   1.863 +  uint32_t available = std::min(toPopBytes, mBuffer.Length());
   1.864 +  uint32_t silenceOffset = toPopBytes - available;
   1.865 +
   1.866 +  uint8_t* wpos = reinterpret_cast<uint8_t*>(aBuffer);
   1.867 +
   1.868 +  memset(wpos, 0, silenceOffset);
   1.869 +  wpos += silenceOffset;
   1.870 +
   1.871 +  void* input[2];
   1.872 +  uint32_t input_size[2];
   1.873 +  mBuffer.PopElements(available, &input[0], &input_size[0], &input[1], &input_size[1]);
   1.874 +  memcpy(wpos, input[0], input_size[0]);
   1.875 +  wpos += input_size[0];
   1.876 +  memcpy(wpos, input[1], input_size[1]);
   1.877 +
   1.878 +  GetBufferInsertTime(aTimeMs);
   1.879 +
   1.880 +  return aFrames;
   1.881 +}
   1.882 +
   1.883 +long
   1.884 +AudioStream::GetTimeStretched(void* aBuffer, long aFrames, int64_t &aTimeMs)
   1.885 +{
   1.886 +  long processedFrames = 0;
   1.887 +
   1.888 +  // We need to call the non-locking version, because we already have the lock.
   1.889 +  if (EnsureTimeStretcherInitializedUnlocked() != NS_OK) {
   1.890 +    return 0;
   1.891 +  }
   1.892 +
   1.893 +  uint8_t* wpos = reinterpret_cast<uint8_t*>(aBuffer);
   1.894 +  double playbackRate = static_cast<double>(mInRate) / mOutRate;
   1.895 +  uint32_t toPopBytes = FramesToBytes(ceil(aFrames / playbackRate));
   1.896 +  uint32_t available = 0;
   1.897 +  bool lowOnBufferedData = false;
   1.898 +  do {
   1.899 +    // Check if we already have enough data in the time stretcher pipeline.
   1.900 +    if (mTimeStretcher->numSamples() <= static_cast<uint32_t>(aFrames)) {
   1.901 +      void* input[2];
   1.902 +      uint32_t input_size[2];
   1.903 +      available = std::min(mBuffer.Length(), toPopBytes);
   1.904 +      if (available != toPopBytes) {
   1.905 +        lowOnBufferedData = true;
   1.906 +      }
   1.907 +      mBuffer.PopElements(available, &input[0], &input_size[0],
   1.908 +                                     &input[1], &input_size[1]);
   1.909 +      mReadPoint += BytesToFrames(available);
   1.910 +      for(uint32_t i = 0; i < 2; i++) {
   1.911 +        mTimeStretcher->putSamples(reinterpret_cast<AudioDataValue*>(input[i]), BytesToFrames(input_size[i]));
   1.912 +      }
   1.913 +    }
   1.914 +    uint32_t receivedFrames = mTimeStretcher->receiveSamples(reinterpret_cast<AudioDataValue*>(wpos), aFrames - processedFrames);
   1.915 +    wpos += FramesToBytes(receivedFrames);
   1.916 +    processedFrames += receivedFrames;
   1.917 +  } while (processedFrames < aFrames && !lowOnBufferedData);
   1.918 +
   1.919 +  GetBufferInsertTime(aTimeMs);
   1.920 +
   1.921 +  return processedFrames;
   1.922 +}
   1.923 +
   1.924 +long
   1.925 +AudioStream::DataCallback(void* aBuffer, long aFrames)
   1.926 +{
   1.927 +  MonitorAutoLock mon(mMonitor);
   1.928 +  uint32_t available = std::min(static_cast<uint32_t>(FramesToBytes(aFrames)), mBuffer.Length());
   1.929 +  NS_ABORT_IF_FALSE(available % mBytesPerFrame == 0, "Must copy complete frames");
   1.930 +  AudioDataValue* output = reinterpret_cast<AudioDataValue*>(aBuffer);
   1.931 +  uint32_t underrunFrames = 0;
   1.932 +  uint32_t servicedFrames = 0;
   1.933 +  int64_t insertTime;
   1.934 +
   1.935 +  // NOTE: wasapi (others?) can call us back *after* stop()/Shutdown() (mState == SHUTDOWN)
   1.936 +  // Bug 996162
   1.937 +
   1.938 +  // callback tells us cubeb succeeded initializing
   1.939 +  if (mState == STARTED) {
   1.940 +    // For low-latency streams, we want to minimize any built-up data when
   1.941 +    // we start getting callbacks.
   1.942 +    // Simple version - contract on first callback only.
   1.943 +    if (mLatencyRequest == LowLatency) {
   1.944 +#ifdef PR_LOGGING
   1.945 +      uint32_t old_len = mBuffer.Length();
   1.946 +#endif
   1.947 +      available = mBuffer.ContractTo(FramesToBytes(aFrames));
   1.948 +#ifdef PR_LOGGING
   1.949 +      TimeStamp now = TimeStamp::Now();
   1.950 +      if (!mStartTime.IsNull()) {
   1.951 +        int64_t timeMs = (now - mStartTime).ToMilliseconds();
   1.952 +        PR_LOG(gAudioStreamLog, PR_LOG_WARNING,
   1.953 +               ("Stream took %lldms to start after first Write() @ %u", timeMs, mOutRate));
   1.954 +      } else {
   1.955 +        PR_LOG(gAudioStreamLog, PR_LOG_WARNING,
   1.956 +          ("Stream started before Write() @ %u", mOutRate));
   1.957 +      }
   1.958 +
   1.959 +      if (old_len != available) {
   1.960 +        // Note that we may have dropped samples in Write() as well!
   1.961 +        PR_LOG(gAudioStreamLog, PR_LOG_WARNING,
   1.962 +               ("AudioStream %p dropped %u + %u initial frames @ %u", this,
   1.963 +                 mReadPoint, BytesToFrames(old_len - available), mOutRate));
   1.964 +        mReadPoint += BytesToFrames(old_len - available);
   1.965 +      }
   1.966 +#endif
   1.967 +    }
   1.968 +    mState = RUNNING;
   1.969 +  }
   1.970 +
   1.971 +  if (available) {
   1.972 +    // When we are playing a low latency stream, and it is the first time we are
   1.973 +    // getting data from the buffer, we prefer to add the silence for an
   1.974 +    // underrun at the beginning of the buffer, so the first buffer is not cut
   1.975 +    // in half by the silence inserted to compensate for the underrun.
   1.976 +    if (mInRate == mOutRate) {
   1.977 +      if (mLatencyRequest == LowLatency && !mWritten) {
   1.978 +        servicedFrames = GetUnprocessedWithSilencePadding(output, aFrames, insertTime);
   1.979 +      } else {
   1.980 +        servicedFrames = GetUnprocessed(output, aFrames, insertTime);
   1.981 +      }
   1.982 +    } else {
   1.983 +      servicedFrames = GetTimeStretched(output, aFrames, insertTime);
   1.984 +    }
   1.985 +    float scaled_volume = float(GetVolumeScale() * mVolume);
   1.986 +
   1.987 +    ScaleAudioSamples(output, aFrames * mOutChannels, scaled_volume);
   1.988 +
   1.989 +    NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Must copy complete frames");
   1.990 +
   1.991 +    // Notify any blocked Write() call that more space is available in mBuffer.
   1.992 +    mon.NotifyAll();
   1.993 +  } else {
   1.994 +    GetBufferInsertTime(insertTime);
   1.995 +  }
   1.996 +
   1.997 +  underrunFrames = aFrames - servicedFrames;
   1.998 +
   1.999 +  if (mState != DRAINING) {
  1.1000 +    uint8_t* rpos = static_cast<uint8_t*>(aBuffer) + FramesToBytes(aFrames - underrunFrames);
  1.1001 +    memset(rpos, 0, FramesToBytes(underrunFrames));
  1.1002 +    if (underrunFrames) {
  1.1003 +      PR_LOG(gAudioStreamLog, PR_LOG_WARNING,
  1.1004 +             ("AudioStream %p lost %d frames", this, underrunFrames));
  1.1005 +    }
  1.1006 +    mLostFrames += underrunFrames;
  1.1007 +    servicedFrames += underrunFrames;
  1.1008 +  }
  1.1009 +
  1.1010 +  WriteDumpFile(mDumpFile, this, aFrames, aBuffer);
  1.1011 +  // Don't log if we're not interested or if the stream is inactive
  1.1012 +  if (PR_LOG_TEST(GetLatencyLog(), PR_LOG_DEBUG) &&
  1.1013 +      mState != SHUTDOWN &&
  1.1014 +      insertTime != INT64_MAX && servicedFrames > underrunFrames) {
  1.1015 +    uint32_t latency = UINT32_MAX;
  1.1016 +    if (cubeb_stream_get_latency(mCubebStream, &latency)) {
  1.1017 +      NS_WARNING("Could not get latency from cubeb.");
  1.1018 +    }
  1.1019 +    TimeStamp now = TimeStamp::Now();
  1.1020 +
  1.1021 +    mLatencyLog->Log(AsyncLatencyLogger::AudioStream, reinterpret_cast<uint64_t>(this),
  1.1022 +                     insertTime, now);
  1.1023 +    mLatencyLog->Log(AsyncLatencyLogger::Cubeb, reinterpret_cast<uint64_t>(mCubebStream.get()),
  1.1024 +                     (latency * 1000) / mOutRate, now);
  1.1025 +  }
  1.1026 +
  1.1027 +  mAudioClock.UpdateWritePosition(servicedFrames);
  1.1028 +  return servicedFrames;
  1.1029 +}
  1.1030 +
  1.1031 +void
  1.1032 +AudioStream::StateCallback(cubeb_state aState)
  1.1033 +{
  1.1034 +  MonitorAutoLock mon(mMonitor);
  1.1035 +  if (aState == CUBEB_STATE_DRAINED) {
  1.1036 +    mState = DRAINED;
  1.1037 +  } else if (aState == CUBEB_STATE_ERROR) {
  1.1038 +    LOG(("AudioStream::StateCallback() state %d cubeb error", mState));
  1.1039 +    mState = ERRORED;
  1.1040 +  }
  1.1041 +  mon.NotifyAll();
  1.1042 +}
  1.1043 +
  1.1044 +AudioClock::AudioClock(AudioStream* aStream)
  1.1045 + :mAudioStream(aStream),
  1.1046 +  mOldOutRate(0),
  1.1047 +  mBasePosition(0),
  1.1048 +  mBaseOffset(0),
  1.1049 +  mOldBaseOffset(0),
  1.1050 +  mOldBasePosition(0),
  1.1051 +  mPlaybackRateChangeOffset(0),
  1.1052 +  mPreviousPosition(0),
  1.1053 +  mWritten(0),
  1.1054 +  mOutRate(0),
  1.1055 +  mInRate(0),
  1.1056 +  mPreservesPitch(true),
  1.1057 +  mCompensatingLatency(false)
  1.1058 +{}
  1.1059 +
  1.1060 +void AudioClock::Init()
  1.1061 +{
  1.1062 +  mOutRate = mAudioStream->GetRate();
  1.1063 +  mInRate = mAudioStream->GetRate();
  1.1064 +  mOldOutRate = mOutRate;
  1.1065 +}
  1.1066 +
  1.1067 +void AudioClock::UpdateWritePosition(uint32_t aCount)
  1.1068 +{
  1.1069 +  mWritten += aCount;
  1.1070 +}
  1.1071 +
  1.1072 +uint64_t AudioClock::GetPositionUnlocked()
  1.1073 +{
  1.1074 +  // GetPositionInFramesUnlocked() asserts it owns the monitor
  1.1075 +  int64_t position = mAudioStream->GetPositionInFramesUnlocked();
  1.1076 +  int64_t diffOffset;
  1.1077 +  NS_ASSERTION(position < 0 || (mInRate != 0 && mOutRate != 0), "AudioClock not initialized.");
  1.1078 +  if (position >= 0) {
  1.1079 +    if (position < mPlaybackRateChangeOffset) {
  1.1080 +      // See if we are still playing frames pushed with the old playback rate in
  1.1081 +      // the backend. If we are, use the old output rate to compute the
  1.1082 +      // position.
  1.1083 +      mCompensatingLatency = true;
  1.1084 +      diffOffset = position - mOldBaseOffset;
  1.1085 +      position = static_cast<uint64_t>(mOldBasePosition +
  1.1086 +        static_cast<float>(USECS_PER_S * diffOffset) / mOldOutRate);
  1.1087 +      mPreviousPosition = position;
  1.1088 +      return position;
  1.1089 +    }
  1.1090 +
  1.1091 +    if (mCompensatingLatency) {
  1.1092 +      diffOffset = position - mPlaybackRateChangeOffset;
  1.1093 +      mCompensatingLatency = false;
  1.1094 +      mBasePosition = mPreviousPosition;
  1.1095 +    } else {
  1.1096 +      diffOffset = position - mPlaybackRateChangeOffset;
  1.1097 +    }
  1.1098 +    position =  static_cast<uint64_t>(mBasePosition +
  1.1099 +      (static_cast<float>(USECS_PER_S * diffOffset) / mOutRate));
  1.1100 +    return position;
  1.1101 +  }
  1.1102 +  return UINT64_MAX;
  1.1103 +}
  1.1104 +
  1.1105 +uint64_t AudioClock::GetPositionInFrames()
  1.1106 +{
  1.1107 +  return (GetPositionUnlocked() * mOutRate) / USECS_PER_S;
  1.1108 +}
  1.1109 +
  1.1110 +void AudioClock::SetPlaybackRateUnlocked(double aPlaybackRate)
  1.1111 +{
  1.1112 +  // GetPositionInFramesUnlocked() asserts it owns the monitor
  1.1113 +  int64_t position = mAudioStream->GetPositionInFramesUnlocked();
  1.1114 +  if (position > mPlaybackRateChangeOffset) {
  1.1115 +    mOldBasePosition = mBasePosition;
  1.1116 +    mBasePosition = GetPositionUnlocked();
  1.1117 +    mOldBaseOffset = mPlaybackRateChangeOffset;
  1.1118 +    mBaseOffset = position;
  1.1119 +    mPlaybackRateChangeOffset = mWritten;
  1.1120 +    mOldOutRate = mOutRate;
  1.1121 +    mOutRate = static_cast<int>(mInRate / aPlaybackRate);
  1.1122 +  } else {
  1.1123 +    // The playbackRate has been changed before the end of the latency
  1.1124 +    // compensation phase. We don't update the mOld* variable. That way, the
  1.1125 +    // last playbackRate set is taken into account.
  1.1126 +    mBasePosition = GetPositionUnlocked();
  1.1127 +    mBaseOffset = position;
  1.1128 +    mPlaybackRateChangeOffset = mWritten;
  1.1129 +    mOutRate = static_cast<int>(mInRate / aPlaybackRate);
  1.1130 +  }
  1.1131 +}
  1.1132 +
  1.1133 +double AudioClock::GetPlaybackRate()
  1.1134 +{
  1.1135 +  return static_cast<double>(mInRate) / mOutRate;
  1.1136 +}
  1.1137 +
  1.1138 +void AudioClock::SetPreservesPitch(bool aPreservesPitch)
  1.1139 +{
  1.1140 +  mPreservesPitch = aPreservesPitch;
  1.1141 +}
  1.1142 +
  1.1143 +bool AudioClock::GetPreservesPitch()
  1.1144 +{
  1.1145 +  return mPreservesPitch;
  1.1146 +}
  1.1147 +} // namespace mozilla

mercurial