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