Title: [287157] trunk/Source/WebCore
Revision
287157
Author
[email protected]
Date
2021-12-16 14:02:54 -0800 (Thu, 16 Dec 2021)

Log Message

Allow AudioSampleDataSource to increase/decrease buffered data progressively
https://bugs.webkit.org/show_bug.cgi?id=233422

Reviewed by Eric Carlson.

AudioSampleDataSource does the link between push audio sources and pull audio sinks.
As such, it needs to do buffering. If buffering is too small, data may be missing and audio glitches will be heard.
If buffering is too large, latency will be added which might be undesirable, especially if audio is being played with video.
We generally want buffered audio to stay within a certain range.

To make this happen, when buffering is too high, we convert the data with a slightly lower sample rate to push less samples, until we are back to normal buffering.
Conversely, when buffering is too low, we convert the data with a slightly higher sample rate to push more samples, until we are back to normal buffering.
We do this with 3 converters that we select based on amount of buffered data.
This behavior is encapsulated in AudioSampleDataConverter.

We simplify AudioSampleDataSource implementation by always recomputing the sample offset when there is not enough data.
In that case, we wait for 50ms of buffered data, which is the average buffer we expect, to restart pulling data.
All values owned by AudioSampleDataSource (m_expectedNextPushedSampleTimeValue, m_converterInputOffset, m_converterInputOffset, m_outputSampleOffset, m_lastBufferedAmount)
are all in the outgoing timeline/sampleRate.

This adaptation is only enabled when AudioSampleDataSource::pullSamples is called.
For pullAvailableSamplesAsChunks and pullAvailableSampleChunk, the puller is supposed to be in sync with the pusher.
For that reason, we make sure to always write the expected number of audio frames when pullSamples is called, even if converter fails.

We fix a potential busy loop in AudioSampleDataSource::pullAvailableSamplesAsChunks in case endFrame is lower than startFrame, which is computed from timeStamp input parameter.

Update MockAudioSharedUnit to be closer to a real source by increasing the queue priority and schedule rendering tasks from the queue instead of relying on
a main thread timer which can have hiccups.

Manually tested.

* SourcesCocoa.txt:
* WebCore.xcodeproj/project.pbxproj:
* platform/audio/cocoa/AudioSampleDataConverter.h: Added.
* platform/audio/cocoa/AudioSampleDataConverter.mm: Added.
* platform/audio/cocoa/AudioSampleDataSource.h:
* platform/audio/cocoa/AudioSampleDataSource.mm:
* platform/mediastream/mac/MockAudioSharedUnit.h:
* platform/mediastream/mac/MockAudioSharedUnit.mm:

Modified Paths

Added Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (287156 => 287157)


--- trunk/Source/WebCore/ChangeLog	2021-12-16 22:00:44 UTC (rev 287156)
+++ trunk/Source/WebCore/ChangeLog	2021-12-16 22:02:54 UTC (rev 287157)
@@ -1,3 +1,45 @@
+2021-12-16  Youenn Fablet  <[email protected]>
+
+        Allow AudioSampleDataSource to increase/decrease buffered data progressively
+        https://bugs.webkit.org/show_bug.cgi?id=233422
+
+        Reviewed by Eric Carlson.
+
+        AudioSampleDataSource does the link between push audio sources and pull audio sinks.
+        As such, it needs to do buffering. If buffering is too small, data may be missing and audio glitches will be heard.
+        If buffering is too large, latency will be added which might be undesirable, especially if audio is being played with video.
+        We generally want buffered audio to stay within a certain range.
+
+        To make this happen, when buffering is too high, we convert the data with a slightly lower sample rate to push less samples, until we are back to normal buffering.
+        Conversely, when buffering is too low, we convert the data with a slightly higher sample rate to push more samples, until we are back to normal buffering.
+        We do this with 3 converters that we select based on amount of buffered data.
+        This behavior is encapsulated in AudioSampleDataConverter.
+
+        We simplify AudioSampleDataSource implementation by always recomputing the sample offset when there is not enough data.
+        In that case, we wait for 50ms of buffered data, which is the average buffer we expect, to restart pulling data.
+        All values owned by AudioSampleDataSource (m_expectedNextPushedSampleTimeValue, m_converterInputOffset, m_converterInputOffset, m_outputSampleOffset, m_lastBufferedAmount)
+        are all in the outgoing timeline/sampleRate.
+
+        This adaptation is only enabled when AudioSampleDataSource::pullSamples is called.
+        For pullAvailableSamplesAsChunks and pullAvailableSampleChunk, the puller is supposed to be in sync with the pusher.
+        For that reason, we make sure to always write the expected number of audio frames when pullSamples is called, even if converter fails.
+
+        We fix a potential busy loop in AudioSampleDataSource::pullAvailableSamplesAsChunks in case endFrame is lower than startFrame, which is computed from timeStamp input parameter.
+
+        Update MockAudioSharedUnit to be closer to a real source by increasing the queue priority and schedule rendering tasks from the queue instead of relying on
+        a main thread timer which can have hiccups.
+
+        Manually tested.
+
+        * SourcesCocoa.txt:
+        * WebCore.xcodeproj/project.pbxproj:
+        * platform/audio/cocoa/AudioSampleDataConverter.h: Added.
+        * platform/audio/cocoa/AudioSampleDataConverter.mm: Added.
+        * platform/audio/cocoa/AudioSampleDataSource.h:
+        * platform/audio/cocoa/AudioSampleDataSource.mm:
+        * platform/mediastream/mac/MockAudioSharedUnit.h:
+        * platform/mediastream/mac/MockAudioSharedUnit.mm:
+
 2021-12-16  Sihui Liu  <[email protected]>
 
         REGRESSION (r286601): storage/filesystemaccess/sync-access-handle-read-write-worker.html and file-system-access/sandboxed_FileSystemSyncAccessHandle-truncate.https.tentative.worker.html are consistently failing

Modified: trunk/Source/WebCore/SourcesCocoa.txt (287156 => 287157)


--- trunk/Source/WebCore/SourcesCocoa.txt	2021-12-16 22:00:44 UTC (rev 287156)
+++ trunk/Source/WebCore/SourcesCocoa.txt	2021-12-16 22:02:54 UTC (rev 287157)
@@ -224,6 +224,7 @@
 platform/audio/cocoa/AudioFileReaderCocoa.cpp
 platform/audio/cocoa/AudioOutputUnitAdaptor.cpp
 platform/audio/cocoa/AudioSampleBufferList.cpp
+platform/audio/cocoa/AudioSampleDataConverter.mm
 platform/audio/cocoa/AudioSampleDataSource.mm
 platform/audio/cocoa/CAAudioStreamDescription.cpp
 platform/audio/cocoa/CARingBuffer.cpp
@@ -315,7 +316,7 @@
 platform/graphics/avfoundation/objc/InbandChapterTrackPrivateAVFObjC.mm @no-unify
 platform/graphics/avfoundation/objc/LocalSampleBufferDisplayLayer.mm
 platform/graphics/avfoundation/objc/MediaPlaybackTargetPickerMac.mm
-platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm
+platform/graphics/avfoundation/objc/MediaPlayerPrivateAVFoundationObjC.mm @no-unify
 platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm @no-unify
 platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.mm
 platform/graphics/avfoundation/objc/MediaSampleAVFObjC.mm
@@ -322,7 +323,7 @@
 platform/graphics/avfoundation/objc/MediaSourcePrivateAVFObjC.mm
 platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm @no-unify
 platform/graphics/avfoundation/objc/SourceBufferParserAVFObjC.mm
-platform/graphics/avfoundation/objc/VideoLayerManagerObjC.mm
+platform/graphics/avfoundation/objc/VideoLayerManagerObjC.mm @no-unify
 platform/graphics/avfoundation/objc/VideoTrackPrivateAVFObjC.cpp
 platform/graphics/avfoundation/objc/VideoTrackPrivateMediaSourceAVFObjC.mm
 platform/graphics/avfoundation/objc/WebCoreAVFResourceLoader.mm

Modified: trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj (287156 => 287157)


--- trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2021-12-16 22:00:44 UTC (rev 287156)
+++ trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2021-12-16 22:02:54 UTC (rev 287157)
@@ -1228,6 +1228,9 @@
 		41FCCC3B2746675600892AD6 /* CoreAudioCaptureSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F3BB5831E709EE400C701F2 /* CoreAudioCaptureSource.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		41FCD6B923CE015500C62567 /* SampleBufferDisplayLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 414598BE23C8AAB8002B9CC8 /* SampleBufferDisplayLayer.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		41FCD6BB23CE027700C62567 /* LocalSampleBufferDisplayLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 414598C023C8AD78002B9CC8 /* LocalSampleBufferDisplayLayer.h */; settings = {ATTRIBUTES = (Private, ); }; };
+		41FFD2C327563E0D00501BBF /* AudioSampleDataConverter.h in Headers */ = {isa = PBXBuildFile; fileRef = 41FFD2C027563DFF00501BBF /* AudioSampleDataConverter.h */; settings = {ATTRIBUTES = (Private, ); }; };
+		41FFD2C42756570F00501BBF /* VideoLayerManagerObjC.mm in Sources */ = {isa = PBXBuildFile; fileRef = 52D5A18D1C54590300DE34A3 /* VideoLayerManagerObjC.mm */; };
+		41FFD2C62756573E00501BBF /* MediaPlayerPrivateAVFoundationObjC.mm in Sources */ = {isa = PBXBuildFile; fileRef = DF9AFD7113FC31D80015FEB7 /* MediaPlayerPrivateAVFoundationObjC.mm */; };
 		427DA71D13735DFA007C57FB /* JSServiceWorkerInternals.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 427DA71B13735DFA007C57FB /* JSServiceWorkerInternals.cpp */; };
 		427DA71E13735DFA007C57FB /* JSServiceWorkerInternals.h in Headers */ = {isa = PBXBuildFile; fileRef = 427DA71C13735DFA007C57FB /* JSServiceWorkerInternals.h */; };
 		43107BE218CC19DE00CC18E8 /* SelectorPseudoTypeMap.h in Headers */ = {isa = PBXBuildFile; fileRef = 43107BE118CC19DE00CC18E8 /* SelectorPseudoTypeMap.h */; };
@@ -8943,6 +8946,8 @@
 		41FCB75F214866FF0038ADC6 /* RTCRtpCodecParameters.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RTCRtpCodecParameters.h; sourceTree = "<group>"; };
 		41FCB760214867000038ADC6 /* RTCRtpRtxParameters.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RTCRtpRtxParameters.h; sourceTree = "<group>"; };
 		41FCB761214867000038ADC6 /* RTCRtpEncodingParameters.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RTCRtpEncodingParameters.h; sourceTree = "<group>"; };
+		41FFD2C027563DFF00501BBF /* AudioSampleDataConverter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioSampleDataConverter.h; sourceTree = "<group>"; };
+		41FFD2C227563E0000501BBF /* AudioSampleDataConverter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AudioSampleDataConverter.mm; sourceTree = "<group>"; };
 		427DA71B13735DFA007C57FB /* JSServiceWorkerInternals.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSServiceWorkerInternals.cpp; sourceTree = "<group>"; };
 		427DA71C13735DFA007C57FB /* JSServiceWorkerInternals.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSServiceWorkerInternals.h; sourceTree = "<group>"; };
 		43107BE118CC19DE00CC18E8 /* SelectorPseudoTypeMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SelectorPseudoTypeMap.h; sourceTree = "<group>"; };
@@ -30064,6 +30069,8 @@
 				1DB66D37253678EA00B671B9 /* AudioOutputUnitAdaptor.h */,
 				073B87621E43859D0071C0EC /* AudioSampleBufferList.cpp */,
 				073B87631E43859D0071C0EC /* AudioSampleBufferList.h */,
+				41FFD2C027563DFF00501BBF /* AudioSampleDataConverter.h */,
+				41FFD2C227563E0000501BBF /* AudioSampleDataConverter.mm */,
 				073B87651E43859D0071C0EC /* AudioSampleDataSource.h */,
 				073B87641E43859D0071C0EC /* AudioSampleDataSource.mm */,
 				073B87571E40DCFD0071C0EC /* CAAudioStreamDescription.cpp */,
@@ -33322,6 +33329,7 @@
 				FD31608612B026F700C1A359 /* AudioResampler.h in Headers */,
 				FD31608812B026F700C1A359 /* AudioResamplerKernel.h in Headers */,
 				073B87671E4385AC0071C0EC /* AudioSampleBufferList.h in Headers */,
+				41FFD2C327563E0D00501BBF /* AudioSampleDataConverter.h in Headers */,
 				073B87691E4385AC0071C0EC /* AudioSampleDataSource.h in Headers */,
 				FD8C46EC154608E700A5910C /* AudioScheduledSourceNode.h in Headers */,
 				CDA7982A170A3D0000D45C55 /* AudioSession.h in Headers */,
@@ -38443,6 +38451,7 @@
 				2D9BF7431DBFDC3E007A7D99 /* MediaKeySystemAccess.cpp in Sources */,
 				9ACC079825C7267700DC6386 /* MediaKeySystemController.cpp in Sources */,
 				9ACC079625C725EE00DC6386 /* MediaKeySystemRequest.cpp in Sources */,
+				41FFD2C62756573E00501BBF /* MediaPlayerPrivateAVFoundationObjC.mm in Sources */,
 				CDC8B5A2180463470016E685 /* MediaPlayerPrivateMediaSourceAVFObjC.mm in Sources */,
 				CDA9593524123CB800910EEF /* MediaSessionHelperIOS.mm in Sources */,
 				07638A9A1884487200E15A1B /* MediaSessionManagerIOS.mm in Sources */,
@@ -39072,6 +39081,7 @@
 				7CE68344192143A800F4D928 /* UserMessageHandlerDescriptor.cpp in Sources */,
 				7C73FB07191EF417007DE061 /* UserMessageHandlersNamespace.cpp in Sources */,
 				3FBC4AF3189881560046EE38 /* VideoFullscreenInterfaceAVKit.mm in Sources */,
+				41FFD2C42756570F00501BBF /* VideoLayerManagerObjC.mm in Sources */,
 				26F9A83818A046AC00AEB88A /* ViewportConfiguration.cpp in Sources */,
 				CDED1C3C24CD305700934E12 /* VP9UtilitiesCocoa.mm in Sources */,
 				A14832B1187F61E100DA63A6 /* WAKAppKitStubs.m in Sources */,

Modified: trunk/Source/WebCore/platform/audio/cocoa/AudioSampleBufferList.cpp (287156 => 287157)


--- trunk/Source/WebCore/platform/audio/cocoa/AudioSampleBufferList.cpp	2021-12-16 22:00:44 UTC (rev 287156)
+++ trunk/Source/WebCore/platform/audio/cocoa/AudioSampleBufferList.cpp	2021-12-16 22:02:54 UTC (rev 287157)
@@ -297,17 +297,12 @@
         return 0;
     }
 
-    LOG_ERROR("AudioSampleBufferList::copyFrom(%p) AudioConverterFillComplexBuffer returned error %d (%.4s)", this, (int)err, (char*)&err);
+    RELEASE_LOG_ERROR(Media, "AudioSampleBufferList::copyFrom(%p) AudioConverterFillComplexBuffer returned error %d (%.4s)", this, (int)err, (char*)&err);
     m_sampleCount = std::min(m_sampleCapacity, static_cast<size_t>(samplesConverted));
     zero();
     return err;
 }
 
-OSStatus AudioSampleBufferList::copyFrom(AudioSampleBufferList& source, size_t frameCount, AudioConverterRef converter)
-{
-    return copyFrom(source.bufferList(), frameCount, converter);
-}
-
 OSStatus AudioSampleBufferList::copyFrom(CARingBuffer& ringBuffer, size_t sampleCount, uint64_t startFrame, CARingBuffer::FetchMode mode)
 {
     reset();

Modified: trunk/Source/WebCore/platform/audio/cocoa/AudioSampleBufferList.h (287156 => 287157)


--- trunk/Source/WebCore/platform/audio/cocoa/AudioSampleBufferList.h	2021-12-16 22:00:44 UTC (rev 287156)
+++ trunk/Source/WebCore/platform/audio/cocoa/AudioSampleBufferList.h	2021-12-16 22:02:54 UTC (rev 287157)
@@ -50,7 +50,6 @@
 
     OSStatus copyFrom(const AudioSampleBufferList&, size_t count = SIZE_MAX);
     OSStatus copyFrom(const AudioBufferList&, size_t frameCount, AudioConverterRef);
-    OSStatus copyFrom(AudioSampleBufferList&, size_t frameCount, AudioConverterRef);
     OSStatus copyFrom(CARingBuffer&, size_t frameCount, uint64_t startFrame, CARingBuffer::FetchMode);
 
     OSStatus mixFrom(const AudioSampleBufferList&, size_t count = SIZE_MAX);

Added: trunk/Source/WebCore/platform/audio/cocoa/AudioSampleDataConverter.h (0 => 287157)


--- trunk/Source/WebCore/platform/audio/cocoa/AudioSampleDataConverter.h	                        (rev 0)
+++ trunk/Source/WebCore/platform/audio/cocoa/AudioSampleDataConverter.h	2021-12-16 22:02:54 UTC (rev 287157)
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+typedef struct AudioBufferList AudioBufferList;
+struct AudioStreamBasicDescription;
+typedef struct OpaqueAudioConverter* AudioConverterRef;
+
+namespace WebCore {
+
+class AudioSampleBufferList;
+class CAAudioStreamDescription;
+class PlatformAudioData;
+
+class AudioSampleDataConverter {
+    WTF_MAKE_FAST_ALLOCATED;
+public:
+    AudioSampleDataConverter() = default;
+    ~AudioSampleDataConverter();
+
+    OSStatus setFormats(const CAAudioStreamDescription& inputDescription, const CAAudioStreamDescription& outputDescription);
+    bool updateBufferedAmount(size_t currentBufferedAmount);
+    OSStatus convert(const AudioBufferList&, AudioSampleBufferList&, size_t sampleCount);
+    size_t regularBufferSize() const { return m_regularBufferSize; }
+    bool isRegular() const { return m_selectedConverter == m_regularConverter; }
+
+private:
+    size_t m_highBufferSize { 0 };
+    size_t m_regularHighBufferSize { 0 };
+    size_t m_regularBufferSize { 0 };
+    size_t m_regularLowBufferSize { 0 };
+    size_t m_lowBufferSize { 0 };
+
+    class Converter {
+    public:
+        Converter() = default;
+        ~Converter();
+
+        OSStatus initialize(const AudioStreamBasicDescription& inputDescription, const AudioStreamBasicDescription& outputDescription);
+        operator AudioConverterRef() const { return m_audioConverter; }
+
+    private:
+        AudioConverterRef m_audioConverter { nullptr };
+    };
+
+    Converter m_lowConverter;
+    Converter m_regularConverter;
+    Converter m_highConverter;
+    AudioConverterRef m_selectedConverter;
+};
+
+} // namespace WebCore

Added: trunk/Source/WebCore/platform/audio/cocoa/AudioSampleDataConverter.mm (0 => 287157)


--- trunk/Source/WebCore/platform/audio/cocoa/AudioSampleDataConverter.mm	                        (rev 0)
+++ trunk/Source/WebCore/platform/audio/cocoa/AudioSampleDataConverter.mm	2021-12-16 22:02:54 UTC (rev 287157)
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2021 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+#import "AudioSampleDataConverter.h"
+
+#import "AudioSampleBufferList.h"
+#import <AudioToolbox/AudioConverter.h>
+#import <pal/cf/AudioToolboxSoftLink.h>
+
+namespace WebCore {
+
+AudioSampleDataConverter::~AudioSampleDataConverter()
+{
+}
+
+OSStatus AudioSampleDataConverter::setFormats(const CAAudioStreamDescription& inputDescription, const CAAudioStreamDescription& outputDescription)
+{
+    constexpr double buffer100ms = 0.100;
+    constexpr double buffer60ms = 0.060;
+    constexpr double buffer50ms = 0.050;
+    constexpr double buffer40ms = 0.040;
+    constexpr double buffer20ms = 0.020;
+    m_highBufferSize = outputDescription.sampleRate() * buffer100ms;
+    m_regularHighBufferSize = outputDescription.sampleRate() * buffer60ms;
+    m_regularBufferSize = outputDescription.sampleRate() * buffer50ms;
+    m_regularLowBufferSize = outputDescription.sampleRate() * buffer40ms;
+    m_lowBufferSize = outputDescription.sampleRate() * buffer20ms;
+
+    m_selectedConverter = nullptr;
+
+    auto converterOutputDescription = outputDescription.streamDescription();
+    constexpr double slightlyHigherPitch = 1.05;
+    converterOutputDescription.mSampleRate = slightlyHigherPitch * outputDescription.streamDescription().mSampleRate;
+    if (auto error = m_lowConverter.initialize(inputDescription.streamDescription(), converterOutputDescription); error != noErr)
+        return error;
+
+    constexpr double slightlyLowerPitch = 0.95;
+    converterOutputDescription.mSampleRate = slightlyLowerPitch * outputDescription.streamDescription().mSampleRate;
+    if (auto error = m_highConverter.initialize(inputDescription.streamDescription(), converterOutputDescription); error != noErr)
+        return error;
+
+    if (inputDescription == outputDescription)
+        return noErr;
+
+    if (auto error = m_regularConverter.initialize(inputDescription.streamDescription(), outputDescription.streamDescription()); error != noErr)
+        return error;
+
+    m_selectedConverter = m_regularConverter;
+    return noErr;
+}
+
+bool AudioSampleDataConverter::updateBufferedAmount(size_t currentBufferedAmount)
+{
+    if (currentBufferedAmount) {
+        if (m_selectedConverter == m_regularConverter) {
+            if (currentBufferedAmount <= m_lowBufferSize)
+                m_selectedConverter = m_lowConverter;
+            else if (currentBufferedAmount >= m_highBufferSize)
+                m_selectedConverter = m_highConverter;
+        } else if (m_selectedConverter == m_highConverter) {
+            if (currentBufferedAmount < m_regularLowBufferSize)
+                m_selectedConverter = m_regularConverter;
+        } else if (currentBufferedAmount > m_regularHighBufferSize)
+            m_selectedConverter = m_regularConverter;
+    }
+    return !!m_selectedConverter;
+}
+
+OSStatus AudioSampleDataConverter::convert(const AudioBufferList& inputBuffer, AudioSampleBufferList& outputBuffer, size_t sampleCount)
+{
+    outputBuffer.reset();
+    return outputBuffer.copyFrom(inputBuffer, sampleCount, m_selectedConverter);
+}
+
+OSStatus AudioSampleDataConverter::Converter::initialize(const AudioStreamBasicDescription& inputDescription, const AudioStreamBasicDescription& outputDescription)
+{
+    if (m_audioConverter) {
+        PAL::AudioConverterDispose(m_audioConverter);
+        m_audioConverter = nullptr;
+    }
+
+    return PAL::AudioConverterNew(&inputDescription, &outputDescription, &m_audioConverter);
+}
+
+AudioSampleDataConverter::Converter::~Converter()
+{
+    if (m_audioConverter)
+        PAL::AudioConverterDispose(m_audioConverter);
+}
+
+} // namespace WebCore

Modified: trunk/Source/WebCore/platform/audio/cocoa/AudioSampleDataSource.h (287156 => 287157)


--- trunk/Source/WebCore/platform/audio/cocoa/AudioSampleDataSource.h	2021-12-16 22:00:44 UTC (rev 287156)
+++ trunk/Source/WebCore/platform/audio/cocoa/AudioSampleDataSource.h	2021-12-16 22:02:54 UTC (rev 287157)
@@ -25,6 +25,7 @@
 
 #pragma once
 
+#include "AudioSampleDataConverter.h"
 #include "CARingBuffer.h"
 #include <CoreAudio/CoreAudioTypes.h>
 #include <wtf/LoggerHelper.h>
@@ -102,13 +103,15 @@
 
     uint64_t m_lastPushedSampleCount { 0 };
     size_t m_waitToStartForPushCount { 2 };
-    MediaTime m_expectedNextPushedSampleTime { MediaTime::invalidTime() };
-    bool m_isFirstPull { true };
 
-    MediaTime m_inputSampleOffset;
+    int64_t m_expectedNextPushedSampleTimeValue { 0 };
+    int64_t m_converterInputOffset { 0 };
+    std::optional<int64_t> m_inputSampleOffset;
     int64_t m_outputSampleOffset { 0 };
+    uint64_t m_lastBufferedAmount { 0 };
 
-    AudioConverterRef m_converter;
+    AudioSampleDataConverter m_converter;
+
     RefPtr<AudioSampleBufferList> m_scratchBuffer;
 
     UniqueRef<CARingBuffer> m_ringBuffer;
@@ -117,10 +120,8 @@
     float m_volume { 1.0 };
     bool m_muted { false };
     bool m_shouldComputeOutputSampleOffset { true };
-    uint64_t m_endFrameWhenNotEnoughData { 0 };
 
     bool m_isInNeedOfMoreData { false };
-
 #if !RELEASE_LOG_DISABLED
     Ref<const Logger> m_logger;
     const void* m_logIdentifier;

Modified: trunk/Source/WebCore/platform/audio/cocoa/AudioSampleDataSource.mm (287156 => 287157)


--- trunk/Source/WebCore/platform/audio/cocoa/AudioSampleDataSource.mm	2021-12-16 22:00:44 UTC (rev 287156)
+++ trunk/Source/WebCore/platform/audio/cocoa/AudioSampleDataSource.mm	2021-12-16 22:02:54 UTC (rev 287157)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 Apple Inc. All rights reserved.
+ * Copyright (C) 2017-2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -51,7 +51,6 @@
 
 AudioSampleDataSource::AudioSampleDataSource(size_t maximumSampleCount, LoggerHelper& loggerHelper, size_t waitToStartForPushCount)
     : m_waitToStartForPushCount(waitToStartForPushCount)
-    , m_inputSampleOffset(MediaTime::invalidTime())
     , m_ringBuffer(makeUniqueRef<CARingBuffer>())
     , m_maximumSampleCount(maximumSampleCount)
 #if !RELEASE_LOG_DISABLED
@@ -66,31 +65,12 @@
 
 AudioSampleDataSource::~AudioSampleDataSource()
 {
-    if (m_converter)
-        PAL::AudioConverterDispose(m_converter);
 }
 
 OSStatus AudioSampleDataSource::setupConverter()
 {
     ASSERT(m_inputDescription && m_outputDescription);
-
-    if (m_converter) {
-        PAL::AudioConverterDispose(m_converter);
-        m_converter = nullptr;
-    }
-
-    if (*m_inputDescription == *m_outputDescription)
-        return 0;
-
-    OSStatus err = PAL::AudioConverterNew(&m_inputDescription->streamDescription(), &m_outputDescription->streamDescription(), &m_converter);
-    if (err) {
-        RunLoop::main().dispatch([this, protectedThis = Ref { *this }, err] {
-            ERROR_LOG("AudioConverterNew returned error ", err);
-        });
-    }
-
-    return err;
-
+    return m_converter.setFormats(*m_inputDescription, *m_outputDescription);
 }
 
 OSStatus AudioSampleDataSource::setInputFormat(const CAAudioStreamDescription& format)
@@ -109,6 +89,9 @@
     ASSERT(m_inputDescription);
     ASSERT(format.sampleRate() >= 0);
 
+    if (m_outputDescription && *m_outputDescription == format)
+        return noErr;
+
     m_outputDescription = CAAudioStreamDescription { format };
 
     {
@@ -117,6 +100,7 @@
         DisableMallocRestrictionsForCurrentThreadScope disableMallocRestrictions;
         m_ringBuffer->allocate(format, static_cast<size_t>(m_maximumSampleCount));
         m_scratchBuffer = AudioSampleBufferList::create(m_outputDescription->streamDescription(), m_maximumSampleCount);
+        m_converterInputOffset = 0;
     }
 
     return setupConverter();
@@ -140,35 +124,45 @@
 
 void AudioSampleDataSource::pushSamplesInternal(const AudioBufferList& bufferList, const MediaTime& presentationTime, size_t sampleCount)
 {
-    MediaTime sampleTime = presentationTime;
+    int64_t ringBufferIndexToWrite = presentationTime.toTimeScale(m_outputDescription->sampleRate()).timeValue();
 
+    int64_t offset = 0;
     const AudioBufferList* sampleBufferList;
-    if (m_converter) {
+
+    if (m_converter.updateBufferedAmount(m_lastBufferedAmount)) {
         m_scratchBuffer->reset();
-        OSStatus err = m_scratchBuffer->copyFrom(bufferList, sampleCount, m_converter);
-        if (err)
-            return;
+        m_converter.convert(bufferList, *m_scratchBuffer, sampleCount);
+        auto expectedSampleCount = sampleCount * m_outputDescription->sampleRate() / m_inputDescription->sampleRate();
 
+        if (m_converter.isRegular() && expectedSampleCount > m_scratchBuffer->sampleCount()) {
+            // Sometimes converter is not writing enough data, for instance on first chunk conversion.
+            // Pretend this is the case to keep pusher and puller in sync.
+            offset = 0;
+            sampleCount = expectedSampleCount;
+            if (m_scratchBuffer->sampleCount() > sampleCount)
+                m_scratchBuffer->setSampleCount(sampleCount);
+        } else {
+            offset = m_scratchBuffer->sampleCount() - expectedSampleCount;
+            sampleCount = m_scratchBuffer->sampleCount();
+        }
         sampleBufferList = m_scratchBuffer->bufferList().list();
-        sampleCount = m_scratchBuffer->sampleCount();
-        sampleTime = presentationTime.toTimeScale(m_outputDescription->sampleRate(), MediaTime::RoundingFlags::TowardZero);
     } else
         sampleBufferList = &bufferList;
 
-    if (m_expectedNextPushedSampleTime.isValid() && abs(m_expectedNextPushedSampleTime - sampleTime).timeValue() == 1)
-        sampleTime = m_expectedNextPushedSampleTime;
-    m_expectedNextPushedSampleTime = sampleTime + MediaTime(sampleCount, sampleTime.timeScale());
+    if (!m_inputSampleOffset) {
+        m_inputSampleOffset = 0 - ringBufferIndexToWrite;
+        ringBufferIndexToWrite = 0;
+    } else
+        ringBufferIndexToWrite += *m_inputSampleOffset;
 
-    if (m_inputSampleOffset == MediaTime::invalidTime())
-        m_inputSampleOffset = MediaTime(1 - sampleTime.timeValue(), sampleTime.timeScale());
-    sampleTime += m_inputSampleOffset;
+    if (m_converterInputOffset)
+        ringBufferIndexToWrite += m_converterInputOffset;
 
-#if !LOG_DISABLED
-    uint64_t startFrame1 = 0;
-    uint64_t endFrame1 = 0;
-    m_ringBuffer->getCurrentFrameBounds(startFrame1, endFrame1);
-#endif
+    if (m_expectedNextPushedSampleTimeValue && abs((float)m_expectedNextPushedSampleTimeValue - (float)ringBufferIndexToWrite) <= 1)
+        ringBufferIndexToWrite = m_expectedNextPushedSampleTimeValue;
 
+    m_expectedNextPushedSampleTimeValue = ringBufferIndexToWrite + sampleCount;
+
     if (m_isInNeedOfMoreData) {
         m_isInNeedOfMoreData = false;
         DisableMallocRestrictionsForCurrentThreadScope disableMallocRestrictions;
@@ -176,7 +170,10 @@
             ALWAYS_LOG(logIdentifier, "needed more data, pushing ", sampleCount, " samples");
         });
     }
-    m_ringBuffer->store(sampleBufferList, sampleCount, sampleTime.timeValue());
+
+    m_ringBuffer->store(sampleBufferList, sampleCount, ringBufferIndexToWrite);
+
+    m_converterInputOffset += offset;
     m_lastPushedSampleCount = sampleCount;
 }
 
@@ -194,21 +191,6 @@
     pushSamplesInternal(*downcast<WebAudioBufferList>(audioData).list(), sampleTime, sampleCount);
 }
 
-static inline int64_t computeOffsetDelay(double sampleRate, uint64_t lastPushedSampleCount)
-{
-    const double twentyMS = .02;
-    const double tenMS = .01;
-    const double fiveMS = .005;
-
-    if (lastPushedSampleCount > sampleRate * twentyMS)
-        return sampleRate * twentyMS;
-    if (lastPushedSampleCount > sampleRate * tenMS)
-        return sampleRate * tenMS;
-    if (lastPushedSampleCount > sampleRate * fiveMS)
-        return sampleRate * fiveMS;
-    return 0;
-}
-
 bool AudioSampleDataSource::pullSamples(AudioBufferList& buffer, size_t sampleCount, uint64_t timeStamp, double /*hostTime*/, PullMode mode)
 {
     size_t byteCount = sampleCount * m_outputDescription->bytesPerFrame();
@@ -220,7 +202,7 @@
         return false;
     }
 
-    if (m_muted || m_inputSampleOffset == MediaTime::invalidTime()) {
+    if (m_muted || !m_inputSampleOffset) {
         if (mode != AudioSampleDataSource::Mix)
             AudioSampleBufferList::zeroABL(buffer, byteCount);
         return false;
@@ -230,33 +212,19 @@
     uint64_t endFrame = 0;
     m_ringBuffer->getCurrentFrameBounds(startFrame, endFrame);
 
+    ASSERT(m_waitToStartForPushCount);
+
+    uint64_t buffered = endFrame - startFrame;
     if (m_shouldComputeOutputSampleOffset) {
-        uint64_t buffered = endFrame - startFrame;
-        if (m_isFirstPull) {
-            auto minimumBuffer = m_waitToStartForPushCount * m_lastPushedSampleCount;
-            if (buffered >= minimumBuffer) {
-                m_outputSampleOffset = startFrame - timeStamp;
-                m_shouldComputeOutputSampleOffset = false;
-                m_endFrameWhenNotEnoughData = 0;
-            } else {
-                // We wait for one chunk of value before starting to play.
-                if (mode != AudioSampleDataSource::Mix)
-                    AudioSampleBufferList::zeroABL(buffer, byteCount);
-                return false;
-            }
-        } else {
-            if (buffered < sampleCount * 2 || (m_endFrameWhenNotEnoughData && m_endFrameWhenNotEnoughData == endFrame)) {
-                if (mode != AudioSampleDataSource::Mix)
-                    AudioSampleBufferList::zeroABL(buffer, byteCount);
-                return false;
-            }
-
-            m_shouldComputeOutputSampleOffset = false;
-            m_endFrameWhenNotEnoughData = 0;
-
-            m_outputSampleOffset = (endFrame - sampleCount) - timeStamp;
-            m_outputSampleOffset -= computeOffsetDelay(m_outputDescription->sampleRate(), m_lastPushedSampleCount);
+        auto minimumBuffer = std::max<size_t>(m_waitToStartForPushCount * m_lastPushedSampleCount, m_converter.regularBufferSize());
+        if (buffered < minimumBuffer) {
+            // We wait for one chunk of value before starting to play.
+            if (mode != AudioSampleDataSource::Mix)
+                AudioSampleBufferList::zeroABL(buffer, byteCount);
+            return false;
         }
+        m_outputSampleOffset = endFrame - timeStamp - minimumBuffer;
+        m_shouldComputeOutputSampleOffset = false;
     }
 
     timeStamp += m_outputSampleOffset;
@@ -269,23 +237,13 @@
                 ERROR_LOG(logIdentifier, "need more data, sample ", timeStamp, " with offset ", outputSampleOffset, ", trying to get ", sampleCount, " samples, but not completely in range [", startFrame, " .. ", endFrame, "]");
             });
         }
-        if (timeStamp < startFrame || timeStamp >= endFrame) {
-            // We are out of the window, let's restart the offset computation.
-            m_shouldComputeOutputSampleOffset = true;
-
-            if (timeStamp >= endFrame)
-                m_endFrameWhenNotEnoughData = endFrame;
-        } else {
-            // We are too close from endFrame, let's wait for more data to be pushed.
-            m_outputSampleOffset -= sampleCount;
-        }
+        m_shouldComputeOutputSampleOffset = true;
         if (mode != AudioSampleDataSource::Mix)
             AudioSampleBufferList::zeroABL(buffer, byteCount);
         return false;
     }
 
-    m_isFirstPull = false;
-
+    m_lastBufferedAmount = endFrame - timeStamp - sampleCount;
     return pullSamplesInternal(buffer, sampleCount, timeStamp, mode);
 }
 
@@ -320,12 +278,12 @@
     if (buffer.mNumberBuffers != m_ringBuffer->channelCount())
         return false;
 
-    if (m_muted)
+    if (m_muted || !m_inputSampleOffset)
         return false;
 
     if (m_shouldComputeOutputSampleOffset) {
         m_shouldComputeOutputSampleOffset = false;
-        m_outputSampleOffset = m_inputSampleOffset.timeValue() * m_outputDescription->sampleRate() / m_inputSampleOffset.timeScale();
+        m_outputSampleOffset = *m_inputSampleOffset;
     }
 
     timeStamp += m_outputSampleOffset;
@@ -354,6 +312,10 @@
 
     startFrame = timeStamp;
 
+    ASSERT(endFrame >= startFrame);
+    if (endFrame < startFrame)
+        return false;
+
     if (m_muted) {
         AudioSampleBufferList::zeroABL(buffer, sampleCountPerChunk * m_outputDescription->bytesPerFrame());
         while (endFrame - startFrame >= sampleCountPerChunk) {

Modified: trunk/Source/WebCore/platform/mediastream/mac/MockAudioSharedUnit.h (287156 => 287157)


--- trunk/Source/WebCore/platform/mediastream/mac/MockAudioSharedUnit.h	2021-12-16 22:00:44 UTC (rev 287156)
+++ trunk/Source/WebCore/platform/mediastream/mac/MockAudioSharedUnit.h	2021-12-16 22:02:54 UTC (rev 287157)
@@ -62,15 +62,16 @@
 
     void delaySamples(Seconds) final;
 
+    void start();
     CapabilityValueOrRange sampleRateCapacities() const final { return CapabilityValueOrRange(44100, 48000); }
 
     void tick();
 
-    void render(Seconds);
+    void render(MonotonicTime);
     void emitSampleBuffers(uint32_t frameCount);
     void reconfigure();
 
-    static Seconds renderInterval() { return 60_ms; }
+    static Seconds renderInterval() { return 20_ms; }
 
     std::unique_ptr<WebAudioBufferList> m_audioBufferList;
 
@@ -83,6 +84,7 @@
 
     Vector<float> m_bipBopBuffer;
     bool m_hasAudioUnit { false };
+    bool m_isProducingData { false };
 
     RunLoop::Timer<MockAudioSharedUnit> m_timer;
     MonotonicTime m_lastRenderTime { MonotonicTime::nan() };

Modified: trunk/Source/WebCore/platform/mediastream/mac/MockAudioSharedUnit.mm (287156 => 287157)


--- trunk/Source/WebCore/platform/mediastream/mac/MockAudioSharedUnit.mm	2021-12-16 22:00:44 UTC (rev 287156)
+++ trunk/Source/WebCore/platform/mediastream/mac/MockAudioSharedUnit.mm	2021-12-16 22:02:54 UTC (rev 287157)
@@ -101,8 +101,8 @@
 }
 
 MockAudioSharedUnit::MockAudioSharedUnit()
-    : m_timer(RunLoop::current(), this, &MockAudioSharedUnit::tick)
-    , m_workQueue(WorkQueue::create("MockAudioSharedUnit Capture Queue"))
+    : m_timer(RunLoop::current(), this, &MockAudioSharedUnit::start)
+    , m_workQueue(WorkQueue::create("MockAudioSharedUnit Capture Queue", WorkQueue::QOS::UserInteractive))
 {
 }
 
@@ -127,13 +127,11 @@
     if (!hasAudioUnit())
         return 0;
 
-    m_timer.stop();
     m_lastRenderTime = MonotonicTime::nan();
     m_workQueue->dispatch([this] {
         reconfigure();
         callOnMainThread([this] {
-            m_lastRenderTime = MonotonicTime::now();
-            m_timer.startRepeating(renderInterval());
+            startInternal();
         });
     });
     return 0;
@@ -142,57 +140,45 @@
 void MockAudioSharedUnit::cleanupAudioUnit()
 {
     m_hasAudioUnit = false;
-    m_timer.stop();
+    m_isProducingData = false;
     m_lastRenderTime = MonotonicTime::nan();
 }
 
 OSStatus MockAudioSharedUnit::startInternal()
 {
+    start();
+    return 0;
+}
+
+void MockAudioSharedUnit::start()
+{
     if (!m_hasAudioUnit)
         m_hasAudioUnit = true;
 
     m_lastRenderTime = MonotonicTime::now();
-    m_timer.startRepeating(renderInterval());
-    return 0;
+    m_isProducingData = true;
+    m_workQueue->dispatch([this, renderTime = m_lastRenderTime] {
+        render(renderTime);
+    });
 }
 
 void MockAudioSharedUnit::stopInternal()
 {
+    m_isProducingData = false;
     if (!m_hasAudioUnit)
         return;
-    m_timer.stop();
     m_lastRenderTime = MonotonicTime::nan();
 }
 
 bool MockAudioSharedUnit::isProducingData() const
 {
-    return m_timer.isActive();
+    return m_isProducingData;
 }
 
-void MockAudioSharedUnit::tick()
-{
-    if (std::isnan(m_lastRenderTime))
-        m_lastRenderTime = MonotonicTime::now();
-
-    MonotonicTime now = MonotonicTime::now();
-
-    if (m_delayUntil) {
-        if (m_delayUntil < now)
-            return;
-        m_delayUntil = MonotonicTime();
-    }
-
-    Seconds delta = now - m_lastRenderTime;
-    m_lastRenderTime = now;
-
-    m_workQueue->dispatch([this, delta] {
-        render(delta);
-    });
-}
-
 void MockAudioSharedUnit::delaySamples(Seconds delta)
 {
-    m_delayUntil = MonotonicTime::now() + delta;
+    stopInternal();
+    m_timer.startOneShot(delta);
 }
 
 void MockAudioSharedUnit::reconfigure()
@@ -244,9 +230,24 @@
     audioSamplesAvailable(PAL::toMediaTime(startTime), *m_audioBufferList, CAAudioStreamDescription(m_streamFormat), frameCount);
 }
 
-void MockAudioSharedUnit::render(Seconds delta)
+void MockAudioSharedUnit::render(MonotonicTime renderTime)
 {
     ASSERT(!isMainThread());
+    if (!isProducingData())
+        return;
+
+    auto delta = renderInterval();
+    auto currentTime = MonotonicTime::now();
+    auto nextRenderTime = renderTime + delta;
+    Seconds nextRenderDelay = nextRenderTime.secondsSinceEpoch() - currentTime.secondsSinceEpoch();
+    if (nextRenderDelay.seconds() < 0) {
+        nextRenderTime = currentTime;
+        nextRenderDelay = 0_s;
+    }
+    m_workQueue->dispatchAfter(nextRenderDelay, [this, nextRenderTime] {
+        render(nextRenderTime);
+    });
+
     if (!m_audioBufferList || !m_bipBopBuffer.size())
         reconfigure();
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to