summaryrefslogtreecommitdiffstats
path: root/media/muxers
diff options
context:
space:
mode:
authormcasas <mcasas@chromium.org>2016-02-18 12:11:36 -0800
committerCommit bot <commit-bot@chromium.org>2016-02-18 20:12:55 +0000
commit7db2a519b8121924ae9b86599e493a0fd68ad4ea (patch)
tree6f72bca7136e63ab3329c2f64aa788af29b91fd5 /media/muxers
parent724f2baac94ce55ea4d38f5f7efe067bc86590df (diff)
downloadchromium_src-7db2a519b8121924ae9b86599e493a0fd68ad4ea.zip
chromium_src-7db2a519b8121924ae9b86599e493a0fd68ad4ea.tar.gz
chromium_src-7db2a519b8121924ae9b86599e493a0fd68ad4ea.tar.bz2
Move media/capture/webm_muxer* to media/muxers
WebmMuxer is used to multiplex incoming encoded video and audio into a webm live stream. It is only used from content's MediaRecorderHandler. This CL moves WebmMuxer to media/muxers where it belongs, since it has nothing to do with capture/; that is made even more relevant in a parallel CL (http://crrev.com/1699553002) that isolates the said media/capture. This CL doesn't modify code (except a minor style reformatting caught by the presubmit). Narrowed some DEPS too. BUG=584797 Review URL: https://codereview.chromium.org/1710713002 Cr-Commit-Position: refs/heads/master@{#376243}
Diffstat (limited to 'media/muxers')
-rw-r--r--media/muxers/DEPS3
-rw-r--r--media/muxers/OWNERS2
-rw-r--r--media/muxers/webm_muxer.cc289
-rw-r--r--media/muxers/webm_muxer.h142
-rw-r--r--media/muxers/webm_muxer_unittest.cc250
5 files changed, 686 insertions, 0 deletions
diff --git a/media/muxers/DEPS b/media/muxers/DEPS
new file mode 100644
index 0000000..dd27068
--- /dev/null
+++ b/media/muxers/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+third_party/libwebm",
+]
diff --git a/media/muxers/OWNERS b/media/muxers/OWNERS
new file mode 100644
index 0000000..088dd9c
--- /dev/null
+++ b/media/muxers/OWNERS
@@ -0,0 +1,2 @@
+mcasas@chromium.org
+miu@chromium.org \ No newline at end of file
diff --git a/media/muxers/webm_muxer.cc b/media/muxers/webm_muxer.cc
new file mode 100644
index 0000000..e00982a
--- /dev/null
+++ b/media/muxers/webm_muxer.cc
@@ -0,0 +1,289 @@
+// 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/muxers/webm_muxer.h"
+
+#include "base/bind.h"
+#include "media/audio/audio_parameters.h"
+#include "media/base/limits.h"
+#include "media/base/video_frame.h"
+#include "media/filters/opus_constants.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace media {
+
+namespace {
+
+void WriteOpusHeader(const media::AudioParameters& params, uint8_t* header) {
+ // See https://wiki.xiph.org/OggOpus#ID_Header.
+ // Set magic signature.
+ std::string label = "OpusHead";
+ memcpy(header + OPUS_EXTRADATA_LABEL_OFFSET, label.c_str(), label.size());
+ // Set Opus version.
+ header[OPUS_EXTRADATA_VERSION_OFFSET] = 1;
+ // Set channel count.
+ header[OPUS_EXTRADATA_CHANNELS_OFFSET] = params.channels();
+ // Set pre-skip
+ uint16_t skip = 0;
+ memcpy(header + OPUS_EXTRADATA_SKIP_SAMPLES_OFFSET, &skip, sizeof(uint16_t));
+ // Set original input sample rate in Hz.
+ uint32_t sample_rate = params.sample_rate();
+ memcpy(header + OPUS_EXTRADATA_SAMPLE_RATE_OFFSET, &sample_rate,
+ sizeof(uint32_t));
+ // Set output gain in dB.
+ uint16_t gain = 0;
+ memcpy(header + OPUS_EXTRADATA_GAIN_OFFSET, &gain, 2);
+
+ // Set channel mapping.
+ if (params.channels() > 2) {
+ // Also possible to have a multistream, not supported for now.
+ DCHECK_LE(params.channels(), OPUS_MAX_VORBIS_CHANNELS);
+ header[OPUS_EXTRADATA_CHANNEL_MAPPING_OFFSET] = 1;
+ // Assuming no coupled streams. This should actually be
+ // channels() - |coupled_streams|.
+ header[OPUS_EXTRADATA_NUM_STREAMS_OFFSET] = params.channels();
+ header[OPUS_EXTRADATA_NUM_COUPLED_OFFSET] = 0;
+ // Set the actual stream map.
+ for (int i = 0; i < params.channels(); ++i) {
+ header[OPUS_EXTRADATA_STREAM_MAP_OFFSET + i] =
+ kOpusVorbisChannelMap[params.channels() - 1][i];
+ }
+ } else {
+ header[OPUS_EXTRADATA_CHANNEL_MAPPING_OFFSET] = 0;
+ }
+}
+
+static double GetFrameRate(const scoped_refptr<VideoFrame>& video_frame) {
+ const double kZeroFrameRate = 0.0;
+ const double kDefaultFrameRate = 30.0;
+
+ double frame_rate = kDefaultFrameRate;
+ if (!video_frame->metadata()->GetDouble(VideoFrameMetadata::FRAME_RATE,
+ &frame_rate) ||
+ frame_rate <= kZeroFrameRate ||
+ frame_rate > media::limits::kMaxFramesPerSecond) {
+ frame_rate = kDefaultFrameRate;
+ }
+ return frame_rate;
+}
+
+} // anonymous namespace
+
+WebmMuxer::WebmMuxer(VideoCodec codec,
+ bool has_video,
+ bool has_audio,
+ const WriteDataCB& write_data_callback)
+ : use_vp9_(codec == kCodecVP9),
+ video_track_index_(0),
+ audio_track_index_(0),
+ has_video_(has_video),
+ has_audio_(has_audio),
+ write_data_callback_(write_data_callback),
+ position_(0) {
+ DCHECK(has_video_ || has_audio_);
+ DCHECK(!write_data_callback_.is_null());
+ DCHECK(codec == kCodecVP8 || codec == kCodecVP9)
+ << " Only Vp8 and VP9 are supported in WebmMuxer";
+
+ 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");
+
+ // Creation is done on a different thread than main activities.
+ thread_checker_.DetachFromThread();
+}
+
+WebmMuxer::~WebmMuxer() {
+ // No need to segment_.Finalize() since is not Seekable(), i.e. a live
+ // stream, but is a good practice.
+ DCHECK(thread_checker_.CalledOnValidThread());
+ segment_.Finalize();
+}
+
+void WebmMuxer::OnEncodedVideo(const scoped_refptr<VideoFrame>& video_frame,
+ scoped_ptr<std::string> encoded_data,
+ base::TimeTicks timestamp,
+ bool is_key_frame) {
+ DVLOG(1) << __FUNCTION__ << " - " << encoded_data->size() << "B";
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!video_track_index_) {
+ // |track_index_|, cannot be zero (!), initialize WebmMuxer in that case.
+ // http://www.matroska.org/technical/specs/index.html#Tracks
+ AddVideoTrack(video_frame->visible_rect().size(),
+ GetFrameRate(video_frame));
+ if (first_frame_timestamp_.is_null())
+ first_frame_timestamp_ = timestamp;
+ }
+
+ // TODO(ajose): Support multiple tracks: http://crbug.com/528523
+ if (has_audio_ && !audio_track_index_) {
+ DVLOG(1) << __FUNCTION__ << ": delaying until audio track ready.";
+ if (is_key_frame) // Upon Key frame reception, empty the encoded queue.
+ encoded_frames_queue_.clear();
+
+ encoded_frames_queue_.push_back(make_scoped_ptr(new EncodedVideoFrame(
+ std::move(encoded_data), timestamp, is_key_frame)));
+ return;
+ }
+
+ // Dump all saved encoded video frames if any.
+ while (!encoded_frames_queue_.empty()) {
+ AddFrame(std::move(encoded_frames_queue_.front()->data), video_track_index_,
+ encoded_frames_queue_.front()->timestamp,
+ encoded_frames_queue_.front()->is_keyframe);
+ encoded_frames_queue_.pop_front();
+ }
+
+ AddFrame(std::move(encoded_data), video_track_index_, timestamp,
+ is_key_frame);
+}
+
+void WebmMuxer::OnEncodedAudio(const media::AudioParameters& params,
+ scoped_ptr<std::string> encoded_data,
+ base::TimeTicks timestamp) {
+ DVLOG(1) << __FUNCTION__ << " - " << encoded_data->size() << "B";
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!audio_track_index_) {
+ AddAudioTrack(params);
+ if (first_frame_timestamp_.is_null())
+ first_frame_timestamp_ = timestamp;
+ }
+
+ // TODO(ajose): Don't drop audio data: http://crbug.com/547948
+ // TODO(ajose): Support multiple tracks: http://crbug.com/528523
+ if (has_video_ && !video_track_index_) {
+ DVLOG(1) << __FUNCTION__ << ": delaying until video track ready.";
+ return;
+ }
+
+ // Dump all saved encoded video frames if any.
+ while (!encoded_frames_queue_.empty()) {
+ AddFrame(std::move(encoded_frames_queue_.front()->data), video_track_index_,
+ encoded_frames_queue_.front()->timestamp,
+ encoded_frames_queue_.front()->is_keyframe);
+ encoded_frames_queue_.pop_front();
+ }
+
+ AddFrame(std::move(encoded_data), audio_track_index_, timestamp,
+ true /* is_key_frame -- always true for audio */);
+}
+
+void WebmMuxer::AddVideoTrack(const gfx::Size& frame_size, double frame_rate) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(0u, video_track_index_)
+ << "WebmMuxer can only be initialized once.";
+
+ video_track_index_ =
+ segment_.AddVideoTrack(frame_size.width(), frame_size.height(), 0);
+ DCHECK_GT(video_track_index_, 0u);
+
+ mkvmuxer::VideoTrack* const video_track =
+ reinterpret_cast<mkvmuxer::VideoTrack*>(
+ segment_.GetTrackByNumber(video_track_index_));
+ DCHECK(video_track);
+ video_track->set_codec_id(use_vp9_ ? mkvmuxer::Tracks::kVp9CodecId
+ : mkvmuxer::Tracks::kVp8CodecId);
+ DCHECK_EQ(0ull, video_track->crop_right());
+ DCHECK_EQ(0ull, video_track->crop_left());
+ DCHECK_EQ(0ull, video_track->crop_top());
+ DCHECK_EQ(0ull, video_track->crop_bottom());
+
+ video_track->set_frame_rate(frame_rate);
+ video_track->set_default_duration(base::Time::kNanosecondsPerSecond /
+ frame_rate);
+ // Segment's timestamps should be in milliseconds, DCHECK it. See
+ // http://www.webmproject.org/docs/container/#muxer-guidelines
+ DCHECK_EQ(1000000ull, segment_.GetSegmentInfo()->timecode_scale());
+}
+
+void WebmMuxer::AddAudioTrack(const media::AudioParameters& params) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(0u, audio_track_index_)
+ << "WebmMuxer audio can only be initialised once.";
+
+ audio_track_index_ =
+ segment_.AddAudioTrack(params.sample_rate(), params.channels(), 0);
+ DCHECK_GT(audio_track_index_, 0u);
+
+ mkvmuxer::AudioTrack* const audio_track =
+ reinterpret_cast<mkvmuxer::AudioTrack*>(
+ segment_.GetTrackByNumber(audio_track_index_));
+ DCHECK(audio_track);
+ audio_track->set_codec_id(mkvmuxer::Tracks::kOpusCodecId);
+
+ DCHECK_EQ(params.sample_rate(), audio_track->sample_rate());
+ DCHECK_EQ(params.channels(), static_cast<int>(audio_track->channels()));
+
+ uint8_t opus_header[OPUS_EXTRADATA_SIZE];
+ WriteOpusHeader(params, opus_header);
+
+ if (!audio_track->SetCodecPrivate(opus_header, OPUS_EXTRADATA_SIZE))
+ LOG(ERROR) << __FUNCTION__ << ": failed to set opus header.";
+
+ // Segment's timestamps should be in milliseconds, DCHECK it. See
+ // http://www.webmproject.org/docs/container/#muxer-guidelines
+ DCHECK_EQ(1000000ull, segment_.GetSegmentInfo()->timecode_scale());
+}
+
+mkvmuxer::int32 WebmMuxer::Write(const void* buf, mkvmuxer::uint32 len) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(buf);
+ 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.";
+}
+
+void WebmMuxer::AddFrame(scoped_ptr<std::string> encoded_data,
+ uint8_t track_index,
+ base::TimeTicks timestamp,
+ bool is_key_frame) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!has_video_ || video_track_index_);
+ DCHECK(!has_audio_ || audio_track_index_);
+
+ most_recent_timestamp_ =
+ std::max(most_recent_timestamp_, timestamp - first_frame_timestamp_);
+
+ segment_.AddFrame(reinterpret_cast<const uint8_t*>(encoded_data->data()),
+ encoded_data->size(), track_index,
+ most_recent_timestamp_.InMicroseconds() *
+ base::Time::kNanosecondsPerMicrosecond,
+ is_key_frame);
+}
+
+WebmMuxer::EncodedVideoFrame::EncodedVideoFrame(scoped_ptr<std::string> data,
+ base::TimeTicks timestamp,
+ bool is_keyframe)
+ : data(std::move(data)), timestamp(timestamp), is_keyframe(is_keyframe) {}
+
+WebmMuxer::EncodedVideoFrame::~EncodedVideoFrame() {}
+
+} // namespace media
diff --git a/media/muxers/webm_muxer.h b/media/muxers/webm_muxer.h
new file mode 100644
index 0000000..227ce56
--- /dev/null
+++ b/media/muxers/webm_muxer.h
@@ -0,0 +1,142 @@
+// 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_MUXERS_WEBM_MUXER_H_
+#define MEDIA_MUXERS_WEBM_MUXER_H_
+
+#include <stdint.h>
+
+#include <deque>
+
+#include "base/callback.h"
+#include "base/macros.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 "media/base/video_codecs.h"
+#include "third_party/libwebm/source/mkvmuxer.hpp"
+
+namespace gfx {
+class Size;
+} // namespace gfx
+
+namespace media {
+
+class VideoFrame;
+class AudioParameters;
+
+// 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 or audio frame. WebM container has no
+// Trailer.
+// Clients will push encoded VPx video frames and Opus audio frames one by one
+// via OnEncoded{Video|Audio}(). libwebm will eventually ping the WriteDataCB
+// passed on contructor with the wrapped encoded data.
+// WebmMuxer is designed for use on a single thread.
+// [1] http://www.webmproject.org/docs/container/
+// [2] http://www.matroska.org/technical/specs/index.html
+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.
+ using WriteDataCB = base::Callback<void(base::StringPiece)>;
+
+ // |codec| can be VP8 or VP9 and should coincide with whatever is sent in
+ // OnEncodedVideo().
+ WebmMuxer(VideoCodec codec,
+ bool has_video_,
+ bool has_audio_,
+ const WriteDataCB& write_data_callback);
+ ~WebmMuxer() override;
+
+ // Functions to add video and audio frames with |encoded_data.data()|
+ // to WebM Segment.
+ void OnEncodedVideo(const scoped_refptr<VideoFrame>& video_frame,
+ scoped_ptr<std::string> encoded_data,
+ base::TimeTicks timestamp,
+ bool is_key_frame);
+ void OnEncodedAudio(const media::AudioParameters& params,
+ scoped_ptr<std::string> encoded_data,
+ base::TimeTicks timestamp);
+
+ private:
+ friend class WebmMuxerTest;
+
+ // Methods for creating and adding video and audio tracks, called upon
+ // receiving the first frame of a given Track.
+ // AddVideoTrack adds |frame_size| and |frame_rate| to the Segment
+ // info, although individual frames passed to OnEncodedVideo() can have any
+ // frame size.
+ void AddVideoTrack(const gfx::Size& frame_size, double frame_rate);
+ void AddAudioTrack(const media::AudioParameters& params);
+
+ // 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;
+
+ // Helper to simplify saving frames.
+ void AddFrame(scoped_ptr<std::string> encoded_data,
+ uint8_t track_index,
+ base::TimeTicks timestamp,
+ bool is_key_frame);
+
+ // Used to DCHECK that we are called on the correct thread.
+ base::ThreadChecker thread_checker_;
+
+ // Video Codec configured: VP9 if true, otherwise VP8 is used by default.
+ const bool use_vp9_;
+
+ // Caller-side identifiers to interact with |segment_|, initialised upon
+ // first frame arrival to Add{Video, Audio}Track().
+ uint8_t video_track_index_;
+ uint8_t audio_track_index_;
+
+ // Origin of times for frame timestamps.
+ base::TimeTicks first_frame_timestamp_;
+ base::TimeDelta most_recent_timestamp_;
+
+ // TODO(ajose): Change these when support is added for multiple tracks.
+ // http://crbug.com/528523
+ const bool has_video_;
+ const bool has_audio_;
+
+ // 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_;
+
+ // Hold on to all encoded video frames to dump them with and when audio is
+ // received, if expected, since WebM headers can only be written once.
+ struct EncodedVideoFrame {
+ EncodedVideoFrame(scoped_ptr<std::string> data,
+ base::TimeTicks timestamp,
+ bool is_keyframe);
+ ~EncodedVideoFrame();
+
+ scoped_ptr<std::string> data;
+ base::TimeTicks timestamp;
+ bool is_keyframe;
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(EncodedVideoFrame);
+ };
+ std::deque<scoped_ptr<EncodedVideoFrame>> encoded_frames_queue_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebmMuxer);
+};
+
+} // namespace media
+
+#endif // MEDIA_MUXERS_WEBM_MUXER_H_
diff --git a/media/muxers/webm_muxer_unittest.cc b/media/muxers/webm_muxer_unittest.cc
new file mode 100644
index 0000000..03634de
--- /dev/null
+++ b/media/muxers/webm_muxer_unittest.cc
@@ -0,0 +1,250 @@
+// 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 <stddef.h>
+#include <stdint.h>
+
+#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/audio/audio_parameters.h"
+#include "media/base/channel_layout.h"
+#include "media/base/video_frame.h"
+#include "media/muxers/webm_muxer.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::AllOf;
+using ::testing::AnyNumber;
+using ::testing::AtLeast;
+using ::testing::Eq;
+using ::testing::InSequence;
+using ::testing::Mock;
+using ::testing::Not;
+using ::testing::Sequence;
+using ::testing::TestWithParam;
+using ::testing::ValuesIn;
+using ::testing::WithArgs;
+
+namespace media {
+
+struct kTestParams {
+ VideoCodec codec;
+ size_t num_video_tracks;
+ size_t num_audio_tracks;
+};
+
+class WebmMuxerTest : public TestWithParam<kTestParams> {
+ public:
+ WebmMuxerTest()
+ : webm_muxer_(
+ GetParam().codec,
+ GetParam().num_video_tracks,
+ GetParam().num_audio_tracks,
+ base::Bind(&WebmMuxerTest::WriteCallback, base::Unretained(this))),
+ last_encoded_length_(0),
+ accumulated_position_(0) {
+ EXPECT_EQ(webm_muxer_.Position(), 0);
+ const mkvmuxer::int64 kRandomNewPosition = 333;
+ EXPECT_EQ(webm_muxer_.Position(kRandomNewPosition), -1);
+ EXPECT_FALSE(webm_muxer_.Seekable());
+ }
+
+ MOCK_METHOD1(WriteCallback, void(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();
+ }
+
+ mkvmuxer::Segment::Mode GetWebmSegmentMode() const {
+ return webm_muxer_.segment_.mode();
+ }
+
+ 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 the WriteCallback is called with appropriate params when
+// WebmMuxer::Write() method is called.
+TEST_P(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_P(WebmMuxerTest, OnEncodedVideoTwoFrames) {
+ if (GetParam().num_audio_tracks > 0)
+ return;
+
+ const gfx::Size frame_size(160, 80);
+ const scoped_refptr<VideoFrame> video_frame =
+ VideoFrame::CreateBlackFrame(frame_size);
+ const std::string encoded_data("abcdefghijklmnopqrstuvwxyz");
+
+ EXPECT_CALL(*this, WriteCallback(_))
+ .Times(AtLeast(1))
+ .WillRepeatedly(
+ WithArgs<0>(Invoke(this, &WebmMuxerTest::SaveEncodedDataLen)));
+ webm_muxer_.OnEncodedVideo(video_frame,
+ make_scoped_ptr(new std::string(encoded_data)),
+ base::TimeTicks::Now(), 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_));
+ EXPECT_EQ(GetWebmSegmentMode(), mkvmuxer::Segment::kLive);
+
+ 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(video_frame,
+ make_scoped_ptr(new std::string(encoded_data)),
+ base::TimeTicks::Now(), 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_);
+}
+
+TEST_P(WebmMuxerTest, OnEncodedAudioTwoFrames) {
+ if (GetParam().num_video_tracks > 0)
+ return;
+
+ const int sample_rate = 48000;
+ const int bits_per_sample = 16;
+ const int frames_per_buffer = 480;
+ media::AudioParameters audio_params(
+ media::AudioParameters::Format::AUDIO_PCM_LOW_LATENCY,
+ media::CHANNEL_LAYOUT_MONO, sample_rate, bits_per_sample,
+ frames_per_buffer);
+
+ const std::string encoded_data("abcdefghijklmnopqrstuvwxyz");
+
+ EXPECT_CALL(*this, WriteCallback(_))
+ .Times(AtLeast(1))
+ .WillRepeatedly(
+ WithArgs<0>(Invoke(this, &WebmMuxerTest::SaveEncodedDataLen)));
+ webm_muxer_.OnEncodedAudio(audio_params,
+ make_scoped_ptr(new std::string(encoded_data)),
+ base::TimeTicks::Now());
+
+ // 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_));
+ EXPECT_EQ(GetWebmSegmentMode(), mkvmuxer::Segment::kLive);
+
+ 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_.OnEncodedAudio(audio_params,
+ make_scoped_ptr(new std::string(encoded_data)),
+ base::TimeTicks::Now());
+
+ // 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_);
+}
+
+// This test verifies that when video data comes before audio data, we save the
+// encoded video frames and add it to the video track when audio data arrives.
+TEST_P(WebmMuxerTest, VideoIsStoredWhileWaitingForAudio) {
+ // This test is only relevant if we have both kinds of tracks.
+ if (GetParam().num_video_tracks == 0 || GetParam().num_audio_tracks == 0)
+ return;
+
+ // First send a video keyframe
+ const gfx::Size frame_size(160, 80);
+ const scoped_refptr<VideoFrame> video_frame =
+ VideoFrame::CreateBlackFrame(frame_size);
+ const std::string encoded_video("thisisanencodedvideopacket");
+ webm_muxer_.OnEncodedVideo(video_frame,
+ make_scoped_ptr(new std::string(encoded_video)),
+ base::TimeTicks::Now(), true /* keyframe */);
+ // A few encoded non key frames.
+ const int kNumNonKeyFrames = 2;
+ for (int i = 0; i < kNumNonKeyFrames; ++i) {
+ webm_muxer_.OnEncodedVideo(video_frame,
+ make_scoped_ptr(new std::string(encoded_video)),
+ base::TimeTicks::Now(), false /* keyframe */);
+ }
+
+ // Send some audio. The header will be written and muxing will proceed
+ // normally.
+ const int sample_rate = 48000;
+ const int bits_per_sample = 16;
+ const int frames_per_buffer = 480;
+ media::AudioParameters audio_params(
+ media::AudioParameters::Format::AUDIO_PCM_LOW_LATENCY,
+ media::CHANNEL_LAYOUT_MONO, sample_rate, bits_per_sample,
+ frames_per_buffer);
+ const std::string encoded_audio("thisisanencodedaudiopacket");
+
+ // We should first get the encoded video frames, then the encoded audio frame.
+ Sequence s;
+ EXPECT_CALL(*this, WriteCallback(Eq(encoded_video)))
+ .Times(1 + kNumNonKeyFrames)
+ .InSequence(s);
+ EXPECT_CALL(*this, WriteCallback(Eq(encoded_audio))).Times(1).InSequence(s);
+ // We'll also get lots of other header-related stuff.
+ EXPECT_CALL(*this, WriteCallback(
+ AllOf(Not(Eq(encoded_video)), Not(Eq(encoded_audio)))))
+ .Times(AnyNumber());
+ webm_muxer_.OnEncodedAudio(audio_params,
+ make_scoped_ptr(new std::string(encoded_audio)),
+ base::TimeTicks::Now());
+}
+
+const kTestParams kTestCases[] = {
+ // TODO: consider not enumerating every combination by hand.
+ {kCodecVP8, 1 /* num_video_tracks */, 0 /*num_audio_tracks*/},
+ {kCodecVP8, 0, 1},
+ {kCodecVP8, 1, 1},
+ {kCodecVP9, 1, 0},
+ {kCodecVP9, 0, 1},
+ {kCodecVP9, 1, 1},
+};
+
+INSTANTIATE_TEST_CASE_P(, WebmMuxerTest, ValuesIn(kTestCases));
+
+} // namespace media