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/. */
7 #include "AudioInputSource.h"
9 #include "CallbackThreadRegistry.h"
10 #include "GraphDriver.h"
15 extern mozilla::LazyLogModule gMediaTrackGraphLog
;
19 #endif // LOG_INTERNAL
20 #define LOG_INTERNAL(level, msg, ...) \
21 MOZ_LOG(gMediaTrackGraphLog, LogLevel::level, (msg, ##__VA_ARGS__))
26 #define LOG(msg, ...) LOG_INTERNAL(Debug, msg, ##__VA_ARGS__)
31 #define LOGW(msg, ...) LOG_INTERNAL(Warning, msg, ##__VA_ARGS__)
36 #define LOGE(msg, ...) LOG_INTERNAL(Error, msg, ##__VA_ARGS__)
41 #define LOGV(msg, ...) LOG_INTERNAL(Verbose, msg, ##__VA_ARGS__)
43 AudioInputSource::AudioInputSource(RefPtr
<EventListener
>&& aListener
,
45 CubebUtils::AudioDeviceID aDeviceId
,
46 uint32_t aChannelCount
, bool aIsVoice
,
47 const PrincipalHandle
& aPrincipalHandle
,
48 TrackRate aSourceRate
, TrackRate aTargetRate
)
51 mChannelCount(aChannelCount
),
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
);
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
);
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
),
79 LOGE("AudioInputSource %p, cannot create an audio input stream!",
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
);
93 LOG("AudioInputSource %p, start", this);
94 MOZ_ALWAYS_SUCCEEDS(mTaskThread
->Dispatch(
95 NS_NewRunnableFunction(__func__
, [this, self
= RefPtr(this)]() mutable {
97 LOGE("AudioInputSource %p, no audio input stream!", self
.get());
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",
108 if (int r
= mStream
->Start(); r
!= CUBEB_OK
) {
110 "AudioInputSource %p, cannot start its audio input stream! The "
111 "stream is destroyed directly!",
114 mConfiguredProcessingParams
= CUBEB_INPUT_PROCESSING_PARAM_NONE
;
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
);
126 LOG("AudioInputSource %p, stop", this);
127 MOZ_ALWAYS_SUCCEEDS(mTaskThread
->Dispatch(
128 NS_NewRunnableFunction(__func__
, [this, self
= RefPtr(this)]() mutable {
130 LOGE("AudioInputSource %p, has no audio input stream to stop!",
134 if (int r
= mStream
->Stop(); r
!= CUBEB_OK
) {
136 "AudioInputSource %p, cannot stop its audio input stream! The "
137 "stream is going to be destroyed forcefully",
141 mConfiguredProcessingParams
= CUBEB_INPUT_PROCESSING_PARAM_NONE
;
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
);
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
),
164 "AudioInputSource %p, has no audio input stream to set "
165 "processing params on!",
167 holder
.Reject(CUBEB_ERROR
,
168 "AudioInputSource::SetProcessingParams no stream");
171 cubeb_input_processing_params supportedParams
;
172 auto handle
= CubebUtils::GetCubeb();
173 int r
= cubeb_get_supported_input_processing_params(handle
->Context(),
176 holder
.Reject(CUBEB_ERROR_NOT_SUPPORTED
,
177 "AudioInputSource::SetProcessingParams");
180 aParams
&= supportedParams
;
181 if (aParams
== mConfiguredProcessingParams
) {
182 holder
.Resolve(aParams
, "AudioInputSource::SetProcessingParams");
185 mConfiguredProcessingParams
= aParams
;
186 r
= mStream
->SetProcessingParams(aParams
);
188 holder
.Resolve(aParams
, "AudioInputSource::SetProcessingParams");
191 holder
.Reject(r
, "AudioInputSource::SetProcessingParams");
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
201 mSPSCQueue
.ResetConsumerThreadId();
205 Maybe
<media::TimeUnit
> latency
;
206 while (mSPSCQueue
.AvailableRead()) {
208 DebugOnly
<int> reads
= mSPSCQueue
.Dequeue(&data
, 1);
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
);
219 mDriftCorrector
.SetSourceLatency(*latency
);
221 return mDriftCorrector
.RequestFrames(raw
, static_cast<uint32_t>(aDuration
));
224 long AudioInputSource::DataCallback(const void* aBuffer
, long aFrames
) {
225 TRACE_AUDIO_CALLBACK_FRAME_COUNT("AudioInputSource real-time budget", aFrames
,
227 TRACE("AudioInputSource::DataCallback");
229 const AudioDataValue
* source
=
230 reinterpret_cast<const AudioDataValue
*>(aBuffer
);
232 AudioChunk c
= AudioChunk::FromInterleavedBuffer(
233 source
, static_cast<size_t>(aFrames
), mChannelCount
, mPrincipalHandle
);
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();
244 CallbackThreadRegistry::Get()->Register(mAudioThreadId
,
245 "NativeAudioCallback");
250 int writes
= mSPSCQueue
.Enqueue(data
);
252 LOGW("AudioInputSource %p, buffer is full. Dropping %ld frames", this,
255 LOGV("AudioInputSource %p, enqueue %ld frames (%d AudioChunks)", this,
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
;
273 MOZ_ASSERT(aState
== CUBEB_STATE_ERROR
);
274 LOG("AudioInputSource %p, error happend", this);
275 state
= EventListener::State::Error
;
277 // This can be called on any thread, so we forward the event to main thread
279 NS_DispatchToMainThread(
280 NS_NewRunnableFunction(__func__
, [self
= RefPtr(this), s
= state
] {
281 self
->mEventListener
->AudioStateCallback(self
->mId
, s
);
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
289 NS_DispatchToMainThread(
290 NS_NewRunnableFunction(__func__
, [self
= RefPtr(this)] {
291 self
->mEventListener
->AudioDeviceChanged(self
->mId
);
295 bool AudioInputSource::CheckThreadIdChanged() {
296 ProfilerThreadId id
= profiler_current_thread_id();
297 if (id
!= mAudioThreadId
) {
310 } // namespace mozilla