tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AudioStream.cpp (25359B)


      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 "AudioStream.h"
      7 
      8 #include <math.h>
      9 #include <stdio.h>
     10 #include <string.h>
     11 
     12 #include <algorithm>
     13 
     14 #include "AudioConverter.h"
     15 #include "CubebUtils.h"
     16 #include "UnderrunHandler.h"
     17 #include "VideoUtils.h"
     18 #include "mozilla/Logging.h"
     19 #include "mozilla/Monitor.h"
     20 #include "mozilla/Mutex.h"
     21 #include "mozilla/Sprintf.h"
     22 #include "mozilla/dom/AudioDeviceInfo.h"
     23 #include "nsNativeCharsetUtils.h"
     24 #include "nsPrintfCString.h"
     25 #include "prdtoa.h"
     26 #if defined(XP_WIN)
     27 #  include "nsXULAppAPI.h"
     28 #endif
     29 #include "CallbackThreadRegistry.h"
     30 #include "RLBoxSoundTouch.h"
     31 #include "Tracing.h"
     32 #include "mozilla/StaticPrefs_media.h"
     33 #include "webaudio/blink/DenormalDisabler.h"
     34 
     35 namespace mozilla {
     36 
     37 LazyLogModule gAudioStreamLog("AudioStream");
     38 // For simple logs
     39 #define LOG(x, ...)                                  \
     40  MOZ_LOG(gAudioStreamLog, mozilla::LogLevel::Debug, \
     41          ("%p " x, this, ##__VA_ARGS__))
     42 #define LOGW(x, ...)                                   \
     43  MOZ_LOG(gAudioStreamLog, mozilla::LogLevel::Warning, \
     44          ("%p " x, this, ##__VA_ARGS__))
     45 #define LOGE(x, ...)                                                          \
     46  NS_DebugBreak(NS_DEBUG_WARNING,                                             \
     47                nsPrintfCString("%p " x, this, ##__VA_ARGS__).get(), nullptr, \
     48                __FILE__, __LINE__)
     49 
     50 /**
     51 * Keep a list of frames sent to the audio engine in each DataCallback along
     52 * with the playback rate at the moment. Since the playback rate and number of
     53 * underrun frames can vary in each callback. We need to keep the whole history
     54 * in order to calculate the playback position of the audio engine correctly.
     55 */
     56 class FrameHistory {
     57  struct Chunk {
     58    uint32_t servicedFrames;
     59    uint32_t totalFrames;
     60    uint32_t rate;
     61  };
     62 
     63  template <typename T>
     64  static T FramesToUs(uint32_t frames, uint32_t rate) {
     65    return static_cast<T>(frames) * USECS_PER_S / rate;
     66  }
     67 
     68 public:
     69  FrameHistory() : mBaseOffset(0), mBasePosition(0) {}
     70 
     71  void Append(uint32_t aServiced, uint32_t aUnderrun, uint32_t aRate) {
     72    /* In most case where playback rate stays the same and we don't underrun
     73     * frames, we are able to merge chunks to avoid lose of precision to add up
     74     * in compressing chunks into |mBaseOffset| and |mBasePosition|.
     75     */
     76    if (!mChunks.IsEmpty()) {
     77      Chunk& c = mChunks.LastElement();
     78      // 2 chunks (c1 and c2) can be merged when rate is the same and
     79      // adjacent frames are zero. That is, underrun frames in c1 are zero
     80      // or serviced frames in c2 are zero.
     81      if (c.rate == aRate &&
     82          (c.servicedFrames == c.totalFrames || aServiced == 0)) {
     83        c.servicedFrames += aServiced;
     84        c.totalFrames += aServiced + aUnderrun;
     85        return;
     86      }
     87    }
     88    Chunk* p = mChunks.AppendElement();
     89    p->servicedFrames = aServiced;
     90    p->totalFrames = aServiced + aUnderrun;
     91    p->rate = aRate;
     92  }
     93 
     94  /**
     95   * @param frames The playback position in frames of the audio engine.
     96   * @return The playback position in microseconds of the audio engine,
     97   *         adjusted by playback rate changes and underrun frames.
     98   */
     99  int64_t GetPosition(int64_t frames) {
    100    // playback position should not go backward.
    101    MOZ_ASSERT(frames >= mBaseOffset);
    102    while (true) {
    103      if (mChunks.IsEmpty()) {
    104        return static_cast<int64_t>(mBasePosition);
    105      }
    106      const Chunk& c = mChunks[0];
    107      if (frames <= mBaseOffset + c.totalFrames) {
    108        uint32_t delta = frames - mBaseOffset;
    109        delta = std::min(delta, c.servicedFrames);
    110        return static_cast<int64_t>(mBasePosition) +
    111               FramesToUs<int64_t>(delta, c.rate);
    112      }
    113      // Since the playback position of the audio engine will not go backward,
    114      // we are able to compress chunks so that |mChunks| won't grow
    115      // unlimitedly. Note that we lose precision in converting integers into
    116      // floats and inaccuracy will accumulate over time. However, for a 24hr
    117      // long, sample rate = 44.1k file, the error will be less than 1
    118      // microsecond after playing 24 hours. So we are fine with that.
    119      mBaseOffset += c.totalFrames;
    120      mBasePosition += FramesToUs<double>(c.servicedFrames, c.rate);
    121      mChunks.RemoveElementAt(0);
    122    }
    123  }
    124 
    125 private:
    126  AutoTArray<Chunk, 7> mChunks;
    127  int64_t mBaseOffset;
    128  double mBasePosition;
    129 };
    130 
    131 AudioStream::AudioStream(DataSource& aSource, uint32_t aInRate,
    132                         uint32_t aOutputChannels,
    133                         AudioConfig::ChannelLayout::ChannelMap aChannelMap)
    134    : mTimeStretcher(nullptr),
    135      mAudioClock(aInRate),
    136      mChannelMap(aChannelMap),
    137      mMonitor("AudioStream"),
    138      mOutChannels(aOutputChannels),
    139      mState(INITIALIZED),
    140      mDataSource(aSource),
    141      mAudioThreadId(ProfilerThreadId{}),
    142      mSandboxed(CubebUtils::SandboxEnabled()),
    143      mPlaybackComplete(false),
    144      mPlaybackRate(1.0f),
    145      mPreservesPitch(true),
    146      mCallbacksStarted(false) {}
    147 
    148 AudioStream::~AudioStream() {
    149  LOG("deleted, state %d", mState.load());
    150  MOZ_ASSERT(mState == SHUTDOWN && !mCubebStream,
    151             "Should've called ShutDown() before deleting an AudioStream");
    152 }
    153 
    154 size_t AudioStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
    155  size_t amount = aMallocSizeOf(this);
    156 
    157  // Possibly add in the future:
    158  // - mTimeStretcher
    159  // - mCubebStream
    160 
    161  return amount;
    162 }
    163 
    164 nsresult AudioStream::EnsureTimeStretcherInitialized() {
    165  AssertIsOnAudioThread();
    166  if (!mTimeStretcher) {
    167    auto timestretcher = MakeUnique<RLBoxSoundTouch>();
    168    if (!timestretcher || !timestretcher->Init()) {
    169      return NS_ERROR_FAILURE;
    170    }
    171    mTimeStretcher = timestretcher.release();
    172 
    173    mTimeStretcher->setSampleRate(mAudioClock.GetInputRate());
    174    mTimeStretcher->setChannels(mOutChannels);
    175    mTimeStretcher->setPitch(1.0);
    176 
    177    // SoundTouch v2.1.2 uses automatic time-stretch settings with the following
    178    // values:
    179    // Tempo 0.5: 90ms sequence, 20ms seekwindow, 8ms overlap
    180    // Tempo 2.0: 40ms sequence, 15ms seekwindow, 8ms overlap
    181    // We are going to use a smaller 10ms sequence size to improve speech
    182    // clarity, giving more resolution at high tempo and less reverb at low
    183    // tempo. Maintain 15ms seekwindow and 8ms overlap for smoothness.
    184    mTimeStretcher->setSetting(
    185        SETTING_SEQUENCE_MS,
    186        StaticPrefs::media_audio_playbackrate_soundtouch_sequence_ms());
    187    mTimeStretcher->setSetting(
    188        SETTING_SEEKWINDOW_MS,
    189        StaticPrefs::media_audio_playbackrate_soundtouch_seekwindow_ms());
    190    mTimeStretcher->setSetting(
    191        SETTING_OVERLAP_MS,
    192        StaticPrefs::media_audio_playbackrate_soundtouch_overlap_ms());
    193  }
    194  return NS_OK;
    195 }
    196 
    197 nsresult AudioStream::SetPlaybackRate(double aPlaybackRate) {
    198  TRACE_COMMENT("AudioStream::SetPlaybackRate", "%f", aPlaybackRate);
    199  NS_ASSERTION(
    200      aPlaybackRate > 0.0,
    201      "Can't handle negative or null playbackrate in the AudioStream.");
    202  if (aPlaybackRate == mPlaybackRate) {
    203    return NS_OK;
    204  }
    205 
    206  mPlaybackRate = static_cast<float>(aPlaybackRate);
    207 
    208  return NS_OK;
    209 }
    210 
    211 nsresult AudioStream::SetPreservesPitch(bool aPreservesPitch) {
    212  TRACE_COMMENT("AudioStream::SetPreservesPitch", "%d", aPreservesPitch);
    213  if (aPreservesPitch == mPreservesPitch) {
    214    return NS_OK;
    215  }
    216 
    217  mPreservesPitch = aPreservesPitch;
    218 
    219  return NS_OK;
    220 }
    221 
    222 template <typename Function, typename... Args>
    223 int AudioStream::InvokeCubeb(Function aFunction, Args&&... aArgs) {
    224  mMonitor.AssertCurrentThreadOwns();
    225  MonitorAutoUnlock mon(mMonitor);
    226  return aFunction(mCubebStream.get(), std::forward<Args>(aArgs)...);
    227 }
    228 
    229 nsresult AudioStream::Init(AudioDeviceInfo* aSinkInfo)
    230    MOZ_NO_THREAD_SAFETY_ANALYSIS {
    231  auto startTime = TimeStamp::Now();
    232  TRACE("AudioStream::Init");
    233 
    234  LOG("%s channels: %d, rate: %d", __FUNCTION__, mOutChannels,
    235      mAudioClock.GetInputRate());
    236 
    237  mSinkInfo = aSinkInfo;
    238 
    239  cubeb_stream_params params;
    240  params.rate = mAudioClock.GetInputRate();
    241  params.channels = mOutChannels;
    242  params.layout = static_cast<uint32_t>(mChannelMap);
    243  params.format = CubebUtils::ToCubebFormat<AUDIO_OUTPUT_FORMAT>::value;
    244  params.prefs = CubebUtils::GetDefaultStreamPrefs(CUBEB_DEVICE_TYPE_OUTPUT);
    245  params.input_params = CUBEB_INPUT_PROCESSING_PARAM_NONE;
    246 
    247  // This is noop if MOZ_DUMP_AUDIO is not set.
    248  mDumpFile.Open("AudioStream", mOutChannels, mAudioClock.GetInputRate());
    249 
    250  RefPtr<CubebUtils::CubebHandle> handle = CubebUtils::GetCubeb();
    251  if (!handle) {
    252    LOGE("Can't get cubeb context!");
    253    CubebUtils::ReportCubebStreamInitFailure(true);
    254    return NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR;
    255  }
    256 
    257  mCubeb = handle;
    258  return OpenCubeb(handle->Context(), params, startTime,
    259                   CubebUtils::GetFirstStream());
    260 }
    261 
    262 nsresult AudioStream::OpenCubeb(cubeb* aContext, cubeb_stream_params& aParams,
    263                                TimeStamp aStartTime, bool aIsFirst) {
    264  TRACE("AudioStream::OpenCubeb");
    265  MOZ_ASSERT(aContext);
    266 
    267  cubeb_stream* stream = nullptr;
    268  /* Convert from milliseconds to frames. */
    269  uint32_t latency_frames =
    270      CubebUtils::GetCubebPlaybackLatencyInMilliseconds() * aParams.rate / 1000;
    271  cubeb_devid deviceID = nullptr;
    272  if (mSinkInfo && mSinkInfo->DeviceID()) {
    273    deviceID = mSinkInfo->DeviceID();
    274  }
    275  if (CubebUtils::CubebStreamInit(aContext, &stream, "AudioStream", nullptr,
    276                                  nullptr, deviceID, &aParams, latency_frames,
    277                                  DataCallback_S, StateCallback_S,
    278                                  this) == CUBEB_OK) {
    279    mCubebStream.reset(stream);
    280    CubebUtils::ReportCubebBackendUsed();
    281  } else {
    282    LOGE("OpenCubeb() failed to init cubeb");
    283    CubebUtils::ReportCubebStreamInitFailure(aIsFirst);
    284    return NS_ERROR_FAILURE;
    285  }
    286 
    287  TimeDuration timeDelta = TimeStamp::Now() - aStartTime;
    288  LOG("creation time %sfirst: %u ms", aIsFirst ? "" : "not ",
    289      (uint32_t)timeDelta.ToMilliseconds());
    290 
    291  return NS_OK;
    292 }
    293 
    294 void AudioStream::SetVolume(double aVolume) {
    295  TRACE_COMMENT("AudioStream::SetVolume", "%f", aVolume);
    296  MOZ_ASSERT(aVolume >= 0.0 && aVolume <= 1.0, "Invalid volume");
    297 
    298  MOZ_ASSERT(mState != SHUTDOWN, "Don't set volume after shutdown.");
    299  if (mState == ERRORED) {
    300    return;
    301  }
    302 
    303  MonitorAutoLock mon(mMonitor);
    304  if (InvokeCubeb(cubeb_stream_set_volume,
    305                  aVolume * CubebUtils::GetVolumeScale()) != CUBEB_OK) {
    306    LOGE("Could not change volume on cubeb stream.");
    307  }
    308 }
    309 
    310 void AudioStream::SetStreamName(const nsAString& aStreamName) {
    311  TRACE("AudioStream::SetStreamName");
    312 
    313  nsAutoCString aRawStreamName;
    314  nsresult rv = NS_CopyUnicodeToNative(aStreamName, aRawStreamName);
    315 
    316  if (NS_FAILED(rv) || aStreamName.IsEmpty()) {
    317    return;
    318  }
    319 
    320  MonitorAutoLock mon(mMonitor);
    321  int r = InvokeCubeb(cubeb_stream_set_name, aRawStreamName.get());
    322  if (r && r != CUBEB_ERROR_NOT_SUPPORTED) {
    323    LOGE("Could not set cubeb stream name.");
    324  }
    325 }
    326 
    327 RefPtr<MediaSink::EndedPromise> AudioStream::Start() {
    328  TRACE("AudioStream::Start");
    329  MOZ_ASSERT(mState == INITIALIZED);
    330  mState = STARTED;
    331  RefPtr<MediaSink::EndedPromise> promise;
    332  {
    333    MonitorAutoLock mon(mMonitor);
    334    // As cubeb might call audio stream's state callback very soon after we
    335    // start cubeb, we have to create the promise beforehand in order to handle
    336    // the case where we immediately get `drained`.
    337    promise = mEndedPromise.Ensure(__func__);
    338    mPlaybackComplete = false;
    339 
    340    if (InvokeCubeb(cubeb_stream_start) != CUBEB_OK) {
    341      mState = ERRORED;
    342      mEndedPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
    343    }
    344 
    345    LOG("started, state %s", mState == STARTED   ? "STARTED"
    346                             : mState == DRAINED ? "DRAINED"
    347                                                 : "ERRORED");
    348  }
    349  return promise;
    350 }
    351 
    352 void AudioStream::Pause() {
    353  TRACE("AudioStream::Pause");
    354  MOZ_ASSERT(mState != INITIALIZED, "Must be Start()ed.");
    355  MOZ_ASSERT(mState != STOPPED, "Already Pause()ed.");
    356  MOZ_ASSERT(mState != SHUTDOWN, "Already ShutDown()ed.");
    357 
    358  // Do nothing if we are already drained or errored.
    359  if (mState == DRAINED || mState == ERRORED) {
    360    return;
    361  }
    362 
    363  MonitorAutoLock mon(mMonitor);
    364  if (InvokeCubeb(cubeb_stream_stop) != CUBEB_OK) {
    365    mState = ERRORED;
    366  } else if (mState != DRAINED && mState != ERRORED) {
    367    // Don't transition to other states if we are already
    368    // drained or errored.
    369    mState = STOPPED;
    370  }
    371 }
    372 
    373 void AudioStream::Resume() {
    374  TRACE("AudioStream::Resume");
    375  MOZ_ASSERT(mState != INITIALIZED, "Must be Start()ed.");
    376  MOZ_ASSERT(mState != STARTED, "Already Start()ed.");
    377  MOZ_ASSERT(mState != SHUTDOWN, "Already ShutDown()ed.");
    378 
    379  // Do nothing if we are already drained or errored.
    380  if (mState == DRAINED || mState == ERRORED) {
    381    return;
    382  }
    383 
    384  MonitorAutoLock mon(mMonitor);
    385  if (InvokeCubeb(cubeb_stream_start) != CUBEB_OK) {
    386    mState = ERRORED;
    387  } else if (mState != DRAINED && mState != ERRORED) {
    388    // Don't transition to other states if we are already
    389    // drained or errored.
    390    mState = STARTED;
    391  }
    392 }
    393 
    394 void AudioStream::ShutDown() {
    395  TRACE("AudioStream::ShutDown");
    396  LOG("ShutDown, state %d", mState.load());
    397 
    398  MonitorAutoLock mon(mMonitor);
    399  if (mCubebStream) {
    400    // Force stop to put the cubeb stream in a stable state before deletion.
    401    InvokeCubeb(cubeb_stream_stop);
    402    // Must not try to shut down cubeb from within the lock!  wasapi may still
    403    // call our callback after Pause()/stop()!?! Bug 996162
    404    cubeb_stream* cubeb = mCubebStream.release();
    405    MonitorAutoUnlock unlock(mMonitor);
    406    cubeb_stream_destroy(cubeb);
    407  }
    408 
    409  // After `cubeb_stream_stop` has been called, there is no audio thread
    410  // anymore. We can delete the time stretcher.
    411  if (mTimeStretcher) {
    412    delete mTimeStretcher;
    413    mTimeStretcher = nullptr;
    414  }
    415 
    416  mState = SHUTDOWN;
    417  mEndedPromise.ResolveIfExists(true, __func__);
    418 }
    419 
    420 int64_t AudioStream::GetPosition() {
    421  TRACE("AudioStream::GetPosition");
    422 #ifndef XP_MACOSX
    423  MonitorAutoLock mon(mMonitor);
    424 #endif
    425  int64_t frames = GetPositionInFramesUnlocked();
    426  return frames >= 0 ? mAudioClock.GetPosition(frames) : -1;
    427 }
    428 
    429 int64_t AudioStream::GetPositionInFrames() {
    430  TRACE("AudioStream::GetPositionInFrames");
    431 #ifndef XP_MACOSX
    432  MonitorAutoLock mon(mMonitor);
    433 #endif
    434  int64_t frames = GetPositionInFramesUnlocked();
    435 
    436  return frames >= 0 ? mAudioClock.GetPositionInFrames(frames) : -1;
    437 }
    438 
    439 int64_t AudioStream::GetPositionInFramesUnlocked() {
    440  TRACE("AudioStream::GetPositionInFramesUnlocked");
    441 #ifndef XP_MACOSX
    442  mMonitor.AssertCurrentThreadOwns();
    443 #endif
    444 
    445  if (mState == ERRORED) {
    446    return -1;
    447  }
    448 
    449  uint64_t position = 0;
    450  int rv;
    451 
    452 #ifndef XP_MACOSX
    453  rv = InvokeCubeb(cubeb_stream_get_position, &position);
    454 #else
    455  rv = cubeb_stream_get_position(mCubebStream.get(), &position);
    456 #endif
    457 
    458  if (rv != CUBEB_OK) {
    459    return -1;
    460  }
    461  return static_cast<int64_t>(std::min<uint64_t>(position, INT64_MAX));
    462 }
    463 
    464 bool AudioStream::IsValidAudioFormat(Chunk* aChunk) {
    465  if (aChunk->Rate() != mAudioClock.GetInputRate()) {
    466    LOGW("mismatched sample %u, mInRate=%u", aChunk->Rate(),
    467         mAudioClock.GetInputRate());
    468    return false;
    469  }
    470 
    471  return aChunk->Channels() <= 8;
    472 }
    473 
    474 void AudioStream::GetUnprocessed(AudioBufferWriter& aWriter) {
    475  TRACE("AudioStream::GetUnprocessed");
    476  AssertIsOnAudioThread();
    477  // Flush the timestretcher pipeline, if we were playing using a playback rate
    478  // other than 1.0.
    479  if (mTimeStretcher) {
    480    // Get number of samples and based on this either receive samples or write
    481    // silence.  At worst, the attacker can supply weird sound samples or
    482    // result in us writing silence.
    483    auto numSamples = mTimeStretcher->numSamples().unverified_safe_because(
    484        "We only use this to decide whether to receive samples or write "
    485        "silence.");
    486    if (numSamples) {
    487      RLBoxSoundTouch* timeStretcher = mTimeStretcher;
    488      aWriter.Write(
    489          [timeStretcher](AudioDataValue* aPtr, uint32_t aFrames) {
    490            return timeStretcher->receiveSamples(aPtr, aFrames);
    491          },
    492          aWriter.Available());
    493 
    494      // TODO: There might be still unprocessed samples in the stretcher.
    495      // We should either remove or flush them so they won't be in the output
    496      // next time we switch a playback rate other than 1.0.
    497      mTimeStretcher->numUnprocessedSamples().copy_and_verify([](auto samples) {
    498        NS_WARNING_ASSERTION(samples == 0, "no samples");
    499      });
    500    } else {
    501      // Don't need it anymore: playbackRate is 1.0, and the time stretcher has
    502      // been flushed.
    503      delete mTimeStretcher;
    504      mTimeStretcher = nullptr;
    505    }
    506  }
    507 
    508  while (aWriter.Available() > 0) {
    509    uint32_t count = mDataSource.PopFrames(aWriter.Ptr(), aWriter.Available(),
    510                                           mAudioThreadChanged);
    511    if (count == 0) {
    512      break;
    513    }
    514    aWriter.Advance(count);
    515  }
    516 }
    517 
    518 void AudioStream::GetTimeStretched(AudioBufferWriter& aWriter) {
    519  TRACE("AudioStream::GetTimeStretched");
    520  AssertIsOnAudioThread();
    521  if (EnsureTimeStretcherInitialized() != NS_OK) {
    522    return;
    523  }
    524 
    525  uint32_t toPopFrames =
    526      ceil(aWriter.Available() * mAudioClock.GetPlaybackRate());
    527 
    528  if (!mTimeStretcher) {
    529    return;
    530  }
    531 
    532  // At each iteration, get number of samples and (based on this) write from
    533  // the data source or silence. At worst, if the number of samples is a lie
    534  // (i.e., under attacker control) we'll either not write anything or keep
    535  // writing noise. This is safe because all the memory operations within the
    536  // loop (and after) are checked.
    537  while (mTimeStretcher->numSamples().unverified_safe_because(
    538             "Only used to decide whether to put samples.") <
    539         aWriter.Available()) {
    540    // pop into a temp buffer, and put into the stretcher.
    541    AutoTArray<AudioDataValue, 1000> buf;
    542    auto size = CheckedUint32(mOutChannels) * toPopFrames;
    543    if (!size.isValid()) {
    544      // The overflow should not happen in normal case.
    545      LOGW("Invalid member data: %d channels, %d frames", mOutChannels,
    546           toPopFrames);
    547      return;
    548    }
    549    buf.SetLength(size.value());
    550    // ensure no variable channel count or something like that
    551    uint32_t count =
    552        mDataSource.PopFrames(buf.Elements(), toPopFrames, mAudioThreadChanged);
    553    if (count == 0) {
    554      break;
    555    }
    556    mTimeStretcher->putSamples(buf.Elements(), count);
    557  }
    558 
    559  auto* timeStretcher = mTimeStretcher;
    560  aWriter.Write(
    561      [timeStretcher](AudioDataValue* aPtr, uint32_t aFrames) {
    562        return timeStretcher->receiveSamples(aPtr, aFrames);
    563      },
    564      aWriter.Available());
    565 }
    566 
    567 bool AudioStream::CheckThreadIdChanged() {
    568  ProfilerThreadId id = profiler_current_thread_id();
    569  if (id != mAudioThreadId) {
    570    mAudioThreadId = id;
    571    mAudioThreadChanged = true;
    572    return true;
    573  }
    574  mAudioThreadChanged = false;
    575  return false;
    576 }
    577 
    578 void AudioStream::AssertIsOnAudioThread() const {
    579  // This can be called right after CheckThreadIdChanged, because the audio
    580  // thread can change when not sandboxed.
    581  MOZ_ASSERT(mAudioThreadId.load() == profiler_current_thread_id());
    582 }
    583 
    584 void AudioStream::UpdatePlaybackRateIfNeeded() {
    585  AssertIsOnAudioThread();
    586  if (mAudioClock.GetPreservesPitch() == mPreservesPitch &&
    587      mAudioClock.GetPlaybackRate() == mPlaybackRate) {
    588    return;
    589  }
    590 
    591  EnsureTimeStretcherInitialized();
    592 
    593  mAudioClock.SetPlaybackRate(mPlaybackRate);
    594  mAudioClock.SetPreservesPitch(mPreservesPitch);
    595 
    596  if (!mTimeStretcher) {
    597    return;
    598  }
    599 
    600  if (mPreservesPitch) {
    601    mTimeStretcher->setTempo(mPlaybackRate);
    602    mTimeStretcher->setRate(1.0f);
    603  } else {
    604    mTimeStretcher->setTempo(1.0f);
    605    mTimeStretcher->setRate(mPlaybackRate);
    606  }
    607 }
    608 
    609 long AudioStream::DataCallback(void* aBuffer, long aFrames) {
    610  if (CheckThreadIdChanged() && !mSandboxed) {
    611    CallbackThreadRegistry::Get()->Register(mAudioThreadId,
    612                                            "NativeAudioCallback");
    613  }
    614  WebCore::DenormalDisabler disabler;
    615  if (!mCallbacksStarted) {
    616    mCallbacksStarted = true;
    617  }
    618 
    619  TRACE_AUDIO_CALLBACK_FRAME_COUNT("AudioStream real-time budget", aFrames,
    620                                   mAudioClock.GetInputRate());
    621  TRACE("AudioStream::DataCallback");
    622  MOZ_ASSERT(mState != SHUTDOWN, "No data callback after shutdown");
    623 
    624  if (SoftRealTimeLimitReached()) {
    625    DemoteThreadFromRealTime();
    626  }
    627 
    628  UpdatePlaybackRateIfNeeded();
    629 
    630  auto writer = AudioBufferWriter(
    631      Span<AudioDataValue>(reinterpret_cast<AudioDataValue*>(aBuffer),
    632                           mOutChannels * aFrames),
    633      mOutChannels, aFrames);
    634 
    635  if (mAudioClock.GetInputRate() == mAudioClock.GetOutputRate()) {
    636    GetUnprocessed(writer);
    637  } else {
    638    GetTimeStretched(writer);
    639  }
    640 
    641  // Always send audible frames first, and silent frames later.
    642  // Otherwise it will break the assumption of FrameHistory.
    643  if (!mDataSource.Ended()) {
    644 #ifndef XP_MACOSX
    645    MonitorAutoLock mon(mMonitor);
    646 #endif
    647    mAudioClock.UpdateFrameHistory(aFrames - writer.Available(),
    648                                   writer.Available(), mAudioThreadChanged);
    649    if (writer.Available() > 0) {
    650      TRACE_COMMENT("AudioStream::DataCallback", "Underrun: %d frames missing",
    651                    writer.Available());
    652      LOGW("lost %d frames", writer.Available());
    653      writer.WriteZeros(writer.Available());
    654    }
    655  } else {
    656    // No more new data in the data source, and the drain has completed. We
    657    // don't need the time stretcher anymore at this point.
    658    if (mTimeStretcher && writer.Available()) {
    659      delete mTimeStretcher;
    660      mTimeStretcher = nullptr;
    661    }
    662 #ifndef XP_MACOSX
    663    MonitorAutoLock mon(mMonitor);
    664 #endif
    665    mAudioClock.UpdateFrameHistory(aFrames - writer.Available(), 0,
    666                                   mAudioThreadChanged);
    667  }
    668 
    669  mDumpFile.Write(static_cast<const AudioDataValue*>(aBuffer),
    670                  aFrames * mOutChannels);
    671 
    672  if (!mSandboxed && writer.Available() != 0) {
    673    CallbackThreadRegistry::Get()->Unregister(mAudioThreadId);
    674  }
    675  return aFrames - writer.Available();
    676 }
    677 
    678 void AudioStream::StateCallback(cubeb_state aState) {
    679  MOZ_ASSERT(mState != SHUTDOWN, "No state callback after shutdown");
    680  LOG("StateCallback, mState=%d cubeb_state=%d", mState.load(), aState);
    681 
    682  MonitorAutoLock mon(mMonitor);
    683  if (aState == CUBEB_STATE_DRAINED) {
    684    LOG("Drained");
    685    mState = DRAINED;
    686    mPlaybackComplete = true;
    687    mEndedPromise.ResolveIfExists(true, __func__);
    688  } else if (aState == CUBEB_STATE_ERROR) {
    689    LOGE("StateCallback() state %d cubeb error", mState.load());
    690    mState = ERRORED;
    691    mPlaybackComplete = true;
    692    mEndedPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
    693  }
    694 }
    695 
    696 bool AudioStream::IsPlaybackCompleted() const { return mPlaybackComplete; }
    697 
    698 AudioClock::AudioClock(uint32_t aInRate)
    699    : mOutRate(aInRate),
    700      mInRate(aInRate),
    701      mPreservesPitch(true),
    702      mFrameHistory(new FrameHistory()) {}
    703 
    704 // Audio thread only
    705 void AudioClock::UpdateFrameHistory(uint32_t aServiced, uint32_t aUnderrun,
    706                                    bool aAudioThreadChanged) {
    707 #ifdef XP_MACOSX
    708  if (aAudioThreadChanged) {
    709    mCallbackInfoQueue.ResetProducerThreadId();
    710  }
    711  // Flush the local items, if any, and then attempt to enqueue the current
    712  // item. This is only a fallback mechanism, under non-critical load this is
    713  // just going to enqueue an item in the queue.
    714  while (!mAudioThreadCallbackInfo.IsEmpty()) {
    715    CallbackInfo& info = mAudioThreadCallbackInfo[0];
    716    // If still full, keep it audio-thread side for now.
    717    if (mCallbackInfoQueue.Enqueue(info) != 1) {
    718      break;
    719    }
    720    mAudioThreadCallbackInfo.RemoveElementAt(0);
    721  }
    722  CallbackInfo info(aServiced, aUnderrun, mOutRate);
    723  if (mCallbackInfoQueue.Enqueue(info) != 1) {
    724    NS_WARNING(
    725        "mCallbackInfoQueue full, storing the values in the audio thread.");
    726    mAudioThreadCallbackInfo.AppendElement(info);
    727  }
    728 #else
    729  MutexAutoLock lock(mMutex);
    730  mFrameHistory->Append(aServiced, aUnderrun, mOutRate);
    731 #endif
    732 }
    733 
    734 int64_t AudioClock::GetPositionInFrames(int64_t aFrames) {
    735  CheckedInt64 v = UsecsToFrames(GetPosition(aFrames), mInRate);
    736  return v.isValid() ? v.value() : -1;
    737 }
    738 
    739 int64_t AudioClock::GetPosition(int64_t frames) {
    740 #ifdef XP_MACOSX
    741  // Dequeue all history info, and apply them before returning the position
    742  // based on frame history.
    743  CallbackInfo info;
    744  while (mCallbackInfoQueue.Dequeue(&info, 1)) {
    745    mFrameHistory->Append(info.mServiced, info.mUnderrun, info.mOutputRate);
    746  }
    747 #else
    748  MutexAutoLock lock(mMutex);
    749 #endif
    750  return mFrameHistory->GetPosition(frames);
    751 }
    752 
    753 void AudioClock::SetPlaybackRate(double aPlaybackRate) {
    754  mOutRate = static_cast<uint32_t>(mInRate / aPlaybackRate);
    755 }
    756 
    757 double AudioClock::GetPlaybackRate() const {
    758  return static_cast<double>(mInRate) / mOutRate;
    759 }
    760 
    761 void AudioClock::SetPreservesPitch(bool aPreservesPitch) {
    762  mPreservesPitch = aPreservesPitch;
    763 }
    764 
    765 bool AudioClock::GetPreservesPitch() const { return mPreservesPitch; }
    766 
    767 #undef LOG
    768 #undef LOGW
    769 #undef LOGE
    770 
    771 }  // namespace mozilla