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 "AudioScheduledSourceNode.h" 30 31#include "AudioContext.h" 32#include "AudioUtilities.h" 33#include "Event.h" 34#include "ScriptController.h" 35#include <algorithm> 36#include <wtf/MathExtras.h> 37 38#if PLATFORM(IOS) 39#include "ScriptController.h" 40#endif 41 42namespace WebCore { 43 44const double AudioScheduledSourceNode::UnknownTime = -1; 45 46AudioScheduledSourceNode::AudioScheduledSourceNode(AudioContext* context, float sampleRate) 47 : AudioNode(context, sampleRate) 48 , m_playbackState(UNSCHEDULED_STATE) 49 , m_startTime(0) 50 , m_endTime(UnknownTime) 51 , m_hasEndedListener(false) 52{ 53} 54 55void 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 = std::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 140void AudioScheduledSourceNode::start(double when, ExceptionCode& ec) 141{ 142 ASSERT(isMainThread()); 143 144 if (ScriptController::processingUserGesture()) 145 context()->removeBehaviorRestriction(AudioContext::RequireUserGestureForAudioStartRestriction); 146 147 if (m_playbackState != UNSCHEDULED_STATE) { 148 ec = INVALID_STATE_ERR; 149 return; 150 } 151 152 m_startTime = when; 153 m_playbackState = SCHEDULED_STATE; 154} 155 156void AudioScheduledSourceNode::stop(double when, ExceptionCode& ec) 157{ 158 ASSERT(isMainThread()); 159 if ((m_playbackState == UNSCHEDULED_STATE) || (m_endTime != UnknownTime)) { 160 ec = INVALID_STATE_ERR; 161 return; 162 } 163 164 when = std::max<double>(0, when); 165 m_endTime = when; 166} 167 168#if ENABLE(LEGACY_WEB_AUDIO) 169void AudioScheduledSourceNode::noteOn(double when, ExceptionCode& ec) 170{ 171 start(when, ec); 172} 173 174void AudioScheduledSourceNode::noteOff(double when, ExceptionCode& ec) 175{ 176 stop(when, ec); 177} 178#endif 179 180void AudioScheduledSourceNode::setOnended(PassRefPtr<EventListener> listener) 181{ 182 m_hasEndedListener = listener; 183 setAttributeEventListener(eventNames().endedEvent, listener); 184} 185 186void AudioScheduledSourceNode::finish() 187{ 188 if (m_playbackState != FINISHED_STATE) { 189 // Let the context dereference this AudioNode. 190 context()->notifyNodeFinishedProcessing(this); 191 m_playbackState = FINISHED_STATE; 192 context()->decrementActiveSourceCount(); 193 } 194 195 if (m_hasEndedListener) 196 callOnMainThread(&AudioScheduledSourceNode::notifyEndedDispatch, this); 197} 198 199void AudioScheduledSourceNode::notifyEndedDispatch(void* userData) 200{ 201 static_cast<AudioScheduledSourceNode*>(userData)->notifyEnded(); 202} 203 204void AudioScheduledSourceNode::notifyEnded() 205{ 206 EventListener* listener = onended(); 207 if (!listener) 208 return; 209 210 RefPtr<Event> event = Event::create(eventNames().endedEvent, FALSE, FALSE); 211 event->setTarget(this); 212 listener->handleEvent(context()->scriptExecutionContext(), event.get()); 213} 214 215} // namespace WebCore 216 217#endif // ENABLE(WEB_AUDIO) 218