Title: [239145] trunk
Revision
239145
Author
commit-qu...@webkit.org
Date
2018-12-12 18:34:19 -0800 (Wed, 12 Dec 2018)

Log Message

Implement non-timeslice mode encoding for MediaRecorder
https://bugs.webkit.org/show_bug.cgi?id=192069
<rdar://problem/46443290>

Patch by YUHAN WU <yuhan...@apple.com> on 2018-12-12
Reviewed by Eric Carlson.

Source/WebCore:

Implement the encoding for non-timeslice mode of MediaRecorder.
It only supports to record MP4 file through H264 and AAC encoding, we will need to support more MIME types and encoding methods.
Add a API in internals to allow testings to turn on the mock source.

Test: http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable.html

* Modules/mediarecorder/MediaRecorder.cpp:
(WebCore::MediaRecorder::create):
(WebCore::MediaRecorder::setCustomPrivateRecorderCreator):
(WebCore::MediaRecorder::getPrivateImpl):
(WebCore::MediaRecorder::MediaRecorder):
(WebCore::MediaRecorder::stopRecording):
(WebCore::MediaRecorder::stopRecordingInternal):
(WebCore::MediaRecorder::createRecordingDataBlob):
(WebCore::MediaRecorder::scheduleDeferredTask):
* Modules/mediarecorder/MediaRecorder.h:
* Modules/mediarecorder/MediaRecorder.idl:
* SourcesCocoa.txt:
* WebCore.xcodeproj/project.pbxproj:
* platform/mediarecorder/MediaRecorderPrivate.h:
(WebCore::MediaRecorderPrivate::stopRecording):
* platform/mediarecorder/MediaRecorderPrivateAVFImpl.cpp: Added.
(WebCore::MediaRecorderPrivateAVFImpl::create):
(WebCore::MediaRecorderPrivateAVFImpl::MediaRecorderPrivateAVFImpl):
(WebCore::MediaRecorderPrivateAVFImpl::sampleBufferUpdated):
(WebCore::MediaRecorderPrivateAVFImpl::audioSamplesAvailable):
(WebCore::MediaRecorderPrivateAVFImpl::stopRecording):
(WebCore::MediaRecorderPrivateAVFImpl::fetchData):
(WebCore::MediaRecorderPrivateAVFImpl::mimeType):
* platform/mediarecorder/MediaRecorderPrivateAVFImpl.h: Added.
* platform/mediarecorder/MediaRecorderPrivateMock.cpp:
(WebCore::MediaRecorderPrivateMock::fetchData):
(WebCore::MediaRecorderPrivateMock::mimeType):
* platform/mediarecorder/MediaRecorderPrivateMock.h:
* platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.h: added.
* platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.mm: Added.
(WebCore::MediaRecorderPrivateWriter::clear):
(WebCore::MediaRecorderPrivateWriter::setupWriter):
(WebCore::MediaRecorderPrivateWriter::setVideoInput):
(WebCore::MediaRecorderPrivateWriter::setAudioInput):
(WebCore::copySampleBufferWithCurrentTimeStamp):
(WebCore::MediaRecorderPrivateWriter::appendVideoSampleBuffer):
(WebCore::MediaRecorderPrivateWriter::appendAudioSampleBuffer):
(WebCore::MediaRecorderPrivateWriter::stopRecording):
* testing/Internals.cpp:
(WebCore::createRecorderMockSource):
(WebCore::Internals::setCustomPrivateRecorderCreator):
* testing/Internals.h:
* testing/Internals.idl:

LayoutTests:

Create new tests for encoding of MediaRecorder. Check if the produced video and audio are correct.
Add code to turn on the mock source of MediaRecorder for the two old tests because the real source is enabled by default.

* http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable-expected.txt: Added.
* http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable.html: Added.
* http/wpt/mediarecorder/MediaRecorder-dataavailable.html:
* http/wpt/mediarecorder/MediaRecorder-mock-dataavailable.html:

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (239144 => 239145)


--- trunk/LayoutTests/ChangeLog	2018-12-13 02:28:52 UTC (rev 239144)
+++ trunk/LayoutTests/ChangeLog	2018-12-13 02:34:19 UTC (rev 239145)
@@ -1,3 +1,19 @@
+2018-12-12  YUHAN WU  <yuhan...@apple.com>
+
+        Implement non-timeslice mode encoding for MediaRecorder
+        https://bugs.webkit.org/show_bug.cgi?id=192069
+        <rdar://problem/46443290>
+
+        Reviewed by Eric Carlson.
+
+        Create new tests for encoding of MediaRecorder. Check if the produced video and audio are correct.
+        Add code to turn on the mock source of MediaRecorder for the two old tests because the real source is enabled by default.
+
+        * http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable-expected.txt: Added.
+        * http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable.html: Added.
+        * http/wpt/mediarecorder/MediaRecorder-dataavailable.html:
+        * http/wpt/mediarecorder/MediaRecorder-mock-dataavailable.html:
+
 2018-12-12  Justin Fan  <justin_...@apple.com>
 
         [WebGPU] Vertex buffers and WebGPUInputState

Added: trunk/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable-expected.txt (0 => 239145)


--- trunk/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable-expected.txt	2018-12-13 02:34:19 UTC (rev 239145)
@@ -0,0 +1,4 @@
+
+
+PASS MediaRecorder can successfully record the video for a audio-video stream 
+

Added: trunk/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable.html (0 => 239145)


--- trunk/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable.html	                        (rev 0)
+++ trunk/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable.html	2018-12-13 02:34:19 UTC (rev 239145)
@@ -0,0 +1,114 @@
+<!doctype html>
+<html>
+<head>
+    <title>MediaRecorder Dataavailable</title>
+    <link rel="help" href=""
+    <script src=""
+    <script src=""
+    <script src=""
+    <link rel="stylesheet" href=""
+</head>
+<body>
+<div>
+    <video id="player">
+    </video>
+</div>
+<div>
+    <canvas id="canvas" width="200" height="200">
+    </canvas>
+    <canvas id="frame" width="200" height="200">
+    </canvas>
+</div>
+<script>
+    var context;
+    var drawStartTime;
+
+    function createVideoStream() {
+        const canvas = document.getElementById("canvas");
+        context = canvas.getContext('2d');
+        return canvas.captureStream();
+    }
+
+    function doRedImageDraw() {
+        if (context) {
+            context.fillStyle = "#ff0000";
+            context.fillRect(0, 0, 200, 200);
+            if (Date.now() - drawStartTime < 500) {
+                window.requestAnimationFrame(doRedImageDraw);
+            } else {
+                drawStartTime = Date.now();
+                doGreenImageDraw();
+            }
+        }
+    }
+
+    function doGreenImageDraw() {
+        if (context) {
+            context.fillStyle = "#00ff00";
+            context.fillRect(0, 0, 200, 200);
+            if (Date.now() - drawStartTime < 2000) {
+                window.requestAnimationFrame(doGreenImageDraw);
+            }
+        }
+    }
+
+    async_test(t => {
+        const ac = new AudioContext();
+        const osc = ac.createOscillator();
+        const dest = ac.createMediaStreamDestination();
+        const audio = dest.stream;
+        osc.connect(dest);
+
+        const video = createVideoStream();
+        assert_equals(video.getAudioTracks().length, 0, "video mediastream starts with no audio track");
+        assert_equals(audio.getAudioTracks().length, 1, "audio mediastream starts with one audio track");
+        video.addTrack(audio.getAudioTracks()[0]);
+        assert_equals(video.getAudioTracks().length, 1, "video mediastream starts with one audio track");
+        const recorder = new MediaRecorder(video);
+        let mode = 0;
+
+        recorder._ondataavailable_ = t.step_func(blobEvent => {
+            assert_true(blobEvent instanceof BlobEvent, 'the type of event should be BlobEvent');
+            assert_equals(blobEvent.type, 'dataavailable', 'the event type should be dataavailable');
+            assert_true(blobEvent.isTrusted, 'isTrusted should be true when the event is created by C++');
+            assert_true(blobEvent.data instanceof Blob, 'the type of data should be Blob');
+            assert_true(blobEvent.data.size > 0, 'the blob should contain some buffers');
+            player.src = ""
+            const resFrame = document.getElementById("frame");
+            const resContext = resFrame.getContext('2d');
+
+            player._oncanplay_ = () => {
+                assert_greater_than(player.duration, 0.1, 'the duration should be greater than 100ms');
+                player.play();
+            };
+            player._onplay_ = () => {
+                player.pause();
+                player.currentTime = 0.05;
+            };
+            player._onseeked_ = () => {
+                resContext.drawImage(player, 0, 0);
+                if (!mode) {
+                    _assertPixelApprox(resFrame, 25, 25, 255, 0, 0, 255, "25, 25", "255, 0, 0, 255", 20);
+                    _assertPixelApprox(resFrame, 50, 50, 255, 0, 0, 255, "50, 50", "255, 0, 0, 255", 20);
+                    mode = 1;
+                    player.currentTime = player.duration - 0.05;
+                } else {
+                    _assertPixelApprox(resFrame, 20, 20, 0, 255, 0, 255, "20, 20", "0, 255, 0, 255", 20);
+                    _assertPixelApprox(resFrame, 199, 199, 0, 255, 0, 255, "199, 199", "0, 255, 0, 255", 20);
+                    t.done();
+                }
+            };
+            player.load();
+        });
+        drawStartTime = Date.now();
+        doRedImageDraw();
+        recorder.start();
+        assert_equals(recorder.state, 'recording', 'MediaRecorder has been started successfully');
+        setTimeout(() => {
+            recorder.stop();
+        }, 2000);
+    }, 'MediaRecorder can successfully record the video for a audio-video stream');
+
+</script>
+</body>
+</html>
\ No newline at end of file

Modified: trunk/LayoutTests/http/wpt/mediarecorder/MediaRecorder-dataavailable.html (239144 => 239145)


--- trunk/LayoutTests/http/wpt/mediarecorder/MediaRecorder-dataavailable.html	2018-12-13 02:28:52 UTC (rev 239144)
+++ trunk/LayoutTests/http/wpt/mediarecorder/MediaRecorder-dataavailable.html	2018-12-13 02:34:19 UTC (rev 239145)
@@ -12,6 +12,9 @@
 <script>
     var context;
 
+    if (window.internals)
+        internals.setCustomPrivateRecorderCreator();
+
     function createVideoStream() {
         const canvas = document.getElementById("canvas");
         context = canvas.getContext('2d');
@@ -44,7 +47,7 @@
         drawSomethingOnCanvas();
         setTimeout(() => {
             recorder.stop();
-        }, 2000)
+        }, 1000)
     }, 'MediaRecorder will fire a dataavailable event with a blob data for a video-only stream when stop() is called');
 
     async_test(t => {
@@ -65,7 +68,7 @@
         setTimeout(() => {
             recorder.stop();
             osc.stop();
-        }, 2000);
+        }, 1000);
     }, 'MediaRecorder will fire a dataavailable event with a blob data for a audio-only stream when stop() is called');
 
     async_test(t => {
@@ -93,7 +96,7 @@
         setTimeout(() => {
             recorder.stop();
             osc.stop();
-        }, 2000);
+        }, 1000);
     }, 'MediaRecorder will fire a dataavailable event with a blob data for a video-audio stream when stop() is called');
 
 </script>

Modified: trunk/LayoutTests/http/wpt/mediarecorder/MediaRecorder-mock-dataavailable.html (239144 => 239145)


--- trunk/LayoutTests/http/wpt/mediarecorder/MediaRecorder-mock-dataavailable.html	2018-12-13 02:28:52 UTC (rev 239144)
+++ trunk/LayoutTests/http/wpt/mediarecorder/MediaRecorder-mock-dataavailable.html	2018-12-13 02:34:19 UTC (rev 239145)
@@ -12,6 +12,9 @@
 <script>
     var context;
 
+    if (window.internals)
+        internals.setCustomPrivateRecorderCreator();
+
     function createVideoStream() {
         const canvas = document.getElementById("canvas");
         context = canvas.getContext('2d');
@@ -41,7 +44,7 @@
         drawSomethingOnCanvas();
         setTimeout(() => {
             recorder.stop();
-        }, 2000);
+        }, 1000);
     }, 'MediaRecorder will fire a dataavailable event which only contains video buffers for a video-only stream when stop() is called');
 
     async_test(t => {
@@ -67,7 +70,7 @@
         setTimeout(() => {
             recorder.stop();
             osc.stop();
-        }, 2000);
+        }, 1000);
     }, 'MediaRecorder will fire a dataavailable event which only contains audio buffers for a audio-only stream when stop() is called');
 
     async_test(t => {
@@ -100,7 +103,7 @@
         setTimeout(() => {
             recorder.stop();
             osc.stop();
-        }, 2000);
+        }, 1000);
     }, 'MediaRecorder will fire a dataavailable event which only contains both video and audio buffers when stop() is called');
 
 </script>

Modified: trunk/Source/WebCore/ChangeLog (239144 => 239145)


--- trunk/Source/WebCore/ChangeLog	2018-12-13 02:28:52 UTC (rev 239144)
+++ trunk/Source/WebCore/ChangeLog	2018-12-13 02:34:19 UTC (rev 239145)
@@ -1,3 +1,61 @@
+2018-12-12  YUHAN WU  <yuhan...@apple.com>
+
+        Implement non-timeslice mode encoding for MediaRecorder
+        https://bugs.webkit.org/show_bug.cgi?id=192069
+        <rdar://problem/46443290>
+
+        Reviewed by Eric Carlson.
+
+        Implement the encoding for non-timeslice mode of MediaRecorder.
+        It only supports to record MP4 file through H264 and AAC encoding, we will need to support more MIME types and encoding methods.
+        Add a API in internals to allow testings to turn on the mock source.
+
+        Test: http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable.html
+
+        * Modules/mediarecorder/MediaRecorder.cpp:
+        (WebCore::MediaRecorder::create):
+        (WebCore::MediaRecorder::setCustomPrivateRecorderCreator):
+        (WebCore::MediaRecorder::getPrivateImpl):
+        (WebCore::MediaRecorder::MediaRecorder):
+        (WebCore::MediaRecorder::stopRecording):
+        (WebCore::MediaRecorder::stopRecordingInternal):
+        (WebCore::MediaRecorder::createRecordingDataBlob):
+        (WebCore::MediaRecorder::scheduleDeferredTask):
+        * Modules/mediarecorder/MediaRecorder.h:
+        * Modules/mediarecorder/MediaRecorder.idl:
+        * SourcesCocoa.txt:
+        * WebCore.xcodeproj/project.pbxproj:
+        * platform/mediarecorder/MediaRecorderPrivate.h:
+        (WebCore::MediaRecorderPrivate::stopRecording):
+        * platform/mediarecorder/MediaRecorderPrivateAVFImpl.cpp: Added.
+        (WebCore::MediaRecorderPrivateAVFImpl::create):
+        (WebCore::MediaRecorderPrivateAVFImpl::MediaRecorderPrivateAVFImpl):
+        (WebCore::MediaRecorderPrivateAVFImpl::sampleBufferUpdated):
+        (WebCore::MediaRecorderPrivateAVFImpl::audioSamplesAvailable):
+        (WebCore::MediaRecorderPrivateAVFImpl::stopRecording):
+        (WebCore::MediaRecorderPrivateAVFImpl::fetchData):
+        (WebCore::MediaRecorderPrivateAVFImpl::mimeType):
+        * platform/mediarecorder/MediaRecorderPrivateAVFImpl.h: Added.
+        * platform/mediarecorder/MediaRecorderPrivateMock.cpp:
+        (WebCore::MediaRecorderPrivateMock::fetchData):
+        (WebCore::MediaRecorderPrivateMock::mimeType):
+        * platform/mediarecorder/MediaRecorderPrivateMock.h:
+        * platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.h: added.
+        * platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.mm: Added.
+        (WebCore::MediaRecorderPrivateWriter::clear):
+        (WebCore::MediaRecorderPrivateWriter::setupWriter):
+        (WebCore::MediaRecorderPrivateWriter::setVideoInput):
+        (WebCore::MediaRecorderPrivateWriter::setAudioInput):
+        (WebCore::copySampleBufferWithCurrentTimeStamp):
+        (WebCore::MediaRecorderPrivateWriter::appendVideoSampleBuffer):
+        (WebCore::MediaRecorderPrivateWriter::appendAudioSampleBuffer):
+        (WebCore::MediaRecorderPrivateWriter::stopRecording):
+        * testing/Internals.cpp:
+        (WebCore::createRecorderMockSource):
+        (WebCore::Internals::setCustomPrivateRecorderCreator):
+        * testing/Internals.h:
+        * testing/Internals.idl:
+
 2018-12-12  Justin Fan  <justin_...@apple.com>
 
         [WebGPU] Vertex buffers and WebGPUInputState

Modified: trunk/Source/WebCore/Modules/mediarecorder/MediaRecorder.cpp (239144 => 239145)


--- trunk/Source/WebCore/Modules/mediarecorder/MediaRecorder.cpp	2018-12-13 02:28:52 UTC (rev 239144)
+++ trunk/Source/WebCore/Modules/mediarecorder/MediaRecorder.cpp	2018-12-13 02:34:19 UTC (rev 239145)
@@ -33,22 +33,48 @@
 #include "Document.h"
 #include "EventNames.h"
 #include "MediaRecorderErrorEvent.h"
-#include "MediaRecorderPrivateMock.h"
+#include "MediaRecorderPrivate.h"
+#include "SharedBuffer.h"
 
+#if PLATFORM(COCOA)
+#include "MediaRecorderPrivateAVFImpl.h"
+#endif
+
 namespace WebCore {
 
-Ref<MediaRecorder> MediaRecorder::create(Document& document, Ref<MediaStream>&& stream, Options&& options)
+creatorFunction MediaRecorder::m_customCreator = nullptr;
+
+ExceptionOr<Ref<MediaRecorder>> MediaRecorder::create(Document& document, Ref<MediaStream>&& stream, Options&& options)
 {
-    auto recorder = adoptRef(*new MediaRecorder(document, WTFMove(stream), WTFMove(options)));
+    auto privateInstance = MediaRecorder::getPrivateImpl(stream->privateStream());
+    if (!privateInstance)
+        return Exception { NotSupportedError, "The MediaRecorder is unsupported on this platform"_s };
+    auto recorder = adoptRef(*new MediaRecorder(document, WTFMove(stream), WTFMove(privateInstance), WTFMove(options)));
     recorder->suspendIfNeeded();
-    return recorder;
+    return WTFMove(recorder);
 }
 
-MediaRecorder::MediaRecorder(Document& document, Ref<MediaStream>&& stream, Options&& option)
+void MediaRecorder::setCustomPrivateRecorderCreator(creatorFunction creator)
+{
+    m_customCreator = creator;
+}
+
+std::unique_ptr<MediaRecorderPrivate> MediaRecorder::getPrivateImpl(const MediaStreamPrivate& stream)
+{
+    if (m_customCreator)
+        return m_customCreator();
+    
+#if PLATFORM(COCOA)
+    return MediaRecorderPrivateAVFImpl::create(stream);
+#endif
+    return nullptr;
+}
+
+MediaRecorder::MediaRecorder(Document& document, Ref<MediaStream>&& stream, std::unique_ptr<MediaRecorderPrivate>&& privateImpl, Options&& option)
     : ActiveDOMObject(&document)
     , m_options(WTFMove(option))
     , m_stream(WTFMove(stream))
-    , m_private(makeUniqueRef<MediaRecorderPrivateMock>()) // FIXME: we will need to decide which MediaRecorderPrivate instance to create based on the mock enabled feature flag
+    , m_private(WTFMove(privateImpl))
 {
     m_tracks = WTF::map(m_stream->getTracks(), [] (auto&& track) -> Ref<MediaStreamTrackPrivate> {
         return track->privateTrack();
@@ -95,7 +121,7 @@
 {
     if (state() == RecordingState::Inactive)
         return Exception { InvalidStateError, "The MediaRecorder's state cannot be inactive"_s };
-
+    
     scheduleDeferredTask([this] {
         if (!m_isActive || state() == RecordingState::Inactive)
             return;
@@ -102,7 +128,7 @@
 
         stopRecordingInternal();
         ASSERT(m_state == RecordingState::Inactive);
-        dispatchEvent(BlobEvent::create(eventNames().dataavailableEvent, Event::CanBubble::No, Event::IsCancelable::No, m_private->fetchData()));
+        dispatchEvent(BlobEvent::create(eventNames().dataavailableEvent, Event::CanBubble::No, Event::IsCancelable::No, createRecordingDataBlob()));
         if (!m_isActive)
             return;
         dispatchEvent(Event::create(eventNames().stopEvent, Event::CanBubble::No, Event::IsCancelable::No));
@@ -119,8 +145,17 @@
         track->removeObserver(*this);
 
     m_state = RecordingState::Inactive;
+    m_private->stopRecording();
 }
 
+Ref<Blob> MediaRecorder::createRecordingDataBlob()
+{
+    auto data = ""
+    if (!data)
+        return Blob::create();
+    return Blob::create(*data, m_private->mimeType());
+}
+
 void MediaRecorder::didAddOrRemoveTrack()
 {
     scheduleDeferredTask([this] {
@@ -164,6 +199,7 @@
     auto* scriptExecutionContext = this->scriptExecutionContext();
     if (!scriptExecutionContext)
         return;
+
     scriptExecutionContext->postTask([protectedThis = makeRef(*this), function = WTFMove(function)] (auto&) {
         function();
     });

Modified: trunk/Source/WebCore/Modules/mediarecorder/MediaRecorder.h (239144 => 239145)


--- trunk/Source/WebCore/Modules/mediarecorder/MediaRecorder.h	2018-12-13 02:28:52 UTC (rev 239144)
+++ trunk/Source/WebCore/Modules/mediarecorder/MediaRecorder.h	2018-12-13 02:34:19 UTC (rev 239145)
@@ -34,9 +34,12 @@
 
 namespace WebCore {
 
+class Blob;
 class Document;
 class MediaRecorderPrivate;
 
+typedef std::unique_ptr<MediaRecorderPrivate>(*creatorFunction)();
+
 class MediaRecorder final
     : public ActiveDOMObject
     , public RefCounted<MediaRecorder>
@@ -56,8 +59,10 @@
     
     ~MediaRecorder();
     
-    static Ref<MediaRecorder> create(Document&, Ref<MediaStream>&&, Options&& = { });
+    static ExceptionOr<Ref<MediaRecorder>> create(Document&, Ref<MediaStream>&&, Options&& = { });
     
+    WEBCORE_EXPORT static void setCustomPrivateRecorderCreator(creatorFunction);
+    
     RecordingState state() const { return m_state; }
     
     using RefCounted::ref;
@@ -67,8 +72,12 @@
     ExceptionOr<void> stopRecording();
     
 private:
-    MediaRecorder(Document&, Ref<MediaStream>&&, Options&& = { });
+    MediaRecorder(Document&, Ref<MediaStream>&&, std::unique_ptr<MediaRecorderPrivate>&&, Options&& = { });
     
+    static std::unique_ptr<MediaRecorderPrivate> getPrivateImpl(const MediaStreamPrivate&);
+    
+    Ref<Blob> createRecordingDataBlob();
+
     // EventTarget
     void refEventTarget() final { ref(); }
     void derefEventTarget() final { deref(); }
@@ -96,15 +105,17 @@
     void scheduleDeferredTask(Function<void()>&&);
     void setNewRecordingState(RecordingState, Ref<Event>&&);
     
+    static creatorFunction m_customCreator;
+    
     Options m_options;
     Ref<MediaStream> m_stream;
-    UniqueRef<MediaRecorderPrivate> m_private;
+    std::unique_ptr<MediaRecorderPrivate> m_private;
     RecordingState m_state { RecordingState::Inactive };
     Vector<Ref<MediaStreamTrackPrivate>> m_tracks;
     
     bool m_isActive { true };
 };
-    
+
 } // namespace WebCore
 
 #endif // ENABLE(MEDIA_STREAM)

Modified: trunk/Source/WebCore/Modules/mediarecorder/MediaRecorder.idl (239144 => 239145)


--- trunk/Source/WebCore/Modules/mediarecorder/MediaRecorder.idl	2018-12-13 02:28:52 UTC (rev 239144)
+++ trunk/Source/WebCore/Modules/mediarecorder/MediaRecorder.idl	2018-12-13 02:34:19 UTC (rev 239145)
@@ -29,6 +29,7 @@
     Conditional=MEDIA_STREAM,
     Constructor(MediaStream stream, optional MediaRecorderOptions options),
     ConstructorCallWith=Document,
+    ConstructorMayThrowException,
     EnabledAtRuntime=MediaRecorder,
     Exposed=Window
 ] interface MediaRecorder : EventTarget {

Modified: trunk/Source/WebCore/SourcesCocoa.txt (239144 => 239145)


--- trunk/Source/WebCore/SourcesCocoa.txt	2018-12-13 02:28:52 UTC (rev 239144)
+++ trunk/Source/WebCore/SourcesCocoa.txt	2018-12-13 02:34:19 UTC (rev 239145)
@@ -482,6 +482,9 @@
 platform/mac/WebPlaybackControlsManager.mm
 platform/mac/WidgetMac.mm
 
+platform/mediarecorder/MediaRecorderPrivateAVFImpl.cpp
+platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.mm
+
 platform/mediasession/mac/MediaSessionInterruptionProviderMac.mm
 
 platform/mediastream/ios/AVAudioSessionCaptureDevice.mm

Modified: trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj (239144 => 239145)


--- trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2018-12-13 02:28:52 UTC (rev 239144)
+++ trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2018-12-13 02:34:19 UTC (rev 239145)
@@ -1392,6 +1392,8 @@
 		4D3B00AB215D69A70076B983 /* MediaRecorder.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D3B00A9215D69A70076B983 /* MediaRecorder.h */; };
 		4D3B00AF215D6A690076B983 /* BlobEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D3B00AD215D6A690076B983 /* BlobEvent.h */; };
 		4D3B5016217E58B700665DB1 /* MediaRecorderPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D3B5014217E58B700665DB1 /* MediaRecorderPrivate.h */; };
+		4D73F946218BC5FA003A3ED6 /* MediaRecorderPrivateAVFImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D73F944218BC5FA003A3ED6 /* MediaRecorderPrivateAVFImpl.h */; };
+		4D73F94E218C4A87003A3ED6 /* MediaRecorderPrivateWriterCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D73F94C218C4A87003A3ED6 /* MediaRecorderPrivateWriterCocoa.h */; };
 		4DB7130D216ECB4D0096A4DD /* MediaRecorderErrorEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DB7130C216EC2BD0096A4DD /* MediaRecorderErrorEvent.h */; };
 		4E1959220A39DABA00220FE5 /* MediaFeatureNames.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E1959200A39DABA00220FE5 /* MediaFeatureNames.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		4E19592A0A39DACC00220FE5 /* MediaQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E1959240A39DACC00220FE5 /* MediaQuery.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -7939,6 +7941,10 @@
 		4D3B00A9215D69A70076B983 /* MediaRecorder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MediaRecorder.h; sourceTree = "<group>"; };
 		4D3B00AD215D6A690076B983 /* BlobEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlobEvent.h; sourceTree = "<group>"; };
 		4D3B5014217E58B700665DB1 /* MediaRecorderPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MediaRecorderPrivate.h; sourceTree = "<group>"; };
+		4D73F944218BC5FA003A3ED6 /* MediaRecorderPrivateAVFImpl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MediaRecorderPrivateAVFImpl.h; sourceTree = "<group>"; };
+		4D73F945218BC5FA003A3ED6 /* MediaRecorderPrivateAVFImpl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MediaRecorderPrivateAVFImpl.cpp; sourceTree = "<group>"; };
+		4D73F94C218C4A87003A3ED6 /* MediaRecorderPrivateWriterCocoa.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MediaRecorderPrivateWriterCocoa.h; sourceTree = "<group>"; };
+		4D73F94D218C4A87003A3ED6 /* MediaRecorderPrivateWriterCocoa.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = MediaRecorderPrivateWriterCocoa.mm; sourceTree = "<group>"; wrapsLines = 0; };
 		4D7EB3F4217C6AE600D64888 /* BlobEvent.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = BlobEvent.cpp; sourceTree = "<group>"; };
 		4D9F6B642182532B0092A9C5 /* MediaRecorderPrivateMock.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MediaRecorderPrivateMock.h; sourceTree = "<group>"; };
 		4D9F6B652182532B0092A9C5 /* MediaRecorderPrivateMock.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MediaRecorderPrivateMock.cpp; sourceTree = "<group>"; };
@@ -18191,7 +18197,10 @@
 		4D3B5012217E58A300665DB1 /* mediarecorder */ = {
 			isa = PBXGroup;
 			children = (
+				4D3C05D421AF480900F2890A /* cocoa */,
 				4D3B5014217E58B700665DB1 /* MediaRecorderPrivate.h */,
+				4D73F945218BC5FA003A3ED6 /* MediaRecorderPrivateAVFImpl.cpp */,
+				4D73F944218BC5FA003A3ED6 /* MediaRecorderPrivateAVFImpl.h */,
 				4D9F6B652182532B0092A9C5 /* MediaRecorderPrivateMock.cpp */,
 				4D9F6B642182532B0092A9C5 /* MediaRecorderPrivateMock.h */,
 			);
@@ -18198,6 +18207,15 @@
 			path = mediarecorder;
 			sourceTree = "<group>";
 		};
+		4D3C05D421AF480900F2890A /* cocoa */ = {
+			isa = PBXGroup;
+			children = (
+				4D73F94C218C4A87003A3ED6 /* MediaRecorderPrivateWriterCocoa.h */,
+				4D73F94D218C4A87003A3ED6 /* MediaRecorderPrivateWriterCocoa.mm */,
+			);
+			path = cocoa;
+			sourceTree = "<group>";
+		};
 		510310421BA8C64C003329C0 /* client */ = {
 			isa = PBXGroup;
 			children = (
@@ -30187,6 +30205,8 @@
 				4D3B00AB215D69A70076B983 /* MediaRecorder.h in Headers */,
 				4DB7130D216ECB4D0096A4DD /* MediaRecorderErrorEvent.h in Headers */,
 				4D3B5016217E58B700665DB1 /* MediaRecorderPrivate.h in Headers */,
+				4D73F946218BC5FA003A3ED6 /* MediaRecorderPrivateAVFImpl.h in Headers */,
+				4D73F94E218C4A87003A3ED6 /* MediaRecorderPrivateWriterCocoa.h in Headers */,
 				C90843D01B18E47D00B68564 /* MediaRemoteControls.h in Headers */,
 				CD8ACA8F1D23971900ECC59E /* MediaRemoteSoftLink.h in Headers */,
 				CEEFCD7A19DB31F7003876D7 /* MediaResourceLoader.h in Headers */,

Modified: trunk/Source/WebCore/platform/mediarecorder/MediaRecorderPrivate.h (239144 => 239145)


--- trunk/Source/WebCore/platform/mediarecorder/MediaRecorderPrivate.h	2018-12-13 02:28:52 UTC (rev 239144)
+++ trunk/Source/WebCore/platform/mediarecorder/MediaRecorderPrivate.h	2018-12-13 02:34:19 UTC (rev 239145)
@@ -35,10 +35,10 @@
 namespace WebCore {
 
 class AudioStreamDescription;
-class Blob;
-class PlatformAudioData;
 class MediaSample;
 class MediaStreamTrackPrivate;
+class PlatformAudioData;
+class SharedBuffer;
 
 class MediaRecorderPrivate {
 public:
@@ -45,8 +45,10 @@
     virtual void sampleBufferUpdated(MediaStreamTrackPrivate&, MediaSample&) = 0;
     virtual void audioSamplesAvailable(MediaStreamTrackPrivate&, const WTF::MediaTime&, const PlatformAudioData&, const AudioStreamDescription&, size_t) = 0;
     
-    virtual Ref<Blob> fetchData() = 0;
+    virtual RefPtr<SharedBuffer> fetchData() = 0;
+    virtual const String& mimeType() = 0;
     virtual ~MediaRecorderPrivate() = default;
+    virtual void stopRecording() { }
 };
     
 } // namespace WebCore

Added: trunk/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateAVFImpl.cpp (0 => 239145)


--- trunk/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateAVFImpl.cpp	                        (rev 0)
+++ trunk/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateAVFImpl.cpp	2018-12-13 02:34:19 UTC (rev 239145)
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2018 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+
+#include "config.h"
+#include "MediaRecorderPrivateAVFImpl.h"
+
+#if ENABLE(MEDIA_STREAM)
+
+#include "AudioStreamDescription.h"
+#include "MediaSample.h"
+#include "MediaStreamPrivate.h"
+#include "SharedBuffer.h"
+#include "WebAudioBufferList.h"
+
+namespace WebCore {
+
+std::unique_ptr<MediaRecorderPrivateAVFImpl> MediaRecorderPrivateAVFImpl::create(const MediaStreamPrivate& stream)
+{
+    auto instance = std::unique_ptr<MediaRecorderPrivateAVFImpl>(new MediaRecorderPrivateAVFImpl(stream));
+    if (!instance->m_isWriterReady)
+        return nullptr;
+    return instance;
+}
+
+MediaRecorderPrivateAVFImpl::MediaRecorderPrivateAVFImpl(const MediaStreamPrivate& stream)
+{
+    if (!m_writer.setupWriter())
+        return;
+    auto tracks = stream.tracks();
+    bool videoSelected = false;
+    bool audioSelected = false;
+    for (auto& track : tracks) {
+        if (!track->enabled() || track->ended())
+            continue;
+        switch (track->type()) {
+        case RealtimeMediaSource::Type::Video: {
+            auto& settings = track->settings();
+            if (videoSelected || !settings.supportsWidth() || !settings.supportsHeight())
+                break;
+            // FIXME: we will need to implement support for multiple video tracks, currently we only choose the first track as the recorded track.
+            // FIXME: we would better to throw an exception to _javascript_ if setVideoInput failed
+            if (!m_writer.setVideoInput(settings.width(), settings.height()))
+                return;
+            m_recordedVideoTrackID = track->id();
+            videoSelected = true;
+            break;
+        }
+        case RealtimeMediaSource::Type::Audio: {
+            if (audioSelected)
+                break;
+            // FIXME: we will need to implement support for multiple audio tracks, currently we only choose the first track as the recorded track.
+            // FIXME: we would better to throw an exception to _javascript_ if setAudioInput failed
+            if (!m_writer.setAudioInput())
+                return;
+            m_recordedAudioTrackID = track->id();
+            audioSelected = true;
+            break;
+        }
+        case RealtimeMediaSource::Type::None:
+            break;
+        }
+    }
+    m_isWriterReady = true;
+}
+
+void MediaRecorderPrivateAVFImpl::sampleBufferUpdated(MediaStreamTrackPrivate& track, MediaSample& sampleBuffer)
+{
+    if (track.id() != m_recordedVideoTrackID)
+        return;
+    m_writer.appendVideoSampleBuffer(sampleBuffer.platformSample().sample.cmSampleBuffer);
+}
+
+void MediaRecorderPrivateAVFImpl::audioSamplesAvailable(MediaStreamTrackPrivate& track, const WTF::MediaTime& mediaTime, const PlatformAudioData& data, const AudioStreamDescription& description, size_t sampleCount)
+{
+    if (track.id() != m_recordedAudioTrackID)
+        return;
+    ASSERT(is<WebAudioBufferList>(data));
+    ASSERT(description.platformDescription().type == PlatformDescription::CAAudioStreamBasicType);
+    m_writer.appendAudioSampleBuffer(data, description, mediaTime, sampleCount);
+}
+
+void MediaRecorderPrivateAVFImpl::stopRecording()
+{
+    m_writer.stopRecording();
+}
+
+RefPtr<SharedBuffer> MediaRecorderPrivateAVFImpl::fetchData()
+{
+    return m_writer.fetchData();
+}
+
+const String& MediaRecorderPrivateAVFImpl::mimeType()
+{
+    static NeverDestroyed<const String> mp4MimeType(MAKE_STATIC_STRING_IMPL("video/mp4"));
+    // FIXME: we will need to support more MIME types.
+    return mp4MimeType;
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(MEDIA_STREAM)

Copied: trunk/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateAVFImpl.h (from rev 239144, trunk/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateMock.h) (0 => 239145)


--- trunk/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateAVFImpl.h	                        (rev 0)
+++ trunk/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateAVFImpl.h	2018-12-13 02:34:19 UTC (rev 239145)
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2018 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. AND ITS CONTRIBUTORS ``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 ITS 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
+
+#if ENABLE(MEDIA_STREAM)
+
+#include "MediaRecorderPrivate.h"
+#include "MediaRecorderPrivateWriterCocoa.h"
+
+namespace WebCore {
+
+class MediaStreamPrivate;
+
+class MediaRecorderPrivateAVFImpl final : public MediaRecorderPrivate {
+public:
+    static std::unique_ptr<MediaRecorderPrivateAVFImpl> create(const MediaStreamPrivate&);
+
+private:
+    explicit MediaRecorderPrivateAVFImpl(const MediaStreamPrivate&);
+
+    void sampleBufferUpdated(MediaStreamTrackPrivate&, MediaSample&) final;
+    void audioSamplesAvailable(MediaStreamTrackPrivate&, const WTF::MediaTime&, const PlatformAudioData&, const AudioStreamDescription&, size_t) final;
+    RefPtr<SharedBuffer> fetchData() final;
+    const String& mimeType() final;
+    void stopRecording();
+    
+    String m_recordedVideoTrackID;
+    String m_recordedAudioTrackID;
+
+    MediaRecorderPrivateWriter m_writer;
+    
+    bool m_isWriterReady { false };
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(MEDIA_STREAM)

Modified: trunk/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateMock.cpp (239144 => 239145)


--- trunk/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateMock.cpp	2018-12-13 02:28:52 UTC (rev 239144)
+++ trunk/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateMock.cpp	2018-12-13 02:34:19 UTC (rev 239145)
@@ -28,8 +28,8 @@
 
 #if ENABLE(MEDIA_STREAM)
 
-#include "Blob.h"
 #include "MediaStreamTrackPrivate.h"
+#include "SharedBuffer.h"
 
 namespace WebCore {
 
@@ -56,15 +56,21 @@
     m_buffer.append("\r\n---------\r\n");
 }
 
-Ref<Blob> MediaRecorderPrivateMock::fetchData()
+RefPtr<SharedBuffer> MediaRecorderPrivateMock::fetchData()
 {
     auto locker = holdLock(m_bufferLock);
     Vector<uint8_t> value(m_buffer.length());
     memcpy(value.data(), m_buffer.characters8(), m_buffer.length());
     m_buffer.clear();
-    return Blob::create(WTFMove(value), "text/plain");
+    return SharedBuffer::create(WTFMove(value));
 }
 
+const String& MediaRecorderPrivateMock::mimeType()
+{
+    static NeverDestroyed<const String> textPlainMimeType(MAKE_STATIC_STRING_IMPL("text/plain"));
+    return textPlainMimeType;
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(MEDIA_STREAM)

Modified: trunk/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateMock.h (239144 => 239145)


--- trunk/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateMock.h	2018-12-13 02:28:52 UTC (rev 239144)
+++ trunk/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateMock.h	2018-12-13 02:34:19 UTC (rev 239145)
@@ -32,14 +32,14 @@
 
 namespace WebCore {
 
-class Blob;
 class MediaStreamTrackPrivate;
 
-class MediaRecorderPrivateMock final : public MediaRecorderPrivate {
+class WEBCORE_EXPORT MediaRecorderPrivateMock final : public MediaRecorderPrivate {
 private:
     void sampleBufferUpdated(MediaStreamTrackPrivate&, MediaSample&) final;
     void audioSamplesAvailable(MediaStreamTrackPrivate&, const WTF::MediaTime&, const PlatformAudioData&, const AudioStreamDescription&, size_t) final;
-    Ref<Blob> fetchData() final;
+    RefPtr<SharedBuffer> fetchData() final;
+    const String& mimeType() final;
     
     void generateMockString(MediaStreamTrackPrivate&);
 

Copied: trunk/Source/WebCore/platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.h (from rev 239144, trunk/Source/WebCore/platform/mediarecorder/MediaRecorderPrivate.h) (0 => 239145)


--- trunk/Source/WebCore/platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.h	                        (rev 0)
+++ trunk/Source/WebCore/platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.h	2018-12-13 02:34:19 UTC (rev 239145)
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2018 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. AND ITS CONTRIBUTORS ``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 ITS 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
+
+#if ENABLE(MEDIA_STREAM)
+
+#include "SharedBuffer.h"
+#include <wtf/Deque.h>
+#include <wtf/Lock.h>
+#include <wtf/RetainPtr.h>
+#include <wtf/ThreadSafeRefCounted.h>
+#include <wtf/threads/BinarySemaphore.h>
+
+typedef struct opaqueCMSampleBuffer *CMSampleBufferRef;
+
+OBJC_CLASS AVAssetWriter;
+OBJC_CLASS AVAssetWriterInput;
+
+namespace WTF {
+class MediaTime;
+}
+
+namespace WebCore {
+
+class AudioStreamDescription;
+class PlatformAudioData;
+
+class MediaRecorderPrivateWriter : public ThreadSafeRefCounted<MediaRecorderPrivateWriter, WTF::DestructionThread::Main>, public CanMakeWeakPtr<MediaRecorderPrivateWriter> {
+public:
+    MediaRecorderPrivateWriter() = default;
+    ~MediaRecorderPrivateWriter();
+    
+    bool setupWriter();
+    bool setVideoInput(int width, int height);
+    bool setAudioInput();
+    void appendVideoSampleBuffer(CMSampleBufferRef);
+    void appendAudioSampleBuffer(const PlatformAudioData&, const AudioStreamDescription&, const WTF::MediaTime&, size_t);
+    void stopRecording();
+    RefPtr<SharedBuffer> fetchData();
+    
+private:
+    void clear();
+
+    RetainPtr<AVAssetWriter> m_writer;
+    RetainPtr<AVAssetWriterInput> m_videoInput;
+    RetainPtr<AVAssetWriterInput> m_audioInput;
+
+    String m_path;
+    Lock m_videoLock;
+    Lock m_audioLock;
+    BinarySemaphore m_finishWritingSemaphore;
+    BinarySemaphore m_finishWritingAudioSemaphore;
+    BinarySemaphore m_finishWritingVideoSemaphore;
+    bool m_hasStartedWriting { false };
+    bool m_isStopped { false };
+    bool m_isFirstAudioSample { true };
+    dispatch_queue_t m_audioPullQueue;
+    dispatch_queue_t m_videoPullQueue;
+    Deque<RetainPtr<CMSampleBufferRef>> m_videoBufferPool;
+    Deque<RetainPtr<CMSampleBufferRef>> m_audioBufferPool;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(MEDIA_STREAM)

Added: trunk/Source/WebCore/platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.mm (0 => 239145)


--- trunk/Source/WebCore/platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.mm	                        (rev 0)
+++ trunk/Source/WebCore/platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.mm	2018-12-13 02:34:19 UTC (rev 239145)
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2018 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+ */
+
+#include "config.h"
+#include "MediaRecorderPrivateWriterCocoa.h"
+
+#if ENABLE(MEDIA_STREAM) && USE(AVFOUNDATION)
+
+#include "AudioStreamDescription.h"
+#include "FileSystem.h"
+#include "Logging.h"
+#include "WebAudioBufferList.h"
+#include <AVFoundation/AVAssetWriter.h>
+#include <AVFoundation/AVAssetWriterInput.h>
+#include <pal/cf/CoreMediaSoftLink.h>
+
+typedef AVAssetWriter AVAssetWriterType;
+typedef AVAssetWriterInput AVAssetWriterInputType;
+
+SOFT_LINK_FRAMEWORK_OPTIONAL(AVFoundation)
+
+SOFT_LINK_CLASS(AVFoundation, AVAssetWriter)
+SOFT_LINK_CLASS(AVFoundation, AVAssetWriterInput)
+
+SOFT_LINK_CONSTANT(AVFoundation, AVFileTypeMPEG4, NSString *)
+SOFT_LINK_CONSTANT(AVFoundation, AVVideoCodecKey, NSString *)
+SOFT_LINK_CONSTANT(AVFoundation, AVVideoCodecH264, NSString *)
+SOFT_LINK_CONSTANT(AVFoundation, AVVideoWidthKey, NSString *)
+SOFT_LINK_CONSTANT(AVFoundation, AVVideoHeightKey, NSString *)
+SOFT_LINK_CONSTANT(AVFoundation, AVMediaTypeVideo, NSString *)
+SOFT_LINK_CONSTANT(AVFoundation, AVMediaTypeAudio, NSString *)
+SOFT_LINK_CONSTANT(AVFoundation, AVEncoderBitRateKey, NSString *)
+SOFT_LINK_CONSTANT(AVFoundation, AVFormatIDKey, NSString *)
+SOFT_LINK_CONSTANT(AVFoundation, AVNumberOfChannelsKey, NSString *)
+SOFT_LINK_CONSTANT(AVFoundation, AVSampleRateKey, NSString *)
+
+SOFT_LINK_CONSTANT(AVFoundation, AVVideoExpectedSourceFrameRateKey, NSString *)
+SOFT_LINK_CONSTANT(AVFoundation, AVVideoProfileLevelKey, NSString *)
+SOFT_LINK_CONSTANT(AVFoundation, AVVideoAverageBitRateKey, NSString *)
+SOFT_LINK_CONSTANT(AVFoundation, AVVideoMaxKeyFrameIntervalKey, NSString *)
+SOFT_LINK_CONSTANT(AVFoundation, AVVideoProfileLevelH264MainAutoLevel, NSString *)
+SOFT_LINK_CONSTANT(AVFoundation, AVVideoCompressionPropertiesKey, NSString *)
+
+#define AVFileTypeMPEG4 getAVFileTypeMPEG4()
+#define AVMediaTypeAudio getAVMediaTypeAudio()
+#define AVMediaTypeVideo getAVMediaTypeVideo()
+#define AVVideoCodecKey getAVVideoCodecKey()
+#define AVVideoCodecH264 getAVVideoCodecH264()
+#define AVVideoWidthKey getAVVideoWidthKey()
+#define AVVideoHeightKey getAVVideoHeightKey()
+#define AVEncoderBitRateKey getAVEncoderBitRateKey()
+#define AVFormatIDKey getAVFormatIDKey()
+#define AVNumberOfChannelsKey getAVNumberOfChannelsKey()
+#define AVSampleRateKey getAVSampleRateKey()
+
+#define AVVideoExpectedSourceFrameRateKey getAVVideoExpectedSourceFrameRateKey()
+#define AVVideoProfileLevelKey getAVVideoProfileLevelKey()
+#define AVVideoAverageBitRateKey getAVVideoAverageBitRateKey()
+#define AVVideoMaxKeyFrameIntervalKey getAVVideoMaxKeyFrameIntervalKey()
+#define AVVideoProfileLevelH264MainAutoLevel getAVVideoProfileLevelH264MainAutoLevel()
+#define AVVideoCompressionPropertiesKey getAVVideoCompressionPropertiesKey()
+
+namespace WebCore {
+
+using namespace PAL;
+
+MediaRecorderPrivateWriter::~MediaRecorderPrivateWriter()
+{
+    clear();
+}
+
+void MediaRecorderPrivateWriter::clear()
+{
+    if (m_videoInput) {
+        m_videoInput.clear();
+        dispatch_release(m_videoPullQueue);
+    }
+    if (m_audioInput) {
+        m_audioInput.clear();
+        dispatch_release(m_audioPullQueue);
+    }
+    if (m_writer)
+        m_writer.clear();
+}
+
+bool MediaRecorderPrivateWriter::setupWriter()
+{
+    ASSERT(!m_writer);
+    
+    NSString *directory = FileSystem::createTemporaryDirectory(@"videos");
+    NSString *filename = [NSString stringWithFormat:@"/%lld.mp4", CMClockGetTime(CMClockGetHostTimeClock()).value];
+    NSString *path = [directory stringByAppendingString:filename];
+
+    NSURL *outputURL = [NSURL fileURLWithPath:path];
+    m_path = [path UTF8String];
+    NSError *error = nil;
+    m_writer = adoptNS([allocAVAssetWriterInstance() initWithURL:outputURL fileType:AVFileTypeMPEG4 error:&error]);
+    if (error) {
+        RELEASE_LOG_ERROR(MediaStream, "create AVAssetWriter instance failed with error code %ld", (long)error.code);
+        m_writer = nullptr;
+        return false;
+    }
+    return true;
+}
+
+bool MediaRecorderPrivateWriter::setVideoInput(int width, int height)
+{
+    ASSERT(!m_videoInput);
+    
+    NSDictionary *compressionProperties = @{
+        AVVideoAverageBitRateKey : [NSNumber numberWithInt:width * height * 12],
+        AVVideoExpectedSourceFrameRateKey : @(30),
+        AVVideoMaxKeyFrameIntervalKey : @(120),
+        AVVideoProfileLevelKey : AVVideoProfileLevelH264MainAutoLevel
+    };
+
+    NSDictionary *videoSettings = @{
+        AVVideoCodecKey: AVVideoCodecH264,
+        AVVideoWidthKey: [NSNumber numberWithInt:width],
+        AVVideoHeightKey: [NSNumber numberWithInt:height],
+        AVVideoCompressionPropertiesKey: compressionProperties
+    };
+    
+    m_videoInput = adoptNS([allocAVAssetWriterInputInstance() initWithMediaType:AVMediaTypeVideo outputSettings:videoSettings sourceFormatHint:nil]);
+    [m_videoInput setExpectsMediaDataInRealTime:true];
+    
+    if (![m_writer canAddInput:m_videoInput.get()]) {
+        m_videoInput = nullptr;
+        RELEASE_LOG_ERROR(MediaStream, "the video input is not allowed to add to the AVAssetWriter");
+        return false;
+    }
+    [m_writer addInput:m_videoInput.get()];
+    m_videoPullQueue = dispatch_queue_create("WebCoreVideoRecordingPullBufferQueue", DISPATCH_QUEUE_SERIAL);
+    return true;
+}
+
+bool MediaRecorderPrivateWriter::setAudioInput()
+{
+    ASSERT(!m_audioInput);
+
+    NSDictionary *audioSettings = @{
+        AVEncoderBitRateKey : @(28000),
+        AVFormatIDKey : @(kAudioFormatMPEG4AAC),
+        AVNumberOfChannelsKey : @(1),
+        AVSampleRateKey : @(22050)
+    };
+
+    m_audioInput = adoptNS([allocAVAssetWriterInputInstance() initWithMediaType:AVMediaTypeAudio outputSettings:audioSettings sourceFormatHint:nil]);
+    [m_audioInput setExpectsMediaDataInRealTime:true];
+    
+    if (![m_writer canAddInput:m_audioInput.get()]) {
+        m_audioInput = nullptr;
+        RELEASE_LOG_ERROR(MediaStream, "the audio input is not allowed to add to the AVAssetWriter");
+        return false;
+    }
+    [m_writer addInput:m_audioInput.get()];
+    m_audioPullQueue = dispatch_queue_create("WebCoreAudioRecordingPullBufferQueue", DISPATCH_QUEUE_SERIAL);
+    return true;
+}
+
+static inline CMSampleBufferRef copySampleBufferWithCurrentTimeStamp(CMSampleBufferRef originalBuffer)
+{
+    CMTime startTime = CMClockGetTime(CMClockGetHostTimeClock());
+    CMItemCount count = 0;
+    CMSampleBufferGetSampleTimingInfoArray(originalBuffer, 0, nil, &count);
+    
+    Vector<CMSampleTimingInfo> timeInfo(count);
+    CMSampleBufferGetSampleTimingInfoArray(originalBuffer, count, timeInfo.data(), &count);
+    
+    for (CMItemCount i = 0; i < count; i++) {
+        timeInfo[i].decodeTimeStamp = kCMTimeInvalid;
+        timeInfo[i].presentationTimeStamp = startTime;
+    }
+    
+    CMSampleBufferRef newBuffer;
+    CMSampleBufferCreateCopyWithNewTiming(kCFAllocatorDefault, originalBuffer, count, timeInfo.data(), &newBuffer);
+    return newBuffer;
+}
+
+void MediaRecorderPrivateWriter::appendVideoSampleBuffer(CMSampleBufferRef sampleBuffer)
+{
+    ASSERT(m_videoInput);
+    if (m_isStopped)
+        return;
+
+    if (!m_hasStartedWriting) {
+        if (![m_writer startWriting]) {
+            m_isStopped = true;
+            RELEASE_LOG_ERROR(MediaStream, "create AVAssetWriter instance failed with error code %ld", (long)[m_writer error]);
+            return;
+        }
+        [m_writer startSessionAtSourceTime:CMClockGetTime(CMClockGetHostTimeClock())];
+        m_hasStartedWriting = true;
+        RefPtr<MediaRecorderPrivateWriter> protectedThis = this;
+        [m_videoInput requestMediaDataWhenReadyOnQueue:m_videoPullQueue usingBlock:[this, protectedThis = WTFMove(protectedThis)] {
+            do {
+                if (![m_videoInput isReadyForMoreMediaData])
+                    break;
+                auto locker = holdLock(m_videoLock);
+                if (m_videoBufferPool.isEmpty())
+                    break;
+                auto buffer = m_videoBufferPool.takeFirst();
+                locker.unlockEarly();
+                if (![m_videoInput appendSampleBuffer:buffer.get()])
+                    break;
+            } while (true);
+            if (m_isStopped && m_videoBufferPool.isEmpty()) {
+                [m_videoInput markAsFinished];
+                m_finishWritingVideoSemaphore.signal();
+            }
+        }];
+        return;
+    }
+    CMSampleBufferRef bufferWithCurrentTime = copySampleBufferWithCurrentTimeStamp(sampleBuffer);
+    auto locker = holdLock(m_videoLock);
+    m_videoBufferPool.append(retainPtr(bufferWithCurrentTime));
+}
+
+void MediaRecorderPrivateWriter::appendAudioSampleBuffer(const PlatformAudioData& data, const AudioStreamDescription& description, const WTF::MediaTime&, size_t sampleCount)
+{
+    ASSERT(m_audioInput);
+    if ((!m_hasStartedWriting && m_videoInput) || m_isStopped)
+        return;
+    CMSampleBufferRef sampleBuffer;
+    CMFormatDescriptionRef format;
+    OSStatus error;
+    auto& basicDescription = *WTF::get<const AudioStreamBasicDescription*>(description.platformDescription().description);
+    error = CMAudioFormatDescriptionCreate(kCFAllocatorDefault, &basicDescription, 0, NULL, 0, NULL, NULL, &format);
+    if (m_isFirstAudioSample) {
+        if (!m_videoInput) {
+            // audio-only recording.
+            if (![m_writer startWriting]) {
+                m_isStopped = true;
+                return;
+            }
+            [m_writer startSessionAtSourceTime:CMClockGetTime(CMClockGetHostTimeClock())];
+            m_hasStartedWriting = true;
+        }
+        m_isFirstAudioSample = false;
+        RefPtr<MediaRecorderPrivateWriter> protectedThis = this;
+        [m_audioInput requestMediaDataWhenReadyOnQueue:m_audioPullQueue usingBlock:[this, protectedThis] {
+            do {
+                if (![m_audioInput isReadyForMoreMediaData])
+                    break;
+                auto locker = holdLock(m_audioLock);
+                if (m_audioBufferPool.isEmpty())
+                    break;
+                auto buffer = m_audioBufferPool.takeFirst();
+                locker.unlockEarly();
+                [m_audioInput appendSampleBuffer:buffer.get()];
+            } while (true);
+            if (m_isStopped && m_audioBufferPool.isEmpty()) {
+                [m_audioInput markAsFinished];
+                m_finishWritingAudioSemaphore.signal();
+            }
+        }];
+    }
+    CMTime startTime = CMClockGetTime(CMClockGetHostTimeClock());
+
+    error = CMAudioSampleBufferCreateWithPacketDescriptions(kCFAllocatorDefault, NULL, false, NULL, NULL, format, sampleCount, startTime, NULL, &sampleBuffer);
+    if (error)
+        return;
+    error = CMSampleBufferSetDataBufferFromAudioBufferList(sampleBuffer, kCFAllocatorDefault, kCFAllocatorDefault, 0, downcast<WebAudioBufferList>(data).list());
+    if (error)
+        return;
+
+    auto locker = holdLock(m_audioLock);
+    m_audioBufferPool.append(retainPtr(sampleBuffer));
+}
+
+void MediaRecorderPrivateWriter::stopRecording()
+{
+    m_isStopped = true;
+    if (!m_hasStartedWriting)
+        return;
+    ASSERT([m_writer status] == AVAssetWriterStatusWriting);
+    if (m_videoInput)
+        m_finishWritingVideoSemaphore.wait();
+
+    if (m_audioInput)
+        m_finishWritingAudioSemaphore.wait();
+    auto weakPtr = makeWeakPtr(*this);
+    [m_writer finishWritingWithCompletionHandler:[this, weakPtr] {
+        m_finishWritingSemaphore.signal();
+        callOnMainThread([this, weakPtr] {
+            if (!weakPtr)
+                return;
+            m_isStopped = false;
+            m_hasStartedWriting = false;
+            m_isFirstAudioSample = true;
+            clear();
+        });
+    }];
+}
+
+RefPtr<SharedBuffer> MediaRecorderPrivateWriter::fetchData()
+{
+    if ((m_path.isEmpty() && !m_isStopped) || !m_hasStartedWriting)
+        return nullptr;
+    
+    m_finishWritingSemaphore.wait();
+    return SharedBuffer::createWithContentsOfFile(m_path);
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(MEDIA_STREAM) && USE(AVFOUNDATION)

Modified: trunk/Source/WebCore/testing/Internals.cpp (239144 => 239145)


--- trunk/Source/WebCore/testing/Internals.cpp	2018-12-13 02:28:52 UTC (rev 239144)
+++ trunk/Source/WebCore/testing/Internals.cpp	2018-12-13 02:34:19 UTC (rev 239145)
@@ -228,6 +228,8 @@
 #endif
 
 #if ENABLE(MEDIA_STREAM)
+#include "MediaRecorder.h"
+#include "MediaRecorderPrivateMock.h"
 #include "MediaStream.h"
 #include "MockRealtimeMediaSourceCenter.h"
 #endif
@@ -1473,6 +1475,16 @@
     WebCore::DeprecatedGlobalSettings::setMockCaptureDevicesEnabled(enabled);
 }
 
+static std::unique_ptr<MediaRecorderPrivate> createRecorderMockSource()
+{
+    return std::unique_ptr<MediaRecorderPrivateMock>(new MediaRecorderPrivateMock);
+}
+
+void Internals::setCustomPrivateRecorderCreator()
+{
+    WebCore::MediaRecorder::setCustomPrivateRecorderCreator(createRecorderMockSource);
+}
+
 #endif
 
 ExceptionOr<Ref<DOMRect>> Internals::absoluteCaretBounds()

Modified: trunk/Source/WebCore/testing/Internals.h (239144 => 239145)


--- trunk/Source/WebCore/testing/Internals.h	2018-12-13 02:28:52 UTC (rev 239144)
+++ trunk/Source/WebCore/testing/Internals.h	2018-12-13 02:34:19 UTC (rev 239145)
@@ -507,6 +507,7 @@
 
 #if ENABLE(MEDIA_STREAM)
     void setMockMediaCaptureDevicesEnabled(bool);
+    void setCustomPrivateRecorderCreator();
 #endif
 
 #if ENABLE(WEB_RTC)

Modified: trunk/Source/WebCore/testing/Internals.idl (239144 => 239145)


--- trunk/Source/WebCore/testing/Internals.idl	2018-12-13 02:28:52 UTC (rev 239144)
+++ trunk/Source/WebCore/testing/Internals.idl	2018-12-13 02:34:19 UTC (rev 239145)
@@ -575,6 +575,7 @@
     [Conditional=WIRELESS_PLAYBACK_TARGET] void setMockMediaPlaybackTargetPickerEnabled(boolean enabled);
     [Conditional=WIRELESS_PLAYBACK_TARGET, MayThrowException] void setMockMediaPlaybackTargetPickerState(DOMString deviceName, DOMString deviceState);
     [Conditional=MEDIA_STREAM] void setMockMediaCaptureDevicesEnabled(boolean enabled);
+    [Conditional=MEDIA_STREAM] void setCustomPrivateRecorderCreator();
 
     [Conditional=WEB_RTC] void emulateRTCPeerConnectionPlatformEvent(RTCPeerConnection connection, DOMString action);
     [Conditional=WEB_RTC] void useMockRTCPeerConnectionFactory(DOMString testCase);
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to