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