void AudioSummingJunction::changedOutputs() { ASSERT(deferredTaskHandler().isGraphOwner()); if (!m_renderingStateNeedUpdating) { deferredTaskHandler().markSummingJunctionDirty(this); m_renderingStateNeedUpdating = true; } }
void AudioNodeOutput::updateNumberOfChannels() { DCHECK(deferredTaskHandler().isAudioThread()); ASSERT(deferredTaskHandler().isGraphOwner()); if (m_numberOfChannels != m_desiredNumberOfChannels) { m_numberOfChannels = m_desiredNumberOfChannels; updateInternalBus(); propagateChannelCount(); } }
void AudioNodeOutput::propagateChannelCount() { DCHECK(deferredTaskHandler().isAudioThread()); ASSERT(deferredTaskHandler().isGraphOwner()); if (isChannelCountKnown()) { // Announce to any nodes we're connected to that we changed our channel // count for its input. for (AudioNodeInput* i : m_inputs) i->handler().checkNumberOfChannelsForInput(i); } }
void AudioNodeInput::updateInternalBus() { DCHECK(deferredTaskHandler().isAudioThread()); ASSERT(deferredTaskHandler().isGraphOwner()); unsigned numberOfInputChannels = numberOfChannels(); if (numberOfInputChannels == m_internalSummingBus->numberOfChannels()) return; m_internalSummingBus = AudioBus::create(numberOfInputChannels, AudioUtilities::kRenderQuantumFrames); }
void OfflineAudioContext::handlePostOfflineRenderTasks() { ASSERT(isAudioThread()); // OfflineGraphAutoLocker here locks the audio graph for the same reason // above in |handlePreOfflineRenderTasks|. OfflineGraphAutoLocker locker(this); deferredTaskHandler().breakConnections(); releaseFinishedSourceNodes(); deferredTaskHandler().handleDeferredTasks(); deferredTaskHandler().requestToDeleteHandlersOnMainThread(); }
void AudioNodeOutput::disconnectAllParams() { ASSERT(deferredTaskHandler().isGraphOwner()); // AudioParam::disconnect() changes m_params by calling removeParam(). while (!m_params.isEmpty()) (*m_params.begin())->disconnect(*this); }
AudioBus* AudioNodeInput::pull(AudioBus* inPlaceBus, size_t framesToProcess) { DCHECK(deferredTaskHandler().isAudioThread()); // Handle single connection case. if (numberOfRenderingConnections() == 1 && handler().internalChannelCountMode() == AudioHandler::Max) { // The output will optimize processing using inPlaceBus if it's able. AudioNodeOutput* output = this->renderingOutput(0); return output->pull(inPlaceBus, framesToProcess); } AudioBus* internalSummingBus = this->internalSummingBus(); if (!numberOfRenderingConnections()) { // At least, generate silence if we're not connected to anything. // FIXME: if we wanted to get fancy, we could propagate a 'silent hint' here // to optimize the downstream graph processing. internalSummingBus->zero(); return internalSummingBus; } // Handle multiple connections case. sumAllConnections(internalSummingBus, framesToProcess); return internalSummingBus; }
void AudioNodeInput::sumAllConnections(AudioBus* summingBus, size_t framesToProcess) { DCHECK(deferredTaskHandler().isAudioThread()); // We shouldn't be calling this method if there's only one connection, since // it's less efficient. // DCHECK(numberOfRenderingConnections() > 1 || // handler().internalChannelCountMode() != AudioHandler::Max); DCHECK(summingBus); if (!summingBus) return; summingBus->zero(); AudioBus::ChannelInterpretation interpretation = handler().internalChannelInterpretation(); for (unsigned i = 0; i < numberOfRenderingConnections(); ++i) { AudioNodeOutput* output = renderingOutput(i); DCHECK(output); // Render audio from this output. AudioBus* connectionBus = output->pull(0, framesToProcess); // Sum, with unity-gain. summingBus->sumFrom(*connectionBus, interpretation); } }
AudioContext* AudioParamHandler::context() const { // TODO(tkent): We can remove this dangerous function by removing // AudioContext dependency from AudioParamTimeline. ASSERT_WITH_SECURITY_IMPLICATION(deferredTaskHandler().isAudioThread()); return &m_context; }
void AbstractAudioContext::clear() { m_destinationNode.clear(); // The audio rendering thread is dead. Nobody will schedule AudioHandler // deletion. Let's do it ourselves. deferredTaskHandler().clearHandlersToBeDeleted(); m_isCleared = true; }
void AudioNodeOutput::dispose() { m_didCallDispose = true; deferredTaskHandler().removeMarkedAudioNodeOutput(this); disconnectAll(); DCHECK(m_inputs.isEmpty()); DCHECK(m_params.isEmpty()); }
void AudioNodeOutput::setNumberOfChannels(unsigned numberOfChannels) { DCHECK_LE(numberOfChannels, BaseAudioContext::maxNumberOfChannels()); ASSERT(deferredTaskHandler().isGraphOwner()); m_desiredNumberOfChannels = numberOfChannels; if (deferredTaskHandler().isAudioThread()) { // If we're in the audio thread then we can take care of it right away (we // should be at the very start or end of a rendering quantum). updateNumberOfChannels(); } else { DCHECK(!m_didCallDispose); // Let the context take care of it in the audio thread in the pre and post // render tasks. deferredTaskHandler().markAudioNodeOutputDirty(this); } }
void AudioParamHandler::calculateSampleAccurateValues(float* values, unsigned numberOfValues) { bool isSafe = deferredTaskHandler().isAudioThread() && values && numberOfValues; ASSERT(isSafe); if (!isSafe) return; calculateFinalValues(values, numberOfValues, true); }
void AudioNodeOutput::enable() { ASSERT(deferredTaskHandler().isGraphOwner()); if (!m_isEnabled) { m_isEnabled = true; for (AudioNodeInput* i : m_inputs) i->enable(*this); } }
void AudioSummingJunction::updateRenderingState() { ASSERT(deferredTaskHandler().isAudioThread()); ASSERT(deferredTaskHandler().isGraphOwner()); if (m_renderingStateNeedUpdating) { // Copy from m_outputs to m_renderingOutputs. m_renderingOutputs.resize(m_outputs.size()); unsigned j = 0; for (AudioNodeOutput* output : m_outputs) { m_renderingOutputs[j++] = output; output->updateRenderingState(); } didUpdate(); m_renderingStateNeedUpdating = false; } }
AbstractAudioContext::~AbstractAudioContext() { deferredTaskHandler().contextWillBeDestroyed(); // AudioNodes keep a reference to their context, so there should be no way to be in the destructor if there are still AudioNodes around. ASSERT(!m_isInitialized); ASSERT(!m_activeSourceNodes.size()); ASSERT(!m_finishedSourceHandlers.size()); ASSERT(!m_isResolvingResumePromises); ASSERT(!m_resumeResolvers.size()); }
void AudioParamHandler::disconnect(AudioNodeOutput& output) { ASSERT(deferredTaskHandler().isGraphOwner()); if (m_outputs.contains(&output)) { m_outputs.remove(&output); changedOutputs(); output.removeParam(*this); } }
void AbstractAudioContext::handlePostRenderTasks() { ASSERT(isAudioThread()); // Must use a tryLock() here too. Don't worry, the lock will very rarely be contended and this method is called frequently. // The worst that can happen is that there will be some nodes which will take slightly longer than usual to be deleted or removed // from the render graph (in which case they'll render silence). if (tryLock()) { // Take care of AudioNode tasks where the tryLock() failed previously. deferredTaskHandler().breakConnections(); // Dynamically clean up nodes which are no longer needed. releaseFinishedSourceNodes(); deferredTaskHandler().handleDeferredTasks(); deferredTaskHandler().requestToDeleteHandlersOnMainThread(); unlock(); } }
AudioBus* AudioNodeInput::bus() { DCHECK(deferredTaskHandler().isAudioThread()); // Handle single connection specially to allow for in-place processing. if (numberOfRenderingConnections() == 1 && handler().internalChannelCountMode() == AudioHandler::Max) return renderingOutput(0)->bus(); // Multiple connections case or complex ChannelCountMode (or no connections). return internalSummingBus(); }
void AudioParamHandler::connect(AudioNodeOutput& output) { ASSERT(deferredTaskHandler().isGraphOwner()); if (m_outputs.contains(&output)) return; output.addParam(*this); m_outputs.add(&output); changedOutputs(); }
void AudioContext::stopRendering() { ASSERT(isMainThread()); ASSERT(destination()); if (contextState() == Running) { destination()->audioDestinationHandler().stopRendering(); setContextState(Suspended); deferredTaskHandler().clearHandlersToBeDeleted(); } }
void AudioNodeInput::connect(AudioNodeOutput& output) { ASSERT(deferredTaskHandler().isGraphOwner()); // Check if we're already connected to this output. if (m_outputs.contains(&output)) return; output.addInput(*this); m_outputs.add(&output); changedOutputs(); }
void AudioNodeInput::disable(AudioNodeOutput& output) { ASSERT(deferredTaskHandler().isGraphOwner()); DCHECK(m_outputs.contains(&output)); m_disabledOutputs.add(&output); m_outputs.remove(&output); changedOutputs(); // Propagate disabled state to outputs. handler().disableOutputsIfNecessary(); }
float AudioParamHandler::value() { // Update value for timeline. if (deferredTaskHandler().isAudioThread()) { bool hasValue; float timelineValue = m_timeline.valueForContextTime(context(), narrowPrecisionToFloat(m_value), hasValue); if (hasValue) m_value = timelineValue; } return narrowPrecisionToFloat(m_value); }
void AudioNodeInput::enable(AudioNodeOutput& output) { ASSERT(deferredTaskHandler().isGraphOwner()); // Move output from disabled list to active list. m_outputs.add(&output); if (m_disabledOutputs.size() > 0) { DCHECK(m_disabledOutputs.contains(&output)); m_disabledOutputs.remove(&output); } changedOutputs(); // Propagate enabled state to outputs. handler().enableOutputsIfNecessary(); }
bool OfflineAudioContext::handlePreOfflineRenderTasks() { ASSERT(isAudioThread()); // OfflineGraphAutoLocker here locks the audio graph for this scope. Note // that this locker does not use tryLock() inside because the timing of // suspension MUST NOT be delayed. OfflineGraphAutoLocker locker(this); deferredTaskHandler().handleDeferredTasks(); handleStoppableSourceNodes(); return shouldSuspend(); }
void AbstractAudioContext::handlePreRenderTasks() { ASSERT(isAudioThread()); // At the beginning of every render quantum, try to update the internal rendering graph state (from main thread changes). // It's OK if the tryLock() fails, we'll just take slightly longer to pick up the changes. if (tryLock()) { deferredTaskHandler().handleDeferredTasks(); resolvePromisesForResume(); // Check to see if source nodes can be stopped because the end time has passed. handleStoppableSourceNodes(); unlock(); } }
AudioBus* AudioNodeOutput::pull(AudioBus* inPlaceBus, size_t framesToProcess) { DCHECK(deferredTaskHandler().isAudioThread()); DCHECK(m_renderingFanOutCount > 0 || m_renderingParamFanOutCount > 0); // Causes our AudioNode to process if it hasn't already for this render // quantum. We try to do in-place processing (using inPlaceBus) if at all // possible, but we can't process in-place if we're connected to more than one // input (fan-out > 1). In this case pull() is called multiple times per // rendering quantum, and the processIfNecessary() call below will cause our // node to process() only the first time, caching the output in // m_internalOutputBus for subsequent calls. m_isInPlace = inPlaceBus && inPlaceBus->numberOfChannels() == numberOfChannels() && (m_renderingFanOutCount + m_renderingParamFanOutCount) == 1; m_inPlaceBus = m_isInPlace ? inPlaceBus : 0; handler().processIfNecessary(framesToProcess); return bus(); }
void AudioParamHandler::calculateFinalValues(float* values, unsigned numberOfValues, bool sampleAccurate) { bool isGood = deferredTaskHandler().isAudioThread() && values && numberOfValues; ASSERT(isGood); if (!isGood) return; // The calculated result will be the "intrinsic" value summed with all audio-rate connections. if (sampleAccurate) { // Calculate sample-accurate (a-rate) intrinsic values. calculateTimelineValues(values, numberOfValues); } else { // Calculate control-rate (k-rate) intrinsic value. bool hasValue; float timelineValue = m_timeline.valueForContextTime(context(), narrowPrecisionToFloat(m_value), hasValue); if (hasValue) m_value = timelineValue; values[0] = narrowPrecisionToFloat(m_value); } // Now sum all of the audio-rate connections together (unity-gain summing junction). // Note that connections would normally be mono, but we mix down to mono if necessary. RefPtr<AudioBus> summingBus = AudioBus::create(1, numberOfValues, false); summingBus->setChannelMemory(0, values, numberOfValues); for (unsigned i = 0; i < numberOfRenderingConnections(); ++i) { AudioNodeOutput* output = renderingOutput(i); ASSERT(output); // Render audio from this output. AudioBus* connectionBus = output->pull(0, AudioHandler::ProcessingSizeInFrames); // Sum, with unity-gain. summingBus->sumFrom(*connectionBus); } }
void AudioNodeInput::disconnect(AudioNodeOutput& output) { ASSERT(deferredTaskHandler().isGraphOwner()); // First try to disconnect from "active" connections. if (m_outputs.contains(&output)) { m_outputs.remove(&output); changedOutputs(); output.removeInput(*this); // Note: it's important to return immediately after removeInput() calls // since the node may be deleted. return; } // Otherwise, try to disconnect from disabled connections. if (m_disabledOutputs.contains(&output)) { m_disabledOutputs.remove(&output); output.removeInput(*this); // Note: it's important to return immediately after all removeInput() calls // since the node may be deleted. return; } ASSERT_NOT_REACHED(); }