void AudioDSPKernelProcessor::process(const AudioBus* source, AudioBus* destination, size_t framesToProcess) { ASSERT(source && destination); if (!source || !destination) return; if (!isInitialized()) { destination->zero(); return; } MutexTryLocker tryLocker(m_processLock); if (tryLocker.locked()) { bool channelCountMatches = source->numberOfChannels() == destination->numberOfChannels() && source->numberOfChannels() == m_kernels.size(); ASSERT(channelCountMatches); if (!channelCountMatches) return; for (unsigned i = 0; i < m_kernels.size(); ++i) m_kernels[i]->process(source->channel(i)->data(), destination->channel(i)->mutableData(), framesToProcess); } else { // Unfortunately, the kernel is being processed by another thread. // See also ConvolverNode::process(). destination->zero(); } }
void MediaStreamAudioSourceNode::process(size_t numberOfFrames) { AudioBus* outputBus = output(0)->bus(); if (!audioSourceProvider()) { outputBus->zero(); return; } if (!mediaStream() || m_sourceNumberOfChannels != outputBus->numberOfChannels()) { outputBus->zero(); return; } // Use a tryLock() to avoid contention in the real-time audio thread. // If we fail to acquire the lock then the MediaStream must be in the middle of // a format change, so we output silence in this case. MutexTryLocker tryLocker(m_processLock); if (tryLocker.locked()) audioSourceProvider()->provideInput(outputBus, numberOfFrames); else { // We failed to acquire the lock. outputBus->zero(); } }
void MediaElementAudioSourceNode::process(size_t numberOfFrames) { AudioBus* outputBus = output(0)->bus(); if (!mediaElement() || !m_sourceNumberOfChannels || !m_sourceSampleRate) { outputBus->zero(); return; } // Use a tryLock() to avoid contention in the real-time audio thread. // If we fail to acquire the lock then the HTMLMediaElement must be in the middle of // reconfiguring its playback engine, so we output silence in this case. MutexTryLocker tryLocker(m_processLock); if (tryLocker.locked()) { if (AudioSourceProvider* provider = mediaElement()->audioSourceProvider()) { if (m_multiChannelResampler.get()) { ASSERT(m_sourceSampleRate != sampleRate()); m_multiChannelResampler->process(provider, outputBus, numberOfFrames); } else { // Bypass the resampler completely if the source is at the context's sample-rate. ASSERT(m_sourceSampleRate == sampleRate()); provider->provideInput(outputBus, numberOfFrames); } } else { // Either this port doesn't yet support HTMLMediaElement audio stream access, // or the stream is not yet available. outputBus->zero(); } } else { // We failed to acquire the lock. outputBus->zero(); } }
double ConvolverNode::latencyTime() const { MutexTryLocker tryLocker(m_processLock); if (tryLocker.locked()) return m_reverb ? m_reverb->latencyFrames() / static_cast<double>(sampleRate()) : 0; // Since we don't want to block the Audio Device thread, we return a large value // instead of trying to acquire the lock. return std::numeric_limits<double>::infinity(); }
void AudioBufferSourceNode::process(size_t framesToProcess) { AudioBus* outputBus = output(0)->bus(); if (!isInitialized()) { outputBus->zero(); return; } // The audio thread can't block on this lock, so we call tryLock() instead. MutexTryLocker tryLocker(m_processLock); if (tryLocker.locked()) { if (!buffer()) { outputBus->zero(); return; } // After calling setBuffer() with a buffer having a different number of channels, there can in rare cases be a slight delay // before the output bus is updated to the new number of channels because of use of tryLocks() in the context's updating system. // In this case, if the the buffer has just been changed and we're not quite ready yet, then just output silence. if (numberOfChannels() != buffer()->numberOfChannels()) { outputBus->zero(); return; } size_t quantumFrameOffset; size_t bufferFramesToProcess; updateSchedulingInfo(framesToProcess, outputBus, quantumFrameOffset, bufferFramesToProcess); if (!bufferFramesToProcess) { outputBus->zero(); return; } for (unsigned i = 0; i < outputBus->numberOfChannels(); ++i) m_destinationChannels[i] = outputBus->channel(i)->mutableData(); // Render by reading directly from the buffer. if (!renderFromBuffer(outputBus, quantumFrameOffset, bufferFramesToProcess)) { outputBus->zero(); return; } // Apply the gain (in-place) to the output bus. float totalGain = gain()->value() * m_buffer->gain(); outputBus->copyWithGainFrom(*outputBus, &m_lastGain, totalGain); outputBus->clearSilentFlag(); } else { // Too bad - the tryLock() failed. We must be in the middle of changing buffers and were already outputting silence anyway. outputBus->zero(); } }
double AudioDSPKernelProcessor::latencyTime() const { ASSERT(!isMainThread()); MutexTryLocker tryLocker(m_processLock); if (tryLocker.locked()) { // It is expected that all the kernels have the same latencyTime. return !m_kernels.isEmpty() ? m_kernels.front()->latencyTime() : 0; } // Since we don't want to block the Audio Device thread, we return a large // value instead of trying to acquire the lock. return std::numeric_limits<double>::infinity(); }
double ConvolverHandler::tailTime() const { MutexTryLocker tryLocker(m_processLock); if (tryLocker.locked()) return m_reverb ? m_reverb->impulseResponseLength() / static_cast<double>(sampleRate()) : 0; // Since we don't want to block the Audio Device thread, we return a large // value instead of trying to acquire the lock. return std::numeric_limits<double>::infinity(); }
void PannerNode::process(size_t framesToProcess) { AudioBus* destination = output(0)->bus(); if (!isInitialized() || !input(0)->isConnected() || !m_panner.get()) { destination->zero(); return; } AudioBus* source = input(0)->bus(); if (!source) { destination->zero(); return; } // The audio thread can't block on this lock, so we call tryLock() instead. MutexTryLocker tryLocker(m_processLock); MutexTryLocker tryListenerLocker(listener()->listenerLock()); if (tryLocker.locked() && tryListenerLocker.locked()) { // HRTFDatabase should be loaded before proceeding for offline audio context when the panning model is HRTF. if (m_panningModel == Panner::PanningModelHRTF && !m_hrtfDatabaseLoader->isLoaded()) { if (context()->isOfflineContext()) { m_hrtfDatabaseLoader->waitForLoaderThreadCompletion(); } else { destination->zero(); return; } } // Apply the panning effect. double azimuth; double elevation; azimuthElevation(&azimuth, &elevation); m_panner->pan(azimuth, elevation, source, destination, framesToProcess); // Get the distance and cone gain. float totalGain = distanceConeGain(); // Snap to desired gain at the beginning. if (m_lastGain == -1.0) m_lastGain = totalGain; // Apply gain in-place with de-zippering. destination->copyWithGainFrom(*destination, &m_lastGain, totalGain); } else { // Too bad - The tryLock() failed. // We must be in the middle of changing the properties of the panner or the listener. destination->zero(); } }
void MediaElementAudioSourceHandler::process(size_t numberOfFrames) { AudioBus* outputBus = output(0).bus(); if (!mediaElement() || !m_sourceNumberOfChannels || !m_sourceSampleRate) { outputBus->zero(); return; } // Use a tryLock() to avoid contention in the real-time audio thread. // If we fail to acquire the lock then the HTMLMediaElement must be in the middle of // reconfiguring its playback engine, so we output silence in this case. MutexTryLocker tryLocker(m_processLock); if (tryLocker.locked()) { if (AudioSourceProvider* provider = mediaElement()->audioSourceProvider()) { // Grab data from the provider so that the element continues to make progress, even if // we're going to output silence anyway. if (m_multiChannelResampler.get()) { ASSERT(m_sourceSampleRate != sampleRate()); m_multiChannelResampler->process(provider, outputBus, numberOfFrames); } else { // Bypass the resampler completely if the source is at the context's sample-rate. ASSERT(m_sourceSampleRate == sampleRate()); provider->provideInput(outputBus, numberOfFrames); } // Output silence if we don't have access to the element. if (!passesCORSAccessCheck()) { if (m_maybePrintCORSMessage) { // Print a CORS message, but just once for each change in the current media // element source, and only if we have a document to print to. m_maybePrintCORSMessage = false; if (context()->executionContext()) { context()->executionContext()->postTask(FROM_HERE, createCrossThreadTask(&MediaElementAudioSourceHandler::printCORSMessage, this, m_currentSrcString)); } } outputBus->zero(); } } else { // Either this port doesn't yet support HTMLMediaElement audio stream access, // or the stream is not yet available. outputBus->zero(); } } else { // We failed to acquire the lock. outputBus->zero(); } }
void AudioBufferSourceNode::process(size_t framesToProcess) { AudioBus* outputBus = output(0)->bus(); if (!isInitialized()) { outputBus->zero(); return; } // The audio thread can't block on this lock, so we call tryLock() instead. MutexTryLocker tryLocker(m_processLock); if (tryLocker.locked()) { if (!buffer()) { outputBus->zero(); return; } size_t quantumFrameOffset; size_t bufferFramesToProcess; updateSchedulingInfo(framesToProcess, outputBus, quantumFrameOffset, bufferFramesToProcess); if (!bufferFramesToProcess) { outputBus->zero(); return; } for (unsigned i = 0; i < outputBus->numberOfChannels(); ++i) m_destinationChannels[i] = outputBus->channel(i)->mutableData(); // Render by reading directly from the buffer. if (!renderFromBuffer(outputBus, quantumFrameOffset, bufferFramesToProcess)) { outputBus->zero(); return; } // Apply the gain (in-place) to the output bus. float totalGain = gain()->value() * m_buffer->gain(); outputBus->copyWithGainFrom(*outputBus, &m_lastGain, totalGain); outputBus->clearSilentFlag(); } else { // Too bad - the tryLock() failed. We must be in the middle of changing buffers and were already outputting silence anyway. outputBus->zero(); } }
void MediaElementAudioSourceHandler::process(size_t numberOfFrames) { AudioBus* outputBus = output(0).bus(); // Use a tryLock() to avoid contention in the real-time audio thread. // If we fail to acquire the lock then the HTMLMediaElement must be in the // middle of reconfiguring its playback engine, so we output silence in this // case. MutexTryLocker tryLocker(m_processLock); if (tryLocker.locked()) { if (!mediaElement() || !m_sourceNumberOfChannels || !m_sourceSampleRate) { outputBus->zero(); return; } AudioSourceProvider& provider = mediaElement()->getAudioSourceProvider(); // Grab data from the provider so that the element continues to make // progress, even if we're going to output silence anyway. if (m_multiChannelResampler.get()) { DCHECK_NE(m_sourceSampleRate, sampleRate()); m_multiChannelResampler->process(&provider, outputBus, numberOfFrames); } else { // Bypass the resampler completely if the source is at the context's // sample-rate. DCHECK_EQ(m_sourceSampleRate, sampleRate()); provider.provideInput(outputBus, numberOfFrames); } // Output silence if we don't have access to the element. if (!passesCORSAccessCheck()) { if (m_maybePrintCORSMessage) { // Print a CORS message, but just once for each change in the current // media element source, and only if we have a document to print to. m_maybePrintCORSMessage = false; if (context()->getExecutionContext()) { context()->getExecutionContext()->postTask( BLINK_FROM_HERE, createCrossThreadTask( &MediaElementAudioSourceHandler::printCORSMessage, PassRefPtr<MediaElementAudioSourceHandler>(this), m_currentSrcString)); } } outputBus->zero(); } } else { // We failed to acquire the lock. outputBus->zero(); } }
bool AudioParamTimeline::hasValues() const { MutexTryLocker tryLocker(m_eventsLock); if (tryLocker.locked()) return m_events.size(); // Can't get the lock so that means the main thread is trying to insert an event. Just // return true then. If the main thread releases the lock before valueForContextTime or // valuesForFrameRange runs, then the there will be an event on the timeline, so everything // is fine. If the lock is held so that neither valueForContextTime nor valuesForFrameRange // can run, this is ok too, because they have tryLocks to produce a default value. The // event will then get processed in the next rendering quantum. // // Don't want to return false here because that would confuse the processing of the timeline // if previously we returned true and now suddenly return false, only to return true on the // next rendering quantum. Currently, once a timeline has been introduced it is always true // forever because m_events never shrinks. return true; }
void WebMediaPlayerClientImpl::AudioSourceProviderImpl::provideInput(AudioBus* bus, size_t framesToProcess) { ASSERT(bus); if (!bus) return; MutexTryLocker tryLocker(provideInputLock); if (!tryLocker.locked() || !m_webAudioSourceProvider || !m_client.get()) { bus->zero(); return; } // Wrap the AudioBus channel data using WebVector. size_t n = bus->numberOfChannels(); WebVector<float*> webAudioData(n); for (size_t i = 0; i < n; ++i) webAudioData[i] = bus->channel(i)->mutableData(); m_webAudioSourceProvider->provideInput(webAudioData, framesToProcess); }
float AudioParamTimeline::valuesForFrameRange( size_t startFrame, size_t endFrame, float defaultValue, float* values, unsigned numberOfValues, double sampleRate, double controlRate) { // We can't contend the lock in the realtime audio thread. MutexTryLocker tryLocker(m_eventsLock); if (!tryLocker.locked()) { if (values) { for (unsigned i = 0; i < numberOfValues; ++i) values[i] = defaultValue; } return defaultValue; } return valuesForFrameRangeImpl(startFrame, endFrame, defaultValue, values, numberOfValues, sampleRate, controlRate); }
void BiquadDSPKernel::process(const float* source, float* destination, size_t framesToProcess) { ASSERT(source); ASSERT(destination); ASSERT(biquadProcessor()); // Recompute filter coefficients if any of the parameters have changed. // FIXME: as an optimization, implement a way that a Biquad object can simply copy its internal filter coefficients from another Biquad object. // Then re-factor this code to only run for the first BiquadDSPKernel of each BiquadProcessor. // The audio thread can't block on this lock; skip updating the coefficients for this block if // necessary. We'll get them the next time around. { MutexTryLocker tryLocker(m_processLock); if (tryLocker.locked()) updateCoefficientsIfNecessary(); } m_biquad.process(source, destination, framesToProcess); }
float AudioParamTimeline::valueForContextTime(AbstractAudioContext* context, float defaultValue, bool& hasValue) { ASSERT(context); { MutexTryLocker tryLocker(m_eventsLock); if (!tryLocker.locked() || !context || !m_events.size() || context->currentTime() < m_events[0].time()) { hasValue = false; return defaultValue; } } // Ask for just a single value. float value; double sampleRate = context->sampleRate(); size_t startFrame = context->currentSampleFrame(); double controlRate = sampleRate / AudioHandler::ProcessingSizeInFrames; // one parameter change per render quantum value = valuesForFrameRange(startFrame, startFrame + 1, defaultValue, &value, 1, sampleRate, controlRate); hasValue = true; return value; }
void PannerNode::process(size_t framesToProcess) { AudioBus* destination = output(0)->bus(); if (!isInitialized() || !input(0)->isConnected() || !m_panner.get()) { destination->zero(); return; } AudioBus* source = input(0)->bus(); if (!source) { destination->zero(); return; } // The audio thread can't block on this lock, so we call tryLock() instead. MutexTryLocker tryLocker(m_pannerLock); if (tryLocker.locked()) { // Apply the panning effect. double azimuth; double elevation; getAzimuthElevation(&azimuth, &elevation); m_panner->pan(azimuth, elevation, source, destination, framesToProcess); // Get the distance and cone gain. double totalGain = distanceConeGain(); // Snap to desired gain at the beginning. if (m_lastGain == -1.0) m_lastGain = totalGain; // Apply gain in-place with de-zippering. destination->copyWithGainFrom(*destination, &m_lastGain, totalGain); } else { // Too bad - The tryLock() failed. We must be in the middle of changing the panner. destination->zero(); } }
void ConvolverNode::process(size_t framesToProcess) { AudioBus* outputBus = output(0)->bus(); ASSERT(outputBus); // Synchronize with possible dynamic changes to the impulse response. MutexTryLocker tryLocker(m_processLock); if (tryLocker.locked()) { if (!isInitialized() || !m_reverb.get()) outputBus->zero(); else { // Process using the convolution engine. // Note that we can handle the case where nothing is connected to the input, in which case we'll just feed silence into the convolver. // FIXME: If we wanted to get fancy we could try to factor in the 'tail time' and stop processing once the tail dies down if // we keep getting fed silence. m_reverb->process(input(0)->bus(), outputBus, framesToProcess); } } else { // Too bad - the tryLock() failed. We must be in the middle of setting a new impulse response. outputBus->zero(); } }
float AudioParamTimeline::valueForContextTime(AudioContext* context, float defaultValue, bool& hasValue) { ASSERT(context); { MutexTryLocker tryLocker(m_eventsLock); if (!tryLocker.locked() || !context || !m_events.size() || context->currentTime() < m_events[0].time()) { hasValue = false; return defaultValue; } } // Ask for just a single value. float value; float sampleRate = context->sampleRate(); float startTime = narrowPrecisionToFloat(context->currentTime()); float endTime = startTime + 1.1f / sampleRate; // time just beyond one sample-frame float controlRate = sampleRate / AudioNode::ProcessingSizeInFrames; // one parameter change per render quantum value = valuesForTimeRange(startTime, endTime, defaultValue, &value, 1, sampleRate, controlRate); hasValue = true; return value; }
void WaveShaperProcessor::process(const AudioBus* source, AudioBus* destination, size_t framesToProcess) { if (!isInitialized()) { destination->zero(); return; } bool channelCountMatches = source->numberOfChannels() == destination->numberOfChannels() && source->numberOfChannels() == m_kernels.size(); ASSERT(channelCountMatches); if (!channelCountMatches) return; // The audio thread can't block on this lock, so we call tryLock() instead. MutexTryLocker tryLocker(m_processLock); if (tryLocker.locked()) { // For each channel of our input, process using the corresponding WaveShaperDSPKernel into the output channel. for (unsigned i = 0; i < m_kernels.size(); ++i) m_kernels[i]->process(source->channel(i)->data(), destination->channel(i)->mutableData(), framesToProcess); } else { // Too bad - the tryLock() failed. We must be in the middle of a setCurve() call. destination->zero(); } }
void ScriptProcessorHandler::process(size_t framesToProcess) { // Discussion about inputs and outputs: // As in other AudioNodes, ScriptProcessorNode uses an AudioBus for its input // and output (see inputBus and outputBus below). Additionally, there is a // double-buffering for input and output which is exposed directly to // JavaScript (see inputBuffer and outputBuffer below). This node is the // producer for inputBuffer and the consumer for outputBuffer. The JavaScript // code is the consumer of inputBuffer and the producer for outputBuffer. // Get input and output busses. AudioBus* inputBus = input(0).bus(); AudioBus* outputBus = output(0).bus(); // Get input and output buffers. We double-buffer both the input and output // sides. unsigned doubleBufferIndex = this->doubleBufferIndex(); bool isDoubleBufferIndexGood = doubleBufferIndex < 2 && doubleBufferIndex < m_inputBuffers.size() && doubleBufferIndex < m_outputBuffers.size(); DCHECK(isDoubleBufferIndexGood); if (!isDoubleBufferIndexGood) return; AudioBuffer* inputBuffer = m_inputBuffers[doubleBufferIndex].get(); AudioBuffer* outputBuffer = m_outputBuffers[doubleBufferIndex].get(); // Check the consistency of input and output buffers. unsigned numberOfInputChannels = m_internalInputBus->numberOfChannels(); bool buffersAreGood = outputBuffer && bufferSize() == outputBuffer->length() && m_bufferReadWriteIndex + framesToProcess <= bufferSize(); // If the number of input channels is zero, it's ok to have inputBuffer = 0. if (m_internalInputBus->numberOfChannels()) buffersAreGood = buffersAreGood && inputBuffer && bufferSize() == inputBuffer->length(); DCHECK(buffersAreGood); if (!buffersAreGood) return; // We assume that bufferSize() is evenly divisible by framesToProcess - should // always be true, but we should still check. bool isFramesToProcessGood = framesToProcess && bufferSize() >= framesToProcess && !(bufferSize() % framesToProcess); DCHECK(isFramesToProcessGood); if (!isFramesToProcessGood) return; unsigned numberOfOutputChannels = outputBus->numberOfChannels(); bool channelsAreGood = (numberOfInputChannels == m_numberOfInputChannels) && (numberOfOutputChannels == m_numberOfOutputChannels); DCHECK(channelsAreGood); if (!channelsAreGood) return; for (unsigned i = 0; i < numberOfInputChannels; ++i) m_internalInputBus->setChannelMemory( i, inputBuffer->getChannelData(i)->data() + m_bufferReadWriteIndex, framesToProcess); if (numberOfInputChannels) m_internalInputBus->copyFrom(*inputBus); // Copy from the output buffer to the output. for (unsigned i = 0; i < numberOfOutputChannels; ++i) memcpy(outputBus->channel(i)->mutableData(), outputBuffer->getChannelData(i)->data() + m_bufferReadWriteIndex, sizeof(float) * framesToProcess); // Update the buffering index. m_bufferReadWriteIndex = (m_bufferReadWriteIndex + framesToProcess) % bufferSize(); // m_bufferReadWriteIndex will wrap back around to 0 when the current input // and output buffers are full. // When this happens, fire an event and swap buffers. if (!m_bufferReadWriteIndex) { // Avoid building up requests on the main thread to fire process events when // they're not being handled. This could be a problem if the main thread is // very busy doing other things and is being held up handling previous // requests. The audio thread can't block on this lock, so we call // tryLock() instead. MutexTryLocker tryLocker(m_processEventLock); if (!tryLocker.locked()) { // We're late in handling the previous request. The main thread must be // very busy. The best we can do is clear out the buffer ourself here. outputBuffer->zero(); } else if (context()->getExecutionContext()) { // With the realtime context, execute the script code asynchronously // and do not wait. if (context()->hasRealtimeConstraint()) { // Fire the event on the main thread with the appropriate buffer // index. context()->getExecutionContext()->postTask( BLINK_FROM_HERE, createCrossThreadTask(&ScriptProcessorHandler::fireProcessEvent, crossThreadUnretained(this), m_doubleBufferIndex)); } else { // If this node is in the offline audio context, use the // waitable event to synchronize to the offline rendering thread. std::unique_ptr<WaitableEvent> waitableEvent = wrapUnique(new WaitableEvent()); context()->getExecutionContext()->postTask( BLINK_FROM_HERE, createCrossThreadTask( &ScriptProcessorHandler::fireProcessEventForOfflineAudioContext, crossThreadUnretained(this), m_doubleBufferIndex, crossThreadUnretained(waitableEvent.get()))); // Okay to block the offline audio rendering thread since it is // not the actual audio device thread. waitableEvent->wait(); } } swapBuffers(); } }
void ScriptProcessorHandler::process(size_t framesToProcess) { // Discussion about inputs and outputs: // As in other AudioNodes, ScriptProcessorNode uses an AudioBus for its input and output (see inputBus and outputBus below). // Additionally, there is a double-buffering for input and output which is exposed directly to JavaScript (see inputBuffer and outputBuffer below). // This node is the producer for inputBuffer and the consumer for outputBuffer. // The JavaScript code is the consumer of inputBuffer and the producer for outputBuffer. // Get input and output busses. AudioBus* inputBus = input(0).bus(); AudioBus* outputBus = output(0).bus(); // Get input and output buffers. We double-buffer both the input and output sides. unsigned doubleBufferIndex = this->doubleBufferIndex(); bool isDoubleBufferIndexGood = doubleBufferIndex < 2 && doubleBufferIndex < m_inputBuffers.size() && doubleBufferIndex < m_outputBuffers.size(); ASSERT(isDoubleBufferIndexGood); if (!isDoubleBufferIndexGood) return; AudioBuffer* inputBuffer = m_inputBuffers[doubleBufferIndex].get(); AudioBuffer* outputBuffer = m_outputBuffers[doubleBufferIndex].get(); // Check the consistency of input and output buffers. unsigned numberOfInputChannels = m_internalInputBus->numberOfChannels(); bool buffersAreGood = outputBuffer && bufferSize() == outputBuffer->length() && m_bufferReadWriteIndex + framesToProcess <= bufferSize(); // If the number of input channels is zero, it's ok to have inputBuffer = 0. if (m_internalInputBus->numberOfChannels()) buffersAreGood = buffersAreGood && inputBuffer && bufferSize() == inputBuffer->length(); ASSERT(buffersAreGood); if (!buffersAreGood) return; // We assume that bufferSize() is evenly divisible by framesToProcess - should always be true, but we should still check. bool isFramesToProcessGood = framesToProcess && bufferSize() >= framesToProcess && !(bufferSize() % framesToProcess); ASSERT(isFramesToProcessGood); if (!isFramesToProcessGood) return; unsigned numberOfOutputChannels = outputBus->numberOfChannels(); bool channelsAreGood = (numberOfInputChannels == m_numberOfInputChannels) && (numberOfOutputChannels == m_numberOfOutputChannels); ASSERT(channelsAreGood); if (!channelsAreGood) return; for (unsigned i = 0; i < numberOfInputChannels; ++i) m_internalInputBus->setChannelMemory(i, inputBuffer->getChannelData(i)->data() + m_bufferReadWriteIndex, framesToProcess); if (numberOfInputChannels) m_internalInputBus->copyFrom(*inputBus); // Copy from the output buffer to the output. for (unsigned i = 0; i < numberOfOutputChannels; ++i) memcpy(outputBus->channel(i)->mutableData(), outputBuffer->getChannelData(i)->data() + m_bufferReadWriteIndex, sizeof(float) * framesToProcess); // Update the buffering index. m_bufferReadWriteIndex = (m_bufferReadWriteIndex + framesToProcess) % bufferSize(); // m_bufferReadWriteIndex will wrap back around to 0 when the current input and output buffers are full. // When this happens, fire an event and swap buffers. if (!m_bufferReadWriteIndex) { // Avoid building up requests on the main thread to fire process events when they're not being handled. // This could be a problem if the main thread is very busy doing other things and is being held up handling previous requests. // The audio thread can't block on this lock, so we call tryLock() instead. MutexTryLocker tryLocker(m_processEventLock); if (!tryLocker.locked()) { // We're late in handling the previous request. The main thread must be very busy. // The best we can do is clear out the buffer ourself here. outputBuffer->zero(); } else if (context()->executionContext()) { // Fire the event on the main thread, not this one (which is the realtime audio thread). m_doubleBufferIndexForEvent = m_doubleBufferIndex; context()->executionContext()->postTask(BLINK_FROM_HERE, createCrossThreadTask(&ScriptProcessorHandler::fireProcessEvent, PassRefPtr<ScriptProcessorHandler>(this))); } swapBuffers(); } }
void AudioBufferSourceNode::process(size_t framesToProcess) { AudioBus* outputBus = output(0)->bus(); if (!isInitialized()) { outputBus->zero(); return; } // The audio thread can't block on this lock, so we call tryLock() instead. MutexTryLocker tryLocker(m_processLock); if (tryLocker.locked()) { // Check if it's time to start playing. double sampleRate = this->sampleRate(); size_t quantumStartFrame = context()->currentSampleFrame(); size_t quantumEndFrame = quantumStartFrame + framesToProcess; size_t startFrame = AudioUtilities::timeToSampleFrame(m_startTime, sampleRate); size_t endFrame = m_endTime == UnknownTime ? 0 : AudioUtilities::timeToSampleFrame(m_endTime, sampleRate); // If we know the end time and it's already passed, then don't bother doing any more rendering this cycle. if (m_endTime != UnknownTime && endFrame <= quantumStartFrame) { m_virtualReadIndex = 0; finish(); } if (m_playbackState == UNSCHEDULED_STATE || m_playbackState == FINISHED_STATE || !buffer() || startFrame >= quantumEndFrame) { // FIXME: can optimize here by propagating silent hint instead of forcing the whole chain to process silence. outputBus->zero(); return; } if (m_playbackState == SCHEDULED_STATE) { // Increment the active source count only if we're transitioning from SCHEDULED_STATE to PLAYING_STATE. m_playbackState = PLAYING_STATE; context()->incrementActiveSourceCount(); } size_t quantumFrameOffset = startFrame > quantumStartFrame ? startFrame - quantumStartFrame : 0; quantumFrameOffset = min(quantumFrameOffset, framesToProcess); // clamp to valid range size_t bufferFramesToProcess = framesToProcess - quantumFrameOffset; for (unsigned i = 0; i < outputBus->numberOfChannels(); ++i) m_destinationChannels[i] = outputBus->channel(i)->mutableData(); // Render by reading directly from the buffer. renderFromBuffer(outputBus, quantumFrameOffset, bufferFramesToProcess); // Apply the gain (in-place) to the output bus. float totalGain = gain()->value() * m_buffer->gain(); outputBus->copyWithGainFrom(*outputBus, &m_lastGain, totalGain); // If the end time is somewhere in the middle of this time quantum, then simply zero out the // frames starting at the end time. if (m_endTime != UnknownTime && endFrame >= quantumStartFrame && endFrame < quantumEndFrame) { size_t zeroStartFrame = endFrame - quantumStartFrame; size_t framesToZero = framesToProcess - zeroStartFrame; bool isSafe = zeroStartFrame < framesToProcess && framesToZero <= framesToProcess && zeroStartFrame + framesToZero <= framesToProcess; ASSERT(isSafe); if (isSafe) { for (unsigned i = 0; i < outputBus->numberOfChannels(); ++i) memset(m_destinationChannels[i] + zeroStartFrame, 0, sizeof(float) * framesToZero); } m_virtualReadIndex = 0; finish(); } outputBus->clearSilentFlag(); } else { // Too bad - the tryLock() failed. We must be in the middle of changing buffers and were already outputting silence anyway. outputBus->zero(); } }