summaryrefslogtreecommitdiffstats
path: root/dom/media/AudioInputSource.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/media/AudioInputSource.cpp222
1 files changed, 222 insertions, 0 deletions
diff --git a/dom/media/AudioInputSource.cpp b/dom/media/AudioInputSource.cpp
new file mode 100644
index 0000000000..15d35bb373
--- /dev/null
+++ b/dom/media/AudioInputSource.cpp
@@ -0,0 +1,222 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "AudioInputSource.h"
+
+#include "CallbackThreadRegistry.h"
+#include "GraphDriver.h"
+
+namespace mozilla {
+
+extern mozilla::LazyLogModule gMediaTrackGraphLog;
+
+#ifdef LOG_INTERNAL
+# undef LOG_INTERNAL
+#endif // LOG_INTERNAL
+#define LOG_INTERNAL(level, msg, ...) \
+ MOZ_LOG(gMediaTrackGraphLog, LogLevel::level, (msg, ##__VA_ARGS__))
+
+#ifdef LOG
+# undef LOG
+#endif // LOG
+#define LOG(msg, ...) LOG_INTERNAL(Debug, msg, ##__VA_ARGS__)
+
+#ifdef LOGW
+# undef LOGW
+#endif // LOGW
+#define LOGW(msg, ...) LOG_INTERNAL(Warning, msg, ##__VA_ARGS__)
+
+#ifdef LOGE
+# undef LOGE
+#endif // LOGE
+#define LOGE(msg, ...) LOG_INTERNAL(Error, msg, ##__VA_ARGS__)
+
+#ifdef LOGV
+# undef LOGV
+#endif // LOGV
+#define LOGV(msg, ...) LOG_INTERNAL(Verbose, msg, ##__VA_ARGS__)
+
+AudioInputSource::AudioInputSource(RefPtr<EventListener>&& aListener,
+ Id aSourceId,
+ CubebUtils::AudioDeviceID aDeviceId,
+ uint32_t aChannelCount, bool aIsVoice,
+ const PrincipalHandle& aPrincipalHandle,
+ TrackRate aSourceRate, TrackRate aTargetRate,
+ uint32_t aBufferMs)
+ : mId(aSourceId),
+ mDeviceId(aDeviceId),
+ mChannelCount(aChannelCount),
+ mRate(aSourceRate),
+ mIsVoice(aIsVoice),
+ mPrincipalHandle(aPrincipalHandle),
+ mSandboxed(CubebUtils::SandboxEnabled()),
+ mAudioThreadId(ProfilerThreadId{}),
+ mEventListener(std::move(aListener)),
+ mTaskThread(CUBEB_TASK_THREAD),
+ mDriftCorrector(static_cast<uint32_t>(aSourceRate),
+ static_cast<uint32_t>(aTargetRate), aBufferMs,
+ aPrincipalHandle) {
+ MOZ_ASSERT(mChannelCount > 0);
+ MOZ_ASSERT(mEventListener);
+}
+
+void AudioInputSource::Start() {
+ // This is called on MediaTrackGraph's graph thread, which can be the cubeb
+ // stream's callback thread. Running cubeb operations within cubeb stream
+ // callback thread can cause the deadlock on Linux, so we dispatch those
+ // operations to the task thread.
+ MOZ_ASSERT(mTaskThread);
+
+ // mSPSCQueue will have a new consumer.
+ mSPSCQueue.ResetConsumerThreadId();
+
+ LOG("AudioInputSource %p, start", this);
+ MOZ_ALWAYS_SUCCEEDS(mTaskThread->Dispatch(
+ NS_NewRunnableFunction(__func__, [self = RefPtr(this)]() mutable {
+ self->mStream = CubebInputStream::Create(
+ self->mDeviceId, self->mChannelCount,
+ static_cast<uint32_t>(self->mRate), self->mIsVoice, self.get());
+ if (!self->mStream) {
+ LOGE("AudioInputSource %p, cannot create an audio input stream!",
+ self.get());
+ return;
+ }
+ if (int r = self->mStream->Start(); r != CUBEB_OK) {
+ LOGE(
+ "AudioInputSource %p, cannot start its audio input stream! The "
+ "stream is destroyed directly!",
+ self.get());
+ self->mStream = nullptr;
+ }
+ })));
+}
+
+void AudioInputSource::Stop() {
+ // This is called on MediaTrackGraph's graph thread, which can be the cubeb
+ // stream's callback thread. Running cubeb operations within cubeb stream
+ // callback thread can cause the deadlock on Linux, so we dispatch those
+ // operations to the task thread.
+ MOZ_ASSERT(mTaskThread);
+
+ LOG("AudioInputSource %p, stop", this);
+ MOZ_ALWAYS_SUCCEEDS(mTaskThread->Dispatch(
+ NS_NewRunnableFunction(__func__, [self = RefPtr(this)]() mutable {
+ if (!self->mStream) {
+ LOGE("AudioInputSource %p, has no audio input stream to stop!",
+ self.get());
+ return;
+ }
+ if (int r = self->mStream->Stop(); r != CUBEB_OK) {
+ LOGE(
+ "AudioInputSource %p, cannot stop its audio input stream! The "
+ "stream is going to be destroyed forcefully",
+ self.get());
+ }
+ self->mStream = nullptr;
+ })));
+}
+
+AudioSegment AudioInputSource::GetAudioSegment(TrackTime aDuration,
+ Consumer aConsumer) {
+ if (aConsumer == Consumer::Changed) {
+ // Reset queue's consumer to avoid hitting the assertion for checking the
+ // consistency of mSPSCQueue's mConsumerId in Dequeue.
+ mSPSCQueue.ResetConsumerThreadId();
+ }
+
+ AudioSegment raw;
+ while (mSPSCQueue.AvailableRead()) {
+ AudioChunk chunk;
+ DebugOnly<int> reads = mSPSCQueue.Dequeue(&chunk, 1);
+ MOZ_ASSERT(reads);
+ raw.AppendAndConsumeChunk(std::move(chunk));
+ }
+
+ return mDriftCorrector.RequestFrames(raw, static_cast<uint32_t>(aDuration));
+}
+
+long AudioInputSource::DataCallback(const void* aBuffer, long aFrames) {
+ const AudioDataValue* source =
+ reinterpret_cast<const AudioDataValue*>(aBuffer);
+
+ AudioChunk c = AudioChunk::FromInterleavedBuffer(
+ source, static_cast<size_t>(aFrames), mChannelCount, mPrincipalHandle);
+
+ // Reset queue's producer to avoid hitting the assertion for checking the
+ // consistency of mSPSCQueue's mProducerId in Enqueue. This can happen when:
+ // 1) cubeb stream is reinitialized behind the scenes for the device changed
+ // events, e.g., users plug/unplug a TRRS mic into/from the built-in jack port
+ // of some old macbooks.
+ // 2) After Start() to Stop() cycle finishes, user call Start() again.
+ if (CheckThreadIdChanged()) {
+ mSPSCQueue.ResetProducerThreadId();
+ if (!mSandboxed) {
+ CallbackThreadRegistry::Get()->Register(mAudioThreadId,
+ "NativeAudioCallback");
+ }
+ }
+
+ int writes = mSPSCQueue.Enqueue(c);
+ if (writes == 0) {
+ LOGW("AudioInputSource %p, buffer is full. Dropping %ld frames", this,
+ aFrames);
+ } else {
+ LOGV("AudioInputSource %p, enqueue %ld frames (%d AudioChunks)", this,
+ aFrames, writes);
+ }
+ return aFrames;
+}
+
+void AudioInputSource::StateCallback(cubeb_state aState) {
+ EventListener::State state;
+ if (aState == CUBEB_STATE_STARTED) {
+ LOG("AudioInputSource %p, stream started", this);
+ state = EventListener::State::Started;
+ } else if (aState == CUBEB_STATE_STOPPED) {
+ LOG("AudioInputSource %p, stream stopped", this);
+ state = EventListener::State::Stopped;
+ } else if (aState == CUBEB_STATE_DRAINED) {
+ LOG("AudioInputSource %p, stream is drained", this);
+ state = EventListener::State::Drained;
+ } else {
+ MOZ_ASSERT(aState == CUBEB_STATE_ERROR);
+ LOG("AudioInputSource %p, error happend", this);
+ state = EventListener::State::Error;
+ }
+ // This can be called on any thread, so we forward the event to main thread
+ // first.
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction(__func__, [self = RefPtr(this), s = state] {
+ self->mEventListener->AudioStateCallback(self->mId, s);
+ }));
+}
+
+void AudioInputSource::DeviceChangedCallback() {
+ LOG("AudioInputSource %p, device changed", this);
+ // This can be called on any thread, so we forward the event to main thread
+ // first.
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction(__func__, [self = RefPtr(this)] {
+ self->mEventListener->AudioDeviceChanged(self->mId);
+ }));
+}
+
+bool AudioInputSource::CheckThreadIdChanged() {
+ ProfilerThreadId id = profiler_current_thread_id();
+ if (id != mAudioThreadId) {
+ mAudioThreadId = id;
+ return true;
+ }
+ return false;
+}
+
+#undef LOG_INTERNAL
+#undef LOG
+#undef LOGW
+#undef LOGE
+#undef LOGV
+
+} // namespace mozilla