Changeset 278287 in webkit
- Timestamp:
- May 31, 2021, 5:07:14 PM (5 years ago)
- Location:
- trunk/Source/WebCore
- Files:
-
- 4 edited
-
ChangeLog (modified) (1 diff)
-
Modules/webaudio/AudioBufferSourceNode.cpp (modified) (18 diffs)
-
Modules/webaudio/AudioBufferSourceNode.h (modified) (5 diffs)
-
Modules/webaudio/AudioBufferSourceNode.idl (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/WebCore/ChangeLog
r278286 r278287 1 2021-05-31 Chris Dumez <cdumez@apple.com> 2 3 Fix thread safety issues in AudioBufferSourceNode 4 https://bugs.webkit.org/show_bug.cgi?id=226448 5 6 Reviewed by Darin Adler. 7 8 Adopt thread safety analysis annotations in AudioBufferSourceNode and fix the bugs 9 that were found by clang: 10 - We were failing to grab the processLock when setting the loop / loopStart / loopEnd attributes 11 on the main thread (Those are set by JS at any time). 12 - propagatesSilence() was failing to grab the lock when checking m_buffer on the audio thread. 13 14 * Modules/webaudio/AudioBufferSourceNode.cpp: 15 (WebCore::AudioBufferSourceNode::process): 16 (WebCore::AudioBufferSourceNode::renderSilenceAndFinishIfNotLooping): 17 (WebCore::AudioBufferSourceNode::renderFromBuffer): 18 (WebCore::AudioBufferSourceNode::setBuffer): 19 (WebCore::AudioBufferSourceNode::setLoop): 20 (WebCore::AudioBufferSourceNode::setLoopStart): 21 (WebCore::AudioBufferSourceNode::setLoopEnd): 22 (WebCore::AudioBufferSourceNode::adjustGrainParameters): 23 (WebCore::AudioBufferSourceNode::totalPitchRate): 24 (WebCore::AudioBufferSourceNode::propagatesSilence const): 25 * Modules/webaudio/AudioBufferSourceNode.h: 26 1 27 2021-05-31 Chris Dumez <cdumez@apple.com> 2 28 -
trunk/Source/WebCore/Modules/webaudio/AudioBufferSourceNode.cpp
r278253 r278287 67 67 auto node = adoptRef(*new AudioBufferSourceNode(context)); 68 68 69 node->setBuffer (WTFMove(options.buffer));69 node->setBufferForBindings(WTFMove(options.buffer)); 70 70 node->detune().setValue(options.detune); 71 node->setLoop (options.loop);72 node->setLoopEnd (options.loopEnd);73 node->setLoopStart (options.loopStart);71 node->setLoopForBindings(options.loop); 72 node->setLoopEndForBindings(options.loopEnd); 73 node->setLoopStartForBindings(options.loopStart); 74 74 node->playbackRate().setValue(options.playbackRate); 75 75 … … 111 111 Locker locker { AdoptLock, m_processLock }; 112 112 113 if (! buffer()) {113 if (!m_buffer) { 114 114 outputBus.zero(); 115 115 return; … … 119 119 // before the output bus is updated to the new number of channels because of use of tryLocks() in the context's updating system. 120 120 // In this case, if the buffer has just been changed and we're not quite ready yet, then just output silence. 121 if (numberOfChannels() != buffer()->numberOfChannels()) {121 if (numberOfChannels() != m_buffer->numberOfChannels()) { 122 122 outputBus.zero(); 123 123 return; … … 149 149 bool AudioBufferSourceNode::renderSilenceAndFinishIfNotLooping(AudioBus*, unsigned index, size_t framesToProcess) 150 150 { 151 if (! loop()) {151 if (!m_isLooping) { 152 152 // If we're not looping, then stop playing when we get to the end. 153 153 … … 172 172 // Basic sanity checking 173 173 ASSERT(bus); 174 ASSERT( buffer());175 if (!bus || ! buffer())174 ASSERT(m_buffer); 175 if (!bus || !m_buffer) 176 176 return false; 177 177 … … 206 206 unsigned writeIndex = destinationFrameOffset; 207 207 208 size_t bufferLength = buffer()->length();209 double bufferSampleRate = buffer()->sampleRate();208 size_t bufferLength = m_buffer->length(); 209 double bufferSampleRate = m_buffer->sampleRate(); 210 210 double pitchRate = totalPitchRate(); 211 211 bool reverse = pitchRate < 0; … … 229 229 double virtualDeltaFrames = maxFrame; 230 230 231 if ( loop()&& (m_loopStart || m_loopEnd) && m_loopStart >= 0 && m_loopEnd > 0 && m_loopStart < m_loopEnd) {231 if (m_isLooping && (m_loopStart || m_loopEnd) && m_loopStart >= 0 && m_loopEnd > 0 && m_loopStart < m_loopEnd) { 232 232 // Convert from seconds to sample-frames. 233 double loopMinFrame = m_loopStart * buffer()->sampleRate();234 double loopMaxFrame = m_loopEnd * buffer()->sampleRate();233 double loopMinFrame = m_loopStart * m_buffer->sampleRate(); 234 double loopMaxFrame = m_loopEnd * m_buffer->sampleRate(); 235 235 236 236 virtualMaxFrame = std::min(loopMaxFrame, virtualMaxFrame); … … 241 241 // If we're looping and the offset (virtualReadIndex) is past the end of the loop, wrap back to the 242 242 // beginning of the loop. For other cases, nothing needs to be done. 243 if ( loop()&& m_virtualReadIndex >= virtualMaxFrame) {244 m_virtualReadIndex = (m_loopStart < 0) ? 0 : (m_loopStart * buffer()->sampleRate());243 if (m_isLooping && m_virtualReadIndex >= virtualMaxFrame) { 244 m_virtualReadIndex = (m_loopStart < 0) ? 0 : (m_loopStart * m_buffer->sampleRate()); 245 245 m_virtualReadIndex = std::min(m_virtualReadIndex, static_cast<double>(bufferLength - 1)); 246 246 } … … 340 340 unsigned readIndex2 = readIndex + 1; 341 341 if (readIndex2 >= maxFrame) 342 readIndex2 = loop()? minFrame : readIndex;342 readIndex2 = m_isLooping ? minFrame : readIndex; 343 343 344 344 // Linear interpolation. … … 369 369 unsigned readIndex2 = readIndex + 1; 370 370 if (readIndex2 >= bufferLength) { 371 if ( loop()) {371 if (m_isLooping) { 372 372 // Make sure to wrap around at the end of the buffer. 373 373 readIndex2 = static_cast<unsigned>(virtualReadIndex + 1 - virtualDeltaFrames); … … 408 408 } 409 409 410 ExceptionOr<void> AudioBufferSourceNode::setBuffer (RefPtr<AudioBuffer>&& buffer)410 ExceptionOr<void> AudioBufferSourceNode::setBufferForBindings(RefPtr<AudioBuffer>&& buffer) 411 411 { 412 412 ASSERT(isMainThread()); 413 413 DEBUG_LOG(LOGIDENTIFIER); 414 415 if (buffer && m_wasBufferSet)416 return Exception { InvalidStateError, "The buffer was already set"_s };417 414 418 415 // The context must be locked since changing the buffer can re-configure the number of channels that are output. … … 421 418 // This synchronizes with process(). 422 419 Locker locker { m_processLock }; 420 421 if (buffer && m_wasBufferSet) 422 return Exception { InvalidStateError, "The buffer was already set"_s }; 423 423 424 424 if (buffer) { … … 456 456 { 457 457 return startPlaying(when, grainOffset, grainDuration); 458 } 459 460 void AudioBufferSourceNode::setLoopForBindings(bool looping) 461 { 462 ASSERT(isMainThread()); 463 Locker locker { m_processLock }; 464 m_isLooping = looping; 465 } 466 467 void AudioBufferSourceNode::setLoopStartForBindings(double loopStart) 468 { 469 ASSERT(isMainThread()); 470 Locker locker { m_processLock }; 471 m_loopStart = loopStart; 472 } 473 474 void AudioBufferSourceNode::setLoopEndForBindings(double loopEnd) 475 { 476 ASSERT(isMainThread()); 477 Locker locker { m_processLock }; 478 m_loopEnd = loopEnd; 458 479 } 459 480 … … 497 518 ASSERT(m_processLock.isHeld()); 498 519 499 auto buffer = this->buffer(); 500 if (!buffer) 520 if (!m_buffer) 501 521 return; 502 522 503 523 // Do sanity checking of grain parameters versus buffer size. 504 double bufferDuration = buffer->duration();524 double bufferDuration = m_buffer->duration(); 505 525 506 526 m_grainOffset = std::min(bufferDuration, m_grainOffset); … … 509 529 m_grainDuration = bufferDuration - m_grainOffset; 510 530 511 if (m_wasGrainDurationGiven && loop()) {531 if (m_wasGrainDurationGiven && m_isLooping) { 512 532 // We're looping a grain with a grain duration specified. Schedule the loop 513 533 // to stop after grainDuration seconds after starting, possibly running the … … 524 544 // Since playbackRate == 1 is very common, it's worth considering quality. 525 545 if (playbackRate().value() < 0) 526 m_virtualReadIndex = AudioUtilities::timeToSampleFrame(m_grainOffset + m_grainDuration, buffer->sampleRate()) - 1;546 m_virtualReadIndex = AudioUtilities::timeToSampleFrame(m_grainOffset + m_grainDuration, m_buffer->sampleRate()) - 1; 527 547 else 528 m_virtualReadIndex = AudioUtilities::timeToSampleFrame(m_grainOffset, buffer->sampleRate());548 m_virtualReadIndex = AudioUtilities::timeToSampleFrame(m_grainOffset, m_buffer->sampleRate()); 529 549 } 530 550 … … 534 554 // Normally it's not an issue because buffers are loaded at the AudioContext's sample-rate, but we can handle it in any case. 535 555 double sampleRateFactor = 1.0; 536 if ( buffer())537 sampleRateFactor = buffer()->sampleRate() / static_cast<double>(sampleRate());556 if (m_buffer) 557 sampleRateFactor = m_buffer->sampleRate() / static_cast<double>(sampleRate()); 538 558 539 559 double basePitchRate = playbackRate().finalValue(); … … 554 574 bool AudioBufferSourceNode::propagatesSilence() const 555 575 { 556 return !isPlayingOrScheduled() || hasFinished() || !m_buffer; 576 ASSERT(context().isAudioThread()); 577 if (!isPlayingOrScheduled() || hasFinished()) 578 return true; 579 580 if (!m_processLock.tryLock()) { 581 // We weren't able to get the lock so we assume we have a buffer. 582 return false; 583 } 584 Locker locker { AdoptLock, m_processLock }; 585 return !m_buffer; 557 586 } 558 587 -
trunk/Source/WebCore/Modules/webaudio/AudioBufferSourceNode.h
r278253 r278287 50 50 void process(size_t framesToProcess) final; 51 51 52 // setBuffer() is called on the main thread. This is the buffer we use for playback. 53 ExceptionOr<void> setBuffer(RefPtr<AudioBuffer>&&); 54 AudioBuffer* buffer() { return m_buffer.get(); } 52 // setBufferForBindings() is called on the main thread. This is the buffer we use for playback. 53 ExceptionOr<void> setBufferForBindings(RefPtr<AudioBuffer>&&); 54 55 // This function does not lock before accessing the buffer and should therefore only be called on the main thread. 56 AudioBuffer* bufferForBindings() WTF_IGNORES_THREAD_SAFETY_ANALYSIS { ASSERT(isMainThread()); return m_buffer.get(); } 55 57 56 58 // numberOfChannels() returns the number of output channels. This value equals the number of channels from the buffer. … … 61 63 ExceptionOr<void> startLater(double when, double grainOffset, std::optional<double> grainDuration); 62 64 63 // Note: the attribute was originally exposed as .looping, but to be more consistent in naming with <audio> 64 // and with how it's described in the specification, the proper attribute name is .loop 65 // The old attribute is kept for backwards compatibility. 66 bool loop() const { return m_isLooping; } 67 void setLoop(bool looping) { m_isLooping = looping; } 65 // This function doesn't grab the lock before accessing m_isLooping and is thus only safe to call on the main thread. 66 bool loopForBindings() const WTF_IGNORES_THREAD_SAFETY_ANALYSIS { ASSERT(isMainThread()); return m_isLooping; } 67 void setLoopForBindings(bool); 68 68 69 69 // Loop times in seconds. 70 double loopStart () const { return m_loopStart; }71 double loopEnd () const { return m_loopEnd; }72 void setLoopStart (double loopStart) { m_loopStart = loopStart; }73 void setLoopEnd (double loopEnd) { m_loopEnd = loopEnd; }70 double loopStartForBindings() const WTF_IGNORES_THREAD_SAFETY_ANALYSIS { ASSERT(isMainThread()); return m_loopStart; } // This function doesn't grab the lock and is thus only safe to call on the main thread. 71 double loopEndForBindings() const WTF_IGNORES_THREAD_SAFETY_ANALYSIS { ASSERT(isMainThread()); return m_loopEnd; } // This function doesn't grab the lock and is thus only safe to call on the main thread. 72 void setLoopStartForBindings(double); 73 void setLoopEndForBindings(double); 74 74 75 75 AudioParam& detune() { return m_detune.get(); } … … 89 89 90 90 ExceptionOr<void> startPlaying(double when, double grainOffset, std::optional<double> grainDuration); 91 void adjustGrainParameters() ;91 void adjustGrainParameters() WTF_REQUIRES_LOCK(m_processLock); 92 92 93 93 // Returns true on success. 94 bool renderFromBuffer(AudioBus*, unsigned destinationFrameOffset, size_t numberOfFrames, double startFrameOffset) ;94 bool renderFromBuffer(AudioBus*, unsigned destinationFrameOffset, size_t numberOfFrames, double startFrameOffset) WTF_REQUIRES_LOCK(m_processLock); 95 95 96 96 // Render silence starting from "index" frame in AudioBus. 97 inline bool renderSilenceAndFinishIfNotLooping(AudioBus*, unsigned index, size_t framesToProcess) ;97 inline bool renderSilenceAndFinishIfNotLooping(AudioBus*, unsigned index, size_t framesToProcess) WTF_REQUIRES_LOCK(m_processLock); 98 98 99 99 // m_buffer holds the sample data which this node outputs. 100 RefPtr<AudioBuffer> m_buffer ;100 RefPtr<AudioBuffer> m_buffer WTF_GUARDED_BY_LOCK(m_processLock); // Only modified on the main thread but used on the audio thread. 101 101 102 102 // Pointers for the buffer and destination. … … 109 109 // If m_isLooping is false, then this node will be done playing and become inactive after it reaches the end of the sample data in the buffer. 110 110 // If true, it will wrap around to the start of the buffer each time it reaches the end. 111 bool m_isLooping { false };111 bool m_isLooping WTF_GUARDED_BY_LOCK(m_processLock) { false }; // Only modified on the main thread but queried on the audio thread. 112 112 113 113 bool m_wasBufferSet { false }; 114 114 115 double m_loopStart { 0 };116 double m_loopEnd { 0 };115 double m_loopStart WTF_GUARDED_BY_LOCK(m_processLock) { 0 }; // Only modified on the main thread but queried on the audio thread. 116 double m_loopEnd WTF_GUARDED_BY_LOCK(m_processLock) { 0 }; // Only modified on the main thread but queried on the audio thread. 117 117 118 118 // m_virtualReadIndex is a sample-frame index into our buffer representing the current playback position. … … 121 121 122 122 // Granular playback 123 bool m_isGrain { false };124 double m_grainOffset { 0 }; // in seconds125 double m_grainDuration ; // in seconds126 double m_wasGrainDurationGiven { false };123 bool m_isGrain WTF_GUARDED_BY_LOCK(m_processLock) { false }; 124 double m_grainOffset WTF_GUARDED_BY_LOCK(m_processLock) { 0 }; // in seconds 125 double m_grainDuration WTF_GUARDED_BY_LOCK(m_processLock); // in seconds 126 double m_wasGrainDurationGiven WTF_GUARDED_BY_LOCK(m_processLock) { false }; 127 127 128 128 // totalPitchRate() returns the instantaneous pitch rate (non-time preserving). 129 129 // It incorporates the base pitch rate, any sample-rate conversion factor from the buffer. 130 double totalPitchRate() ;130 double totalPitchRate() WTF_REQUIRES_LOCK(m_processLock); 131 131 132 132 // This synchronizes process() with setBuffer() which can cause dynamic channel count changes. -
trunk/Source/WebCore/Modules/webaudio/AudioBufferSourceNode.idl
r277530 r278287 32 32 [EnabledBySetting=WebAudio] constructor (BaseAudioContext context, optional AudioBufferSourceOptions options); 33 33 34 attribute AudioBuffer? buffer;34 [ImplementedAs=bufferForBindings] attribute AudioBuffer? buffer; 35 35 36 36 readonly attribute AudioParam playbackRate; 37 37 readonly attribute AudioParam detune; 38 38 39 attribute boolean loop;40 attribute double loopStart;41 attribute double loopEnd;39 [ImplementedAs=loopForBindings] attribute boolean loop; 40 [ImplementedAs=loopStartForBindings] attribute double loopStart; 41 [ImplementedAs=loopEndForBindings] attribute double loopEnd; 42 42 43 43 [ImplementedAs=startLater] undefined start(optional double when = 0, optional double grainOffset = 0, optional double grainDuration);
Note:
See TracChangeset
for help on using the changeset viewer.