tor-browser

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

AudioInputSource.cpp (11365B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=2 et sw=2 tw=80: */
      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 https://mozilla.org/MPL/2.0/. */
      6 
      7 #include "AudioInputSource.h"
      8 
      9 #include "CallbackThreadRegistry.h"
     10 #include "GraphDriver.h"
     11 #include "Tracing.h"
     12 
     13 namespace mozilla {
     14 
     15 extern mozilla::LazyLogModule gMediaTrackGraphLog;
     16 
     17 #ifdef LOG_INTERNAL
     18 #  undef LOG_INTERNAL
     19 #endif  // LOG_INTERNAL
     20 #define LOG_INTERNAL(level, msg, ...) \
     21  MOZ_LOG(gMediaTrackGraphLog, LogLevel::level, (msg, ##__VA_ARGS__))
     22 
     23 #ifdef LOG
     24 #  undef LOG
     25 #endif  // LOG
     26 #define LOG(msg, ...) LOG_INTERNAL(Debug, msg, ##__VA_ARGS__)
     27 
     28 #ifdef LOGW
     29 #  undef LOGW
     30 #endif  // LOGW
     31 #define LOGW(msg, ...) LOG_INTERNAL(Warning, msg, ##__VA_ARGS__)
     32 
     33 #ifdef LOGE
     34 #  undef LOGE
     35 #endif  // LOGE
     36 #define LOGE(msg, ...) LOG_INTERNAL(Error, msg, ##__VA_ARGS__)
     37 
     38 #ifdef LOGV
     39 #  undef LOGV
     40 #endif  // LOGV
     41 #define LOGV(msg, ...) LOG_INTERNAL(Verbose, msg, ##__VA_ARGS__)
     42 
     43 AudioInputSource::AudioInputSource(RefPtr<EventListener>&& aListener,
     44                                   Id aSourceId,
     45                                   CubebUtils::AudioDeviceID aDeviceId,
     46                                   uint32_t aChannelCount, bool aIsVoice,
     47                                   const PrincipalHandle& aPrincipalHandle,
     48                                   TrackRate aSourceRate, TrackRate aTargetRate)
     49    : mId(aSourceId),
     50      mDeviceId(aDeviceId),
     51      mChannelCount(aChannelCount),
     52      mRate(aSourceRate),
     53      mIsVoice(aIsVoice),
     54      mPrincipalHandle(aPrincipalHandle),
     55      mSandboxed(CubebUtils::SandboxEnabled()),
     56      mAudioThreadId(ProfilerThreadId{}),
     57      mEventListener(std::move(aListener)),
     58      mTaskThread(CubebUtils::GetCubebOperationThread()),
     59      mDriftCorrector(static_cast<uint32_t>(aSourceRate),
     60                      static_cast<uint32_t>(aTargetRate), aPrincipalHandle) {
     61  MOZ_ASSERT(mChannelCount > 0);
     62  MOZ_ASSERT(mEventListener);
     63 }
     64 
     65 void AudioInputSource::Init() {
     66  // This is called on MediaTrackGraph's graph thread, which can be the cubeb
     67  // stream's callback thread. Running cubeb operations within cubeb stream
     68  // callback thread can cause the deadlock on Linux, so we dispatch those
     69  // operations to the task thread.
     70  MOZ_ASSERT(mTaskThread);
     71 
     72  LOG("AudioInputSource %p, init", this);
     73  MOZ_ALWAYS_SUCCEEDS(mTaskThread->Dispatch(
     74      NS_NewRunnableFunction(__func__, [this, self = RefPtr(this)]() mutable {
     75        mStream = CubebInputStream::Create(mDeviceId, mChannelCount,
     76                                           static_cast<uint32_t>(mRate),
     77                                           mIsVoice, this);
     78        if (!mStream) {
     79          LOGE("AudioInputSource %p, cannot create an audio input stream!",
     80               self.get());
     81          return;
     82        }
     83      })));
     84 }
     85 
     86 void AudioInputSource::Start() {
     87  // This is called on MediaTrackGraph's graph thread, which can be the cubeb
     88  // stream's callback thread. Running cubeb operations within cubeb stream
     89  // callback thread can cause the deadlock on Linux, so we dispatch those
     90  // operations to the task thread.
     91  MOZ_ASSERT(mTaskThread);
     92 
     93  LOG("AudioInputSource %p, start", this);
     94  MOZ_ALWAYS_SUCCEEDS(mTaskThread->Dispatch(
     95      NS_NewRunnableFunction(__func__, [this, self = RefPtr(this)]() mutable {
     96        if (!mStream) {
     97          LOGE("AudioInputSource %p, no audio input stream!", self.get());
     98          return;
     99        }
    100 
    101        if (uint32_t latency = 0; mStream->Latency(&latency) == CUBEB_OK) {
    102          Data data(LatencyChangeData{media::TimeUnit(latency, mRate)});
    103          if (mSPSCQueue.Enqueue(data) == 0) {
    104            LOGE("AudioInputSource %p, failed to enqueue latency change",
    105                 self.get());
    106          }
    107        }
    108        if (int r = mStream->Start(); r != CUBEB_OK) {
    109          LOGE(
    110              "AudioInputSource %p, cannot start its audio input stream! The "
    111              "stream is destroyed directly!",
    112              self.get());
    113          mStream = nullptr;
    114          mConfiguredProcessingParams = CUBEB_INPUT_PROCESSING_PARAM_NONE;
    115        }
    116      })));
    117 }
    118 
    119 void AudioInputSource::Stop() {
    120  // This is called on MediaTrackGraph's graph thread, which can be the cubeb
    121  // stream's callback thread. Running cubeb operations within cubeb stream
    122  // callback thread can cause the deadlock on Linux, so we dispatch those
    123  // operations to the task thread.
    124  MOZ_ASSERT(mTaskThread);
    125 
    126  LOG("AudioInputSource %p, stop", this);
    127  MOZ_ALWAYS_SUCCEEDS(mTaskThread->Dispatch(
    128      NS_NewRunnableFunction(__func__, [this, self = RefPtr(this)]() mutable {
    129        if (!mStream) {
    130          LOGE("AudioInputSource %p, has no audio input stream to stop!",
    131               self.get());
    132          return;
    133        }
    134        if (int r = mStream->Stop(); r != CUBEB_OK) {
    135          LOGE(
    136              "AudioInputSource %p, cannot stop its audio input stream! The "
    137              "stream is going to be destroyed forcefully",
    138              self.get());
    139        }
    140        mStream = nullptr;
    141        mConfiguredProcessingParams = CUBEB_INPUT_PROCESSING_PARAM_NONE;
    142      })));
    143 }
    144 
    145 auto AudioInputSource::SetRequestedProcessingParams(
    146    cubeb_input_processing_params aParams)
    147    -> RefPtr<SetRequestedProcessingParamsPromise> {
    148  // This is called on MediaTrackGraph's graph thread, which can be the cubeb
    149  // stream's callback thread. Running cubeb operations within cubeb stream
    150  // callback thread can cause the deadlock on Linux, so we dispatch those
    151  // operations to the task thread.
    152  MOZ_ASSERT(mTaskThread);
    153 
    154  LOG("AudioInputSource %p, SetProcessingParams(%s)", this,
    155      CubebUtils::ProcessingParamsToString(aParams).get());
    156  using ProcessingPromise = SetRequestedProcessingParamsPromise;
    157  MozPromiseHolder<ProcessingPromise> holder;
    158  RefPtr<ProcessingPromise> p = holder.Ensure(__func__);
    159  MOZ_ALWAYS_SUCCEEDS(mTaskThread->Dispatch(NS_NewRunnableFunction(
    160      __func__, [this, self = RefPtr(this), holder = std::move(holder),
    161                 aParams]() mutable {
    162        if (!mStream) {
    163          LOGE(
    164              "AudioInputSource %p, has no audio input stream to set "
    165              "processing params on!",
    166              this);
    167          holder.Reject(CUBEB_ERROR,
    168                        "AudioInputSource::SetProcessingParams no stream");
    169          return;
    170        }
    171        cubeb_input_processing_params supportedParams;
    172        auto handle = CubebUtils::GetCubeb();
    173        int r = cubeb_get_supported_input_processing_params(handle->Context(),
    174                                                            &supportedParams);
    175        if (r != CUBEB_OK) {
    176          holder.Reject(CUBEB_ERROR_NOT_SUPPORTED,
    177                        "AudioInputSource::SetProcessingParams");
    178          return;
    179        }
    180        aParams &= supportedParams;
    181        if (aParams == mConfiguredProcessingParams) {
    182          holder.Resolve(aParams, "AudioInputSource::SetProcessingParams");
    183          return;
    184        }
    185        mConfiguredProcessingParams = aParams;
    186        r = mStream->SetProcessingParams(aParams);
    187        if (r == CUBEB_OK) {
    188          holder.Resolve(aParams, "AudioInputSource::SetProcessingParams");
    189          return;
    190        }
    191        holder.Reject(r, "AudioInputSource::SetProcessingParams");
    192      })));
    193  return p;
    194 }
    195 
    196 AudioSegment AudioInputSource::GetAudioSegment(TrackTime aDuration,
    197                                               Consumer aConsumer) {
    198  if (aConsumer == Consumer::Changed) {
    199    // Reset queue's consumer thread to acquire its mReadIndex on the new
    200    // thread.
    201    mSPSCQueue.ResetConsumerThreadId();
    202  }
    203 
    204  AudioSegment raw;
    205  Maybe<media::TimeUnit> latency;
    206  while (mSPSCQueue.AvailableRead()) {
    207    Data data;
    208    DebugOnly<int> reads = mSPSCQueue.Dequeue(&data, 1);
    209    MOZ_ASSERT(reads);
    210    MOZ_ASSERT(!data.is<Empty>());
    211    if (data.is<AudioChunk>()) {
    212      raw.AppendAndConsumeChunk(std::move(data.as<AudioChunk>()));
    213    } else if (data.is<LatencyChangeData>()) {
    214      latency = Some(data.as<LatencyChangeData>().mLatency);
    215    }
    216  }
    217 
    218  if (latency) {
    219    mDriftCorrector.SetSourceLatency(*latency);
    220  }
    221  return mDriftCorrector.RequestFrames(raw, static_cast<uint32_t>(aDuration));
    222 }
    223 
    224 long AudioInputSource::DataCallback(const void* aBuffer, long aFrames) {
    225  TRACE_AUDIO_CALLBACK_FRAME_COUNT("AudioInputSource real-time budget", aFrames,
    226                                   mRate);
    227  TRACE("AudioInputSource::DataCallback");
    228 
    229  const AudioDataValue* source =
    230      reinterpret_cast<const AudioDataValue*>(aBuffer);
    231 
    232  AudioChunk c = AudioChunk::FromInterleavedBuffer(
    233      source, static_cast<size_t>(aFrames), mChannelCount, mPrincipalHandle);
    234 
    235  // Reset queue's producer to avoid hitting the assertion for checking the
    236  // consistency of mSPSCQueue's mProducerId in Enqueue. This can happen when:
    237  // 1) cubeb stream is reinitialized behind the scenes for the device changed
    238  // events, e.g., users plug/unplug a TRRS mic into/from the built-in jack port
    239  // of some old macbooks.
    240  // 2) After Start() to Stop() cycle finishes, user call Start() again.
    241  if (CheckThreadIdChanged()) {
    242    mSPSCQueue.ResetProducerThreadId();
    243    if (!mSandboxed) {
    244      CallbackThreadRegistry::Get()->Register(mAudioThreadId,
    245                                              "NativeAudioCallback");
    246    }
    247  }
    248 
    249  Data data(std::move(c));
    250  int writes = mSPSCQueue.Enqueue(data);
    251  if (writes == 0) {
    252    LOGW("AudioInputSource %p, buffer is full. Dropping %ld frames", this,
    253         aFrames);
    254  } else {
    255    LOGV("AudioInputSource %p, enqueue %ld frames (%d AudioChunks)", this,
    256         aFrames, writes);
    257  }
    258  return aFrames;
    259 }
    260 
    261 void AudioInputSource::StateCallback(cubeb_state aState) {
    262  EventListener::State state;
    263  if (aState == CUBEB_STATE_STARTED) {
    264    LOG("AudioInputSource %p, stream started", this);
    265    state = EventListener::State::Started;
    266  } else if (aState == CUBEB_STATE_STOPPED) {
    267    LOG("AudioInputSource %p, stream stopped", this);
    268    state = EventListener::State::Stopped;
    269  } else if (aState == CUBEB_STATE_DRAINED) {
    270    LOG("AudioInputSource %p, stream is drained", this);
    271    state = EventListener::State::Drained;
    272  } else {
    273    MOZ_ASSERT(aState == CUBEB_STATE_ERROR);
    274    LOG("AudioInputSource %p, error happend", this);
    275    state = EventListener::State::Error;
    276  }
    277  // This can be called on any thread, so we forward the event to main thread
    278  // first.
    279  NS_DispatchToMainThread(
    280      NS_NewRunnableFunction(__func__, [self = RefPtr(this), s = state] {
    281        self->mEventListener->AudioStateCallback(self->mId, s);
    282      }));
    283 }
    284 
    285 void AudioInputSource::DeviceChangedCallback() {
    286  LOG("AudioInputSource %p, device changed", this);
    287  // This can be called on any thread, so we forward the event to main thread
    288  // first.
    289  NS_DispatchToMainThread(
    290      NS_NewRunnableFunction(__func__, [self = RefPtr(this)] {
    291        self->mEventListener->AudioDeviceChanged(self->mId);
    292      }));
    293 }
    294 
    295 bool AudioInputSource::CheckThreadIdChanged() {
    296  ProfilerThreadId id = profiler_current_thread_id();
    297  if (id != mAudioThreadId) {
    298    mAudioThreadId = id;
    299    return true;
    300  }
    301  return false;
    302 }
    303 
    304 #undef LOG_INTERNAL
    305 #undef LOG
    306 #undef LOGW
    307 #undef LOGE
    308 #undef LOGV
    309 
    310 }  // namespace mozilla