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);