<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[198035] trunk/Source/WebCore</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/198035">198035</a></dd>
<dt>Author</dt> <dd>jer.noble@apple.com</dd>
<dt>Date</dt> <dd>2016-03-11 11:35:15 -0800 (Fri, 11 Mar 2016)</dd>
</dl>
<h3>Log Message</h3>
<pre>Web Audio becomes distorted after sample rate changes
https://bugs.webkit.org/show_bug.cgi?id=154538
<rdar://problem/24771292>
Reviewed by Darin Adler.
When the underlying audio hardware sample rate changes, the AudioUnit render callback will begin asking
for fewer or more frames. For example, when the sample rate goes from 44.1kHz to 48kHz, it will ask for
118 samples instead of 128. (And vice-versa, 140 samples instead of 128.) But the Web Audio engine can only
really handle requests in multiples of 128 samples. In the case where there are requests for < 128 samples,
actually render 128, but save off the unrequested samples in a separate bus. Then fill that bus during the
next request.
* platform/audio/AudioBus.cpp:
(WebCore::AudioBus::copyFromRange): Added utility method.
* platform/audio/AudioBus.h:
* platform/audio/ios/AudioDestinationIOS.cpp:
(WebCore::AudioDestinationIOS::AudioDestinationIOS): Create a "spare" bus.
(WebCore::assignAudioBuffersToBus): Moved from inside render.
(WebCore::AudioDestinationIOS::render): Save off extra samples to the "spare" bus.
* platform/audio/ios/AudioDestinationIOS.h:</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoreplatformaudioAudioBuscpp">trunk/Source/WebCore/platform/audio/AudioBus.cpp</a></li>
<li><a href="#trunkSourceWebCoreplatformaudioAudioBush">trunk/Source/WebCore/platform/audio/AudioBus.h</a></li>
<li><a href="#trunkSourceWebCoreplatformaudioiosAudioDestinationIOScpp">trunk/Source/WebCore/platform/audio/ios/AudioDestinationIOS.cpp</a></li>
<li><a href="#trunkSourceWebCoreplatformaudioiosAudioDestinationIOSh">trunk/Source/WebCore/platform/audio/ios/AudioDestinationIOS.h</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/ChangeLog (198034 => 198035)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/ChangeLog        2016-03-11 19:30:55 UTC (rev 198034)
+++ trunk/Source/WebCore/ChangeLog        2016-03-11 19:35:15 UTC (rev 198035)
</span><span class="lines">@@ -1,3 +1,27 @@
</span><ins>+2016-03-10 Jer Noble <jer.noble@apple.com>
+
+ Web Audio becomes distorted after sample rate changes
+ https://bugs.webkit.org/show_bug.cgi?id=154538
+ <rdar://problem/24771292>
+
+ Reviewed by Darin Adler.
+
+ When the underlying audio hardware sample rate changes, the AudioUnit render callback will begin asking
+ for fewer or more frames. For example, when the sample rate goes from 44.1kHz to 48kHz, it will ask for
+ 118 samples instead of 128. (And vice-versa, 140 samples instead of 128.) But the Web Audio engine can only
+ really handle requests in multiples of 128 samples. In the case where there are requests for < 128 samples,
+ actually render 128, but save off the unrequested samples in a separate bus. Then fill that bus during the
+ next request.
+
+ * platform/audio/AudioBus.cpp:
+ (WebCore::AudioBus::copyFromRange): Added utility method.
+ * platform/audio/AudioBus.h:
+ * platform/audio/ios/AudioDestinationIOS.cpp:
+ (WebCore::AudioDestinationIOS::AudioDestinationIOS): Create a "spare" bus.
+ (WebCore::assignAudioBuffersToBus): Moved from inside render.
+ (WebCore::AudioDestinationIOS::render): Save off extra samples to the "spare" bus.
+ * platform/audio/ios/AudioDestinationIOS.h:
+
</ins><span class="cx"> 2016-03-11 Yusuke Suzuki <utatane.tea@gmail.com>
</span><span class="cx">
</span><span class="cx"> Unreviewed build fix after r198023.
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformaudioAudioBuscpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/audio/AudioBus.cpp (198034 => 198035)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/audio/AudioBus.cpp        2016-03-11 19:30:55 UTC (rev 198034)
+++ trunk/Source/WebCore/platform/audio/AudioBus.cpp        2016-03-11 19:35:15 UTC (rev 198035)
</span><span class="lines">@@ -213,6 +213,33 @@
</span><span class="cx"> channel(i)->scale(scale);
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+void AudioBus::copyFromRange(const AudioBus& sourceBus, unsigned startFrame, unsigned endFrame)
+{
+ if (!topologyMatches(sourceBus)) {
+ ASSERT_NOT_REACHED();
+ zero();
+ return;
+ }
+
+ size_t numberOfSourceFrames = sourceBus.length();
+ bool isRangeSafe = startFrame < endFrame && endFrame <= numberOfSourceFrames;
+ ASSERT(isRangeSafe);
+ if (!isRangeSafe) {
+ zero();
+ return;
+ }
+
+ unsigned numberOfChannels = this->numberOfChannels();
+ ASSERT(numberOfChannels <= MaxBusChannels);
+ if (numberOfChannels > MaxBusChannels) {
+ zero();
+ return;
+ }
+
+ for (unsigned i = 0; i < numberOfChannels; ++i)
+ channel(i)->copyFromRange(sourceBus.channel(i), startFrame, endFrame);
+}
+
</ins><span class="cx"> void AudioBus::copyFrom(const AudioBus& sourceBus, ChannelInterpretation channelInterpretation)
</span><span class="cx"> {
</span><span class="cx"> if (&sourceBus == this)
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformaudioAudioBush"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/audio/AudioBus.h (198034 => 198035)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/audio/AudioBus.h        2016-03-11 19:30:55 UTC (rev 198034)
+++ trunk/Source/WebCore/platform/audio/AudioBus.h        2016-03-11 19:35:15 UTC (rev 198035)
</span><span class="lines">@@ -122,6 +122,9 @@
</span><span class="cx"> void reset() { m_isFirstTime = true; } // for de-zippering
</span><span class="cx">
</span><span class="cx"> // Copies the samples from the source bus to this one.
</span><ins>+ void copyFromRange(const AudioBus& sourceBus, unsigned startFrame, unsigned endFrame);
+
+ // Copies the samples from the source bus to this one.
</ins><span class="cx"> // This is just a simple per-channel copy if the number of channels match, otherwise an up-mix or down-mix is done.
</span><span class="cx"> void copyFrom(const AudioBus& sourceBus, ChannelInterpretation = Speakers);
</span><span class="cx">
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformaudioiosAudioDestinationIOScpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/audio/ios/AudioDestinationIOS.cpp (198034 => 198035)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/audio/ios/AudioDestinationIOS.cpp        2016-03-11 19:30:55 UTC (rev 198034)
+++ trunk/Source/WebCore/platform/audio/ios/AudioDestinationIOS.cpp        2016-03-11 19:35:15 UTC (rev 198035)
</span><span class="lines">@@ -101,6 +101,7 @@
</span><span class="cx"> : m_outputUnit(0)
</span><span class="cx"> , m_callback(callback)
</span><span class="cx"> , m_renderBus(AudioBus::create(2, kRenderBufferSize, false))
</span><ins>+ , m_spareBus(AudioBus::create(2, kRenderBufferSize, true))
</ins><span class="cx"> , m_sampleRate(sampleRate)
</span><span class="cx"> , m_isPlaying(false)
</span><span class="cx"> {
</span><span class="lines">@@ -199,19 +200,47 @@
</span><span class="cx"> setIsPlaying(false);
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+static void assignAudioBuffersToBus(AudioBuffer* buffers, AudioBus& bus, UInt32 numberOfBuffers, UInt32 numberOfFrames, UInt32 frameOffset, UInt32 framesThisTime)
+{
+ for (UInt32 i = 0; i < numberOfBuffers; ++i) {
+ UInt32 bytesPerFrame = buffers[i].mDataByteSize / numberOfFrames;
+ UInt32 byteOffset = frameOffset * bytesPerFrame;
+ float* memory = reinterpret_cast<float*>(reinterpret_cast<char*>(buffers[i].mData) + byteOffset);
+ bus.setChannelMemory(i, memory, framesThisTime);
+ }
+}
+
</ins><span class="cx"> // Pulls on our provider to get rendered audio stream.
</span><span class="cx"> OSStatus AudioDestinationIOS::render(UInt32 numberOfFrames, AudioBufferList* ioData)
</span><span class="cx"> {
</span><span class="cx"> AudioBuffer* buffers = ioData->mBuffers;
</span><del>- for (UInt32 frameOffset = 0; frameOffset + kRenderBufferSize <= numberOfFrames; frameOffset += kRenderBufferSize) {
- UInt32 remainingFrames = std::min<UInt32>(kRenderBufferSize, numberOfFrames - frameOffset);
- for (UInt32 i = 0; i < ioData->mNumberBuffers; ++i) {
- UInt32 bytesPerFrame = buffers[i].mDataByteSize / numberOfFrames;
- UInt32 byteOffset = frameOffset * bytesPerFrame;
- float* memory = (float*)((char*)buffers[i].mData + byteOffset);
- m_renderBus->setChannelMemory(i, memory, remainingFrames);
</del><ins>+ UInt32 numberOfBuffers = ioData->mNumberBuffers;
+ UInt32 framesRemaining = numberOfFrames;
+ UInt32 frameOffset = 0;
+ while (framesRemaining > 0) {
+ if (m_firstSpareFrame && m_lastSpareFrame) {
+ ASSERT(m_firstSpareFrame < m_lastSpareFrame);
+ UInt32 framesThisTime = m_lastSpareFrame - m_firstSpareFrame;
+ assignAudioBuffersToBus(buffers, *m_renderBus, numberOfBuffers, numberOfFrames, frameOffset, framesThisTime);
+ m_renderBus->copyFromRange(*m_spareBus, m_firstSpareFrame, m_lastSpareFrame);
+ frameOffset += framesThisTime;
+ framesRemaining -= framesThisTime;
+ m_firstSpareFrame = 0;
+ m_lastSpareFrame = 0;
</ins><span class="cx"> }
</span><del>- m_callback.render(0, m_renderBus.get(), remainingFrames);
</del><ins>+
+ UInt32 framesThisTime = std::min<UInt32>(kRenderBufferSize, framesRemaining);
+ assignAudioBuffersToBus(buffers, *m_renderBus, numberOfBuffers, numberOfFrames, frameOffset, framesThisTime);
+
+ if (framesThisTime < kRenderBufferSize) {
+ m_callback.render(0, m_spareBus.get(), kRenderBufferSize);
+ m_renderBus->copyFromRange(*m_spareBus, 0, framesThisTime);
+ m_firstSpareFrame = framesThisTime;
+ m_lastSpareFrame = kRenderBufferSize - 1;
+ } else
+ m_callback.render(0, m_renderBus.get(), framesThisTime);
+ frameOffset += framesThisTime;
+ framesRemaining -= framesThisTime;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> return noErr;
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformaudioiosAudioDestinationIOSh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/audio/ios/AudioDestinationIOS.h (198034 => 198035)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/audio/ios/AudioDestinationIOS.h        2016-03-11 19:30:55 UTC (rev 198034)
+++ trunk/Source/WebCore/platform/audio/ios/AudioDestinationIOS.h        2016-03-11 19:35:15 UTC (rev 198035)
</span><span class="lines">@@ -65,6 +65,9 @@
</span><span class="cx"> AudioUnit m_outputUnit;
</span><span class="cx"> AudioIOCallback& m_callback;
</span><span class="cx"> RefPtr<AudioBus> m_renderBus;
</span><ins>+ RefPtr<AudioBus> m_spareBus;
+ unsigned m_firstSpareFrame { 0 };
+ unsigned m_lastSpareFrame { 0 };
</ins><span class="cx">
</span><span class="cx"> double m_sampleRate;
</span><span class="cx"> bool m_isPlaying;
</span></span></pre>
</div>
</div>
</body>
</html>