Example #1
0
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();
    }
}
Example #6
0
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();
}
Example #7
0
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();
}
Example #8
0
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();
    }
}
Example #11
0
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;
}
Example #17
0
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();
    }
}
Example #18
0
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();
    }
}
Example #19
0
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();
    }
}
Example #21
0
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();
    }
}
Example #23
0
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();
    }
}