1/*
2 * Copyright (C) 2011, Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25#include "config.h"
26
27#if ENABLE(WEB_AUDIO)
28
29#include "OfflineAudioDestinationNode.h"
30
31#include "AudioBus.h"
32#include "AudioContext.h"
33#include "HRTFDatabaseLoader.h"
34#include <algorithm>
35#include <wtf/IsoMallocInlines.h>
36#include <wtf/MainThread.h>
37
38namespace WebCore {
39
40WTF_MAKE_ISO_ALLOCATED_IMPL(OfflineAudioDestinationNode);
41
42const size_t renderQuantumSize = 128;
43
44OfflineAudioDestinationNode::OfflineAudioDestinationNode(AudioContext& context, AudioBuffer* renderTarget)
45 : AudioDestinationNode(context, renderTarget->sampleRate())
46 , m_renderTarget(renderTarget)
47 , m_startedRendering(false)
48{
49 m_renderBus = AudioBus::create(renderTarget->numberOfChannels(), renderQuantumSize);
50}
51
52OfflineAudioDestinationNode::~OfflineAudioDestinationNode()
53{
54 uninitialize();
55}
56
57void OfflineAudioDestinationNode::initialize()
58{
59 if (isInitialized())
60 return;
61
62 AudioNode::initialize();
63}
64
65void OfflineAudioDestinationNode::uninitialize()
66{
67 if (!isInitialized())
68 return;
69
70 if (m_renderThread) {
71 m_renderThread->waitForCompletion();
72 m_renderThread = nullptr;
73 }
74
75 AudioNode::uninitialize();
76}
77
78void OfflineAudioDestinationNode::startRendering()
79{
80 ALWAYS_LOG(LOGIDENTIFIER);
81
82 ASSERT(isMainThread());
83 ASSERT(m_renderTarget.get());
84 if (!m_renderTarget.get())
85 return;
86
87 if (m_startedRendering)
88 return;
89
90 m_startedRendering = true;
91 ref();
92 // FIXME: Should we call lazyInitialize here?
93 // FIXME: We should probably limit the number of threads we create for offline audio.
94 m_renderThread = Thread::create("offline renderer", [this] {
95 bool didRender = offlineRender();
96 callOnMainThread([this, didRender] {
97 if (didRender)
98 context().fireCompletionEvent();
99 deref();
100 });
101 });
102}
103
104bool OfflineAudioDestinationNode::offlineRender()
105{
106 ASSERT(!isMainThread());
107 ASSERT(m_renderBus.get());
108 if (!m_renderBus.get())
109 return false;
110
111 bool isAudioContextInitialized = context().isInitialized();
112 // FIXME: We used to assert that isAudioContextInitialized is true here.
113 // But it's trivially false in imported/w3c/web-platform-tests/webaudio/the-audio-api/the-offlineaudiocontext-interface/current-time-block-size.html
114 if (!isAudioContextInitialized)
115 return false;
116
117 bool channelsMatch = m_renderBus->numberOfChannels() == m_renderTarget->numberOfChannels();
118 ASSERT(channelsMatch);
119 if (!channelsMatch)
120 return false;
121
122 bool isRenderBusAllocated = m_renderBus->length() >= renderQuantumSize;
123 ASSERT(isRenderBusAllocated);
124 if (!isRenderBusAllocated)
125 return false;
126
127 // Break up the render target into smaller "render quantize" sized pieces.
128 // Render until we're finished.
129 size_t framesToProcess = m_renderTarget->length();
130 unsigned numberOfChannels = m_renderTarget->numberOfChannels();
131
132 unsigned n = 0;
133 while (framesToProcess > 0) {
134 // Render one render quantum.
135 render(0, m_renderBus.get(), renderQuantumSize);
136
137 size_t framesAvailableToCopy = std::min(framesToProcess, renderQuantumSize);
138
139 for (unsigned channelIndex = 0; channelIndex < numberOfChannels; ++channelIndex) {
140 const float* source = m_renderBus->channel(channelIndex)->data();
141 float* destination = m_renderTarget->channelData(channelIndex)->data();
142 memcpy(destination + n, source, sizeof(float) * framesAvailableToCopy);
143 }
144
145 n += framesAvailableToCopy;
146 framesToProcess -= framesAvailableToCopy;
147 }
148
149 return true;
150}
151
152} // namespace WebCore
153
154#endif // ENABLE(WEB_AUDIO)
155