blob: 619c37873b44475730ab727e6fadad7c59c79690 [file] [log] [blame]
/*
* Copyright (C) 2011, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
#include "modules/webaudio/OfflineAudioDestinationNode.h"
#include "core/dom/ExecutionContextTask.h"
#include "modules/webaudio/AudioNodeInput.h"
#include "modules/webaudio/AudioNodeOutput.h"
#include "modules/webaudio/BaseAudioContext.h"
#include "modules/webaudio/OfflineAudioContext.h"
#include "platform/audio/AudioBus.h"
#include "platform/audio/DenormalDisabler.h"
#include "platform/audio/HRTFDatabaseLoader.h"
#include "public/platform/Platform.h"
#include "wtf/PtrUtil.h"
#include <algorithm>
namespace blink {
const size_t OfflineAudioDestinationHandler::renderQuantumSize = 128;
OfflineAudioDestinationHandler::OfflineAudioDestinationHandler(
AudioNode& node,
AudioBuffer* renderTarget)
: AudioDestinationHandler(node, renderTarget->sampleRate()),
m_renderTarget(renderTarget),
m_renderThread(wrapUnique(
Platform::current()->createThread("offline audio renderer"))),
m_framesProcessed(0),
m_framesToProcess(0),
m_isRenderingStarted(false),
m_shouldSuspend(false) {
m_renderBus =
AudioBus::create(renderTarget->numberOfChannels(), renderQuantumSize);
m_framesToProcess = m_renderTarget->length();
// Node-specific defaults.
m_channelCount = m_renderTarget->numberOfChannels();
setInternalChannelCountMode(Explicit);
setInternalChannelInterpretation(AudioBus::Speakers);
}
PassRefPtr<OfflineAudioDestinationHandler>
OfflineAudioDestinationHandler::create(AudioNode& node,
AudioBuffer* renderTarget) {
return adoptRef(new OfflineAudioDestinationHandler(node, renderTarget));
}
OfflineAudioDestinationHandler::~OfflineAudioDestinationHandler() {
DCHECK(!isInitialized());
}
void OfflineAudioDestinationHandler::dispose() {
uninitialize();
AudioDestinationHandler::dispose();
}
void OfflineAudioDestinationHandler::initialize() {
if (isInitialized())
return;
AudioHandler::initialize();
}
void OfflineAudioDestinationHandler::uninitialize() {
if (!isInitialized())
return;
if (m_renderThread)
m_renderThread.reset();
AudioHandler::uninitialize();
}
OfflineAudioContext* OfflineAudioDestinationHandler::context() const {
return static_cast<OfflineAudioContext*>(AudioDestinationHandler::context());
}
unsigned long OfflineAudioDestinationHandler::maxChannelCount() const {
return m_channelCount;
}
void OfflineAudioDestinationHandler::startRendering() {
DCHECK(isMainThread());
DCHECK(m_renderThread);
DCHECK(m_renderTarget);
if (!m_renderTarget)
return;
// Rendering was not started. Starting now.
if (!m_isRenderingStarted) {
m_isRenderingStarted = true;
m_renderThread->getWebTaskRunner()->postTask(
BLINK_FROM_HERE,
crossThreadBind(&OfflineAudioDestinationHandler::startOfflineRendering,
wrapPassRefPtr(this)));
return;
}
// Rendering is already started, which implicitly means we resume the
// rendering by calling |doOfflineRendering| on the render thread.
m_renderThread->getWebTaskRunner()->postTask(
BLINK_FROM_HERE,
crossThreadBind(&OfflineAudioDestinationHandler::doOfflineRendering,
wrapPassRefPtr(this)));
}
void OfflineAudioDestinationHandler::stopRendering() {
// offline audio rendering CANNOT BE stopped by JavaScript.
ASSERT_NOT_REACHED();
}
WebThread* OfflineAudioDestinationHandler::offlineRenderThread() {
DCHECK(m_renderThread);
return m_renderThread.get();
}
void OfflineAudioDestinationHandler::startOfflineRendering() {
DCHECK(!isMainThread());
DCHECK(m_renderBus);
if (!m_renderBus)
return;
bool isAudioContextInitialized = context()->isDestinationInitialized();
DCHECK(isAudioContextInitialized);
if (!isAudioContextInitialized)
return;
bool channelsMatch =
m_renderBus->numberOfChannels() == m_renderTarget->numberOfChannels();
DCHECK(channelsMatch);
if (!channelsMatch)
return;
bool isRenderBusAllocated = m_renderBus->length() >= renderQuantumSize;
DCHECK(isRenderBusAllocated);
if (!isRenderBusAllocated)
return;
// Start rendering.
doOfflineRendering();
}
void OfflineAudioDestinationHandler::doOfflineRendering() {
DCHECK(!isMainThread());
unsigned numberOfChannels = m_renderTarget->numberOfChannels();
// Reset the suspend flag.
m_shouldSuspend = false;
// If there is more to process and there is no suspension at the moment,
// do continue to render quanta. Then calling OfflineAudioContext.resume()
// will pick up the render loop again from where it was suspended.
while (m_framesToProcess > 0 && !m_shouldSuspend) {
// Suspend the rendering and update m_shouldSuspend if a scheduled
// suspend found at the current sample frame. Otherwise render one
// quantum and return false.
m_shouldSuspend =
renderIfNotSuspended(0, m_renderBus.get(), renderQuantumSize);
if (m_shouldSuspend)
return;
size_t framesAvailableToCopy =
std::min(m_framesToProcess, renderQuantumSize);
for (unsigned channelIndex = 0; channelIndex < numberOfChannels;
++channelIndex) {
const float* source = m_renderBus->channel(channelIndex)->data();
float* destination = m_renderTarget->getChannelData(channelIndex)->data();
memcpy(destination + m_framesProcessed, source,
sizeof(float) * framesAvailableToCopy);
}
m_framesProcessed += framesAvailableToCopy;
DCHECK_GE(m_framesToProcess, framesAvailableToCopy);
m_framesToProcess -= framesAvailableToCopy;
}
// Finish up the rendering loop if there is no more to process.
if (!m_framesToProcess)
finishOfflineRendering();
}
void OfflineAudioDestinationHandler::suspendOfflineRendering() {
DCHECK(!isMainThread());
// The actual rendering has been suspended. Notify the context.
if (context()->getExecutionContext()) {
context()->getExecutionContext()->postTask(
BLINK_FROM_HERE,
createCrossThreadTask(&OfflineAudioDestinationHandler::notifySuspend,
PassRefPtr<OfflineAudioDestinationHandler>(this),
context()->currentSampleFrame()));
}
}
void OfflineAudioDestinationHandler::finishOfflineRendering() {
DCHECK(!isMainThread());
// The actual rendering has been completed. Notify the context.
if (context()->getExecutionContext()) {
context()->getExecutionContext()->postTask(
BLINK_FROM_HERE, createCrossThreadTask(
&OfflineAudioDestinationHandler::notifyComplete,
PassRefPtr<OfflineAudioDestinationHandler>(this)));
}
}
void OfflineAudioDestinationHandler::notifySuspend(size_t frame) {
DCHECK(isMainThread());
if (context())
context()->resolveSuspendOnMainThread(frame);
}
void OfflineAudioDestinationHandler::notifyComplete() {
// The OfflineAudioContext might be gone.
if (context())
context()->fireCompletionEvent();
}
bool OfflineAudioDestinationHandler::renderIfNotSuspended(
AudioBus* sourceBus,
AudioBus* destinationBus,
size_t numberOfFrames) {
// We don't want denormals slowing down any of the audio processing
// since they can very seriously hurt performance.
// This will take care of all AudioNodes because they all process within this
// scope.
DenormalDisabler denormalDisabler;
// Need to check if the context actually alive. Otherwise the subsequent
// steps will fail. If the context is not alive somehow, return immediately
// and do nothing.
//
// TODO(hongchan): because the context can go away while rendering, so this
// check cannot guarantee the safe execution of the following steps.
DCHECK(context());
if (!context())
return false;
context()->deferredTaskHandler().setAudioThreadToCurrentThread();
// If the destination node is not initialized, pass the silence to the final
// audio destination (one step before the FIFO). This check is for the case
// where the destination is in the middle of tearing down process.
if (!isInitialized()) {
destinationBus->zero();
return false;
}
// Take care pre-render tasks at the beginning of each render quantum. Then
// it will stop the rendering loop if the context needs to be suspended
// at the beginning of the next render quantum.
if (context()->handlePreOfflineRenderTasks()) {
suspendOfflineRendering();
return true;
}
// Prepare the local audio input provider for this render quantum.
if (sourceBus)
m_localAudioInputProvider.set(sourceBus);
DCHECK_GE(numberOfInputs(), 1u);
if (numberOfInputs() < 1) {
destinationBus->zero();
return false;
}
// This will cause the node(s) connected to us to process, which in turn will
// pull on their input(s), all the way backwards through the rendering graph.
AudioBus* renderedBus = input(0).pull(destinationBus, numberOfFrames);
if (!renderedBus) {
destinationBus->zero();
} else if (renderedBus != destinationBus) {
// in-place processing was not possible - so copy
destinationBus->copyFrom(*renderedBus);
}
// Process nodes which need a little extra help because they are not connected
// to anything, but still need to process.
context()->deferredTaskHandler().processAutomaticPullNodes(numberOfFrames);
// Let the context take care of any business at the end of each render
// quantum.
context()->handlePostOfflineRenderTasks();
// Advance current sample-frame.
size_t newSampleFrame = m_currentSampleFrame + numberOfFrames;
releaseStore(&m_currentSampleFrame, newSampleFrame);
return false;
}
// ----------------------------------------------------------------
OfflineAudioDestinationNode::OfflineAudioDestinationNode(
BaseAudioContext& context,
AudioBuffer* renderTarget)
: AudioDestinationNode(context) {
setHandler(OfflineAudioDestinationHandler::create(*this, renderTarget));
}
OfflineAudioDestinationNode* OfflineAudioDestinationNode::create(
BaseAudioContext* context,
AudioBuffer* renderTarget) {
return new OfflineAudioDestinationNode(*context, renderTarget);
}
} // namespace blink