summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormcasas <mcasas@chromium.org>2015-07-23 03:20:25 -0700
committerCommit bot <commit-bot@chromium.org>2015-07-23 10:20:57 +0000
commit6f2bce27d46c0fd56d26143acdfdcdb1a7fc463f (patch)
tree4c76683bb9fe0d5814033db888c42f4a3a073a7d
parent0bbb0262be0570bb4d9d06444c86ed84609afb1c (diff)
downloadchromium_src-6f2bce27d46c0fd56d26143acdfdcdb1a7fc463f.zip
chromium_src-6f2bce27d46c0fd56d26143acdfdcdb1a7fc463f.tar.gz
chromium_src-6f2bce27d46c0fd56d26143acdfdcdb1a7fc463f.tar.bz2
media/capture: Adding WebmMuxer class and unittests
WebmMuxer gets encoded video packets and pings a callback with packetised "blobs" following Live WebM (simplified Matroska container) format. See DD @ https://goo.gl/kreaQj for the plan and https://codereview.chromium.org/1211973012/ for a hack of all parts together. BUG=262211 Review URL: https://codereview.chromium.org/1225123006 Cr-Commit-Position: refs/heads/master@{#340066}
-rw-r--r--media/BUILD.gn8
-rw-r--r--media/DEPS1
-rw-r--r--media/capture/webm_muxer.cc98
-rw-r--r--media/capture/webm_muxer.h87
-rw-r--r--media/capture/webm_muxer_unittest.cc128
-rw-r--r--media/media.gyp25
-rw-r--r--media/media_options.gni6
-rw-r--r--media/media_variables.gypi4
8 files changed, 355 insertions, 2 deletions
diff --git a/media/BUILD.gn b/media/BUILD.gn
index 8db1443..52935a6 100644
--- a/media/BUILD.gn
+++ b/media/BUILD.gn
@@ -332,6 +332,14 @@ component("media") {
deps += [ "//third_party/libvpx" ]
}
+ if (media_use_libwebm) {
+ sources += [
+ "capture/webm_muxer.cc",
+ "capture/webm_muxer.h",
+ ]
+ deps += [ "//third_party/libwebm" ]
+ }
+
if (is_android) {
sources += [
"capture/video/android/video_capture_device_android.cc",
diff --git a/media/DEPS b/media/DEPS
index 213ce6e..08abb1d 100644
--- a/media/DEPS
+++ b/media/DEPS
@@ -8,6 +8,7 @@ include_rules = [
"+jni",
"+skia/ext",
"+third_party/ffmpeg",
+ "+third_party/libwebm",
"+third_party/libvpx",
"+third_party/libyuv",
"+third_party/opus",
diff --git a/media/capture/webm_muxer.cc b/media/capture/webm_muxer.cc
new file mode 100644
index 0000000..8f67041
--- /dev/null
+++ b/media/capture/webm_muxer.cc
@@ -0,0 +1,98 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/capture/webm_muxer.h"
+
+#include <limits>
+
+namespace media {
+
+WebmMuxer::WebmMuxer(const WriteDataCB& write_data_callback)
+ : write_data_callback_(write_data_callback),
+ position_(0) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ segment_.Init(this);
+ segment_.set_mode(mkvmuxer::Segment::kLive);
+ segment_.OutputCues(false);
+
+ mkvmuxer::SegmentInfo* const info = segment_.GetSegmentInfo();
+ info->set_writing_app("Chrome");
+ info->set_muxing_app("Chrome");
+}
+
+WebmMuxer::~WebmMuxer() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ segment_.Finalize();
+}
+
+uint64_t WebmMuxer::AddVideoTrack(const gfx::Size& frame_size,
+ double frame_rate) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ const uint64_t track_index =
+ segment_.AddVideoTrack(frame_size.width(), frame_size.height(), 0);
+
+ mkvmuxer::VideoTrack* const video_track =
+ reinterpret_cast<mkvmuxer::VideoTrack*>(
+ segment_.GetTrackByNumber(track_index));
+ DCHECK(video_track);
+ video_track->set_codec_id(mkvmuxer::Tracks::kVp8CodecId);
+ DCHECK_EQ(video_track->crop_right(), 0ull);
+ DCHECK_EQ(video_track->crop_left(), 0ull);
+ DCHECK_EQ(video_track->crop_top(), 0ull);
+ DCHECK_EQ(video_track->crop_bottom(), 0ull);
+
+ video_track->set_frame_rate(frame_rate);
+ video_track->set_default_duration(base::Time::kMicrosecondsPerSecond /
+ frame_rate);
+ // Segment's timestamps should be in milliseconds, DCHECK it. See
+ // http://www.webmproject.org/docs/container/#muxer-guidelines
+ DCHECK_EQ(segment_.GetSegmentInfo()->timecode_scale(), 1000000ull);
+
+ return track_index;
+}
+
+void WebmMuxer::OnEncodedVideo(uint64_t track_number,
+ const base::StringPiece& encoded_data,
+ base::TimeDelta timestamp,
+ bool keyframe) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // |track_number|, a caller-side identifier, cannot be zero (!), see
+ // http://www.matroska.org/technical/specs/index.html#Tracks
+ DCHECK_GT(track_number, 0u);
+ DCHECK(segment_.GetTrackByNumber(track_number));
+
+ segment_.AddFrame(reinterpret_cast<const uint8_t*>(encoded_data.data()),
+ encoded_data.size(), track_number, timestamp.InMilliseconds(), keyframe);
+}
+
+mkvmuxer::int32 WebmMuxer::Write(const void* buf, mkvmuxer::uint32 len) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ write_data_callback_.Run(base::StringPiece(reinterpret_cast<const char*>(buf),
+ len));
+ position_ += len;
+ return 0;
+}
+
+mkvmuxer::int64 WebmMuxer::Position() const {
+ return position_.ValueOrDie();
+}
+
+mkvmuxer::int32 WebmMuxer::Position(mkvmuxer::int64 position) {
+ // The stream is not Seekable() so indicate we cannot set the position.
+ return -1;
+}
+
+bool WebmMuxer::Seekable() const {
+ return false;
+}
+
+void WebmMuxer::ElementStartNotify(mkvmuxer::uint64 element_id,
+ mkvmuxer::int64 position) {
+ // This method gets pinged before items are sent to |write_data_callback_|.
+ DCHECK_GE(position, position_.ValueOrDefault(0))
+ << "Can't go back in a live WebM stream.";
+}
+
+} // namespace media
diff --git a/media/capture/webm_muxer.h b/media/capture/webm_muxer.h
new file mode 100644
index 0000000..eb93e00
--- /dev/null
+++ b/media/capture/webm_muxer.h
@@ -0,0 +1,87 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_FILTERS_LIBWEBM_MUXER_H_
+#define MEDIA_FILTERS_LIBWEBM_MUXER_H_
+
+#include <set>
+
+#include "base/callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/numerics/safe_math.h"
+#include "base/strings/string_piece.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "media/base/media_export.h"
+#include "third_party/libwebm/source/mkvmuxer.hpp"
+#include "ui/gfx/geometry/size.h"
+
+namespace media {
+
+// Adapter class to manage a WebM container [1], a simplified version of a
+// Matroska container [2], composed of an EBML header, and a single Segment
+// including at least a Track Section and a number of SimpleBlocks each
+// containing a single encoded video frame. WebM container has no Trailer.
+// Clients will push encoded VPx video frames one by one via OnEncodedVideo()
+// with indication of the Track they belong to, and libwebm will eventually ping
+// the WriteDataCB passed on contructor with the wrapped encoded data. All
+// operations must happen in a single thread, where WebmMuxer is created and
+// destroyed.
+// [1] http://www.webmproject.org/docs/container/
+// [2] http://www.matroska.org/technical/specs/index.html
+// TODO(mcasas): Add support for Audio muxing.
+class MEDIA_EXPORT WebmMuxer : public NON_EXPORTED_BASE(mkvmuxer::IMkvWriter) {
+ public:
+ // Callback to be called when WebmMuxer is ready to write a chunk of data,
+ // either any file header or a SingleBlock. The chunk is described as a byte
+ // array and a byte length.
+ using WriteDataCB = base::Callback<void(const base::StringPiece&)>;
+
+ explicit WebmMuxer(const WriteDataCB& write_data_callback);
+ ~WebmMuxer() override;
+
+ // Creates and adds a new video track. Called before receiving the first
+ // frame of a given Track, adds |frame_size| and |frame_rate| to the Segment
+ // info, although individual frames passed to OnEncodedVideo() can have any
+ // frame size. Returns the track_index to be used for OnEncodedVideo().
+ uint64_t AddVideoTrack(const gfx::Size& frame_size, double frame_rate);
+
+ // Adds a frame with |encoded_data.data()| to WebM Segment. |track_number| is
+ // a caller-side identifier and must have been provided by AddVideoTrack().
+ // TODO(mcasas): Revisit how |encoded_data| is passed once the whole recording
+ // chain is setup (http://crbug.com/262211).
+ void OnEncodedVideo(uint64_t track_number,
+ const base::StringPiece& encoded_data,
+ base::TimeDelta timestamp,
+ bool keyframe);
+
+ private:
+ friend class WebmMuxerTest;
+
+ // IMkvWriter interface.
+ mkvmuxer::int32 Write(const void* buf, mkvmuxer::uint32 len) override;
+ mkvmuxer::int64 Position() const override;
+ mkvmuxer::int32 Position(mkvmuxer::int64 position) override;
+ bool Seekable() const override;
+ void ElementStartNotify(mkvmuxer::uint64 element_id,
+ mkvmuxer::int64 position) override;
+
+ // Used to DCHECK that we are called on the correct thread.
+ base::ThreadChecker thread_checker_;
+
+ // Callback to dump written data as being called by libwebm.
+ const WriteDataCB write_data_callback_;
+
+ // Rolling counter of the position in bytes of the written goo.
+ base::CheckedNumeric<mkvmuxer::int64> position_;
+
+ // The MkvMuxer active element.
+ mkvmuxer::Segment segment_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebmMuxer);
+};
+
+} // namespace media
+
+#endif // MEDIA_FILTERS_LIBWEBM_MUXER_H_
diff --git a/media/capture/webm_muxer_unittest.cc b/media/capture/webm_muxer_unittest.cc
new file mode 100644
index 0000000..6ae525c
--- /dev/null
+++ b/media/capture/webm_muxer_unittest.cc
@@ -0,0 +1,128 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/capture/webm_muxer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::AtLeast;
+using ::testing::Mock;
+using ::testing::WithArgs;
+
+namespace media {
+
+// Dummy interface class to be able to MOCK its only function below.
+class EventHandlerInterface {
+ public:
+ virtual void WriteCallback(const base::StringPiece& encoded_data) = 0;
+ virtual ~EventHandlerInterface() {}
+};
+
+class WebmMuxerTest : public testing::Test, public EventHandlerInterface {
+ public:
+ WebmMuxerTest()
+ : webm_muxer_(base::Bind(&WebmMuxerTest::WriteCallback,
+ base::Unretained(this))),
+ last_encoded_length_(0),
+ accumulated_position_(0) {
+ EXPECT_EQ(webm_muxer_.Position(), 0);
+ EXPECT_FALSE(webm_muxer_.Seekable());
+ EXPECT_EQ(webm_muxer_.segment_.mode(), mkvmuxer::Segment::kLive);
+ }
+
+ MOCK_METHOD1(WriteCallback, void(const base::StringPiece&));
+
+ void SaveEncodedDataLen(const base::StringPiece& encoded_data) {
+ last_encoded_length_ = encoded_data.size();
+ accumulated_position_ += encoded_data.size();
+ }
+
+ mkvmuxer::int64 GetWebmMuxerPosition() const {
+ return webm_muxer_.Position();
+ }
+
+ const mkvmuxer::Segment& GetWebmMuxerSegment() const {
+ return webm_muxer_.segment_;
+ }
+
+ mkvmuxer::int32 WebmMuxerWrite(const void* buf, mkvmuxer::uint32 len) {
+ return webm_muxer_.Write(buf, len);
+ }
+
+ WebmMuxer webm_muxer_;
+
+ size_t last_encoded_length_;
+ int64_t accumulated_position_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebmMuxerTest);
+};
+
+// Checks that AddVideoTrack adds a Track.
+TEST_F(WebmMuxerTest, AddVideoTrack) {
+ const uint64_t track_number = webm_muxer_.AddVideoTrack(gfx::Size(320, 240),
+ 30.0f);
+ EXPECT_TRUE(GetWebmMuxerSegment().GetTrackByNumber(track_number));
+}
+
+// Checks that the WriteCallback is called with appropriate params when
+// WebmMuxer::Write() method is called.
+TEST_F(WebmMuxerTest, Write) {
+ const base::StringPiece encoded_data("abcdefghijklmnopqrstuvwxyz");
+
+ EXPECT_CALL(*this, WriteCallback(encoded_data));
+ WebmMuxerWrite(encoded_data.data(), encoded_data.size());
+
+ EXPECT_EQ(GetWebmMuxerPosition(), static_cast<int64_t>(encoded_data.size()));
+}
+
+// This test sends two frames and checks that the WriteCallback is called with
+// appropriate params in both cases.
+TEST_F(WebmMuxerTest, OnEncodedVideoNormalFrames) {
+ const base::StringPiece encoded_data("abcdefghijklmnopqrstuvwxyz");
+ const uint64_t track_number = webm_muxer_.AddVideoTrack(gfx::Size(320, 240),
+ 30.0f);
+
+ EXPECT_CALL(*this, WriteCallback(_))
+ .Times(AtLeast(1))
+ .WillRepeatedly(WithArgs<0>(
+ Invoke(this, &WebmMuxerTest::SaveEncodedDataLen)));
+ webm_muxer_.OnEncodedVideo(track_number,
+ encoded_data,
+ base::TimeDelta::FromMicroseconds(0),
+ false /* keyframe */);
+
+ // First time around WriteCallback() is pinged a number of times to write the
+ // Matroska header, but at the end it dumps |encoded_data|.
+ EXPECT_EQ(last_encoded_length_, encoded_data.size());
+ EXPECT_EQ(GetWebmMuxerPosition(), accumulated_position_);
+ EXPECT_GE(GetWebmMuxerPosition(), static_cast<int64_t>(last_encoded_length_));
+
+ const int64_t begin_of_second_block = accumulated_position_;
+ EXPECT_CALL(*this, WriteCallback(_))
+ .Times(AtLeast(1))
+ .WillRepeatedly(WithArgs<0>(
+ Invoke(this, &WebmMuxerTest::SaveEncodedDataLen)));
+ webm_muxer_.OnEncodedVideo(track_number,
+ encoded_data,
+ base::TimeDelta::FromMicroseconds(1),
+ false /* keyframe */);
+
+ // The second time around the callbacks should include a SimpleBlock header,
+ // namely the track index, a timestamp and a flags byte, for a total of 6B.
+ EXPECT_EQ(last_encoded_length_, encoded_data.size());
+ EXPECT_EQ(GetWebmMuxerPosition(), accumulated_position_);
+ const uint32_t kSimpleBlockSize = 6u;
+ EXPECT_EQ(static_cast<int64_t>(begin_of_second_block + kSimpleBlockSize +
+ encoded_data.size()),
+ accumulated_position_);
+}
+
+} // namespace media
diff --git a/media/media.gyp b/media/media.gyp
index 8729b5b..e0192fd 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -477,6 +477,8 @@
'capture/video/win/video_capture_device_mf_win.h',
'capture/video/win/video_capture_device_win.cc',
'capture/video/win/video_capture_device_win.h',
+ 'capture/webm_muxer.cc',
+ 'capture/webm_muxer.h',
'cdm/aes_decryptor.cc',
'cdm/aes_decryptor.h',
'cdm/default_cdm_factory.cc',
@@ -687,6 +689,17 @@
'filters/vpx_video_decoder.h',
],
}],
+ ['media_use_libwebm==1', {
+ 'dependencies': [
+ '<(DEPTH)/third_party/libwebm/libwebm.gyp:libwebm',
+ ],
+ }, { # media_use_libwebm==0
+ # Exclude the sources that depend on libwebm.
+ 'sources!': [
+ 'capture/webm_muxer.cc',
+ 'capture/webm_muxer.h',
+ ],
+ }],
['enable_browser_cdms==1', {
'sources': [
'base/browser_cdm.cc',
@@ -1248,6 +1261,7 @@
'filters/video_renderer_algorithm_unittest.cc',
'filters/vp8_bool_decoder_unittest.cc',
'filters/vp8_parser_unittest.cc',
+ 'capture/webm_muxer_unittest.cc',
'formats/common/offset_byte_queue_unittest.cc',
'formats/webm/cluster_builder.cc',
'formats/webm/cluster_builder.h',
@@ -1307,6 +1321,17 @@
'test/pipeline_integration_test_base.cc',
],
}],
+ ['media_use_libwebm==1', {
+ 'dependencies': [
+ '<(DEPTH)/third_party/libwebm/libwebm.gyp:libwebm',
+ ],
+ }, { # media_use_libwebm==0
+ # Exclude the sources that depend on libwebm.
+ 'sources!': [
+ 'capture/webm_muxer_unittest.cc',
+ ],
+ }],
+
['(os_posix==1 and OS!="mac") or (OS=="win" and component!="shared_library" and win_use_allocator_shim==1)', {
'conditions': [
['use_allocator!="none"', {
diff --git a/media/media_options.gni b/media/media_options.gni
index cd7440a..a3b3a8a 100644
--- a/media/media_options.gni
+++ b/media/media_options.gni
@@ -17,10 +17,14 @@ declare_args() {
# decoding of VP9 and VP8A type content.
media_use_libvpx = true
- # Neither Android nor iOS use ffmpeg or libvpx.
+ # Enable libwebm for multiplexing video and audio for JS recording API.
+ media_use_libwebm = true
+
+ # Neither Android nor iOS use ffmpeg, libvpx nor libwebm.
if (is_android || is_ios) {
media_use_ffmpeg = false
media_use_libvpx = false
+ media_use_libwebm = false
}
# Override to dynamically link the cras (ChromeOS audio) library.
diff --git a/media/media_variables.gypi b/media/media_variables.gypi
index f359e36..b203ca0 100644
--- a/media/media_variables.gypi
+++ b/media/media_variables.gypi
@@ -7,14 +7,16 @@
'variables': {
'conditions': [
['OS == "android" or OS == "ios"', {
- # Android and iOS don't use FFmpeg or libvpx by default.
+ # Android and iOS don't use FFmpeg, libvpx nor libwebm by default.
# Set media_use_ffmpeg=1 for Android builds to compile experimental
# support for FFmpeg and the desktop media pipeline.
'media_use_ffmpeg%': 0,
'media_use_libvpx%': 0,
+ 'media_use_libwebm%': 0,
}, {
'media_use_ffmpeg%': 1,
'media_use_libvpx%': 1,
+ 'media_use_libwebm%': 1,
}],
],
},