1 /* 2 * Copyright (C) 2012, 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 "modules/webaudio/AudioScheduledSourceNode.h" 30 31 #include "bindings/v8/ExceptionState.h" 32 #include "core/dom/ExceptionCode.h" 33 #include "core/events/Event.h" 34 #include "platform/audio/AudioUtilities.h" 35 #include "modules/webaudio/AudioContext.h" 36 #include <algorithm> 37 #include "wtf/MathExtras.h" 38 39 using namespace std; 40 41 namespace WebCore { 42 43 const double AudioScheduledSourceNode::UnknownTime = -1; 44 45 AudioScheduledSourceNode::AudioScheduledSourceNode(AudioContext* context, float sampleRate) 46 : AudioSourceNode(context, sampleRate) 47 , m_playbackState(UNSCHEDULED_STATE) 48 , m_startTime(0) 49 , m_endTime(UnknownTime) 50 , m_hasEndedListener(false) 51 , m_stopCalled(false) 52 { 53 } 54 55 void AudioScheduledSourceNode::updateSchedulingInfo(size_t quantumFrameSize, 56 AudioBus* outputBus, 57 size_t& quantumFrameOffset, 58 size_t& nonSilentFramesToProcess) 59 { 60 ASSERT(outputBus); 61 if (!outputBus) 62 return; 63 64 ASSERT(quantumFrameSize == AudioNode::ProcessingSizeInFrames); 65 if (quantumFrameSize != AudioNode::ProcessingSizeInFrames) 66 return; 67 68 double sampleRate = this->sampleRate(); 69 70 // quantumStartFrame : Start frame of the current time quantum. 71 // quantumEndFrame : End frame of the current time quantum. 72 // startFrame : Start frame for this source. 73 // endFrame : End frame for this source. 74 size_t quantumStartFrame = context()->currentSampleFrame(); 75 size_t quantumEndFrame = quantumStartFrame + quantumFrameSize; 76 size_t startFrame = AudioUtilities::timeToSampleFrame(m_startTime, sampleRate); 77 size_t endFrame = m_endTime == UnknownTime ? 0 : AudioUtilities::timeToSampleFrame(m_endTime, sampleRate); 78 79 // If we know the end time and it's already passed, then don't bother doing any more rendering this cycle. 80 if (m_endTime != UnknownTime && endFrame <= quantumStartFrame) 81 finish(); 82 83 if (m_playbackState == UNSCHEDULED_STATE || m_playbackState == FINISHED_STATE || startFrame >= quantumEndFrame) { 84 // Output silence. 85 outputBus->zero(); 86 nonSilentFramesToProcess = 0; 87 return; 88 } 89 90 // Check if it's time to start playing. 91 if (m_playbackState == SCHEDULED_STATE) { 92 // Increment the active source count only if we're transitioning from SCHEDULED_STATE to PLAYING_STATE. 93 m_playbackState = PLAYING_STATE; 94 context()->incrementActiveSourceCount(); 95 } 96 97 quantumFrameOffset = startFrame > quantumStartFrame ? startFrame - quantumStartFrame : 0; 98 quantumFrameOffset = min(quantumFrameOffset, quantumFrameSize); // clamp to valid range 99 nonSilentFramesToProcess = quantumFrameSize - quantumFrameOffset; 100 101 if (!nonSilentFramesToProcess) { 102 // Output silence. 103 outputBus->zero(); 104 return; 105 } 106 107 // Handle silence before we start playing. 108 // Zero any initial frames representing silence leading up to a rendering start time in the middle of the quantum. 109 if (quantumFrameOffset) { 110 for (unsigned i = 0; i < outputBus->numberOfChannels(); ++i) 111 memset(outputBus->channel(i)->mutableData(), 0, sizeof(float) * quantumFrameOffset); 112 } 113 114 // Handle silence after we're done playing. 115 // If the end time is somewhere in the middle of this time quantum, then zero out the 116 // frames from the end time to the very end of the quantum. 117 if (m_endTime != UnknownTime && endFrame >= quantumStartFrame && endFrame < quantumEndFrame) { 118 size_t zeroStartFrame = endFrame - quantumStartFrame; 119 size_t framesToZero = quantumFrameSize - zeroStartFrame; 120 121 bool isSafe = zeroStartFrame < quantumFrameSize && framesToZero <= quantumFrameSize && zeroStartFrame + framesToZero <= quantumFrameSize; 122 ASSERT(isSafe); 123 124 if (isSafe) { 125 if (framesToZero > nonSilentFramesToProcess) 126 nonSilentFramesToProcess = 0; 127 else 128 nonSilentFramesToProcess -= framesToZero; 129 130 for (unsigned i = 0; i < outputBus->numberOfChannels(); ++i) 131 memset(outputBus->channel(i)->mutableData() + zeroStartFrame, 0, sizeof(float) * framesToZero); 132 } 133 134 finish(); 135 } 136 137 return; 138 } 139 140 141 void AudioScheduledSourceNode::start(double when, ExceptionState& exceptionState) 142 { 143 ASSERT(isMainThread()); 144 145 if (m_playbackState != UNSCHEDULED_STATE) { 146 exceptionState.throwDOMException( 147 InvalidStateError, 148 "cannot call start more than once."); 149 return; 150 } 151 152 m_startTime = when; 153 m_playbackState = SCHEDULED_STATE; 154 } 155 156 void AudioScheduledSourceNode::stop(double when, ExceptionState& exceptionState) 157 { 158 ASSERT(isMainThread()); 159 160 if (m_stopCalled) { 161 exceptionState.throwDOMException( 162 InvalidStateError, 163 "cannot call stop more than once."); 164 } else if (m_playbackState == UNSCHEDULED_STATE) { 165 exceptionState.throwDOMException( 166 InvalidStateError, 167 "cannot call stop without calling start first."); 168 } else { 169 // This can only happen from the SCHEDULED_STATE or PLAYING_STATE. The UNSCHEDULED_STATE is 170 // handled above, and the FINISHED_STATE is only reachable after stop() has been called, and 171 // hence m_stopCalled is true. But that case is handled above. 172 when = max(0.0, when); 173 m_endTime = when; 174 m_stopCalled = true; 175 } 176 } 177 178 void AudioScheduledSourceNode::setOnended(PassRefPtr<EventListener> listener, DOMWrapperWorld* isolatedWorld) 179 { 180 m_hasEndedListener = listener; 181 setAttributeEventListener(EventTypeNames::ended, listener, isolatedWorld); 182 } 183 184 void AudioScheduledSourceNode::finish() 185 { 186 if (m_playbackState != FINISHED_STATE) { 187 // Let the context dereference this AudioNode. 188 context()->notifyNodeFinishedProcessing(this); 189 m_playbackState = FINISHED_STATE; 190 context()->decrementActiveSourceCount(); 191 } 192 193 if (m_hasEndedListener) { 194 // |task| will keep the AudioScheduledSourceNode alive until the listener has been handled. 195 OwnPtr<NotifyEndedTask> task = adoptPtr(new NotifyEndedTask(this)); 196 callOnMainThread(&AudioScheduledSourceNode::notifyEndedDispatch, task.leakPtr()); 197 } 198 } 199 200 void AudioScheduledSourceNode::notifyEndedDispatch(void* userData) 201 { 202 OwnPtr<NotifyEndedTask> task = adoptPtr(static_cast<NotifyEndedTask*>(userData)); 203 204 task->notifyEnded(); 205 } 206 207 AudioScheduledSourceNode::NotifyEndedTask::NotifyEndedTask(PassRefPtr<AudioScheduledSourceNode> sourceNode) 208 : m_scheduledNode(sourceNode) 209 { 210 } 211 212 void AudioScheduledSourceNode::NotifyEndedTask::notifyEnded() 213 { 214 RefPtr<Event> event = Event::create(EventTypeNames::ended); 215 event->setTarget(m_scheduledNode); 216 m_scheduledNode->dispatchEvent(event.get()); 217 } 218 219 } // namespace WebCore 220 221 #endif // ENABLE(WEB_AUDIO) 222