NS_IMETHOD Run() { char aLocal; STREAM_LOG(LogLevel::Debug, ("Starting system thread")); profiler_register_thread("MediaStreamGraph", &aLocal); LIFECYCLE_LOG("Starting a new system driver for graph %p\n", mDriver->mGraphImpl); if (mDriver->mPreviousDriver) { LIFECYCLE_LOG("%p releasing an AudioCallbackDriver(%p), for graph %p\n", mDriver, mDriver->mPreviousDriver.get(), mDriver->GraphImpl()); MOZ_ASSERT(!mDriver->AsAudioCallbackDriver()); // Stop and release the previous driver off-main-thread, but only if we're // not in the situation where we've fallen back to a system clock driver // because the osx audio stack is currently switching output device. if (!mDriver->mPreviousDriver->AsAudioCallbackDriver()->IsSwitchingDevice()) { RefPtr<AsyncCubebTask> releaseEvent = new AsyncCubebTask(mDriver->mPreviousDriver->AsAudioCallbackDriver(), AsyncCubebOperation::SHUTDOWN); mDriver->mPreviousDriver = nullptr; releaseEvent->Dispatch(); } } else { MonitorAutoLock mon(mDriver->mGraphImpl->GetMonitor()); MOZ_ASSERT(mDriver->mGraphImpl->MessagesQueued(), "Don't start a graph without messages queued."); mDriver->mGraphImpl->SwapMessageQueues(); } mDriver->RunThread(); return NS_OK; }
NS_IMETHOD Run() { char aLocal; STREAM_LOG(PR_LOG_DEBUG, ("Starting system thread")); profiler_register_thread("MediaStreamGraph", &aLocal); LIFECYCLE_LOG("Starting a new system driver for graph %p\n", mDriver->mGraphImpl); if (mDriver->mPreviousDriver) { LIFECYCLE_LOG("%p releasing an AudioCallbackDriver(%p), for graph %p\n", mDriver, mDriver->mPreviousDriver.get(), mDriver->GraphImpl()); MOZ_ASSERT(!mDriver->AsAudioCallbackDriver()); // Stop and release the previous driver off-main-thread. nsRefPtr<AsyncCubebTask> releaseEvent = new AsyncCubebTask(mDriver->mPreviousDriver->AsAudioCallbackDriver(), AsyncCubebTask::SHUTDOWN); mDriver->mPreviousDriver = nullptr; releaseEvent->Dispatch(); } else { MonitorAutoLock mon(mDriver->mGraphImpl->GetMonitor()); MOZ_ASSERT(mDriver->mGraphImpl->MessagesQueued(), "Don't start a graph without messages queued."); mDriver->mGraphImpl->SwapMessageQueues(); } mDriver->RunThread(); return NS_OK; }
NS_IMETHODIMP AsyncCubebTask::Run() { MOZ_ASSERT(mThread); if (NS_IsMainThread()) { mThread->Shutdown(); // can't shutdown from the thread itself, darn // don't null out mThread! // See bug 999104. we must hold a ref to the thread across Dispatch() // since the internal mthread ref could be released while processing // the Dispatch(), and Dispatch/PutEvent itself doesn't hold a ref; it // assumes the caller does. return NS_OK; } MOZ_ASSERT(mDriver); switch(mOperation) { case AsyncCubebOperation::INIT: LIFECYCLE_LOG("AsyncCubebOperation::INIT\n"); mDriver->Init(); break; case AsyncCubebOperation::SHUTDOWN: LIFECYCLE_LOG("AsyncCubebOperation::SHUTDOWN\n"); mDriver->Stop(); mDriver = nullptr; mShutdownGrip = nullptr; break; case AsyncCubebOperation::SLEEP: { { LIFECYCLE_LOG("AsyncCubebOperation::SLEEP\n"); MonitorAutoLock mon(mDriver->mGraphImpl->GetMonitor()); // We might just have been awoken if (mDriver->mNeedAnotherIteration) { mDriver->mPauseRequested = false; mDriver->mWaitState = AudioCallbackDriver::WAITSTATE_RUNNING; break; } mDriver->Stop(); mDriver->mWaitState = AudioCallbackDriver::WAITSTATE_WAITING_INDEFINITELY; mDriver->mPauseRequested = false; mDriver->mGraphImpl->GetMonitor().Wait(PR_INTERVAL_NO_TIMEOUT); } STREAM_LOG(PR_LOG_DEBUG, ("Restarting audio stream from sleep.")); mDriver->StartStream(); break; } default: MOZ_CRASH("Operation not implemented."); } // and now kill this thread NS_DispatchToMainThread(this); return NS_OK; }
void ThreadedDriver::Start() { LIFECYCLE_LOG("Starting thread for a SystemClockDriver %p\n", mGraphImpl); nsCOMPtr<nsIRunnable> event = new MediaStreamGraphInitThreadRunnable(this); NS_NewNamedThread("MediaStreamGrph", getter_AddRefs(mThread), event); }
void GraphDriver::Shutdown() { if (AsAudioCallbackDriver()) { LIFECYCLE_LOG("Releasing audio driver off main thread (GraphDriver::Shutdown).\n"); nsRefPtr<AsyncCubebTask> releaseEvent = new AsyncCubebTask(AsAudioCallbackDriver(), AsyncCubebTask::SHUTDOWN); releaseEvent->Dispatch(); } }
NS_IMETHODIMP AsyncCubebTask::Run() { MOZ_ASSERT(mThread); if (NS_IsMainThread()) { mThread->Shutdown(); // can't shutdown from the thread itself, darn // don't null out mThread! // See bug 999104. we must hold a ref to the thread across Dispatch() // since the internal mthread ref could be released while processing // the Dispatch(), and Dispatch/PutEvent itself doesn't hold a ref; it // assumes the caller does. return NS_OK; } MOZ_ASSERT(mDriver); switch(mOperation) { case AsyncCubebOperation::INIT: { LIFECYCLE_LOG("AsyncCubebOperation::INIT\n"); mDriver->Init(); mDriver->CompleteAudioContextOperations(mOperation); break; } case AsyncCubebOperation::SHUTDOWN: { LIFECYCLE_LOG("AsyncCubebOperation::SHUTDOWN\n"); mDriver->Stop(); mDriver->CompleteAudioContextOperations(mOperation); mDriver = nullptr; mShutdownGrip = nullptr; break; } default: MOZ_CRASH("Operation not implemented."); } // and now kill this thread NS_DispatchToMainThread(this); return NS_OK; }
void ThreadedDriver::Start() { LIFECYCLE_LOG("Starting thread for a SystemClockDriver %p\n", mGraphImpl); nsCOMPtr<nsIRunnable> event = new MediaStreamGraphInitThreadRunnable(this); // Note: mThread may be null during event->Run() if we pass to NewNamedThread! See AudioInitTask nsresult rv = NS_NewNamedThread("MediaStreamGrph", getter_AddRefs(mThread)); if (NS_SUCCEEDED(rv)) { mThread->Dispatch(event, NS_DISPATCH_NORMAL); } }
void GraphDriver::SwitchAtNextIteration(GraphDriver* aNextDriver) { LIFECYCLE_LOG("Switching to new driver: %p (%s)", aNextDriver, aNextDriver->AsAudioCallbackDriver() ? "AudioCallbackDriver" : "SystemClockDriver"); // Sometimes we switch twice to a new driver per iteration, this is probably a // bug. MOZ_ASSERT(!mNextDriver || mNextDriver->AsAudioCallbackDriver()); mNextDriver = aNextDriver; }
NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread()); LIFECYCLE_LOG("MediaStreamGraphShutdownThreadRunnable for graph %p", mDriver->GraphImpl()); // We can't release an audio driver on the main thread, because it can be // blocking. if (mDriver->AsAudioCallbackDriver()) { LIFECYCLE_LOG("Releasing audio driver off main thread."); RefPtr<AsyncCubebTask> releaseEvent = new AsyncCubebTask(mDriver->AsAudioCallbackDriver(), AsyncCubebOperation::SHUTDOWN); mDriver = nullptr; releaseEvent->Dispatch(); } else { LIFECYCLE_LOG("Dropping driver reference for SystemClockDriver."); mDriver = nullptr; } return NS_OK; }
void GraphDriver::SwitchAtNextIteration(GraphDriver* aNextDriver) { // This is the situation where `mPreviousDriver` is an AudioCallbackDriver // that is switching device, and the graph has found the current driver is not // an AudioCallbackDriver, but tries to switch to a _new_ AudioCallbackDriver // because it found audio has to be output. In this case, simply ignore the // request to switch, since we know we will switch back to the old // AudioCallbackDriver when it has recovered from the device switching. if (aNextDriver->AsAudioCallbackDriver() && mPreviousDriver && mPreviousDriver->AsAudioCallbackDriver()->IsSwitchingDevice() && mPreviousDriver != aNextDriver) { return; } LIFECYCLE_LOG("Switching to new driver: %p (%s)", aNextDriver, aNextDriver->AsAudioCallbackDriver() ? "AudioCallbackDriver" : "SystemClockDriver"); mNextDriver = aNextDriver; }
long AudioCallbackDriver::DataCallback(AudioDataValue* aBuffer, long aFrames) { bool stillProcessing; if (mPauseRequested) { PodZero(aBuffer, aFrames * mGraphImpl->AudioChannelCount()); return aFrames; } #ifdef XP_MACOSX if (OSXDeviceSwitchingWorkaround()) { PodZero(aBuffer, aFrames * mGraphImpl->AudioChannelCount()); return aFrames; } #endif #ifdef DEBUG // DebugOnly<> doesn't work here... it forces an initialization that will cause // mInCallback to be set back to false before we exit the statement. Do it by // hand instead. AutoInCallback aic(this); #endif GraphTime stateComputedTime = StateComputedTime(); if (stateComputedTime == 0) { MonitorAutoLock mon(mGraphImpl->GetMonitor()); // Because this function is called during cubeb_stream_init (to prefill the // audio buffers), it can be that we don't have a message here (because this // driver is the first one for this graph), and the graph would exit. Simply // return here until we have messages. if (!mGraphImpl->MessagesQueued()) { PodZero(aBuffer, aFrames * mGraphImpl->AudioChannelCount()); return aFrames; } mGraphImpl->SwapMessageQueues(); } uint32_t durationMS = aFrames * 1000 / mSampleRate; // For now, simply average the duration with the previous // duration so there is some damping against sudden changes. if (!mIterationDurationMS) { mIterationDurationMS = durationMS; } else { mIterationDurationMS = (mIterationDurationMS*3) + durationMS; mIterationDurationMS /= 4; } mBuffer.SetBuffer(aBuffer, aFrames); // fill part or all with leftover data from last iteration (since we // align to Audio blocks) mScratchBuffer.Empty(mBuffer); // if we totally filled the buffer (and mScratchBuffer isn't empty), // we don't need to run an iteration and if we do so we may overflow. if (mBuffer.Available()) { // State computed time is decided by the audio callback's buffer length. We // compute the iteration start and end from there, trying to keep the amount // of buffering in the graph constant. GraphTime nextStateComputedTime = mGraphImpl->RoundUpToNextAudioBlock(stateComputedTime + mBuffer.Available()); mIterationStart = mIterationEnd; // inGraph is the number of audio frames there is between the state time and // the current time, i.e. the maximum theoretical length of the interval we // could use as [mIterationStart; mIterationEnd]. GraphTime inGraph = stateComputedTime - mIterationStart; // We want the interval [mIterationStart; mIterationEnd] to be before the // interval [stateComputedTime; nextStateComputedTime]. We also want // the distance between these intervals to be roughly equivalent each time, to // ensure there is no clock drift between current time and state time. Since // we can't act on the state time because we have to fill the audio buffer, we // reclock the current time against the state time, here. mIterationEnd = mIterationStart + 0.8 * inGraph; STREAM_LOG(LogLevel::Debug, ("interval[%ld; %ld] state[%ld; %ld] (frames: %ld) (durationMS: %u) (duration ticks: %ld)\n", (long)mIterationStart, (long)mIterationEnd, (long)stateComputedTime, (long)nextStateComputedTime, (long)aFrames, (uint32_t)durationMS, (long)(nextStateComputedTime - stateComputedTime))); mCurrentTimeStamp = TimeStamp::Now(); if (stateComputedTime < mIterationEnd) { STREAM_LOG(LogLevel::Warning, ("Media graph global underrun detected")); mIterationEnd = stateComputedTime; } stillProcessing = mGraphImpl->OneIteration(nextStateComputedTime); } else { NS_WARNING("DataCallback buffer filled entirely from scratch buffer, skipping iteration."); stillProcessing = true; } mBuffer.BufferFilled(); if (mNextDriver && stillProcessing) { { // If the audio stream has not been started by the previous driver or // the graph itself, keep it alive. MonitorAutoLock mon(mGraphImpl->GetMonitor()); if (!IsStarted()) { return aFrames; } } STREAM_LOG(LogLevel::Debug, ("Switching to system driver.")); mNextDriver->SetGraphTime(this, mIterationStart, mIterationEnd); mGraphImpl->SetCurrentDriver(mNextDriver); mNextDriver->Start(); // Returning less than aFrames starts the draining and eventually stops the // audio thread. This function will never get called again. return aFrames - 1; } if (!stillProcessing) { LIFECYCLE_LOG("Stopping audio thread for MediaStreamGraph %p", this); return aFrames - 1; } return aFrames; }
long AudioCallbackDriver::DataCallback(AudioDataValue* aBuffer, long aFrames) { bool stillProcessing; if (mPauseRequested) { PodZero(aBuffer, aFrames * mGraphImpl->AudioChannelCount()); return aFrames; } DebugOnly<AutoInCallback> aic(AutoInCallback(this)); if (mStateComputedTime == 0) { MonitorAutoLock mon(mGraphImpl->GetMonitor()); // Because this function is called during cubeb_stream_init (to prefill the // audio buffers), it can be that we don't have a message here (because this // driver is the first one for this graph), and the graph would exit. Simply // return here until we have messages. if (!mGraphImpl->MessagesQueued()) { PodZero(aBuffer, aFrames * mGraphImpl->AudioChannelCount()); return aFrames; } mGraphImpl->SwapMessageQueues(); } uint32_t durationMS = aFrames * 1000 / mSampleRate; // For now, simply average the duration with the previous // duration so there is some damping against sudden changes. if (!mIterationDurationMS) { mIterationDurationMS = durationMS; } else { mIterationDurationMS += durationMS; mIterationDurationMS /= 2; } mBuffer.SetBuffer(aBuffer, aFrames); mScratchBuffer.Empty(mBuffer); mStateComputedTime = mNextStateComputedTime; // State computed time is decided by the audio callback's buffer length. We // compute the iteration start and end from there, trying to keep the amount // of buffering in the graph constant. mNextStateComputedTime = mGraphImpl->RoundUpToNextAudioBlock(mStateComputedTime + mBuffer.Available()); mIterationStart = mIterationEnd; // inGraph is the number of audio frames there is between the state time and // the current time, i.e. the maximum theoretical length of the interval we // could use as [mIterationStart; mIterationEnd]. GraphTime inGraph = mStateComputedTime - mIterationStart; // We want the interval [mIterationStart; mIterationEnd] to be before the // interval [mStateComputedTime; mNextStateComputedTime]. We also want // the distance between these intervals to be roughly equivalent each time, to // ensure there is no clock drift between current time and state time. Since // we can't act on the state time because we have to fill the audio buffer, we // reclock the current time against the state time, here. mIterationEnd = mIterationStart + 0.8 * inGraph; STREAM_LOG(PR_LOG_DEBUG, ("interval[%ld; %ld] state[%ld; %ld] (frames: %ld) (durationMS: %u) (duration ticks: %ld)\n", (long)mIterationStart, (long)mIterationEnd, (long)mStateComputedTime, (long)mNextStateComputedTime, (long)aFrames, (uint32_t)durationMS, (long)(mNextStateComputedTime - mStateComputedTime))); mCurrentTimeStamp = TimeStamp::Now(); if (mStateComputedTime < mIterationEnd) { STREAM_LOG(PR_LOG_WARNING, ("Media graph global underrun detected")); mIterationEnd = mStateComputedTime; } stillProcessing = mGraphImpl->OneIteration(mIterationStart, mIterationEnd, mStateComputedTime, mNextStateComputedTime); mBuffer.BufferFilled(); if (mNextDriver && stillProcessing) { { // If the audio stream has not been started by the previous driver or // the graph itself, keep it alive. MonitorAutoLock mon(mGraphImpl->GetMonitor()); if (!IsStarted()) { return aFrames; } } STREAM_LOG(PR_LOG_DEBUG, ("Switching to system driver.")); mNextDriver->SetGraphTime(this, mIterationStart, mIterationEnd, mStateComputedTime, mNextStateComputedTime); mGraphImpl->SetCurrentDriver(mNextDriver); mNextDriver->Start(); // Returning less than aFrames starts the draining and eventually stops the // audio thread. This function will never get called again. return aFrames - 1; } if (!stillProcessing) { LIFECYCLE_LOG("Stopping audio thread for MediaStreamGraph %p", this); return aFrames - 1; } return aFrames; }