summaryrefslogtreecommitdiffstats
path: root/chromium/media/formats/webm
diff options
context:
space:
mode:
authorJocelyn Turcotte <jocelyn.turcotte@digia.com>2014-08-08 14:30:41 +0200
committerJocelyn Turcotte <jocelyn.turcotte@digia.com>2014-08-12 13:49:54 +0200
commitab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch)
tree498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/media/formats/webm
parent4ce69f7403811819800e7c5ae1318b2647e778d1 (diff)
Update Chromium to beta version 37.0.2062.68
Change-Id: I188e3b5aff1bec75566014291b654eb19f5bc8ca Reviewed-by: Andras Becsi <andras.becsi@digia.com>
Diffstat (limited to 'chromium/media/formats/webm')
-rw-r--r--chromium/media/formats/webm/chromeos/DEPS4
-rw-r--r--chromium/media/formats/webm/chromeos/ebml_writer.cc33
-rw-r--r--chromium/media/formats/webm/chromeos/ebml_writer.h21
-rw-r--r--chromium/media/formats/webm/chromeos/webm_encoder.cc321
-rw-r--r--chromium/media/formats/webm/chromeos/webm_encoder.h106
-rw-r--r--chromium/media/formats/webm/cluster_builder.cc226
-rw-r--r--chromium/media/formats/webm/cluster_builder.h65
-rw-r--r--chromium/media/formats/webm/tracks_builder.cc384
-rw-r--r--chromium/media/formats/webm/tracks_builder.h91
-rw-r--r--chromium/media/formats/webm/webm_audio_client.cc133
-rw-r--r--chromium/media/formats/webm/webm_audio_client.h54
-rw-r--r--chromium/media/formats/webm/webm_cluster_parser.cc686
-rw-r--r--chromium/media/formats/webm/webm_cluster_parser.h275
-rw-r--r--chromium/media/formats/webm/webm_cluster_parser_unittest.cc957
-rw-r--r--chromium/media/formats/webm/webm_constants.cc14
-rw-r--r--chromium/media/formats/webm/webm_constants.h229
-rw-r--r--chromium/media/formats/webm/webm_content_encodings.cc28
-rw-r--r--chromium/media/formats/webm/webm_content_encodings.h88
-rw-r--r--chromium/media/formats/webm/webm_content_encodings_client.cc265
-rw-r--r--chromium/media/formats/webm/webm_content_encodings_client.h50
-rw-r--r--chromium/media/formats/webm/webm_content_encodings_client_unittest.cc238
-rw-r--r--chromium/media/formats/webm/webm_crypto_helpers.cc62
-rw-r--r--chromium/media/formats/webm/webm_crypto_helpers.h33
-rw-r--r--chromium/media/formats/webm/webm_info_parser.cc103
-rw-r--r--chromium/media/formats/webm/webm_info_parser.h50
-rw-r--r--chromium/media/formats/webm/webm_parser.cc953
-rw-r--r--chromium/media/formats/webm/webm_parser.h158
-rw-r--r--chromium/media/formats/webm/webm_parser_unittest.cc412
-rw-r--r--chromium/media/formats/webm/webm_stream_parser.cc281
-rw-r--r--chromium/media/formats/webm/webm_stream_parser.h89
-rw-r--r--chromium/media/formats/webm/webm_tracks_parser.cc350
-rw-r--r--chromium/media/formats/webm/webm_tracks_parser.h119
-rw-r--r--chromium/media/formats/webm/webm_tracks_parser_unittest.cc185
-rw-r--r--chromium/media/formats/webm/webm_video_client.cc161
-rw-r--r--chromium/media/formats/webm/webm_video_client.h61
-rw-r--r--chromium/media/formats/webm/webm_webvtt_parser.cc78
-rw-r--r--chromium/media/formats/webm/webm_webvtt_parser.h49
-rw-r--r--chromium/media/formats/webm/webm_webvtt_parser_unittest.cc105
38 files changed, 7517 insertions, 0 deletions
diff --git a/chromium/media/formats/webm/chromeos/DEPS b/chromium/media/formats/webm/chromeos/DEPS
new file mode 100644
index 00000000000..a4378dca94a
--- /dev/null
+++ b/chromium/media/formats/webm/chromeos/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+libyuv",
+ "+third_party/libvpx",
+]
diff --git a/chromium/media/formats/webm/chromeos/ebml_writer.cc b/chromium/media/formats/webm/chromeos/ebml_writer.cc
new file mode 100644
index 00000000000..c00063f2b28
--- /dev/null
+++ b/chromium/media/formats/webm/chromeos/ebml_writer.cc
@@ -0,0 +1,33 @@
+// Copyright 2014 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/formats/webm/chromeos/ebml_writer.h"
+
+#include "media/base/media_export.h"
+
+extern "C" {
+#include "third_party/libvpx/source/libvpx/third_party/libmkv/EbmlWriter.h"
+
+EbmlGlobal::EbmlGlobal() {
+}
+
+EbmlGlobal::~EbmlGlobal() {
+}
+
+// These functions must be in the global namespace and visible to libmkv.
+
+void MEDIA_EXPORT Ebml_Write(EbmlGlobal* glob,
+ const void* buffer,
+ unsigned long len) {
+ glob->write_cb.Run(buffer, len);
+}
+
+void MEDIA_EXPORT Ebml_Serialize(EbmlGlobal* glob,
+ const void* buffer,
+ int buffer_size,
+ unsigned long len) {
+ glob->serialize_cb.Run(buffer, buffer_size, len);
+}
+
+} // extern "C"
diff --git a/chromium/media/formats/webm/chromeos/ebml_writer.h b/chromium/media/formats/webm/chromeos/ebml_writer.h
new file mode 100644
index 00000000000..3c1faa04297
--- /dev/null
+++ b/chromium/media/formats/webm/chromeos/ebml_writer.h
@@ -0,0 +1,21 @@
+// Copyright 2014 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_FORMATS_WEBM_CHROMEOS_EBML_WRITER_H_
+#define MEDIA_FORMATS_WEBM_CHROMEOS_EBML_WRITER_H_
+
+#include "base/callback.h"
+
+// This struct serves as a bridge betweeen static libmkv interface and Chrome's
+// base::Callback. Must be in the global namespace. See EbmlWriter.h.
+struct EbmlGlobal {
+ EbmlGlobal();
+ ~EbmlGlobal();
+
+ base::Callback<void(const void* buffer, unsigned long len)> write_cb;
+ base::Callback<void(const void* buffer, int buffer_size, unsigned long len)>
+ serialize_cb;
+};
+
+#endif // MEDIA_FORMATS_WEBM_CHROMEOS_EBML_WRITER_H_
diff --git a/chromium/media/formats/webm/chromeos/webm_encoder.cc b/chromium/media/formats/webm/chromeos/webm_encoder.cc
new file mode 100644
index 00000000000..4b5c782452d
--- /dev/null
+++ b/chromium/media/formats/webm/chromeos/webm_encoder.cc
@@ -0,0 +1,321 @@
+// Copyright 2014 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/formats/webm/chromeos/webm_encoder.h"
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "libyuv/convert.h"
+#include "libyuv/video_common.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+extern "C" {
+// Getting the right degree of C compatibility has been a constant struggle.
+// - Stroustrup, C++ Report, 12(7), July/August 2000.
+#define private priv
+#include "third_party/libvpx/source/libvpx/third_party/libmkv/EbmlIDs.h"
+#include "third_party/libvpx/source/libvpx/third_party/libmkv/EbmlWriter.h"
+#undef private
+}
+
+// Number of encoder threads to use.
+static const int kNumEncoderThreads = 2;
+
+// Need a fixed size serializer for the track ID. libmkv provides a 64 bit
+// one, but not a 32 bit one.
+static void Ebml_SerializeUnsigned32(EbmlGlobal* ebml,
+ unsigned long class_id,
+ uint64_t value) {
+ uint8 size_serialized = 4 | 0x80;
+ Ebml_WriteID(ebml, class_id);
+ Ebml_Serialize(ebml, &size_serialized, sizeof(size_serialized), 1);
+ Ebml_Serialize(ebml, &value, sizeof(value), 4);
+}
+
+// Wrapper functor for vpx_codec_destroy().
+struct VpxCodecDeleter {
+ void operator()(vpx_codec_ctx_t* codec) {
+ vpx_codec_destroy(codec);
+ }
+};
+
+// Wrapper functor for vpx_img_free().
+struct VpxImgDeleter {
+ void operator()(vpx_image_t* image) {
+ vpx_img_free(image);
+ }
+};
+
+namespace media {
+
+namespace chromeos {
+
+WebmEncoder::WebmEncoder(const base::FilePath& output_path,
+ int bitrate,
+ bool realtime)
+ : bitrate_(bitrate),
+ deadline_(realtime ? VPX_DL_REALTIME : VPX_DL_GOOD_QUALITY),
+ output_path_(output_path),
+ has_errors_(false) {
+ ebml_writer_.write_cb = base::Bind(
+ &WebmEncoder::EbmlWrite, base::Unretained(this));
+ ebml_writer_.serialize_cb = base::Bind(
+ &WebmEncoder::EbmlSerialize, base::Unretained(this));
+}
+
+WebmEncoder::~WebmEncoder() {
+}
+
+bool WebmEncoder::EncodeFromSprite(const SkBitmap& sprite,
+ int fps_n,
+ int fps_d) {
+ DCHECK(!sprite.isNull());
+ DCHECK(!sprite.empty());
+
+ has_errors_ = false;
+ width_ = sprite.width();
+ height_ = sprite.width();
+ fps_.num = fps_n;
+ fps_.den = fps_d;
+
+ // Sprite is tiled vertically.
+ frame_count_ = sprite.height() / width_;
+
+ vpx_image_t image;
+ vpx_img_alloc(&image, VPX_IMG_FMT_I420, width_, height_, 16);
+ // Ensure that image is freed after return.
+ scoped_ptr<vpx_image_t, VpxImgDeleter> image_ptr(&image);
+
+ const vpx_codec_iface_t* codec_iface = vpx_codec_vp8_cx();
+ DCHECK(codec_iface);
+ vpx_codec_err_t ret = vpx_codec_enc_config_default(codec_iface, &config_, 0);
+ DCHECK_EQ(VPX_CODEC_OK, ret);
+
+ config_.rc_target_bitrate = bitrate_;
+ config_.g_w = width_;
+ config_.g_h = height_;
+ config_.g_pass = VPX_RC_ONE_PASS;
+ config_.g_profile = 0; // Default profile.
+ config_.g_threads = kNumEncoderThreads;
+ config_.rc_min_quantizer = 0;
+ config_.rc_max_quantizer = 63; // Maximum possible range.
+ config_.g_timebase.num = fps_.den;
+ config_.g_timebase.den = fps_.num;
+ config_.kf_mode = VPX_KF_AUTO; // Auto key frames.
+
+ vpx_codec_ctx_t codec;
+ ret = vpx_codec_enc_init(&codec, codec_iface, &config_, 0);
+ if (ret != VPX_CODEC_OK)
+ return false;
+ // Ensure that codec context is freed after return.
+ scoped_ptr<vpx_codec_ctx_t, VpxCodecDeleter> codec_ptr(&codec);
+
+ SkAutoLockPixels lock_sprite(sprite);
+
+ const uint8* src = reinterpret_cast<const uint8*>(sprite.getAddr32(0, 0));
+ size_t src_frame_size = sprite.getSize();
+ int crop_y = 0;
+
+ if (!WriteWebmHeader())
+ return false;
+
+ for (size_t frame = 0; frame < frame_count_ && !has_errors_; ++frame) {
+ int res = libyuv::ConvertToI420(
+ src, src_frame_size,
+ image.planes[VPX_PLANE_Y], image.stride[VPX_PLANE_Y],
+ image.planes[VPX_PLANE_U], image.stride[VPX_PLANE_U],
+ image.planes[VPX_PLANE_V], image.stride[VPX_PLANE_V],
+ 0, crop_y, // src origin
+ width_, sprite.height(), // src size
+ width_, height_, // dest size
+ libyuv::kRotate0,
+ libyuv::FOURCC_ARGB);
+ if (res) {
+ has_errors_ = true;
+ break;
+ }
+ crop_y += height_;
+
+ ret = vpx_codec_encode(&codec, &image, frame, 1, 0, deadline_);
+ if (ret != VPX_CODEC_OK) {
+ has_errors_ = true;
+ break;
+ }
+
+ vpx_codec_iter_t iter = NULL;
+ const vpx_codec_cx_pkt_t* packet;
+ while (!has_errors_ && (packet = vpx_codec_get_cx_data(&codec, &iter))) {
+ if (packet->kind == VPX_CODEC_CX_FRAME_PKT)
+ WriteWebmBlock(packet);
+ }
+ }
+
+ return WriteWebmFooter();
+}
+
+bool WebmEncoder::WriteWebmHeader() {
+ output_ = base::OpenFile(output_path_, "wb");
+ if (!output_)
+ return false;
+
+ // Global header.
+ StartSubElement(EBML);
+ {
+ Ebml_SerializeUnsigned(&ebml_writer_, EBMLVersion, 1);
+ Ebml_SerializeUnsigned(&ebml_writer_, EBMLReadVersion, 1);
+ Ebml_SerializeUnsigned(&ebml_writer_, EBMLMaxIDLength, 4);
+ Ebml_SerializeUnsigned(&ebml_writer_, EBMLMaxSizeLength, 8);
+ Ebml_SerializeString(&ebml_writer_, DocType, "webm");
+ Ebml_SerializeUnsigned(&ebml_writer_, DocTypeVersion, 2);
+ Ebml_SerializeUnsigned(&ebml_writer_, DocTypeReadVersion, 2);
+ }
+ EndSubElement(); // EBML
+
+ // Single segment with a video track.
+ StartSubElement(Segment);
+ {
+ StartSubElement(Info);
+ {
+ // All timecodes in the segment will be expressed in milliseconds.
+ Ebml_SerializeUnsigned(&ebml_writer_, TimecodeScale, 1000000);
+ double duration = 1000. * frame_count_ * fps_.den / fps_.num;
+ Ebml_SerializeFloat(&ebml_writer_, Segment_Duration, duration);
+ }
+ EndSubElement(); // Info
+
+ StartSubElement(Tracks);
+ {
+ StartSubElement(TrackEntry);
+ {
+ Ebml_SerializeUnsigned(&ebml_writer_, TrackNumber, 1);
+ Ebml_SerializeUnsigned32(&ebml_writer_, TrackUID, 1);
+ Ebml_SerializeUnsigned(&ebml_writer_, TrackType, 1); // Video
+ Ebml_SerializeString(&ebml_writer_, CodecID, "V_VP8");
+
+ StartSubElement(Video);
+ {
+ Ebml_SerializeUnsigned(&ebml_writer_, PixelWidth, width_);
+ Ebml_SerializeUnsigned(&ebml_writer_, PixelHeight, height_);
+ Ebml_SerializeUnsigned(&ebml_writer_, StereoMode, 0); // Mono
+ float fps = static_cast<float>(fps_.num) / fps_.den;
+ Ebml_SerializeFloat(&ebml_writer_, FrameRate, fps);
+ }
+ EndSubElement(); // Video
+ }
+ EndSubElement(); // TrackEntry
+ }
+ EndSubElement(); // Tracks
+
+ StartSubElement(Cluster); {
+ Ebml_SerializeUnsigned(&ebml_writer_, Timecode, 0);
+ } // Cluster left open.
+ } // Segment left open.
+
+ // No check for |has_errors_| here because |false| is only returned when
+ // opening file fails.
+ return true;
+}
+
+void WebmEncoder::WriteWebmBlock(const vpx_codec_cx_pkt_t* packet) {
+ bool is_keyframe = packet->data.frame.flags & VPX_FRAME_IS_KEY;
+ int64_t pts_ms = 1000 * packet->data.frame.pts * fps_.den / fps_.num;
+
+ DVLOG(1) << "Video packet @" << pts_ms << " ms "
+ << packet->data.frame.sz << " bytes "
+ << (is_keyframe ? "K" : "");
+
+ Ebml_WriteID(&ebml_writer_, SimpleBlock);
+
+ uint32 block_length = (packet->data.frame.sz + 4) | 0x10000000;
+ EbmlSerializeHelper(&block_length, 4);
+
+ uint8 track_number = 1 | 0x80;
+ EbmlSerializeHelper(&track_number, 1);
+
+ EbmlSerializeHelper(&pts_ms, 2);
+
+ uint8 flags = 0;
+ if (is_keyframe)
+ flags |= 0x80;
+ if (packet->data.frame.flags & VPX_FRAME_IS_INVISIBLE)
+ flags |= 0x08;
+ EbmlSerializeHelper(&flags, 1);
+
+ EbmlWrite(packet->data.frame.buf, packet->data.frame.sz);
+}
+
+bool WebmEncoder::WriteWebmFooter() {
+ EndSubElement(); // Cluster
+ EndSubElement(); // Segment
+ DCHECK(ebml_sub_elements_.empty());
+ return base::CloseFile(output_) && !has_errors_;
+}
+
+void WebmEncoder::StartSubElement(unsigned long class_id) {
+ Ebml_WriteID(&ebml_writer_, class_id);
+ ebml_sub_elements_.push(ftell(output_));
+ static const uint64_t kUnknownLen = 0x01FFFFFFFFFFFFFFLLU;
+ EbmlSerializeHelper(&kUnknownLen, 8);
+}
+
+void WebmEncoder::EndSubElement() {
+ DCHECK(!ebml_sub_elements_.empty());
+
+ long int end_pos = ftell(output_);
+ long int start_pos = ebml_sub_elements_.top();
+ ebml_sub_elements_.pop();
+
+ uint64_t size = (end_pos - start_pos - 8) | 0x0100000000000000ULL;
+ // Seek to the beginning of the sub-element and patch in the calculated size.
+ if (fseek(output_, start_pos, SEEK_SET)) {
+ has_errors_ = true;
+ LOG(ERROR) << "Error writing to " << output_path_.value();
+ }
+ EbmlSerializeHelper(&size, 8);
+
+ // Restore write position.
+ if (fseek(output_, end_pos, SEEK_SET)) {
+ has_errors_ = true;
+ LOG(ERROR) << "Error writing to " << output_path_.value();
+ }
+}
+
+void WebmEncoder::EbmlWrite(const void* buffer,
+ unsigned long len) {
+ if (fwrite(buffer, 1, len, output_) != len) {
+ has_errors_ = true;
+ LOG(ERROR) << "Error writing to " << output_path_.value();
+ }
+}
+
+template <class T>
+void WebmEncoder::EbmlSerializeHelper(const T* buffer, unsigned long len) {
+ for (int i = len - 1; i >= 0; i--) {
+ uint8 c = *buffer >> (i * CHAR_BIT);
+ EbmlWrite(&c, 1);
+ }
+}
+
+void WebmEncoder::EbmlSerialize(const void* buffer,
+ int buffer_size,
+ unsigned long len) {
+ switch (buffer_size) {
+ case 1:
+ return EbmlSerializeHelper(static_cast<const int8_t*>(buffer), len);
+ case 2:
+ return EbmlSerializeHelper(static_cast<const int16_t*>(buffer), len);
+ case 4:
+ return EbmlSerializeHelper(static_cast<const int32_t*>(buffer), len);
+ case 8:
+ return EbmlSerializeHelper(static_cast<const int64_t*>(buffer), len);
+ default:
+ NOTREACHED() << "Invalid EbmlSerialize length: " << len;
+ }
+}
+
+} // namespace chromeos
+
+} // namespace media
diff --git a/chromium/media/formats/webm/chromeos/webm_encoder.h b/chromium/media/formats/webm/chromeos/webm_encoder.h
new file mode 100644
index 00000000000..fd0fc7582c6
--- /dev/null
+++ b/chromium/media/formats/webm/chromeos/webm_encoder.h
@@ -0,0 +1,106 @@
+// Copyright 2014 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_FORMATS_WEBM_CHROMEOS_WEBM_ENCODER_H_
+#define MEDIA_FORMATS_WEBM_CHROMEOS_WEBM_ENCODER_H_
+
+#include <stdio.h>
+#include <stack>
+
+#include "base/files/file_path.h"
+#include "media/base/media_export.h"
+#include "media/formats/webm/chromeos/ebml_writer.h"
+
+extern "C" {
+#define VPX_CODEC_DISABLE_COMPAT 1
+#include "third_party/libvpx/source/libvpx/vpx/vpx_encoder.h"
+#include "third_party/libvpx/source/libvpx/vpx/vp8cx.h"
+}
+
+class SkBitmap;
+
+namespace base {
+class FilePath;
+}
+
+namespace media {
+
+namespace chromeos {
+
+// WebM encoder using libvpx. Currently only supports one-pass, constant bitrate
+// encoding of short files consisting of a single video track. Seek info and
+// cues are not supported, so generated .webm file does not strictly adhere to
+// WebM standard (http://www.webmproject.org/code/specs/container/).
+class MEDIA_EXPORT WebmEncoder {
+ public:
+ // Create new instance for writing to |output_path|. If |realtime| is |true|,
+ // uses realtime deadline, otherwise - "good quality" deadline.
+ WebmEncoder(const base::FilePath& output_path, int bitrate, bool realtime);
+ ~WebmEncoder();
+
+ // Encodes video from a Nx(N*M) sprite, having M frames of size NxN with FPS
+ // |fps_n/fps_d|. Must be called on a thread that allows disk IO.
+ // Returns |true| iff encoding and writing to file is successful.
+ bool EncodeFromSprite(const SkBitmap& sprite, int fps_n, int fps_d);
+
+ private:
+ // Writes global WebM header and starts a single video track. Returns |false|
+ // if there was an error opening file for writing.
+ bool WriteWebmHeader();
+
+ // Writes VPX packet to output file.
+ void WriteWebmBlock(const vpx_codec_cx_pkt_t* packet);
+
+ // Finishes video track and closes output file. Returns |false| if there were
+ // any error during encoding/writing file.
+ bool WriteWebmFooter();
+
+ // Starts a new WebM sub-element of given type. Those can be nested.
+ void StartSubElement(unsigned long class_id);
+
+ // Closes current top-level sub-element.
+ void EndSubElement();
+
+ // libmkv callbacks.
+ void EbmlWrite(const void* buffer, unsigned long len);
+ void EbmlSerialize(const void* buffer, int buffer_size, unsigned long len);
+
+ template <typename T>
+ void EbmlSerializeHelper(const T* buffer, unsigned long len);
+
+ // Video dimensions and FPS.
+ size_t width_;
+ size_t height_;
+ vpx_rational_t fps_;
+
+ // Number of frames in video.
+ size_t frame_count_;
+
+ // VPX config in use.
+ vpx_codec_enc_cfg_t config_;
+
+ // VPX parameters.
+ int bitrate_;
+ unsigned long deadline_;
+
+ // EbmlWriter context.
+ EbmlGlobal ebml_writer_;
+
+ // Stack with start offsets of currently open sub-elements.
+ std::stack<long int> ebml_sub_elements_;
+
+ base::FilePath output_path_;
+ FILE* output_;
+
+ // True if an error occured while encoding/writing to file.
+ bool has_errors_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebmEncoder);
+};
+
+} // namespace chromeos
+
+} // namespace media
+
+#endif // MEDIA_FORMATS_WEBM_CHROMEOS_WEBM_ENCODER_H_
diff --git a/chromium/media/formats/webm/cluster_builder.cc b/chromium/media/formats/webm/cluster_builder.cc
new file mode 100644
index 00000000000..1a3b358ef99
--- /dev/null
+++ b/chromium/media/formats/webm/cluster_builder.cc
@@ -0,0 +1,226 @@
+// Copyright 2014 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/formats/webm/cluster_builder.h"
+
+#include "base/logging.h"
+#include "media/base/data_buffer.h"
+#include "media/formats/webm/webm_constants.h"
+
+namespace media {
+
+static const uint8 kClusterHeader[] = {
+ 0x1F, 0x43, 0xB6, 0x75, // CLUSTER ID
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // cluster(size = 0)
+ 0xE7, // Timecode ID
+ 0x88, // timecode(size=8)
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // timecode value
+};
+
+static const uint8 kSimpleBlockHeader[] = {
+ 0xA3, // SimpleBlock ID
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SimpleBlock(size = 0)
+};
+
+static const uint8 kBlockGroupHeader[] = {
+ 0xA0, // BlockGroup ID
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // BlockGroup(size = 0)
+ 0x9B, // BlockDuration ID
+ 0x88, // BlockDuration(size = 8)
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // duration
+ 0xA1, // Block ID
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block(size = 0)
+};
+
+static const uint8 kBlockGroupHeaderWithoutBlockDuration[] = {
+ 0xA0, // BlockGroup ID
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // BlockGroup(size = 0)
+ 0xA1, // Block ID
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block(size = 0)
+};
+
+enum {
+ kClusterSizeOffset = 4,
+ kClusterTimecodeOffset = 14,
+
+ kSimpleBlockSizeOffset = 1,
+
+ kBlockGroupSizeOffset = 1,
+ kBlockGroupWithoutBlockDurationBlockSizeOffset = 10,
+ kBlockGroupDurationOffset = 11,
+ kBlockGroupBlockSizeOffset = 20,
+
+ kInitialBufferSize = 32768,
+};
+
+Cluster::Cluster(scoped_ptr<uint8[]> data, int size)
+ : data_(data.Pass()), size_(size) {}
+Cluster::~Cluster() {}
+
+ClusterBuilder::ClusterBuilder() { Reset(); }
+ClusterBuilder::~ClusterBuilder() {}
+
+void ClusterBuilder::SetClusterTimecode(int64 cluster_timecode) {
+ DCHECK_EQ(cluster_timecode_, -1);
+
+ cluster_timecode_ = cluster_timecode;
+
+ // Write the timecode into the header.
+ uint8* buf = buffer_.get() + kClusterTimecodeOffset;
+ for (int i = 7; i >= 0; --i) {
+ buf[i] = cluster_timecode & 0xff;
+ cluster_timecode >>= 8;
+ }
+}
+
+void ClusterBuilder::AddSimpleBlock(int track_num, int64 timecode, int flags,
+ const uint8* data, int size) {
+ int block_size = size + 4;
+ int bytes_needed = sizeof(kSimpleBlockHeader) + block_size;
+ if (bytes_needed > (buffer_size_ - bytes_used_))
+ ExtendBuffer(bytes_needed);
+
+ uint8* buf = buffer_.get() + bytes_used_;
+ int block_offset = bytes_used_;
+ memcpy(buf, kSimpleBlockHeader, sizeof(kSimpleBlockHeader));
+ UpdateUInt64(block_offset + kSimpleBlockSizeOffset, block_size);
+ buf += sizeof(kSimpleBlockHeader);
+
+ WriteBlock(buf, track_num, timecode, flags, data, size);
+
+ bytes_used_ += bytes_needed;
+}
+
+void ClusterBuilder::AddBlockGroup(int track_num, int64 timecode, int duration,
+ int flags, const uint8* data, int size) {
+ AddBlockGroupInternal(track_num, timecode, true, duration, flags, data, size);
+}
+
+void ClusterBuilder::AddBlockGroupWithoutBlockDuration(int track_num,
+ int64 timecode,
+ int flags,
+ const uint8* data,
+ int size) {
+ AddBlockGroupInternal(track_num, timecode, false, 0, flags, data, size);
+}
+
+
+void ClusterBuilder::AddBlockGroupInternal(int track_num, int64 timecode,
+ bool include_block_duration,
+ int duration, int flags,
+ const uint8* data, int size) {
+ int block_size = size + 4;
+ int bytes_needed = block_size;
+ if (include_block_duration) {
+ bytes_needed += sizeof(kBlockGroupHeader);
+ } else {
+ bytes_needed += sizeof(kBlockGroupHeaderWithoutBlockDuration);
+ }
+
+ int block_group_size = bytes_needed - 9;
+
+ if (bytes_needed > (buffer_size_ - bytes_used_))
+ ExtendBuffer(bytes_needed);
+
+ uint8* buf = buffer_.get() + bytes_used_;
+ int block_group_offset = bytes_used_;
+ if (include_block_duration) {
+ memcpy(buf, kBlockGroupHeader, sizeof(kBlockGroupHeader));
+ UpdateUInt64(block_group_offset + kBlockGroupDurationOffset, duration);
+ UpdateUInt64(block_group_offset + kBlockGroupBlockSizeOffset, block_size);
+ buf += sizeof(kBlockGroupHeader);
+ } else {
+ memcpy(buf, kBlockGroupHeaderWithoutBlockDuration,
+ sizeof(kBlockGroupHeaderWithoutBlockDuration));
+ UpdateUInt64(
+ block_group_offset + kBlockGroupWithoutBlockDurationBlockSizeOffset,
+ block_size);
+ buf += sizeof(kBlockGroupHeaderWithoutBlockDuration);
+ }
+
+ UpdateUInt64(block_group_offset + kBlockGroupSizeOffset, block_group_size);
+
+ // Make sure the 4 most-significant bits are 0.
+ // http://www.matroska.org/technical/specs/index.html#block_structure
+ flags &= 0x0f;
+
+ WriteBlock(buf, track_num, timecode, flags, data, size);
+
+ bytes_used_ += bytes_needed;
+}
+
+void ClusterBuilder::WriteBlock(uint8* buf, int track_num, int64 timecode,
+ int flags, const uint8* data, int size) {
+ DCHECK_GE(track_num, 0);
+ DCHECK_LE(track_num, 126);
+ DCHECK_GE(flags, 0);
+ DCHECK_LE(flags, 0xff);
+ DCHECK(data);
+ DCHECK_GT(size, 0);
+ DCHECK_NE(cluster_timecode_, -1);
+
+ int64 timecode_delta = timecode - cluster_timecode_;
+ DCHECK_GE(timecode_delta, -32768);
+ DCHECK_LE(timecode_delta, 32767);
+
+ buf[0] = 0x80 | (track_num & 0x7F);
+ buf[1] = (timecode_delta >> 8) & 0xff;
+ buf[2] = timecode_delta & 0xff;
+ buf[3] = flags & 0xff;
+ memcpy(buf + 4, data, size);
+}
+
+scoped_ptr<Cluster> ClusterBuilder::Finish() {
+ DCHECK_NE(cluster_timecode_, -1);
+
+ UpdateUInt64(kClusterSizeOffset, bytes_used_ - (kClusterSizeOffset + 8));
+
+ scoped_ptr<Cluster> ret(new Cluster(buffer_.Pass(), bytes_used_));
+ Reset();
+ return ret.Pass();
+}
+
+scoped_ptr<Cluster> ClusterBuilder::FinishWithUnknownSize() {
+ DCHECK_NE(cluster_timecode_, -1);
+
+ UpdateUInt64(kClusterSizeOffset, kWebMUnknownSize);
+
+ scoped_ptr<Cluster> ret(new Cluster(buffer_.Pass(), bytes_used_));
+ Reset();
+ return ret.Pass();
+}
+
+void ClusterBuilder::Reset() {
+ buffer_size_ = kInitialBufferSize;
+ buffer_.reset(new uint8[buffer_size_]);
+ memcpy(buffer_.get(), kClusterHeader, sizeof(kClusterHeader));
+ bytes_used_ = sizeof(kClusterHeader);
+ cluster_timecode_ = -1;
+}
+
+void ClusterBuilder::ExtendBuffer(int bytes_needed) {
+ int new_buffer_size = 2 * buffer_size_;
+
+ while ((new_buffer_size - bytes_used_) < bytes_needed)
+ new_buffer_size *= 2;
+
+ scoped_ptr<uint8[]> new_buffer(new uint8[new_buffer_size]);
+
+ memcpy(new_buffer.get(), buffer_.get(), bytes_used_);
+ buffer_.reset(new_buffer.release());
+ buffer_size_ = new_buffer_size;
+}
+
+void ClusterBuilder::UpdateUInt64(int offset, int64 value) {
+ DCHECK_LE(offset + 7, buffer_size_);
+ uint8* buf = buffer_.get() + offset;
+
+ // Fill the last 7 bytes of size field in big-endian order.
+ for (int i = 7; i > 0; i--) {
+ buf[i] = value & 0xff;
+ value >>= 8;
+ }
+}
+
+} // namespace media
diff --git a/chromium/media/formats/webm/cluster_builder.h b/chromium/media/formats/webm/cluster_builder.h
new file mode 100644
index 00000000000..ab5797cd34a
--- /dev/null
+++ b/chromium/media/formats/webm/cluster_builder.h
@@ -0,0 +1,65 @@
+// Copyright 2014 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_FORMATS_WEBM_CLUSTER_BUILDER_H_
+#define MEDIA_FORMATS_WEBM_CLUSTER_BUILDER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/base/buffers.h"
+
+namespace media {
+
+class Cluster {
+ public:
+ Cluster(scoped_ptr<uint8[]> data, int size);
+ ~Cluster();
+
+ const uint8* data() const { return data_.get(); }
+ int size() const { return size_; }
+
+ private:
+ scoped_ptr<uint8[]> data_;
+ int size_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(Cluster);
+};
+
+class ClusterBuilder {
+ public:
+ ClusterBuilder();
+ ~ClusterBuilder();
+
+ void SetClusterTimecode(int64 cluster_timecode);
+ void AddSimpleBlock(int track_num, int64 timecode, int flags,
+ const uint8* data, int size);
+ void AddBlockGroup(int track_num, int64 timecode, int duration, int flags,
+ const uint8* data, int size);
+ void AddBlockGroupWithoutBlockDuration(int track_num, int64 timecode,
+ int flags, const uint8* data, int size);
+
+ scoped_ptr<Cluster> Finish();
+ scoped_ptr<Cluster> FinishWithUnknownSize();
+
+ private:
+ void AddBlockGroupInternal(int track_num, int64 timecode,
+ bool include_block_duration, int duration,
+ int flags, const uint8* data, int size);
+ void Reset();
+ void ExtendBuffer(int bytes_needed);
+ void UpdateUInt64(int offset, int64 value);
+ void WriteBlock(uint8* buf, int track_num, int64 timecode, int flags,
+ const uint8* data, int size);
+
+ scoped_ptr<uint8[]> buffer_;
+ int buffer_size_;
+ int bytes_used_;
+ int64 cluster_timecode_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClusterBuilder);
+};
+
+} // namespace media
+
+#endif // MEDIA_FORMATS_WEBM_CLUSTER_BUILDER_H_
diff --git a/chromium/media/formats/webm/tracks_builder.cc b/chromium/media/formats/webm/tracks_builder.cc
new file mode 100644
index 00000000000..fb402c4729c
--- /dev/null
+++ b/chromium/media/formats/webm/tracks_builder.cc
@@ -0,0 +1,384 @@
+// Copyright 2014 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/formats/webm/tracks_builder.h"
+
+#include "base/logging.h"
+#include "media/formats/webm/webm_constants.h"
+
+namespace media {
+
+// Returns size of an integer, formatted using Matroska serialization.
+static int GetUIntMkvSize(uint64 value) {
+ if (value < 0x07FULL)
+ return 1;
+ if (value < 0x03FFFULL)
+ return 2;
+ if (value < 0x01FFFFFULL)
+ return 3;
+ if (value < 0x0FFFFFFFULL)
+ return 4;
+ if (value < 0x07FFFFFFFFULL)
+ return 5;
+ if (value < 0x03FFFFFFFFFFULL)
+ return 6;
+ if (value < 0x01FFFFFFFFFFFFULL)
+ return 7;
+ return 8;
+}
+
+// Returns the minimium size required to serialize an integer value.
+static int GetUIntSize(uint64 value) {
+ if (value < 0x0100ULL)
+ return 1;
+ if (value < 0x010000ULL)
+ return 2;
+ if (value < 0x01000000ULL)
+ return 3;
+ if (value < 0x0100000000ULL)
+ return 4;
+ if (value < 0x010000000000ULL)
+ return 5;
+ if (value < 0x01000000000000ULL)
+ return 6;
+ if (value < 0x0100000000000000ULL)
+ return 7;
+ return 8;
+}
+
+static int MasterElementSize(int element_id, int payload_size) {
+ return GetUIntSize(element_id) + GetUIntMkvSize(payload_size) + payload_size;
+}
+
+static int IntElementSize(int element_id, int value) {
+ return GetUIntSize(element_id) + 1 + GetUIntSize(value);
+}
+
+static int DoubleElementSize(int element_id) {
+ return GetUIntSize(element_id) + 1 + 8;
+}
+
+static int StringElementSize(int element_id, const std::string& value) {
+ return GetUIntSize(element_id) +
+ GetUIntMkvSize(value.length()) +
+ value.length();
+}
+
+static void SerializeInt(uint8** buf_ptr, int* buf_size_ptr,
+ int64 value, int size) {
+ uint8*& buf = *buf_ptr;
+ int& buf_size = *buf_size_ptr;
+
+ for (int idx = 1; idx <= size; ++idx) {
+ *buf++ = static_cast<uint8>(value >> ((size - idx) * 8));
+ --buf_size;
+ }
+}
+
+static void SerializeDouble(uint8** buf_ptr, int* buf_size_ptr,
+ double value) {
+ // Use a union to convert |value| to native endian integer bit pattern.
+ union {
+ double src;
+ int64 dst;
+ } tmp;
+ tmp.src = value;
+
+ // Write the bytes from native endian |tmp.dst| to big-endian form in |buf|.
+ SerializeInt(buf_ptr, buf_size_ptr, tmp.dst, 8);
+}
+
+static void WriteElementId(uint8** buf, int* buf_size, int element_id) {
+ SerializeInt(buf, buf_size, element_id, GetUIntSize(element_id));
+}
+
+static void WriteUInt(uint8** buf, int* buf_size, uint64 value) {
+ const int size = GetUIntMkvSize(value);
+ value |= (1ULL << (size * 7)); // Matroska formatting
+ SerializeInt(buf, buf_size, value, size);
+}
+
+static void WriteMasterElement(uint8** buf, int* buf_size,
+ int element_id, int payload_size) {
+ WriteElementId(buf, buf_size, element_id);
+ WriteUInt(buf, buf_size, payload_size);
+}
+
+static void WriteIntElement(uint8** buf, int* buf_size,
+ int element_id, int value) {
+ WriteElementId(buf, buf_size, element_id);
+
+ const int size = GetUIntSize(value);
+ WriteUInt(buf, buf_size, size);
+
+ SerializeInt(buf, buf_size, value, size);
+}
+
+static void WriteDoubleElement(uint8** buf, int* buf_size,
+ int element_id, double value) {
+ WriteElementId(buf, buf_size, element_id);
+ WriteUInt(buf, buf_size, 8);
+ SerializeDouble(buf, buf_size, value);
+}
+
+static void WriteStringElement(uint8** buf_ptr, int* buf_size_ptr,
+ int element_id, const std::string& value) {
+ uint8*& buf = *buf_ptr;
+ int& buf_size = *buf_size_ptr;
+
+ WriteElementId(&buf, &buf_size, element_id);
+
+ const uint64 size = value.length();
+ WriteUInt(&buf, &buf_size, size);
+
+ memcpy(buf, value.data(), size);
+ buf += size;
+ buf_size -= size;
+}
+
+TracksBuilder::TracksBuilder(bool allow_invalid_values)
+ : allow_invalid_values_(allow_invalid_values) {}
+TracksBuilder::TracksBuilder()
+ : allow_invalid_values_(false) {}
+TracksBuilder::~TracksBuilder() {}
+
+void TracksBuilder::AddVideoTrack(
+ int track_num,
+ int track_uid,
+ const std::string& codec_id,
+ const std::string& name,
+ const std::string& language,
+ int default_duration,
+ int video_pixel_width,
+ int video_pixel_height) {
+ AddTrackInternal(track_num, kWebMTrackTypeVideo, track_uid, codec_id, name,
+ language, default_duration, video_pixel_width,
+ video_pixel_height, -1, -1);
+}
+
+void TracksBuilder::AddAudioTrack(
+ int track_num,
+ int track_uid,
+ const std::string& codec_id,
+ const std::string& name,
+ const std::string& language,
+ int default_duration,
+ int audio_channels,
+ double audio_sampling_frequency) {
+ AddTrackInternal(track_num, kWebMTrackTypeAudio, track_uid, codec_id, name,
+ language, default_duration, -1, -1, audio_channels,
+ audio_sampling_frequency);
+}
+
+void TracksBuilder::AddTextTrack(
+ int track_num,
+ int track_uid,
+ const std::string& codec_id,
+ const std::string& name,
+ const std::string& language) {
+ AddTrackInternal(track_num, kWebMTrackTypeSubtitlesOrCaptions, track_uid,
+ codec_id, name, language, -1, -1, -1, -1, -1);
+}
+
+std::vector<uint8> TracksBuilder::Finish() {
+ // Allocate the storage
+ std::vector<uint8> buffer;
+ buffer.resize(GetTracksSize());
+
+ // Populate the storage with a tracks header
+ WriteTracks(&buffer[0], buffer.size());
+
+ return buffer;
+}
+
+void TracksBuilder::AddTrackInternal(
+ int track_num,
+ int track_type,
+ int track_uid,
+ const std::string& codec_id,
+ const std::string& name,
+ const std::string& language,
+ int default_duration,
+ int video_pixel_width,
+ int video_pixel_height,
+ int audio_channels,
+ double audio_sampling_frequency) {
+ tracks_.push_back(Track(track_num, track_type, track_uid, codec_id, name,
+ language, default_duration, video_pixel_width,
+ video_pixel_height, audio_channels,
+ audio_sampling_frequency, allow_invalid_values_));
+}
+
+int TracksBuilder::GetTracksSize() const {
+ return MasterElementSize(kWebMIdTracks, GetTracksPayloadSize());
+}
+
+int TracksBuilder::GetTracksPayloadSize() const {
+ int payload_size = 0;
+
+ for (TrackList::const_iterator itr = tracks_.begin();
+ itr != tracks_.end(); ++itr) {
+ payload_size += itr->GetSize();
+ }
+
+ return payload_size;
+}
+
+void TracksBuilder::WriteTracks(uint8* buf, int buf_size) const {
+ WriteMasterElement(&buf, &buf_size, kWebMIdTracks, GetTracksPayloadSize());
+
+ for (TrackList::const_iterator itr = tracks_.begin();
+ itr != tracks_.end(); ++itr) {
+ itr->Write(&buf, &buf_size);
+ }
+}
+
+TracksBuilder::Track::Track(int track_num, int track_type, int track_uid,
+ const std::string& codec_id,
+ const std::string& name,
+ const std::string& language,
+ int default_duration,
+ int video_pixel_width, int video_pixel_height,
+ int audio_channels, double audio_sampling_frequency,
+ bool allow_invalid_values)
+ : track_num_(track_num),
+ track_type_(track_type),
+ track_uid_(track_uid),
+ codec_id_(codec_id),
+ name_(name),
+ language_(language),
+ default_duration_(default_duration),
+ video_pixel_width_(video_pixel_width),
+ video_pixel_height_(video_pixel_height),
+ audio_channels_(audio_channels),
+ audio_sampling_frequency_(audio_sampling_frequency) {
+ if (!allow_invalid_values) {
+ CHECK_GT(track_num_, 0);
+ CHECK_GT(track_type_, 0);
+ CHECK_LT(track_type_, 255);
+ CHECK_GT(track_uid_, 0);
+ if (track_type != kWebMTrackTypeVideo &&
+ track_type != kWebMTrackTypeAudio) {
+ CHECK_EQ(default_duration_, -1);
+ } else {
+ CHECK(default_duration_ == -1 || default_duration_ > 0);
+ }
+
+ if (track_type == kWebMTrackTypeVideo) {
+ CHECK_GT(video_pixel_width_, 0);
+ CHECK_GT(video_pixel_height_, 0);
+ } else {
+ CHECK_EQ(video_pixel_width_, -1);
+ CHECK_EQ(video_pixel_height_, -1);
+ }
+
+ if (track_type == kWebMTrackTypeAudio) {
+ CHECK_GT(audio_channels_, 0);
+ CHECK_GT(audio_sampling_frequency_, 0.0);
+ } else {
+ CHECK_EQ(audio_channels_, -1);
+ CHECK_EQ(audio_sampling_frequency_, -1.0);
+ }
+ }
+}
+
+int TracksBuilder::Track::GetSize() const {
+ return MasterElementSize(kWebMIdTrackEntry, GetPayloadSize());
+}
+
+int TracksBuilder::Track::GetVideoPayloadSize() const {
+ int payload_size = 0;
+
+ if (video_pixel_width_ >= 0)
+ payload_size += IntElementSize(kWebMIdPixelWidth, video_pixel_width_);
+ if (video_pixel_height_ >= 0)
+ payload_size += IntElementSize(kWebMIdPixelHeight, video_pixel_height_);
+
+ return payload_size;
+}
+
+int TracksBuilder::Track::GetAudioPayloadSize() const {
+ int payload_size = 0;
+
+ if (audio_channels_ >= 0)
+ payload_size += IntElementSize(kWebMIdChannels, audio_channels_);
+ if (audio_sampling_frequency_ >= 0)
+ payload_size += DoubleElementSize(kWebMIdSamplingFrequency);
+
+ return payload_size;
+}
+
+int TracksBuilder::Track::GetPayloadSize() const {
+ int size = 0;
+
+ size += IntElementSize(kWebMIdTrackNumber, track_num_);
+ size += IntElementSize(kWebMIdTrackType, track_type_);
+ size += IntElementSize(kWebMIdTrackUID, track_uid_);
+
+ if (default_duration_ >= 0)
+ size += IntElementSize(kWebMIdDefaultDuration, default_duration_);
+
+ if (!codec_id_.empty())
+ size += StringElementSize(kWebMIdCodecID, codec_id_);
+
+ if (!name_.empty())
+ size += StringElementSize(kWebMIdName, name_);
+
+ if (!language_.empty())
+ size += StringElementSize(kWebMIdLanguage, language_);
+
+ if (GetVideoPayloadSize() > 0) {
+ size += MasterElementSize(kWebMIdVideo, GetVideoPayloadSize());
+ }
+
+ if (GetAudioPayloadSize() > 0) {
+ size += MasterElementSize(kWebMIdAudio, GetAudioPayloadSize());
+ }
+
+ return size;
+}
+
+void TracksBuilder::Track::Write(uint8** buf, int* buf_size) const {
+ WriteMasterElement(buf, buf_size, kWebMIdTrackEntry, GetPayloadSize());
+
+ WriteIntElement(buf, buf_size, kWebMIdTrackNumber, track_num_);
+ WriteIntElement(buf, buf_size, kWebMIdTrackType, track_type_);
+ WriteIntElement(buf, buf_size, kWebMIdTrackUID, track_uid_);
+
+ if (default_duration_ >= 0)
+ WriteIntElement(buf, buf_size, kWebMIdDefaultDuration, default_duration_);
+
+ if (!codec_id_.empty())
+ WriteStringElement(buf, buf_size, kWebMIdCodecID, codec_id_);
+
+ if (!name_.empty())
+ WriteStringElement(buf, buf_size, kWebMIdName, name_);
+
+ if (!language_.empty())
+ WriteStringElement(buf, buf_size, kWebMIdLanguage, language_);
+
+ if (GetVideoPayloadSize() > 0) {
+ WriteMasterElement(buf, buf_size, kWebMIdVideo, GetVideoPayloadSize());
+
+ if (video_pixel_width_ >= 0)
+ WriteIntElement(buf, buf_size, kWebMIdPixelWidth, video_pixel_width_);
+
+ if (video_pixel_height_ >= 0)
+ WriteIntElement(buf, buf_size, kWebMIdPixelHeight, video_pixel_height_);
+ }
+
+ if (GetAudioPayloadSize() > 0) {
+ WriteMasterElement(buf, buf_size, kWebMIdAudio, GetAudioPayloadSize());
+
+ if (audio_channels_ >= 0)
+ WriteIntElement(buf, buf_size, kWebMIdChannels, audio_channels_);
+
+ if (audio_sampling_frequency_ >= 0) {
+ WriteDoubleElement(buf, buf_size, kWebMIdSamplingFrequency,
+ audio_sampling_frequency_);
+ }
+ }
+}
+
+} // namespace media
diff --git a/chromium/media/formats/webm/tracks_builder.h b/chromium/media/formats/webm/tracks_builder.h
new file mode 100644
index 00000000000..35f8955c6b0
--- /dev/null
+++ b/chromium/media/formats/webm/tracks_builder.h
@@ -0,0 +1,91 @@
+// Copyright 2014 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_FORMATS_WEBM_TRACKS_BUILDER_H_
+#define MEDIA_FORMATS_WEBM_TRACKS_BUILDER_H_
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+
+namespace media {
+
+class TracksBuilder {
+ public:
+ // If |allow_invalid_values| is false, some AddTrack() parameters will be
+ // basically checked and will assert if out of valid range. |codec_id|,
+ // |name|, |language| and any device-specific constraints are not checked.
+ explicit TracksBuilder(bool allow_invalid_values);
+ TracksBuilder(); // Sets |allow_invalid_values| to false.
+ ~TracksBuilder();
+
+ // Only a non-negative |default_duration| will result in a serialized
+ // kWebMIdDefaultDuration element. Note, 0 is allowed here for testing only
+ // if |allow_invalid_values_| is true, since it is an illegal value for
+ // DefaultDuration. Similar applies to |audio_channels|,
+ // |audio_sampling_frequency|, |video_pixel_width| and |video_pixel_height|.
+ void AddVideoTrack(int track_num, int track_uid, const std::string& codec_id,
+ const std::string& name, const std::string& language,
+ int default_duration, int video_pixel_width,
+ int video_pixel_height);
+ void AddAudioTrack(int track_num, int track_uid, const std::string& codec_id,
+ const std::string& name, const std::string& language,
+ int default_duration, int audio_channels,
+ double audio_sampling_frequency);
+ void AddTextTrack(int track_num, int track_uid, const std::string& codec_id,
+ const std::string& name, const std::string& language);
+
+ std::vector<uint8> Finish();
+
+ private:
+ void AddTrackInternal(int track_num, int track_type, int track_uid,
+ const std::string& codec_id, const std::string& name,
+ const std::string& language, int default_duration,
+ int video_pixel_width, int video_pixel_height,
+ int audio_channels, double audio_sampling_frequency);
+ int GetTracksSize() const;
+ int GetTracksPayloadSize() const;
+ void WriteTracks(uint8* buffer, int buffer_size) const;
+
+ class Track {
+ public:
+ Track(int track_num, int track_type, int track_uid,
+ const std::string& codec_id, const std::string& name,
+ const std::string& language, int default_duration,
+ int video_pixel_width, int video_pixel_height,
+ int audio_channels, double audio_sampling_frequency,
+ bool allow_invalid_values);
+
+ int GetSize() const;
+ void Write(uint8** buf, int* buf_size) const;
+ private:
+ int GetPayloadSize() const;
+ int GetVideoPayloadSize() const;
+ int GetAudioPayloadSize() const;
+
+ int track_num_;
+ int track_type_;
+ int track_uid_;
+ std::string codec_id_;
+ std::string name_;
+ std::string language_;
+ int default_duration_;
+ int video_pixel_width_;
+ int video_pixel_height_;
+ int audio_channels_;
+ double audio_sampling_frequency_;
+ };
+
+ typedef std::list<Track> TrackList;
+ TrackList tracks_;
+ bool allow_invalid_values_;
+
+ DISALLOW_COPY_AND_ASSIGN(TracksBuilder);
+};
+
+} // namespace media
+
+#endif // MEDIA_FORMATS_WEBM_TRACKS_BUILDER_H_
diff --git a/chromium/media/formats/webm/webm_audio_client.cc b/chromium/media/formats/webm/webm_audio_client.cc
new file mode 100644
index 00000000000..6fe9a8434f2
--- /dev/null
+++ b/chromium/media/formats/webm/webm_audio_client.cc
@@ -0,0 +1,133 @@
+// Copyright 2014 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/formats/webm/webm_audio_client.h"
+
+#include "media/base/audio_decoder_config.h"
+#include "media/base/channel_layout.h"
+#include "media/formats/webm/webm_constants.h"
+
+namespace media {
+
+WebMAudioClient::WebMAudioClient(const LogCB& log_cb)
+ : log_cb_(log_cb) {
+ Reset();
+}
+
+WebMAudioClient::~WebMAudioClient() {
+}
+
+void WebMAudioClient::Reset() {
+ channels_ = -1;
+ samples_per_second_ = -1;
+ output_samples_per_second_ = -1;
+}
+
+bool WebMAudioClient::InitializeConfig(
+ const std::string& codec_id, const std::vector<uint8>& codec_private,
+ int64 seek_preroll, int64 codec_delay, bool is_encrypted,
+ AudioDecoderConfig* config) {
+ DCHECK(config);
+
+ AudioCodec audio_codec = kUnknownAudioCodec;
+ if (codec_id == "A_VORBIS") {
+ audio_codec = kCodecVorbis;
+ } else if (codec_id == "A_OPUS") {
+ audio_codec = kCodecOpus;
+ } else {
+ MEDIA_LOG(log_cb_) << "Unsupported audio codec_id " << codec_id;
+ return false;
+ }
+
+ if (samples_per_second_ <= 0)
+ return false;
+
+ // Set channel layout default if a Channels element was not present.
+ if (channels_ == -1)
+ channels_ = 1;
+
+ ChannelLayout channel_layout = GuessChannelLayout(channels_);
+
+ if (channel_layout == CHANNEL_LAYOUT_UNSUPPORTED) {
+ MEDIA_LOG(log_cb_) << "Unsupported channel count " << channels_;
+ return false;
+ }
+
+ int samples_per_second = samples_per_second_;
+ if (output_samples_per_second_ > 0)
+ samples_per_second = output_samples_per_second_;
+
+ const uint8* extra_data = NULL;
+ size_t extra_data_size = 0;
+ if (codec_private.size() > 0) {
+ extra_data = &codec_private[0];
+ extra_data_size = codec_private.size();
+ }
+
+ // Convert |codec_delay| from nanoseconds into frames.
+ int codec_delay_in_frames = 0;
+ if (codec_delay != -1) {
+ codec_delay_in_frames =
+ 0.5 +
+ samples_per_second * (static_cast<double>(codec_delay) /
+ base::Time::kNanosecondsPerSecond);
+ }
+
+ config->Initialize(
+ audio_codec,
+ (audio_codec == kCodecOpus) ? kSampleFormatS16 : kSampleFormatPlanarF32,
+ channel_layout,
+ samples_per_second,
+ extra_data,
+ extra_data_size,
+ is_encrypted,
+ true,
+ base::TimeDelta::FromMicroseconds(
+ (seek_preroll != -1 ? seek_preroll : 0) / 1000),
+ codec_delay_in_frames);
+ return config->IsValidConfig();
+}
+
+bool WebMAudioClient::OnUInt(int id, int64 val) {
+ if (id == kWebMIdChannels) {
+ if (channels_ != -1) {
+ MEDIA_LOG(log_cb_) << "Multiple values for id " << std::hex << id
+ << " specified. (" << channels_ << " and " << val
+ << ")";
+ return false;
+ }
+
+ channels_ = val;
+ }
+ return true;
+}
+
+bool WebMAudioClient::OnFloat(int id, double val) {
+ double* dst = NULL;
+
+ switch (id) {
+ case kWebMIdSamplingFrequency:
+ dst = &samples_per_second_;
+ break;
+ case kWebMIdOutputSamplingFrequency:
+ dst = &output_samples_per_second_;
+ break;
+ default:
+ return true;
+ }
+
+ if (val <= 0)
+ return false;
+
+ if (*dst != -1) {
+ MEDIA_LOG(log_cb_) << "Multiple values for id " << std::hex << id
+ << " specified (" << *dst << " and " << val << ")";
+ return false;
+ }
+
+ *dst = val;
+ return true;
+}
+
+} // namespace media
diff --git a/chromium/media/formats/webm/webm_audio_client.h b/chromium/media/formats/webm/webm_audio_client.h
new file mode 100644
index 00000000000..a723b0d1d48
--- /dev/null
+++ b/chromium/media/formats/webm/webm_audio_client.h
@@ -0,0 +1,54 @@
+// Copyright 2014 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_FORMATS_WEBM_WEBM_AUDIO_CLIENT_H_
+#define MEDIA_FORMATS_WEBM_WEBM_AUDIO_CLIENT_H_
+
+#include <string>
+#include <vector>
+
+#include "media/base/media_log.h"
+#include "media/formats/webm/webm_parser.h"
+
+namespace media {
+class AudioDecoderConfig;
+
+// Helper class used to parse an Audio element inside a TrackEntry element.
+class WebMAudioClient : public WebMParserClient {
+ public:
+ explicit WebMAudioClient(const LogCB& log_cb);
+ virtual ~WebMAudioClient();
+
+ // Reset this object's state so it can process a new audio track element.
+ void Reset();
+
+ // Initialize |config| with the data in |codec_id|, |codec_private|,
+ // |is_encrypted| and the fields parsed from the last audio track element this
+ // object was used to parse.
+ // Returns true if |config| was successfully initialized.
+ // Returns false if there was unexpected values in the provided parameters or
+ // audio track element fields.
+ bool InitializeConfig(const std::string& codec_id,
+ const std::vector<uint8>& codec_private,
+ const int64 seek_preroll,
+ const int64 codec_delay,
+ bool is_encrypted,
+ AudioDecoderConfig* config);
+
+ private:
+ // WebMParserClient implementation.
+ virtual bool OnUInt(int id, int64 val) OVERRIDE;
+ virtual bool OnFloat(int id, double val) OVERRIDE;
+
+ LogCB log_cb_;
+ int channels_;
+ double samples_per_second_;
+ double output_samples_per_second_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebMAudioClient);
+};
+
+} // namespace media
+
+#endif // MEDIA_FORMATS_WEBM_WEBM_AUDIO_CLIENT_H_
diff --git a/chromium/media/formats/webm/webm_cluster_parser.cc b/chromium/media/formats/webm/webm_cluster_parser.cc
new file mode 100644
index 00000000000..172eafafcf2
--- /dev/null
+++ b/chromium/media/formats/webm/webm_cluster_parser.cc
@@ -0,0 +1,686 @@
+// Copyright 2014 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/formats/webm/webm_cluster_parser.h"
+
+#include <vector>
+
+#include "base/logging.h"
+#include "base/sys_byteorder.h"
+#include "media/base/buffers.h"
+#include "media/base/decrypt_config.h"
+#include "media/filters/webvtt_util.h"
+#include "media/formats/webm/webm_constants.h"
+#include "media/formats/webm/webm_crypto_helpers.h"
+#include "media/formats/webm/webm_webvtt_parser.h"
+
+namespace media {
+
+WebMClusterParser::WebMClusterParser(
+ int64 timecode_scale,
+ int audio_track_num,
+ base::TimeDelta audio_default_duration,
+ int video_track_num,
+ base::TimeDelta video_default_duration,
+ const WebMTracksParser::TextTracks& text_tracks,
+ const std::set<int64>& ignored_tracks,
+ const std::string& audio_encryption_key_id,
+ const std::string& video_encryption_key_id,
+ const LogCB& log_cb)
+ : timecode_multiplier_(timecode_scale / 1000.0),
+ ignored_tracks_(ignored_tracks),
+ audio_encryption_key_id_(audio_encryption_key_id),
+ video_encryption_key_id_(video_encryption_key_id),
+ parser_(kWebMIdCluster, this),
+ last_block_timecode_(-1),
+ block_data_size_(-1),
+ block_duration_(-1),
+ block_add_id_(-1),
+ block_additional_data_size_(-1),
+ discard_padding_(-1),
+ cluster_timecode_(-1),
+ cluster_start_time_(kNoTimestamp()),
+ cluster_ended_(false),
+ audio_(audio_track_num, false, audio_default_duration, log_cb),
+ video_(video_track_num, true, video_default_duration, log_cb),
+ ready_buffer_upper_bound_(kNoTimestamp()),
+ log_cb_(log_cb) {
+ for (WebMTracksParser::TextTracks::const_iterator it = text_tracks.begin();
+ it != text_tracks.end();
+ ++it) {
+ text_track_map_.insert(std::make_pair(
+ it->first, Track(it->first, false, kNoTimestamp(), log_cb_)));
+ }
+}
+
+WebMClusterParser::~WebMClusterParser() {}
+
+void WebMClusterParser::Reset() {
+ last_block_timecode_ = -1;
+ cluster_timecode_ = -1;
+ cluster_start_time_ = kNoTimestamp();
+ cluster_ended_ = false;
+ parser_.Reset();
+ audio_.Reset();
+ video_.Reset();
+ ResetTextTracks();
+ ready_buffer_upper_bound_ = kNoTimestamp();
+}
+
+int WebMClusterParser::Parse(const uint8* buf, int size) {
+ audio_.ClearReadyBuffers();
+ video_.ClearReadyBuffers();
+ ClearTextTrackReadyBuffers();
+ ready_buffer_upper_bound_ = kNoTimestamp();
+
+ int result = parser_.Parse(buf, size);
+
+ if (result < 0) {
+ cluster_ended_ = false;
+ return result;
+ }
+
+ cluster_ended_ = parser_.IsParsingComplete();
+ if (cluster_ended_) {
+ // If there were no buffers in this cluster, set the cluster start time to
+ // be the |cluster_timecode_|.
+ if (cluster_start_time_ == kNoTimestamp()) {
+ // If the cluster did not even have a |cluster_timecode_|, signal parse
+ // error.
+ if (cluster_timecode_ < 0)
+ return -1;
+
+ cluster_start_time_ = base::TimeDelta::FromMicroseconds(
+ cluster_timecode_ * timecode_multiplier_);
+ }
+
+ // Reset the parser if we're done parsing so that
+ // it is ready to accept another cluster on the next
+ // call.
+ parser_.Reset();
+
+ last_block_timecode_ = -1;
+ cluster_timecode_ = -1;
+ }
+
+ return result;
+}
+
+const WebMClusterParser::BufferQueue& WebMClusterParser::GetAudioBuffers() {
+ if (ready_buffer_upper_bound_ == kNoTimestamp())
+ UpdateReadyBuffers();
+
+ return audio_.ready_buffers();
+}
+
+const WebMClusterParser::BufferQueue& WebMClusterParser::GetVideoBuffers() {
+ if (ready_buffer_upper_bound_ == kNoTimestamp())
+ UpdateReadyBuffers();
+
+ return video_.ready_buffers();
+}
+
+const WebMClusterParser::TextBufferQueueMap&
+WebMClusterParser::GetTextBuffers() {
+ if (ready_buffer_upper_bound_ == kNoTimestamp())
+ UpdateReadyBuffers();
+
+ // Translate our |text_track_map_| into |text_buffers_map_|, inserting rows in
+ // the output only for non-empty ready_buffer() queues in |text_track_map_|.
+ text_buffers_map_.clear();
+ for (TextTrackMap::const_iterator itr = text_track_map_.begin();
+ itr != text_track_map_.end();
+ ++itr) {
+ const BufferQueue& text_buffers = itr->second.ready_buffers();
+ if (!text_buffers.empty())
+ text_buffers_map_.insert(std::make_pair(itr->first, text_buffers));
+ }
+
+ return text_buffers_map_;
+}
+
+WebMParserClient* WebMClusterParser::OnListStart(int id) {
+ if (id == kWebMIdCluster) {
+ cluster_timecode_ = -1;
+ cluster_start_time_ = kNoTimestamp();
+ } else if (id == kWebMIdBlockGroup) {
+ block_data_.reset();
+ block_data_size_ = -1;
+ block_duration_ = -1;
+ discard_padding_ = -1;
+ discard_padding_set_ = false;
+ } else if (id == kWebMIdBlockAdditions) {
+ block_add_id_ = -1;
+ block_additional_data_.reset();
+ block_additional_data_size_ = -1;
+ }
+
+ return this;
+}
+
+bool WebMClusterParser::OnListEnd(int id) {
+ if (id != kWebMIdBlockGroup)
+ return true;
+
+ // Make sure the BlockGroup actually had a Block.
+ if (block_data_size_ == -1) {
+ MEDIA_LOG(log_cb_) << "Block missing from BlockGroup.";
+ return false;
+ }
+
+ bool result = ParseBlock(false, block_data_.get(), block_data_size_,
+ block_additional_data_.get(),
+ block_additional_data_size_, block_duration_,
+ discard_padding_set_ ? discard_padding_ : 0);
+ block_data_.reset();
+ block_data_size_ = -1;
+ block_duration_ = -1;
+ block_add_id_ = -1;
+ block_additional_data_.reset();
+ block_additional_data_size_ = -1;
+ discard_padding_ = -1;
+ discard_padding_set_ = false;
+ return result;
+}
+
+bool WebMClusterParser::OnUInt(int id, int64 val) {
+ int64* dst;
+ switch (id) {
+ case kWebMIdTimecode:
+ dst = &cluster_timecode_;
+ break;
+ case kWebMIdBlockDuration:
+ dst = &block_duration_;
+ break;
+ case kWebMIdBlockAddID:
+ dst = &block_add_id_;
+ break;
+ default:
+ return true;
+ }
+ if (*dst != -1)
+ return false;
+ *dst = val;
+ return true;
+}
+
+bool WebMClusterParser::ParseBlock(bool is_simple_block, const uint8* buf,
+ int size, const uint8* additional,
+ int additional_size, int duration,
+ int64 discard_padding) {
+ if (size < 4)
+ return false;
+
+ // Return an error if the trackNum > 127. We just aren't
+ // going to support large track numbers right now.
+ if (!(buf[0] & 0x80)) {
+ MEDIA_LOG(log_cb_) << "TrackNumber over 127 not supported";
+ return false;
+ }
+
+ int track_num = buf[0] & 0x7f;
+ int timecode = buf[1] << 8 | buf[2];
+ int flags = buf[3] & 0xff;
+ int lacing = (flags >> 1) & 0x3;
+
+ if (lacing) {
+ MEDIA_LOG(log_cb_) << "Lacing " << lacing << " is not supported yet.";
+ return false;
+ }
+
+ // Sign extend negative timecode offsets.
+ if (timecode & 0x8000)
+ timecode |= ~0xffff;
+
+ const uint8* frame_data = buf + 4;
+ int frame_size = size - (frame_data - buf);
+ return OnBlock(is_simple_block, track_num, timecode, duration, flags,
+ frame_data, frame_size, additional, additional_size,
+ discard_padding);
+}
+
+bool WebMClusterParser::OnBinary(int id, const uint8* data, int size) {
+ switch (id) {
+ case kWebMIdSimpleBlock:
+ return ParseBlock(true, data, size, NULL, -1, -1, 0);
+
+ case kWebMIdBlock:
+ if (block_data_) {
+ MEDIA_LOG(log_cb_) << "More than 1 Block in a BlockGroup is not "
+ "supported.";
+ return false;
+ }
+ block_data_.reset(new uint8[size]);
+ memcpy(block_data_.get(), data, size);
+ block_data_size_ = size;
+ return true;
+
+ case kWebMIdBlockAdditional: {
+ uint64 block_add_id = base::HostToNet64(block_add_id_);
+ if (block_additional_data_) {
+ // TODO(vigneshv): Technically, more than 1 BlockAdditional is allowed
+ // as per matroska spec. But for now we don't have a use case to
+ // support parsing of such files. Take a look at this again when such a
+ // case arises.
+ MEDIA_LOG(log_cb_) << "More than 1 BlockAdditional in a BlockGroup is "
+ "not supported.";
+ return false;
+ }
+ // First 8 bytes of side_data in DecoderBuffer is the BlockAddID
+ // element's value in Big Endian format. This is done to mimic ffmpeg
+ // demuxer's behavior.
+ block_additional_data_size_ = size + sizeof(block_add_id);
+ block_additional_data_.reset(new uint8[block_additional_data_size_]);
+ memcpy(block_additional_data_.get(), &block_add_id,
+ sizeof(block_add_id));
+ memcpy(block_additional_data_.get() + 8, data, size);
+ return true;
+ }
+ case kWebMIdDiscardPadding: {
+ if (discard_padding_set_ || size <= 0 || size > 8)
+ return false;
+ discard_padding_set_ = true;
+
+ // Read in the big-endian integer.
+ discard_padding_ = static_cast<int8>(data[0]);
+ for (int i = 1; i < size; ++i)
+ discard_padding_ = (discard_padding_ << 8) | data[i];
+
+ return true;
+ }
+ default:
+ return true;
+ }
+}
+
+bool WebMClusterParser::OnBlock(bool is_simple_block, int track_num,
+ int timecode,
+ int block_duration,
+ int flags,
+ const uint8* data, int size,
+ const uint8* additional, int additional_size,
+ int64 discard_padding) {
+ DCHECK_GE(size, 0);
+ if (cluster_timecode_ == -1) {
+ MEDIA_LOG(log_cb_) << "Got a block before cluster timecode.";
+ return false;
+ }
+
+ // TODO(acolwell): Should relative negative timecode offsets be rejected? Or
+ // only when the absolute timecode is negative? See http://crbug.com/271794
+ if (timecode < 0) {
+ MEDIA_LOG(log_cb_) << "Got a block with negative timecode offset "
+ << timecode;
+ return false;
+ }
+
+ if (last_block_timecode_ != -1 && timecode < last_block_timecode_) {
+ MEDIA_LOG(log_cb_)
+ << "Got a block with a timecode before the previous block.";
+ return false;
+ }
+
+ Track* track = NULL;
+ StreamParserBuffer::Type buffer_type = DemuxerStream::AUDIO;
+ std::string encryption_key_id;
+ if (track_num == audio_.track_num()) {
+ track = &audio_;
+ encryption_key_id = audio_encryption_key_id_;
+ } else if (track_num == video_.track_num()) {
+ track = &video_;
+ encryption_key_id = video_encryption_key_id_;
+ buffer_type = DemuxerStream::VIDEO;
+ } else if (ignored_tracks_.find(track_num) != ignored_tracks_.end()) {
+ return true;
+ } else if (Track* const text_track = FindTextTrack(track_num)) {
+ if (is_simple_block) // BlockGroup is required for WebVTT cues
+ return false;
+ if (block_duration < 0) // not specified
+ return false;
+ track = text_track;
+ buffer_type = DemuxerStream::TEXT;
+ } else {
+ MEDIA_LOG(log_cb_) << "Unexpected track number " << track_num;
+ return false;
+ }
+
+ last_block_timecode_ = timecode;
+
+ base::TimeDelta timestamp = base::TimeDelta::FromMicroseconds(
+ (cluster_timecode_ + timecode) * timecode_multiplier_);
+
+ scoped_refptr<StreamParserBuffer> buffer;
+ if (buffer_type != DemuxerStream::TEXT) {
+ // The first bit of the flags is set when a SimpleBlock contains only
+ // keyframes. If this is a Block, then inspection of the payload is
+ // necessary to determine whether it contains a keyframe or not.
+ // http://www.matroska.org/technical/specs/index.html
+ bool is_keyframe =
+ is_simple_block ? (flags & 0x80) != 0 : track->IsKeyframe(data, size);
+
+ // Every encrypted Block has a signal byte and IV prepended to it. Current
+ // encrypted WebM request for comments specification is here
+ // http://wiki.webmproject.org/encryption/webm-encryption-rfc
+ scoped_ptr<DecryptConfig> decrypt_config;
+ int data_offset = 0;
+ if (!encryption_key_id.empty() &&
+ !WebMCreateDecryptConfig(
+ data, size,
+ reinterpret_cast<const uint8*>(encryption_key_id.data()),
+ encryption_key_id.size(),
+ &decrypt_config, &data_offset)) {
+ return false;
+ }
+
+ // TODO(wolenetz/acolwell): Validate and use a common cross-parser TrackId
+ // type with remapped bytestream track numbers and allow multiple tracks as
+ // applicable. See https://crbug.com/341581.
+ buffer = StreamParserBuffer::CopyFrom(
+ data + data_offset, size - data_offset,
+ additional, additional_size,
+ is_keyframe, buffer_type, track_num);
+
+ if (decrypt_config)
+ buffer->set_decrypt_config(decrypt_config.Pass());
+ } else {
+ std::string id, settings, content;
+ WebMWebVTTParser::Parse(data, size, &id, &settings, &content);
+
+ std::vector<uint8> side_data;
+ MakeSideData(id.begin(), id.end(),
+ settings.begin(), settings.end(),
+ &side_data);
+
+ // TODO(wolenetz/acolwell): Validate and use a common cross-parser TrackId
+ // type with remapped bytestream track numbers and allow multiple tracks as
+ // applicable. See https://crbug.com/341581.
+ buffer = StreamParserBuffer::CopyFrom(
+ reinterpret_cast<const uint8*>(content.data()),
+ content.length(),
+ &side_data[0],
+ side_data.size(),
+ true, buffer_type, track_num);
+ }
+
+ buffer->set_timestamp(timestamp);
+ if (cluster_start_time_ == kNoTimestamp())
+ cluster_start_time_ = timestamp;
+
+ if (block_duration >= 0) {
+ buffer->set_duration(base::TimeDelta::FromMicroseconds(
+ block_duration * timecode_multiplier_));
+ } else {
+ DCHECK_NE(buffer_type, DemuxerStream::TEXT);
+ buffer->set_duration(track->default_duration());
+ }
+
+ if (discard_padding != 0) {
+ buffer->set_discard_padding(std::make_pair(
+ base::TimeDelta(),
+ base::TimeDelta::FromMicroseconds(discard_padding / 1000)));
+ }
+
+ return track->AddBuffer(buffer);
+}
+
+WebMClusterParser::Track::Track(int track_num,
+ bool is_video,
+ base::TimeDelta default_duration,
+ const LogCB& log_cb)
+ : track_num_(track_num),
+ is_video_(is_video),
+ default_duration_(default_duration),
+ estimated_next_frame_duration_(kNoTimestamp()),
+ log_cb_(log_cb) {
+ DCHECK(default_duration_ == kNoTimestamp() ||
+ default_duration_ > base::TimeDelta());
+}
+
+WebMClusterParser::Track::~Track() {}
+
+base::TimeDelta WebMClusterParser::Track::GetReadyUpperBound() {
+ DCHECK(ready_buffers_.empty());
+ if (last_added_buffer_missing_duration_)
+ return last_added_buffer_missing_duration_->GetDecodeTimestamp();
+
+ return kInfiniteDuration();
+}
+
+void WebMClusterParser::Track::ExtractReadyBuffers(
+ const base::TimeDelta before_timestamp) {
+ DCHECK(ready_buffers_.empty());
+ DCHECK(base::TimeDelta() <= before_timestamp);
+ DCHECK(kNoTimestamp() != before_timestamp);
+
+ if (buffers_.empty())
+ return;
+
+ if (buffers_.back()->GetDecodeTimestamp() < before_timestamp) {
+ // All of |buffers_| are ready.
+ ready_buffers_.swap(buffers_);
+ DVLOG(3) << __FUNCTION__ << " : " << track_num_ << " All "
+ << ready_buffers_.size() << " are ready: before upper bound ts "
+ << before_timestamp.InSecondsF();
+ return;
+ }
+
+ // Not all of |buffers_| are ready yet. Move any that are ready to
+ // |ready_buffers_|.
+ while (true) {
+ const scoped_refptr<StreamParserBuffer>& buffer = buffers_.front();
+ if (buffer->GetDecodeTimestamp() >= before_timestamp)
+ break;
+ ready_buffers_.push_back(buffer);
+ buffers_.pop_front();
+ DCHECK(!buffers_.empty());
+ }
+
+ DVLOG(3) << __FUNCTION__ << " : " << track_num_ << " Only "
+ << ready_buffers_.size() << " ready, " << buffers_.size()
+ << " at or after upper bound ts " << before_timestamp.InSecondsF();
+}
+
+bool WebMClusterParser::Track::AddBuffer(
+ const scoped_refptr<StreamParserBuffer>& buffer) {
+ DVLOG(2) << "AddBuffer() : " << track_num_
+ << " ts " << buffer->timestamp().InSecondsF()
+ << " dur " << buffer->duration().InSecondsF()
+ << " kf " << buffer->IsKeyframe()
+ << " size " << buffer->data_size();
+
+ if (last_added_buffer_missing_duration_) {
+ base::TimeDelta derived_duration =
+ buffer->timestamp() - last_added_buffer_missing_duration_->timestamp();
+ last_added_buffer_missing_duration_->set_duration(derived_duration);
+
+ DVLOG(2) << "AddBuffer() : applied derived duration to held-back buffer : "
+ << " ts "
+ << last_added_buffer_missing_duration_->timestamp().InSecondsF()
+ << " dur "
+ << last_added_buffer_missing_duration_->duration().InSecondsF()
+ << " kf " << last_added_buffer_missing_duration_->IsKeyframe()
+ << " size " << last_added_buffer_missing_duration_->data_size();
+ scoped_refptr<StreamParserBuffer> updated_buffer =
+ last_added_buffer_missing_duration_;
+ last_added_buffer_missing_duration_ = NULL;
+ if (!QueueBuffer(updated_buffer))
+ return false;
+ }
+
+ if (buffer->duration() == kNoTimestamp()) {
+ last_added_buffer_missing_duration_ = buffer;
+ DVLOG(2) << "AddBuffer() : holding back buffer that is missing duration";
+ return true;
+ }
+
+ return QueueBuffer(buffer);
+}
+
+void WebMClusterParser::Track::ApplyDurationEstimateIfNeeded() {
+ if (!last_added_buffer_missing_duration_)
+ return;
+
+ last_added_buffer_missing_duration_->set_duration(GetDurationEstimate());
+
+ DVLOG(2) << "ApplyDurationEstimateIfNeeded() : new dur : "
+ << " ts "
+ << last_added_buffer_missing_duration_->timestamp().InSecondsF()
+ << " dur "
+ << last_added_buffer_missing_duration_->duration().InSecondsF()
+ << " kf " << last_added_buffer_missing_duration_->IsKeyframe()
+ << " size " << last_added_buffer_missing_duration_->data_size();
+
+ // Don't use the applied duration as a future estimation (don't use
+ // QueueBuffer() here.)
+ buffers_.push_back(last_added_buffer_missing_duration_);
+ last_added_buffer_missing_duration_ = NULL;
+}
+
+void WebMClusterParser::Track::ClearReadyBuffers() {
+ // Note that |buffers_| are kept and |estimated_next_frame_duration_| is not
+ // reset here.
+ ready_buffers_.clear();
+}
+
+void WebMClusterParser::Track::Reset() {
+ ClearReadyBuffers();
+ buffers_.clear();
+ last_added_buffer_missing_duration_ = NULL;
+}
+
+bool WebMClusterParser::Track::IsKeyframe(const uint8* data, int size) const {
+ // For now, assume that all blocks are keyframes for datatypes other than
+ // video. This is a valid assumption for Vorbis, WebVTT, & Opus.
+ if (!is_video_)
+ return true;
+
+ // Make sure the block is big enough for the minimal keyframe header size.
+ if (size < 7)
+ return false;
+
+ // The LSb of the first byte must be a 0 for a keyframe.
+ // http://tools.ietf.org/html/rfc6386 Section 19.1
+ if ((data[0] & 0x01) != 0)
+ return false;
+
+ // Verify VP8 keyframe startcode.
+ // http://tools.ietf.org/html/rfc6386 Section 19.1
+ if (data[3] != 0x9d || data[4] != 0x01 || data[5] != 0x2a)
+ return false;
+
+ return true;
+}
+
+bool WebMClusterParser::Track::QueueBuffer(
+ const scoped_refptr<StreamParserBuffer>& buffer) {
+ DCHECK(!last_added_buffer_missing_duration_);
+
+ // WebMClusterParser::OnBlock() gives MEDIA_LOG and parse error on decreasing
+ // block timecode detection within a cluster. Therefore, we should not see
+ // those here.
+ base::TimeDelta previous_buffers_timestamp = buffers_.empty() ?
+ base::TimeDelta() : buffers_.back()->GetDecodeTimestamp();
+ CHECK(previous_buffers_timestamp <= buffer->GetDecodeTimestamp());
+
+ base::TimeDelta duration = buffer->duration();
+ if (duration < base::TimeDelta() || duration == kNoTimestamp()) {
+ MEDIA_LOG(log_cb_) << "Invalid buffer duration: " << duration.InSecondsF();
+ return false;
+ }
+
+ // The estimated frame duration is the minimum non-zero duration since the
+ // last initialization segment. The minimum is used to ensure frame durations
+ // aren't overestimated.
+ if (duration > base::TimeDelta()) {
+ if (estimated_next_frame_duration_ == kNoTimestamp()) {
+ estimated_next_frame_duration_ = duration;
+ } else {
+ estimated_next_frame_duration_ =
+ std::min(duration, estimated_next_frame_duration_);
+ }
+ }
+
+ buffers_.push_back(buffer);
+ return true;
+}
+
+base::TimeDelta WebMClusterParser::Track::GetDurationEstimate() {
+ base::TimeDelta duration = estimated_next_frame_duration_;
+ if (duration != kNoTimestamp()) {
+ DVLOG(3) << __FUNCTION__ << " : using estimated duration";
+ } else {
+ DVLOG(3) << __FUNCTION__ << " : using hardcoded default duration";
+ if (is_video_) {
+ duration = base::TimeDelta::FromMilliseconds(
+ kDefaultVideoBufferDurationInMs);
+ } else {
+ duration = base::TimeDelta::FromMilliseconds(
+ kDefaultAudioBufferDurationInMs);
+ }
+ }
+
+ DCHECK(duration > base::TimeDelta());
+ DCHECK(duration != kNoTimestamp());
+ return duration;
+}
+
+void WebMClusterParser::ClearTextTrackReadyBuffers() {
+ text_buffers_map_.clear();
+ for (TextTrackMap::iterator it = text_track_map_.begin();
+ it != text_track_map_.end();
+ ++it) {
+ it->second.ClearReadyBuffers();
+ }
+}
+
+void WebMClusterParser::ResetTextTracks() {
+ ClearTextTrackReadyBuffers();
+ for (TextTrackMap::iterator it = text_track_map_.begin();
+ it != text_track_map_.end();
+ ++it) {
+ it->second.Reset();
+ }
+}
+
+void WebMClusterParser::UpdateReadyBuffers() {
+ DCHECK(ready_buffer_upper_bound_ == kNoTimestamp());
+ DCHECK(text_buffers_map_.empty());
+
+ if (cluster_ended_) {
+ audio_.ApplyDurationEstimateIfNeeded();
+ video_.ApplyDurationEstimateIfNeeded();
+ // Per OnBlock(), all text buffers should already have valid durations, so
+ // there is no need to call ApplyDurationEstimateIfNeeded() on text tracks
+ // here.
+ ready_buffer_upper_bound_ = kInfiniteDuration();
+ DCHECK(ready_buffer_upper_bound_ == audio_.GetReadyUpperBound());
+ DCHECK(ready_buffer_upper_bound_ == video_.GetReadyUpperBound());
+ } else {
+ ready_buffer_upper_bound_ = std::min(audio_.GetReadyUpperBound(),
+ video_.GetReadyUpperBound());
+ DCHECK(base::TimeDelta() <= ready_buffer_upper_bound_);
+ DCHECK(kNoTimestamp() != ready_buffer_upper_bound_);
+ }
+
+ // Prepare each track's ready buffers for retrieval.
+ audio_.ExtractReadyBuffers(ready_buffer_upper_bound_);
+ video_.ExtractReadyBuffers(ready_buffer_upper_bound_);
+ for (TextTrackMap::iterator itr = text_track_map_.begin();
+ itr != text_track_map_.end();
+ ++itr) {
+ itr->second.ExtractReadyBuffers(ready_buffer_upper_bound_);
+ }
+}
+
+WebMClusterParser::Track*
+WebMClusterParser::FindTextTrack(int track_num) {
+ const TextTrackMap::iterator it = text_track_map_.find(track_num);
+
+ if (it == text_track_map_.end())
+ return NULL;
+
+ return &it->second;
+}
+
+} // namespace media
diff --git a/chromium/media/formats/webm/webm_cluster_parser.h b/chromium/media/formats/webm/webm_cluster_parser.h
new file mode 100644
index 00000000000..ab1d4a11fb1
--- /dev/null
+++ b/chromium/media/formats/webm/webm_cluster_parser.h
@@ -0,0 +1,275 @@
+// Copyright 2014 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_FORMATS_WEBM_WEBM_CLUSTER_PARSER_H_
+#define MEDIA_FORMATS_WEBM_WEBM_CLUSTER_PARSER_H_
+
+#include <deque>
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "media/base/media_export.h"
+#include "media/base/media_log.h"
+#include "media/base/stream_parser.h"
+#include "media/base/stream_parser_buffer.h"
+#include "media/formats/webm/webm_parser.h"
+#include "media/formats/webm/webm_tracks_parser.h"
+
+namespace media {
+
+class MEDIA_EXPORT WebMClusterParser : public WebMParserClient {
+ public:
+ typedef StreamParser::TrackId TrackId;
+ typedef std::deque<scoped_refptr<StreamParserBuffer> > BufferQueue;
+ typedef std::map<TrackId, const BufferQueue> TextBufferQueueMap;
+
+ // Arbitrarily-chosen numbers to estimate the duration of a buffer if none is
+ // set and there is not enough information to get a better estimate.
+ // TODO(wolenetz/acolwell): Parse audio codebook to determine missing audio
+ // frame durations. See http://crbug.com/351166.
+ enum {
+ kDefaultAudioBufferDurationInMs = 23, // Common 1k samples @44.1kHz
+ kDefaultVideoBufferDurationInMs = 42 // Low 24fps to reduce stalls
+ };
+
+ private:
+ // Helper class that manages per-track state.
+ class Track {
+ public:
+ Track(int track_num,
+ bool is_video,
+ base::TimeDelta default_duration,
+ const LogCB& log_cb);
+ ~Track();
+
+ int track_num() const { return track_num_; }
+
+ // If a buffer is currently held aside pending duration calculation, returns
+ // its decode timestamp. Otherwise, returns kInfiniteDuration().
+ base::TimeDelta GetReadyUpperBound();
+
+ // Prepares |ready_buffers_| for retrieval. Prior to calling,
+ // |ready_buffers_| must be empty. Moves all |buffers_| with timestamp
+ // before |before_timestamp| to |ready_buffers_|, preserving their order.
+ void ExtractReadyBuffers(const base::TimeDelta before_timestamp);
+
+ const BufferQueue& ready_buffers() const { return ready_buffers_; }
+
+ // If |last_added_buffer_missing_duration_| is set, updates its duration
+ // relative to |buffer|'s timestamp, and adds it to |buffers_| and unsets
+ // |last_added_buffer_missing_duration_|. Then, if |buffer| is missing
+ // duration, saves |buffer| into |last_added_buffer_missing_duration_|, or
+ // otherwise adds |buffer| to |buffers_|.
+ bool AddBuffer(const scoped_refptr<StreamParserBuffer>& buffer);
+
+ // If |last_added_buffer_missing_duration_| is set, updates its duration to
+ // be non-kNoTimestamp() value of |estimated_next_frame_duration_| or an
+ // arbitrary default, then adds it to |buffers_| and unsets
+ // |last_added_buffer_missing_duration_|. (This method helps stream parser
+ // emit all buffers in a media segment before signaling end of segment.)
+ void ApplyDurationEstimateIfNeeded();
+
+ // Clears |ready_buffers_| (use ExtractReadyBuffers() to fill it again).
+ // Leaves as-is |buffers_| and any possibly held-aside buffer that is
+ // missing duration.
+ void ClearReadyBuffers();
+
+ // Clears all buffer state, including any possibly held-aside buffer that
+ // was missing duration, and all contents of |buffers_| and
+ // |ready_buffers_|.
+ void Reset();
+
+ // Helper function used to inspect block data to determine if the
+ // block is a keyframe.
+ // |data| contains the bytes in the block.
+ // |size| indicates the number of bytes in |data|.
+ bool IsKeyframe(const uint8* data, int size) const;
+
+ base::TimeDelta default_duration() const { return default_duration_; }
+
+ private:
+ // Helper that sanity-checks |buffer| duration, updates
+ // |estimated_next_frame_duration_|, and adds |buffer| to |buffers_|.
+ // Returns false if |buffer| failed sanity check and therefore was not added
+ // to |buffers_|. Returns true otherwise.
+ bool QueueBuffer(const scoped_refptr<StreamParserBuffer>& buffer);
+
+ // Helper that calculates the buffer duration to use in
+ // ApplyDurationEstimateIfNeeded().
+ base::TimeDelta GetDurationEstimate();
+
+ int track_num_;
+ bool is_video_;
+
+ // Parsed track buffers, each with duration and in (decode) timestamp order,
+ // that have not yet been extracted into |ready_buffers_|. Note that up to
+ // one additional buffer missing duration may be tracked by
+ // |last_added_buffer_missing_duration_|.
+ BufferQueue buffers_;
+ scoped_refptr<StreamParserBuffer> last_added_buffer_missing_duration_;
+
+ // Buffers in (decode) timestamp order that were previously parsed into and
+ // extracted from |buffers_|. Buffers are moved from |buffers_| to
+ // |ready_buffers_| by ExtractReadyBuffers() if they are below a specified
+ // upper bound timestamp. Track users can therefore extract only those
+ // parsed buffers which are "ready" for emission (all before some maximum
+ // timestamp).
+ BufferQueue ready_buffers_;
+
+ // If kNoTimestamp(), then |estimated_next_frame_duration_| will be used.
+ base::TimeDelta default_duration_;
+
+ // If kNoTimestamp(), then a default value will be used. This estimate is
+ // the maximum duration seen or derived so far for this track, and is valid
+ // only if |default_duration_| is kNoTimestamp().
+ base::TimeDelta estimated_next_frame_duration_;
+
+ LogCB log_cb_;
+ };
+
+ typedef std::map<int, Track> TextTrackMap;
+
+ public:
+ WebMClusterParser(int64 timecode_scale,
+ int audio_track_num,
+ base::TimeDelta audio_default_duration,
+ int video_track_num,
+ base::TimeDelta video_default_duration,
+ const WebMTracksParser::TextTracks& text_tracks,
+ const std::set<int64>& ignored_tracks,
+ const std::string& audio_encryption_key_id,
+ const std::string& video_encryption_key_id,
+ const LogCB& log_cb);
+ virtual ~WebMClusterParser();
+
+ // Resets the parser state so it can accept a new cluster.
+ void Reset();
+
+ // Parses a WebM cluster element in |buf|.
+ //
+ // Returns -1 if the parse fails.
+ // Returns 0 if more data is needed.
+ // Returns the number of bytes parsed on success.
+ int Parse(const uint8* buf, int size);
+
+ base::TimeDelta cluster_start_time() const { return cluster_start_time_; }
+
+ // Get the current ready buffers resulting from Parse().
+ // If the parse reached the end of cluster and the last buffer was held aside
+ // due to missing duration, the buffer is given an estimated duration and
+ // included in the result.
+ // Otherwise, if there are is a buffer held aside due to missing duration for
+ // any of the tracks, no buffers with same or greater (decode) timestamp will
+ // be included in the buffers.
+ // The returned deques are cleared by Parse() or Reset() and updated by the
+ // next calls to Get{Audio,Video}Buffers().
+ // If no Parse() or Reset() has occurred since the last call to Get{Audio,
+ // Video,Text}Buffers(), then the previous BufferQueue& is returned again
+ // without any recalculation.
+ const BufferQueue& GetAudioBuffers();
+ const BufferQueue& GetVideoBuffers();
+
+ // Constructs and returns a subset of |text_track_map_| containing only
+ // tracks with non-empty buffer queues produced by the last Parse() and
+ // filtered to exclude any buffers that have (decode) timestamp same or
+ // greater than the lowest (decode) timestamp across all tracks of any buffer
+ // held aside due to missing duration (unless the end of cluster has been
+ // reached).
+ // The returned map is cleared by Parse() or Reset() and updated by the next
+ // call to GetTextBuffers().
+ // If no Parse() or Reset() has occurred since the last call to
+ // GetTextBuffers(), then the previous TextBufferQueueMap& is returned again
+ // without any recalculation.
+ const TextBufferQueueMap& GetTextBuffers();
+
+ // Returns true if the last Parse() call stopped at the end of a cluster.
+ bool cluster_ended() const { return cluster_ended_; }
+
+ private:
+ // WebMParserClient methods.
+ virtual WebMParserClient* OnListStart(int id) OVERRIDE;
+ virtual bool OnListEnd(int id) OVERRIDE;
+ virtual bool OnUInt(int id, int64 val) OVERRIDE;
+ virtual bool OnBinary(int id, const uint8* data, int size) OVERRIDE;
+
+ bool ParseBlock(bool is_simple_block, const uint8* buf, int size,
+ const uint8* additional, int additional_size, int duration,
+ int64 discard_padding);
+ bool OnBlock(bool is_simple_block, int track_num, int timecode, int duration,
+ int flags, const uint8* data, int size,
+ const uint8* additional, int additional_size,
+ int64 discard_padding);
+
+ // Resets the Track objects associated with each text track.
+ void ResetTextTracks();
+
+ // Clears the the ready buffers associated with each text track.
+ void ClearTextTrackReadyBuffers();
+
+ // Helper method for Get{Audio,Video,Text}Buffers() that recomputes
+ // |ready_buffer_upper_bound_| and calls ExtractReadyBuffers() on each track.
+ // If |cluster_ended_| is true, first applies duration estimate if needed for
+ // |audio_| and |video_| and sets |ready_buffer_upper_bound_| to
+ // kInfiniteDuration(). Otherwise, sets |ready_buffer_upper_bound_| to the
+ // minimum upper bound across |audio_| and |video_|. (Text tracks can have no
+ // buffers missing duration, so they are not involved in calculating the upper
+ // bound.)
+ // Parse() or Reset() must be called between calls to UpdateReadyBuffers() to
+ // clear each track's ready buffers and to reset |ready_buffer_upper_bound_|
+ // to kNoTimestamp().
+ void UpdateReadyBuffers();
+
+ // Search for the indicated track_num among the text tracks. Returns NULL
+ // if that track num is not a text track.
+ Track* FindTextTrack(int track_num);
+
+ double timecode_multiplier_; // Multiplier used to convert timecodes into
+ // microseconds.
+ std::set<int64> ignored_tracks_;
+ std::string audio_encryption_key_id_;
+ std::string video_encryption_key_id_;
+
+ WebMListParser parser_;
+
+ int64 last_block_timecode_;
+ scoped_ptr<uint8[]> block_data_;
+ int block_data_size_;
+ int64 block_duration_;
+ int64 block_add_id_;
+ scoped_ptr<uint8[]> block_additional_data_;
+ int block_additional_data_size_;
+ int64 discard_padding_;
+ bool discard_padding_set_;
+
+ int64 cluster_timecode_;
+ base::TimeDelta cluster_start_time_;
+ bool cluster_ended_;
+
+ Track audio_;
+ Track video_;
+ TextTrackMap text_track_map_;
+
+ // Subset of |text_track_map_| maintained by GetTextBuffers(), and cleared by
+ // ClearTextTrackReadyBuffers(). Callers of GetTextBuffers() get a const-ref
+ // to this member.
+ TextBufferQueueMap text_buffers_map_;
+
+ // Limits the range of buffers returned by Get{Audio,Video,Text}Buffers() to
+ // this exclusive upper bound. Set to kNoTimestamp(), meaning not yet
+ // calculated, by Reset() and Parse(). If kNoTimestamp(), then
+ // Get{Audio,Video,Text}Buffers() will calculate it to be the minimum (decode)
+ // timestamp across all tracks' |last_buffer_missing_duration_|, or
+ // kInfiniteDuration() if no buffers are currently missing duration.
+ base::TimeDelta ready_buffer_upper_bound_;
+
+ LogCB log_cb_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(WebMClusterParser);
+};
+
+} // namespace media
+
+#endif // MEDIA_FORMATS_WEBM_WEBM_CLUSTER_PARSER_H_
diff --git a/chromium/media/formats/webm/webm_cluster_parser_unittest.cc b/chromium/media/formats/webm/webm_cluster_parser_unittest.cc
new file mode 100644
index 00000000000..55dc791c2ce
--- /dev/null
+++ b/chromium/media/formats/webm/webm_cluster_parser_unittest.cc
@@ -0,0 +1,957 @@
+// Copyright 2014 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 <algorithm>
+#include <cstdlib>
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "media/base/decrypt_config.h"
+#include "media/formats/webm/cluster_builder.h"
+#include "media/formats/webm/webm_cluster_parser.h"
+#include "media/formats/webm/webm_constants.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::InSequence;
+using ::testing::Return;
+using ::testing::_;
+
+namespace media {
+
+typedef WebMTracksParser::TextTracks TextTracks;
+
+enum {
+ kTimecodeScale = 1000000, // Timecode scale for millisecond timestamps.
+ kAudioTrackNum = 1,
+ kVideoTrackNum = 2,
+ kTextTrackNum = 3,
+ kTestAudioFrameDefaultDurationInMs = 13,
+ kTestVideoFrameDefaultDurationInMs = 17
+};
+
+COMPILE_ASSERT(
+ static_cast<int>(kTestAudioFrameDefaultDurationInMs) !=
+ static_cast<int>(WebMClusterParser::kDefaultAudioBufferDurationInMs),
+ test_default_is_same_as_estimation_fallback_audio_duration);
+COMPILE_ASSERT(
+ static_cast<int>(kTestVideoFrameDefaultDurationInMs) !=
+ static_cast<int>(WebMClusterParser::kDefaultVideoBufferDurationInMs),
+ test_default_is_same_as_estimation_fallback_video_duration);
+
+struct BlockInfo {
+ int track_num;
+ int timestamp;
+
+ // Negative value is allowed only for block groups (not simple blocks) and
+ // directs CreateCluster() to exclude BlockDuration entry from the cluster for
+ // this BlockGroup. The absolute value is used for parser verification.
+ // For simple blocks, this value must be non-negative, and is used only for
+ // parser verification.
+ int duration;
+ bool use_simple_block;
+};
+
+static const BlockInfo kDefaultBlockInfo[] = {
+ { kAudioTrackNum, 0, 23, true },
+ { kAudioTrackNum, 23, 23, true },
+ { kVideoTrackNum, 33, 34, true }, // Assumes not using DefaultDuration
+ { kAudioTrackNum, 46, 23, true },
+ { kVideoTrackNum, 67, 33, false },
+ { kAudioTrackNum, 69, 23, false },
+ { kVideoTrackNum, 100, 33, false },
+};
+
+static const uint8 kEncryptedFrame[] = {
+ 0x01, // Block is encrypted
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 // IV
+};
+
+static scoped_ptr<Cluster> CreateCluster(int timecode,
+ const BlockInfo* block_info,
+ int block_count) {
+ ClusterBuilder cb;
+ cb.SetClusterTimecode(0);
+
+ for (int i = 0; i < block_count; i++) {
+ uint8 data[] = { 0x00 };
+ if (block_info[i].use_simple_block) {
+ CHECK_GE(block_info[i].duration, 0);
+ cb.AddSimpleBlock(block_info[i].track_num,
+ block_info[i].timestamp,
+ 0, data, sizeof(data));
+ continue;
+ }
+
+ if (block_info[i].duration < 0) {
+ cb.AddBlockGroupWithoutBlockDuration(block_info[i].track_num,
+ block_info[i].timestamp,
+ 0, data, sizeof(data));
+ continue;
+ }
+
+ cb.AddBlockGroup(block_info[i].track_num,
+ block_info[i].timestamp,
+ block_info[i].duration,
+ 0, data, sizeof(data));
+ }
+
+ return cb.Finish();
+}
+
+// Creates a Cluster with one encrypted Block. |bytes_to_write| is number of
+// bytes of the encrypted frame to write.
+static scoped_ptr<Cluster> CreateEncryptedCluster(int bytes_to_write) {
+ CHECK_GT(bytes_to_write, 0);
+ CHECK_LE(bytes_to_write, static_cast<int>(sizeof(kEncryptedFrame)));
+
+ ClusterBuilder cb;
+ cb.SetClusterTimecode(0);
+ cb.AddSimpleBlock(kVideoTrackNum, 0, 0, kEncryptedFrame, bytes_to_write);
+ return cb.Finish();
+}
+
+static bool VerifyBuffers(const WebMClusterParser::BufferQueue& audio_buffers,
+ const WebMClusterParser::BufferQueue& video_buffers,
+ const WebMClusterParser::BufferQueue& text_buffers,
+ const BlockInfo* block_info,
+ int block_count) {
+ int buffer_count = audio_buffers.size() + video_buffers.size() +
+ text_buffers.size();
+ if (block_count != buffer_count) {
+ DVLOG(1) << __FUNCTION__ << " : block_count (" << block_count
+ << ") mismatches buffer_count (" << buffer_count << ")";
+ return false;
+ }
+
+ size_t audio_offset = 0;
+ size_t video_offset = 0;
+ size_t text_offset = 0;
+ for (int i = 0; i < block_count; i++) {
+ const WebMClusterParser::BufferQueue* buffers = NULL;
+ size_t* offset;
+ StreamParserBuffer::Type expected_type = DemuxerStream::UNKNOWN;
+
+ if (block_info[i].track_num == kAudioTrackNum) {
+ buffers = &audio_buffers;
+ offset = &audio_offset;
+ expected_type = DemuxerStream::AUDIO;
+ } else if (block_info[i].track_num == kVideoTrackNum) {
+ buffers = &video_buffers;
+ offset = &video_offset;
+ expected_type = DemuxerStream::VIDEO;
+ } else if (block_info[i].track_num == kTextTrackNum) {
+ buffers = &text_buffers;
+ offset = &text_offset;
+ expected_type = DemuxerStream::TEXT;
+ } else {
+ LOG(ERROR) << "Unexpected track number " << block_info[i].track_num;
+ return false;
+ }
+
+ if (*offset >= buffers->size()) {
+ DVLOG(1) << __FUNCTION__ << " : Too few buffers (" << buffers->size()
+ << ") for track_num (" << block_info[i].track_num
+ << "), expected at least " << *offset + 1 << " buffers";
+ return false;
+ }
+
+ scoped_refptr<StreamParserBuffer> buffer = (*buffers)[(*offset)++];
+
+ EXPECT_EQ(block_info[i].timestamp, buffer->timestamp().InMilliseconds());
+ EXPECT_EQ(std::abs(block_info[i].duration),
+ buffer->duration().InMilliseconds());
+ EXPECT_EQ(expected_type, buffer->type());
+ EXPECT_EQ(block_info[i].track_num, buffer->track_id());
+ }
+
+ return true;
+}
+
+static bool VerifyBuffers(const scoped_ptr<WebMClusterParser>& parser,
+ const BlockInfo* block_info,
+ int block_count) {
+ const WebMClusterParser::TextBufferQueueMap& text_map =
+ parser->GetTextBuffers();
+ const WebMClusterParser::BufferQueue* text_buffers;
+ const WebMClusterParser::BufferQueue no_text_buffers;
+ if (!text_map.empty())
+ text_buffers = &(text_map.rbegin()->second);
+ else
+ text_buffers = &no_text_buffers;
+
+ return VerifyBuffers(parser->GetAudioBuffers(),
+ parser->GetVideoBuffers(),
+ *text_buffers,
+ block_info,
+ block_count);
+}
+
+static bool VerifyTextBuffers(
+ const scoped_ptr<WebMClusterParser>& parser,
+ const BlockInfo* block_info_ptr,
+ int block_count,
+ int text_track_num,
+ const WebMClusterParser::BufferQueue& text_buffers) {
+ const BlockInfo* const block_info_end = block_info_ptr + block_count;
+
+ typedef WebMClusterParser::BufferQueue::const_iterator TextBufferIter;
+ TextBufferIter buffer_iter = text_buffers.begin();
+ const TextBufferIter buffer_end = text_buffers.end();
+
+ while (block_info_ptr != block_info_end) {
+ const BlockInfo& block_info = *block_info_ptr++;
+
+ if (block_info.track_num != text_track_num)
+ continue;
+
+ EXPECT_FALSE(block_info.use_simple_block);
+ EXPECT_FALSE(buffer_iter == buffer_end);
+
+ const scoped_refptr<StreamParserBuffer> buffer = *buffer_iter++;
+ EXPECT_EQ(block_info.timestamp, buffer->timestamp().InMilliseconds());
+ EXPECT_EQ(std::abs(block_info.duration),
+ buffer->duration().InMilliseconds());
+ EXPECT_EQ(DemuxerStream::TEXT, buffer->type());
+ EXPECT_EQ(text_track_num, buffer->track_id());
+ }
+
+ EXPECT_TRUE(buffer_iter == buffer_end);
+ return true;
+}
+
+static void VerifyEncryptedBuffer(
+ scoped_refptr<StreamParserBuffer> buffer) {
+ EXPECT_TRUE(buffer->decrypt_config());
+ EXPECT_EQ(static_cast<unsigned long>(DecryptConfig::kDecryptionKeySize),
+ buffer->decrypt_config()->iv().length());
+}
+
+static void AppendToEnd(const WebMClusterParser::BufferQueue& src,
+ WebMClusterParser::BufferQueue* dest) {
+ for (WebMClusterParser::BufferQueue::const_iterator itr = src.begin();
+ itr != src.end(); ++itr) {
+ dest->push_back(*itr);
+ }
+}
+
+class WebMClusterParserTest : public testing::Test {
+ public:
+ WebMClusterParserTest()
+ : parser_(new WebMClusterParser(kTimecodeScale,
+ kAudioTrackNum,
+ kNoTimestamp(),
+ kVideoTrackNum,
+ kNoTimestamp(),
+ TextTracks(),
+ std::set<int64>(),
+ std::string(),
+ std::string(),
+ LogCB())) {}
+
+ protected:
+ void ResetParserToHaveDefaultDurations() {
+ base::TimeDelta default_audio_duration = base::TimeDelta::FromMilliseconds(
+ kTestAudioFrameDefaultDurationInMs);
+ base::TimeDelta default_video_duration = base::TimeDelta::FromMilliseconds(
+ kTestVideoFrameDefaultDurationInMs);
+ ASSERT_GE(default_audio_duration, base::TimeDelta());
+ ASSERT_GE(default_video_duration, base::TimeDelta());
+ ASSERT_NE(kNoTimestamp(), default_audio_duration);
+ ASSERT_NE(kNoTimestamp(), default_video_duration);
+
+ parser_.reset(new WebMClusterParser(kTimecodeScale,
+ kAudioTrackNum,
+ default_audio_duration,
+ kVideoTrackNum,
+ default_video_duration,
+ TextTracks(),
+ std::set<int64>(),
+ std::string(),
+ std::string(),
+ LogCB()));
+ }
+
+ scoped_ptr<WebMClusterParser> parser_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(WebMClusterParserTest);
+};
+
+TEST_F(WebMClusterParserTest, HeldBackBufferHoldsBackAllTracks) {
+ // If a buffer is missing duration and is being held back, then all other
+ // tracks' buffers that have same or higher (decode) timestamp should be held
+ // back too to keep the timestamps emitted for a cluster monotonically
+ // non-decreasing and in same order as parsed.
+ InSequence s;
+
+ // Reset the parser to have 3 tracks: text, video (no default frame duration),
+ // and audio (with a default frame duration).
+ TextTracks text_tracks;
+ text_tracks.insert(std::make_pair(TextTracks::key_type(kTextTrackNum),
+ TextTrackConfig(kTextSubtitles, "", "",
+ "")));
+ base::TimeDelta default_audio_duration =
+ base::TimeDelta::FromMilliseconds(kTestAudioFrameDefaultDurationInMs);
+ ASSERT_GE(default_audio_duration, base::TimeDelta());
+ ASSERT_NE(kNoTimestamp(), default_audio_duration);
+ parser_.reset(new WebMClusterParser(kTimecodeScale,
+ kAudioTrackNum,
+ default_audio_duration,
+ kVideoTrackNum,
+ kNoTimestamp(),
+ text_tracks,
+ std::set<int64>(),
+ std::string(),
+ std::string(),
+ LogCB()));
+
+ const BlockInfo kBlockInfo[] = {
+ { kVideoTrackNum, 0, 33, true },
+ { kAudioTrackNum, 0, 23, false },
+ { kTextTrackNum, 10, 42, false },
+ { kAudioTrackNum, 23, kTestAudioFrameDefaultDurationInMs, true },
+ { kVideoTrackNum, 33, 33, true },
+ { kAudioTrackNum, 36, kTestAudioFrameDefaultDurationInMs, true },
+ { kVideoTrackNum, 66, 33, true },
+ { kAudioTrackNum, 70, kTestAudioFrameDefaultDurationInMs, true },
+ { kAudioTrackNum, 83, kTestAudioFrameDefaultDurationInMs, true },
+ };
+
+ const int kExpectedBuffersOnPartialCluster[] = {
+ 0, // Video simple block without DefaultDuration should be held back
+ 0, // Audio buffer ready, but not emitted because its TS >= held back video
+ 0, // Text buffer ready, but not emitted because its TS >= held back video
+ 0, // 2nd audio buffer ready, also not emitted for same reason as first
+ 4, // All previous buffers emitted, 2nd video held back with no duration
+ 4, // 2nd video still has no duration, 3rd audio ready but not emitted
+ 6, // All previous buffers emitted, 3rd video held back with no duration
+ 6, // 3rd video still has no duration, 4th audio ready but not emitted
+ 9, // Cluster end emits all buffers and 3rd video's duration is estimated
+ };
+
+ ASSERT_EQ(arraysize(kBlockInfo), arraysize(kExpectedBuffersOnPartialCluster));
+ int block_count = arraysize(kBlockInfo);
+
+ // Iteratively create a cluster containing the first N+1 blocks and parse all
+ // but the last byte of the cluster (except when N==|block_count|, just parse
+ // the whole cluster). Verify that the corresponding entry in
+ // |kExpectedBuffersOnPartialCluster| identifies the exact subset of
+ // |kBlockInfo| returned by the parser.
+ for (int i = 0; i < block_count; ++i) {
+ if (i > 0)
+ parser_->Reset();
+ // Since we don't know exactly the offsets of each block in the full
+ // cluster, build a cluster with exactly one additional block so that
+ // parse of all but one byte should deterministically parse all but the
+ // last full block. Don't |exceed block_count| blocks though.
+ int blocks_in_cluster = std::min(i + 2, block_count);
+ scoped_ptr<Cluster> cluster(CreateCluster(0, kBlockInfo,
+ blocks_in_cluster));
+ // Parse all but the last byte unless we need to parse the full cluster.
+ bool parse_full_cluster = i == (block_count - 1);
+ int result = parser_->Parse(cluster->data(), parse_full_cluster ?
+ cluster->size() : cluster->size() - 1);
+ if (parse_full_cluster) {
+ DVLOG(1) << "Verifying parse result of full cluster of "
+ << blocks_in_cluster << " blocks";
+ EXPECT_EQ(cluster->size(), result);
+ } else {
+ DVLOG(1) << "Verifying parse result of cluster of "
+ << blocks_in_cluster << " blocks with last block incomplete";
+ EXPECT_GT(cluster->size(), result);
+ EXPECT_LT(0, result);
+ }
+
+ EXPECT_TRUE(VerifyBuffers(parser_, kBlockInfo,
+ kExpectedBuffersOnPartialCluster[i]));
+ }
+}
+
+TEST_F(WebMClusterParserTest, Reset) {
+ InSequence s;
+
+ int block_count = arraysize(kDefaultBlockInfo);
+ scoped_ptr<Cluster> cluster(CreateCluster(0, kDefaultBlockInfo, block_count));
+
+ // Send slightly less than the full cluster so all but the last block is
+ // parsed.
+ int result = parser_->Parse(cluster->data(), cluster->size() - 1);
+ EXPECT_GT(result, 0);
+ EXPECT_LT(result, cluster->size());
+
+ ASSERT_TRUE(VerifyBuffers(parser_, kDefaultBlockInfo, block_count - 1));
+ parser_->Reset();
+
+ // Now parse a whole cluster to verify that all the blocks will get parsed.
+ result = parser_->Parse(cluster->data(), cluster->size());
+ EXPECT_EQ(cluster->size(), result);
+ ASSERT_TRUE(VerifyBuffers(parser_, kDefaultBlockInfo, block_count));
+}
+
+TEST_F(WebMClusterParserTest, ParseClusterWithSingleCall) {
+ int block_count = arraysize(kDefaultBlockInfo);
+ scoped_ptr<Cluster> cluster(CreateCluster(0, kDefaultBlockInfo, block_count));
+
+ int result = parser_->Parse(cluster->data(), cluster->size());
+ EXPECT_EQ(cluster->size(), result);
+ ASSERT_TRUE(VerifyBuffers(parser_, kDefaultBlockInfo, block_count));
+}
+
+TEST_F(WebMClusterParserTest, ParseClusterWithMultipleCalls) {
+ int block_count = arraysize(kDefaultBlockInfo);
+ scoped_ptr<Cluster> cluster(CreateCluster(0, kDefaultBlockInfo, block_count));
+
+ WebMClusterParser::BufferQueue audio_buffers;
+ WebMClusterParser::BufferQueue video_buffers;
+ const WebMClusterParser::BufferQueue no_text_buffers;
+
+ const uint8* data = cluster->data();
+ int size = cluster->size();
+ int default_parse_size = 3;
+ int parse_size = std::min(default_parse_size, size);
+
+ while (size > 0) {
+ int result = parser_->Parse(data, parse_size);
+ ASSERT_GE(result, 0);
+ ASSERT_LE(result, parse_size);
+
+ if (result == 0) {
+ // The parser needs more data so increase the parse_size a little.
+ parse_size += default_parse_size;
+ parse_size = std::min(parse_size, size);
+ continue;
+ }
+
+ AppendToEnd(parser_->GetAudioBuffers(), &audio_buffers);
+ AppendToEnd(parser_->GetVideoBuffers(), &video_buffers);
+
+ parse_size = default_parse_size;
+
+ data += result;
+ size -= result;
+ }
+ ASSERT_TRUE(VerifyBuffers(audio_buffers, video_buffers,
+ no_text_buffers, kDefaultBlockInfo,
+ block_count));
+}
+
+// Verify that both BlockGroups with the BlockDuration before the Block
+// and BlockGroups with the BlockDuration after the Block are supported
+// correctly.
+// Note: Raw bytes are use here because ClusterBuilder only generates
+// one of these scenarios.
+TEST_F(WebMClusterParserTest, ParseBlockGroup) {
+ const BlockInfo kBlockInfo[] = {
+ { kAudioTrackNum, 0, 23, false },
+ { kVideoTrackNum, 33, 34, false },
+ };
+ int block_count = arraysize(kBlockInfo);
+
+ const uint8 kClusterData[] = {
+ 0x1F, 0x43, 0xB6, 0x75, 0x9B, // Cluster(size=27)
+ 0xE7, 0x81, 0x00, // Timecode(size=1, value=0)
+ // BlockGroup with BlockDuration before Block.
+ 0xA0, 0x8A, // BlockGroup(size=10)
+ 0x9B, 0x81, 0x17, // BlockDuration(size=1, value=23)
+ 0xA1, 0x85, 0x81, 0x00, 0x00, 0x00, 0xaa, // Block(size=5, track=1, ts=0)
+ // BlockGroup with BlockDuration after Block.
+ 0xA0, 0x8A, // BlockGroup(size=10)
+ 0xA1, 0x85, 0x82, 0x00, 0x21, 0x00, 0x55, // Block(size=5, track=2, ts=33)
+ 0x9B, 0x81, 0x22, // BlockDuration(size=1, value=34)
+ };
+ const int kClusterSize = sizeof(kClusterData);
+
+ int result = parser_->Parse(kClusterData, kClusterSize);
+ EXPECT_EQ(kClusterSize, result);
+ ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count));
+}
+
+TEST_F(WebMClusterParserTest, ParseSimpleBlockAndBlockGroupMixture) {
+ const BlockInfo kBlockInfo[] = {
+ { kAudioTrackNum, 0, 23, true },
+ { kAudioTrackNum, 23, 23, false },
+ { kVideoTrackNum, 33, 34, true },
+ { kAudioTrackNum, 46, 23, false },
+ { kVideoTrackNum, 67, 33, false },
+ };
+ int block_count = arraysize(kBlockInfo);
+ scoped_ptr<Cluster> cluster(CreateCluster(0, kBlockInfo, block_count));
+
+ int result = parser_->Parse(cluster->data(), cluster->size());
+ EXPECT_EQ(cluster->size(), result);
+ ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count));
+}
+
+TEST_F(WebMClusterParserTest, IgnoredTracks) {
+ std::set<int64> ignored_tracks;
+ ignored_tracks.insert(kTextTrackNum);
+
+ parser_.reset(new WebMClusterParser(kTimecodeScale,
+ kAudioTrackNum,
+ kNoTimestamp(),
+ kVideoTrackNum,
+ kNoTimestamp(),
+ TextTracks(),
+ ignored_tracks,
+ std::string(),
+ std::string(),
+ LogCB()));
+
+ const BlockInfo kInputBlockInfo[] = {
+ { kAudioTrackNum, 0, 23, true },
+ { kAudioTrackNum, 23, 23, true },
+ { kVideoTrackNum, 33, 34, true },
+ { kTextTrackNum, 33, 99, true },
+ { kAudioTrackNum, 46, 23, true },
+ { kVideoTrackNum, 67, 34, true },
+ };
+ int input_block_count = arraysize(kInputBlockInfo);
+
+ const BlockInfo kOutputBlockInfo[] = {
+ { kAudioTrackNum, 0, 23, true },
+ { kAudioTrackNum, 23, 23, true },
+ { kVideoTrackNum, 33, 34, true },
+ { kAudioTrackNum, 46, 23, true },
+ { kVideoTrackNum, 67, 34, true },
+ };
+ int output_block_count = arraysize(kOutputBlockInfo);
+
+ scoped_ptr<Cluster> cluster(
+ CreateCluster(0, kInputBlockInfo, input_block_count));
+
+ int result = parser_->Parse(cluster->data(), cluster->size());
+ EXPECT_EQ(cluster->size(), result);
+ ASSERT_TRUE(VerifyBuffers(parser_, kOutputBlockInfo, output_block_count));
+}
+
+TEST_F(WebMClusterParserTest, ParseTextTracks) {
+ TextTracks text_tracks;
+
+ text_tracks.insert(std::make_pair(TextTracks::key_type(kTextTrackNum),
+ TextTrackConfig(kTextSubtitles, "", "",
+ "")));
+
+ parser_.reset(new WebMClusterParser(kTimecodeScale,
+ kAudioTrackNum,
+ kNoTimestamp(),
+ kVideoTrackNum,
+ kNoTimestamp(),
+ text_tracks,
+ std::set<int64>(),
+ std::string(),
+ std::string(),
+ LogCB()));
+
+ const BlockInfo kInputBlockInfo[] = {
+ { kAudioTrackNum, 0, 23, true },
+ { kAudioTrackNum, 23, 23, true },
+ { kVideoTrackNum, 33, 34, true },
+ { kTextTrackNum, 33, 42, false },
+ { kAudioTrackNum, 46, 23, true },
+ { kTextTrackNum, 55, 44, false },
+ { kVideoTrackNum, 67, 34, true },
+ };
+ int input_block_count = arraysize(kInputBlockInfo);
+
+ scoped_ptr<Cluster> cluster(
+ CreateCluster(0, kInputBlockInfo, input_block_count));
+
+ int result = parser_->Parse(cluster->data(), cluster->size());
+ EXPECT_EQ(cluster->size(), result);
+ ASSERT_TRUE(VerifyBuffers(parser_, kInputBlockInfo, input_block_count));
+}
+
+TEST_F(WebMClusterParserTest, TextTracksSimpleBlock) {
+ TextTracks text_tracks;
+
+ text_tracks.insert(std::make_pair(TextTracks::key_type(kTextTrackNum),
+ TextTrackConfig(kTextSubtitles, "", "",
+ "")));
+
+ parser_.reset(new WebMClusterParser(kTimecodeScale,
+ kAudioTrackNum,
+ kNoTimestamp(),
+ kVideoTrackNum,
+ kNoTimestamp(),
+ text_tracks,
+ std::set<int64>(),
+ std::string(),
+ std::string(),
+ LogCB()));
+
+ const BlockInfo kInputBlockInfo[] = {
+ { kTextTrackNum, 33, 42, true },
+ };
+ int input_block_count = arraysize(kInputBlockInfo);
+
+ scoped_ptr<Cluster> cluster(
+ CreateCluster(0, kInputBlockInfo, input_block_count));
+
+ int result = parser_->Parse(cluster->data(), cluster->size());
+ EXPECT_LT(result, 0);
+}
+
+TEST_F(WebMClusterParserTest, ParseMultipleTextTracks) {
+ TextTracks text_tracks;
+
+ const int kSubtitleTextTrackNum = kTextTrackNum;
+ const int kCaptionTextTrackNum = kTextTrackNum + 1;
+
+ text_tracks.insert(std::make_pair(TextTracks::key_type(kSubtitleTextTrackNum),
+ TextTrackConfig(kTextSubtitles, "", "",
+ "")));
+
+ text_tracks.insert(std::make_pair(TextTracks::key_type(kCaptionTextTrackNum),
+ TextTrackConfig(kTextCaptions, "", "",
+ "")));
+
+ parser_.reset(new WebMClusterParser(kTimecodeScale,
+ kAudioTrackNum,
+ kNoTimestamp(),
+ kVideoTrackNum,
+ kNoTimestamp(),
+ text_tracks,
+ std::set<int64>(),
+ std::string(),
+ std::string(),
+ LogCB()));
+
+ const BlockInfo kInputBlockInfo[] = {
+ { kAudioTrackNum, 0, 23, true },
+ { kAudioTrackNum, 23, 23, true },
+ { kVideoTrackNum, 33, 34, true },
+ { kSubtitleTextTrackNum, 33, 42, false },
+ { kAudioTrackNum, 46, 23, true },
+ { kCaptionTextTrackNum, 55, 44, false },
+ { kVideoTrackNum, 67, 34, true },
+ { kSubtitleTextTrackNum, 67, 33, false },
+ };
+ int input_block_count = arraysize(kInputBlockInfo);
+
+ scoped_ptr<Cluster> cluster(
+ CreateCluster(0, kInputBlockInfo, input_block_count));
+
+ int result = parser_->Parse(cluster->data(), cluster->size());
+ EXPECT_EQ(cluster->size(), result);
+
+ const WebMClusterParser::TextBufferQueueMap& text_map =
+ parser_->GetTextBuffers();
+ for (WebMClusterParser::TextBufferQueueMap::const_iterator itr =
+ text_map.begin();
+ itr != text_map.end();
+ ++itr) {
+ const TextTracks::const_iterator find_result =
+ text_tracks.find(itr->first);
+ ASSERT_TRUE(find_result != text_tracks.end());
+ ASSERT_TRUE(VerifyTextBuffers(parser_, kInputBlockInfo, input_block_count,
+ itr->first, itr->second));
+ }
+}
+
+TEST_F(WebMClusterParserTest, ParseEncryptedBlock) {
+ scoped_ptr<Cluster> cluster(CreateEncryptedCluster(sizeof(kEncryptedFrame)));
+
+ parser_.reset(new WebMClusterParser(kTimecodeScale,
+ kAudioTrackNum,
+ kNoTimestamp(),
+ kVideoTrackNum,
+ kNoTimestamp(),
+ TextTracks(),
+ std::set<int64>(),
+ std::string(),
+ "video_key_id",
+ LogCB()));
+ int result = parser_->Parse(cluster->data(), cluster->size());
+ EXPECT_EQ(cluster->size(), result);
+ ASSERT_EQ(1UL, parser_->GetVideoBuffers().size());
+ scoped_refptr<StreamParserBuffer> buffer = parser_->GetVideoBuffers()[0];
+ VerifyEncryptedBuffer(buffer);
+}
+
+TEST_F(WebMClusterParserTest, ParseBadEncryptedBlock) {
+ scoped_ptr<Cluster> cluster(
+ CreateEncryptedCluster(sizeof(kEncryptedFrame) - 1));
+
+ parser_.reset(new WebMClusterParser(kTimecodeScale,
+ kAudioTrackNum,
+ kNoTimestamp(),
+ kVideoTrackNum,
+ kNoTimestamp(),
+ TextTracks(),
+ std::set<int64>(),
+ std::string(),
+ "video_key_id",
+ LogCB()));
+ int result = parser_->Parse(cluster->data(), cluster->size());
+ EXPECT_EQ(-1, result);
+}
+
+TEST_F(WebMClusterParserTest, ParseInvalidZeroSizedCluster) {
+ const uint8 kBuffer[] = {
+ 0x1F, 0x43, 0xB6, 0x75, 0x80, // CLUSTER (size = 0)
+ };
+
+ EXPECT_EQ(-1, parser_->Parse(kBuffer, sizeof(kBuffer)));
+}
+
+TEST_F(WebMClusterParserTest, ParseInvalidUnknownButActuallyZeroSizedCluster) {
+ const uint8 kBuffer[] = {
+ 0x1F, 0x43, 0xB6, 0x75, 0xFF, // CLUSTER (size = "unknown")
+ 0x1F, 0x43, 0xB6, 0x75, 0x85, // CLUSTER (size = 5)
+ };
+
+ EXPECT_EQ(-1, parser_->Parse(kBuffer, sizeof(kBuffer)));
+}
+
+TEST_F(WebMClusterParserTest, ParseInvalidTextBlockGroupWithoutDuration) {
+ // Text track frames must have explicitly specified BlockGroup BlockDurations.
+ TextTracks text_tracks;
+
+ text_tracks.insert(std::make_pair(TextTracks::key_type(kTextTrackNum),
+ TextTrackConfig(kTextSubtitles, "", "",
+ "")));
+
+ parser_.reset(new WebMClusterParser(kTimecodeScale,
+ kAudioTrackNum,
+ kNoTimestamp(),
+ kVideoTrackNum,
+ kNoTimestamp(),
+ text_tracks,
+ std::set<int64>(),
+ std::string(),
+ std::string(),
+ LogCB()));
+
+ const BlockInfo kBlockInfo[] = {
+ { kTextTrackNum, 33, -42, false },
+ };
+ int block_count = arraysize(kBlockInfo);
+ scoped_ptr<Cluster> cluster(CreateCluster(0, kBlockInfo, block_count));
+ int result = parser_->Parse(cluster->data(), cluster->size());
+ EXPECT_LT(result, 0);
+}
+
+TEST_F(WebMClusterParserTest, ParseWithDefaultDurationsSimpleBlocks) {
+ InSequence s;
+ ResetParserToHaveDefaultDurations();
+
+ EXPECT_LT(kTestAudioFrameDefaultDurationInMs, 23);
+ EXPECT_LT(kTestVideoFrameDefaultDurationInMs, 33);
+
+ const BlockInfo kBlockInfo[] = {
+ { kAudioTrackNum, 0, kTestAudioFrameDefaultDurationInMs, true },
+ { kAudioTrackNum, 23, kTestAudioFrameDefaultDurationInMs, true },
+ { kVideoTrackNum, 33, kTestVideoFrameDefaultDurationInMs, true },
+ { kAudioTrackNum, 46, kTestAudioFrameDefaultDurationInMs, true },
+ { kVideoTrackNum, 67, kTestVideoFrameDefaultDurationInMs, true },
+ { kAudioTrackNum, 69, kTestAudioFrameDefaultDurationInMs, true },
+ { kVideoTrackNum, 100, kTestVideoFrameDefaultDurationInMs, true },
+ };
+
+ int block_count = arraysize(kBlockInfo);
+ scoped_ptr<Cluster> cluster(CreateCluster(0, kBlockInfo, block_count));
+
+ // Send slightly less than the full cluster so all but the last block is
+ // parsed. Though all the blocks are simple blocks, none should be held aside
+ // for duration estimation prior to end of cluster detection because all the
+ // tracks have DefaultDurations.
+ int result = parser_->Parse(cluster->data(), cluster->size() - 1);
+ EXPECT_GT(result, 0);
+ EXPECT_LT(result, cluster->size());
+ ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count - 1));
+
+ parser_->Reset();
+
+ // Now parse a whole cluster to verify that all the blocks will get parsed.
+ result = parser_->Parse(cluster->data(), cluster->size());
+ EXPECT_EQ(cluster->size(), result);
+ ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count));
+}
+
+TEST_F(WebMClusterParserTest, ParseWithoutAnyDurationsSimpleBlocks) {
+ InSequence s;
+
+ // Absent DefaultDuration information, SimpleBlock durations are derived from
+ // inter-buffer track timestamp delta if within the cluster, and are estimated
+ // as the lowest non-zero duration seen so far if the last buffer in the track
+ // in the cluster (independently for each track in the cluster).
+ const BlockInfo kBlockInfo1[] = {
+ { kAudioTrackNum, 0, 23, true },
+ { kAudioTrackNum, 23, 22, true },
+ { kVideoTrackNum, 33, 33, true },
+ { kAudioTrackNum, 45, 23, true },
+ { kVideoTrackNum, 66, 34, true },
+ { kAudioTrackNum, 68, 22, true }, // Estimated from minimum audio dur
+ { kVideoTrackNum, 100, 33, true }, // Estimated from minimum video dur
+ };
+
+ int block_count1 = arraysize(kBlockInfo1);
+ scoped_ptr<Cluster> cluster1(CreateCluster(0, kBlockInfo1, block_count1));
+
+ // Send slightly less than the first full cluster so all but the last video
+ // block is parsed. Verify the last fully parsed audio and video buffer are
+ // both missing from the result (parser should hold them aside for duration
+ // estimation prior to end of cluster detection in the absence of
+ // DefaultDurations.)
+ int result = parser_->Parse(cluster1->data(), cluster1->size() - 1);
+ EXPECT_GT(result, 0);
+ EXPECT_LT(result, cluster1->size());
+ ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo1, block_count1 - 3));
+ EXPECT_EQ(3UL, parser_->GetAudioBuffers().size());
+ EXPECT_EQ(1UL, parser_->GetVideoBuffers().size());
+
+ parser_->Reset();
+
+ // Now parse the full first cluster and verify all the blocks are parsed.
+ result = parser_->Parse(cluster1->data(), cluster1->size());
+ EXPECT_EQ(cluster1->size(), result);
+ ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo1, block_count1));
+
+ // Verify that the estimated frame duration is tracked across clusters for
+ // each track.
+ const BlockInfo kBlockInfo2[] = {
+ { kAudioTrackNum, 200, 22, true }, // Estimate carries over across clusters
+ { kVideoTrackNum, 201, 33, true }, // Estimate carries over across clusters
+ };
+
+ int block_count2 = arraysize(kBlockInfo2);
+ scoped_ptr<Cluster> cluster2(CreateCluster(0, kBlockInfo2, block_count2));
+ result = parser_->Parse(cluster2->data(), cluster2->size());
+ EXPECT_EQ(cluster2->size(), result);
+ ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo2, block_count2));
+}
+
+TEST_F(WebMClusterParserTest, ParseWithoutAnyDurationsBlockGroups) {
+ InSequence s;
+
+ // Absent DefaultDuration and BlockDuration information, BlockGroup block
+ // durations are derived from inter-buffer track timestamp delta if within the
+ // cluster, and are estimated as the lowest non-zero duration seen so far if
+ // the last buffer in the track in the cluster (independently for each track
+ // in the cluster).
+ const BlockInfo kBlockInfo1[] = {
+ { kAudioTrackNum, 0, -23, false },
+ { kAudioTrackNum, 23, -22, false },
+ { kVideoTrackNum, 33, -33, false },
+ { kAudioTrackNum, 45, -23, false },
+ { kVideoTrackNum, 66, -34, false },
+ { kAudioTrackNum, 68, -22, false }, // Estimated from minimum audio dur
+ { kVideoTrackNum, 100, -33, false }, // Estimated from minimum video dur
+ };
+
+ int block_count1 = arraysize(kBlockInfo1);
+ scoped_ptr<Cluster> cluster1(CreateCluster(0, kBlockInfo1, block_count1));
+
+ // Send slightly less than the first full cluster so all but the last video
+ // block is parsed. Verify the last fully parsed audio and video buffer are
+ // both missing from the result (parser should hold them aside for duration
+ // estimation prior to end of cluster detection in the absence of
+ // DefaultDurations.)
+ int result = parser_->Parse(cluster1->data(), cluster1->size() - 1);
+ EXPECT_GT(result, 0);
+ EXPECT_LT(result, cluster1->size());
+ ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo1, block_count1 - 3));
+ EXPECT_EQ(3UL, parser_->GetAudioBuffers().size());
+ EXPECT_EQ(1UL, parser_->GetVideoBuffers().size());
+
+ parser_->Reset();
+
+ // Now parse the full first cluster and verify all the blocks are parsed.
+ result = parser_->Parse(cluster1->data(), cluster1->size());
+ EXPECT_EQ(cluster1->size(), result);
+ ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo1, block_count1));
+
+ // Verify that the estimated frame duration is tracked across clusters for
+ // each track.
+ const BlockInfo kBlockInfo2[] = {
+ { kAudioTrackNum, 200, -22, false },
+ { kVideoTrackNum, 201, -33, false },
+ };
+
+ int block_count2 = arraysize(kBlockInfo2);
+ scoped_ptr<Cluster> cluster2(CreateCluster(0, kBlockInfo2, block_count2));
+ result = parser_->Parse(cluster2->data(), cluster2->size());
+ EXPECT_EQ(cluster2->size(), result);
+ ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo2, block_count2));
+}
+
+// TODO(wolenetz): Is parser behavior correct? See http://crbug.com/363433.
+TEST_F(WebMClusterParserTest,
+ ParseWithDefaultDurationsBlockGroupsWithoutDurations) {
+ InSequence s;
+ ResetParserToHaveDefaultDurations();
+
+ EXPECT_LT(kTestAudioFrameDefaultDurationInMs, 23);
+ EXPECT_LT(kTestVideoFrameDefaultDurationInMs, 33);
+
+ const BlockInfo kBlockInfo[] = {
+ { kAudioTrackNum, 0, -kTestAudioFrameDefaultDurationInMs, false },
+ { kAudioTrackNum, 23, -kTestAudioFrameDefaultDurationInMs, false },
+ { kVideoTrackNum, 33, -kTestVideoFrameDefaultDurationInMs, false },
+ { kAudioTrackNum, 46, -kTestAudioFrameDefaultDurationInMs, false },
+ { kVideoTrackNum, 67, -kTestVideoFrameDefaultDurationInMs, false },
+ { kAudioTrackNum, 69, -kTestAudioFrameDefaultDurationInMs, false },
+ { kVideoTrackNum, 100, -kTestVideoFrameDefaultDurationInMs, false },
+ };
+
+ int block_count = arraysize(kBlockInfo);
+ scoped_ptr<Cluster> cluster(CreateCluster(0, kBlockInfo, block_count));
+
+ // Send slightly less than the full cluster so all but the last block is
+ // parsed. None should be held aside for duration estimation prior to end of
+ // cluster detection because all the tracks have DefaultDurations.
+ int result = parser_->Parse(cluster->data(), cluster->size() - 1);
+ EXPECT_GT(result, 0);
+ EXPECT_LT(result, cluster->size());
+ ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count - 1));
+
+ parser_->Reset();
+
+ // Now parse a whole cluster to verify that all the blocks will get parsed.
+ result = parser_->Parse(cluster->data(), cluster->size());
+ EXPECT_EQ(cluster->size(), result);
+ ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count));
+}
+
+TEST_F(WebMClusterParserTest,
+ ParseDegenerateClusterYieldsHardcodedEstimatedDurations) {
+ const BlockInfo kBlockInfo[] = {
+ {
+ kAudioTrackNum,
+ 0,
+ WebMClusterParser::kDefaultAudioBufferDurationInMs,
+ true
+ }, {
+ kVideoTrackNum,
+ 0,
+ WebMClusterParser::kDefaultVideoBufferDurationInMs,
+ true
+ },
+ };
+
+ int block_count = arraysize(kBlockInfo);
+ scoped_ptr<Cluster> cluster(CreateCluster(0, kBlockInfo, block_count));
+ int result = parser_->Parse(cluster->data(), cluster->size());
+ EXPECT_EQ(cluster->size(), result);
+ ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count));
+}
+
+TEST_F(WebMClusterParserTest,
+ ParseDegenerateClusterWithDefaultDurationsYieldsDefaultDurations) {
+ ResetParserToHaveDefaultDurations();
+
+ const BlockInfo kBlockInfo[] = {
+ { kAudioTrackNum, 0, kTestAudioFrameDefaultDurationInMs, true },
+ { kVideoTrackNum, 0, kTestVideoFrameDefaultDurationInMs, true },
+ };
+
+ int block_count = arraysize(kBlockInfo);
+ scoped_ptr<Cluster> cluster(CreateCluster(0, kBlockInfo, block_count));
+ int result = parser_->Parse(cluster->data(), cluster->size());
+ EXPECT_EQ(cluster->size(), result);
+ ASSERT_TRUE(VerifyBuffers(parser_, kBlockInfo, block_count));
+}
+
+} // namespace media
diff --git a/chromium/media/formats/webm/webm_constants.cc b/chromium/media/formats/webm/webm_constants.cc
new file mode 100644
index 00000000000..d6c5536fa3e
--- /dev/null
+++ b/chromium/media/formats/webm/webm_constants.cc
@@ -0,0 +1,14 @@
+// Copyright 2014 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/formats/webm/webm_constants.h"
+
+namespace media {
+
+const char kWebMCodecSubtitles[] = "D_WEBVTT/SUBTITLES";
+const char kWebMCodecCaptions[] = "D_WEBVTT/CAPTIONS";
+const char kWebMCodecDescriptions[] = "D_WEBVTT/DESCRIPTIONS";
+const char kWebMCodecMetadata[] = "D_WEBVTT/METADATA";
+
+} // namespace media
diff --git a/chromium/media/formats/webm/webm_constants.h b/chromium/media/formats/webm/webm_constants.h
new file mode 100644
index 00000000000..8a0b8a7185e
--- /dev/null
+++ b/chromium/media/formats/webm/webm_constants.h
@@ -0,0 +1,229 @@
+// Copyright 2014 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_FORMATS_WEBM_WEBM_CONSTANTS_H_
+#define MEDIA_FORMATS_WEBM_WEBM_CONSTANTS_H_
+
+#include "base/basictypes.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// WebM element IDs.
+// This is a subset of the IDs in the Matroska spec.
+// http://www.matroska.org/technical/specs/index.html
+const int kWebMIdAESSettingsCipherMode = 0x47E8;
+const int kWebMIdAlphaMode = 0x53C0;
+const int kWebMIdAspectRatioType = 0x54B3;
+const int kWebMIdAttachedFile = 0x61A7;
+const int kWebMIdAttachmentLink = 0x7446;
+const int kWebMIdAttachments = 0x1941A469;
+const int kWebMIdAudio = 0xE1;
+const int kWebMIdBitDepth = 0x6264;
+const int kWebMIdBlock = 0xA1;
+const int kWebMIdBlockAddID = 0xEE;
+const int kWebMIdBlockAdditions = 0x75A1;
+const int kWebMIdBlockAdditional = 0xA5;
+const int kWebMIdBlockDuration = 0x9B;
+const int kWebMIdBlockGroup = 0xA0;
+const int kWebMIdBlockMore = 0xA6;
+const int kWebMIdChannels = 0x9F;
+const int kWebMIdChapCountry = 0x437E;
+const int kWebMIdChapLanguage = 0x437C;
+const int kWebMIdChapProcess = 0x6944;
+const int kWebMIdChapProcessCodecID = 0x6955;
+const int kWebMIdChapProcessCommand = 0x6911;
+const int kWebMIdChapProcessData = 0x6933;
+const int kWebMIdChapProcessPrivate = 0x450D;
+const int kWebMIdChapProcessTime = 0x6922;
+const int kWebMIdChapString = 0x85;
+const int kWebMIdChapterAtom = 0xB6;
+const int kWebMIdChapterDisplay = 0x80;
+const int kWebMIdChapterFlagEnabled = 0x4598;
+const int kWebMIdChapterFlagHidden = 0x98;
+const int kWebMIdChapterPhysicalEquiv = 0x63C3;
+const int kWebMIdChapters = 0x1043A770;
+const int kWebMIdChapterSegmentEditionUID = 0x6EBC;
+const int kWebMIdChapterSegmentUID = 0x6E67;
+const int kWebMIdChapterTimeEnd = 0x92;
+const int kWebMIdChapterTimeStart = 0x91;
+const int kWebMIdChapterTrack = 0x8F;
+const int kWebMIdChapterTrackNumber = 0x89;
+const int kWebMIdChapterTranslate = 0x6924;
+const int kWebMIdChapterTranslateCodec = 0x69BF;
+const int kWebMIdChapterTranslateEditionUID = 0x69FC;
+const int kWebMIdChapterTranslateID = 0x69A5;
+const int kWebMIdChapterUID = 0x73C4;
+const int kWebMIdCluster = 0x1F43B675;
+const int kWebMIdCodecDecodeAll = 0xAA;
+const int kWebMIdCodecDelay = 0x56AA;
+const int kWebMIdCodecID = 0x86;
+const int kWebMIdCodecName = 0x258688;
+const int kWebMIdCodecPrivate = 0x63A2;
+const int kWebMIdCodecState = 0xA4;
+const int kWebMIdColorSpace = 0x2EB524;
+const int kWebMIdContentCompAlgo = 0x4254;
+const int kWebMIdContentCompression = 0x5034;
+const int kWebMIdContentCompSettings = 0x4255;
+const int kWebMIdContentEncAESSettings = 0x47E7;
+const int kWebMIdContentEncAlgo = 0x47E1;
+const int kWebMIdContentEncKeyID = 0x47E2;
+const int kWebMIdContentEncoding = 0x6240;
+const int kWebMIdContentEncodingOrder = 0x5031;
+const int kWebMIdContentEncodings = 0x6D80;
+const int kWebMIdContentEncodingScope = 0x5032;
+const int kWebMIdContentEncodingType = 0x5033;
+const int kWebMIdContentEncryption = 0x5035;
+const int kWebMIdContentSigAlgo = 0x47E5;
+const int kWebMIdContentSigHashAlgo = 0x47E6;
+const int kWebMIdContentSigKeyID = 0x47E4;
+const int kWebMIdContentSignature = 0x47E3;
+const int kWebMIdCRC32 = 0xBF;
+const int kWebMIdCueBlockNumber = 0x5378;
+const int kWebMIdCueClusterPosition = 0xF1;
+const int kWebMIdCueCodecState = 0xEA;
+const int kWebMIdCuePoint = 0xBB;
+const int kWebMIdCueReference = 0xDB;
+const int kWebMIdCueRefTime = 0x96;
+const int kWebMIdCues = 0x1C53BB6B;
+const int kWebMIdCueTime = 0xB3;
+const int kWebMIdCueTrack = 0xF7;
+const int kWebMIdCueTrackPositions = 0xB7;
+const int kWebMIdDateUTC = 0x4461;
+const int kWebMIdDefaultDuration = 0x23E383;
+const int kWebMIdDiscardPadding = 0x75A2;
+const int kWebMIdDisplayHeight = 0x54BA;
+const int kWebMIdDisplayUnit = 0x54B2;
+const int kWebMIdDisplayWidth = 0x54B0;
+const int kWebMIdDocType = 0x4282;
+const int kWebMIdDocTypeReadVersion = 0x4285;
+const int kWebMIdDocTypeVersion = 0x4287;
+const int kWebMIdDuration = 0x4489;
+const int kWebMIdEBMLHeader = 0x1A45DFA3;
+const int kWebMIdEBMLMaxIDLength = 0x42F2;
+const int kWebMIdEBMLMaxSizeLength = 0x42F3;
+const int kWebMIdEBMLReadVersion = 0x42F7;
+const int kWebMIdEBMLVersion = 0x4286;
+const int kWebMIdEditionEntry = 0x45B9;
+const int kWebMIdEditionFlagDefault = 0x45DB;
+const int kWebMIdEditionFlagHidden = 0x45BD;
+const int kWebMIdEditionFlagOrdered = 0x45DD;
+const int kWebMIdEditionUID = 0x45BC;
+const int kWebMIdFileData = 0x465C;
+const int kWebMIdFileDescription = 0x467E;
+const int kWebMIdFileMimeType = 0x4660;
+const int kWebMIdFileName = 0x466E;
+const int kWebMIdFileUID = 0x46AE;
+const int kWebMIdFlagDefault = 0x88;
+const int kWebMIdFlagEnabled = 0xB9;
+const int kWebMIdFlagForced = 0x55AA;
+const int kWebMIdFlagInterlaced = 0x9A;
+const int kWebMIdFlagLacing = 0x9C;
+const int kWebMIdFrameRate = 0x2383E3;
+const int kWebMIdInfo = 0x1549A966;
+const int kWebMIdJoinBlocks = 0xE9;
+const int kWebMIdLaceNumber = 0xCC;
+const int kWebMIdLanguage = 0x22B59C;
+const int kWebMIdMaxBlockAdditionId = 0x55EE;
+const int kWebMIdMaxCache = 0x6DF8;
+const int kWebMIdMinCache = 0x6DE7;
+const int kWebMIdMuxingApp = 0x4D80;
+const int kWebMIdName = 0x536E;
+const int kWebMIdNextFilename = 0x3E83BB;
+const int kWebMIdNextUID = 0x3EB923;
+const int kWebMIdOutputSamplingFrequency = 0x78B5;
+const int kWebMIdPixelCropBottom = 0x54AA;
+const int kWebMIdPixelCropLeft = 0x54CC;
+const int kWebMIdPixelCropRight = 0x54DD;
+const int kWebMIdPixelCropTop = 0x54BB;
+const int kWebMIdPixelHeight = 0xBA;
+const int kWebMIdPixelWidth = 0xB0;
+const int kWebMIdPosition = 0xA7;
+const int kWebMIdPrevFilename = 0x3C83AB;
+const int kWebMIdPrevSize = 0xAB;
+const int kWebMIdPrevUID = 0x3CB923;
+const int kWebMIdReferenceBlock = 0xFB;
+const int kWebMIdReferencePriority = 0xFA;
+const int kWebMIdSamplingFrequency = 0xB5;
+const int kWebMIdSeek = 0x4DBB;
+const int kWebMIdSeekHead = 0x114D9B74;
+const int kWebMIdSeekID = 0x53AB;
+const int kWebMIdSeekPosition = 0x53AC;
+const int kWebMIdSeekPreRoll = 0x56BB;
+const int kWebMIdSegment = 0x18538067;
+const int kWebMIdSegmentFamily = 0x4444;
+const int kWebMIdSegmentFilename = 0x7384;
+const int kWebMIdSegmentUID = 0x73A4;
+const int kWebMIdSilentTrackNumber = 0x58D7;
+const int kWebMIdSilentTracks = 0x5854;
+const int kWebMIdSimpleBlock = 0xA3;
+const int kWebMIdSimpleTag = 0x67C8;
+const int kWebMIdSlices = 0x8E;
+const int kWebMIdStereoMode = 0x53B8;
+const int kWebMIdTag = 0x7373;
+const int kWebMIdTagAttachmentUID = 0x63C6;
+const int kWebMIdTagBinary = 0x4485;
+const int kWebMIdTagChapterUID = 0x63C4;
+const int kWebMIdTagDefault = 0x4484;
+const int kWebMIdTagEditionUID = 0x63C9;
+const int kWebMIdTagLanguage = 0x447A;
+const int kWebMIdTagName = 0x45A3;
+const int kWebMIdTags = 0x1254C367;
+const int kWebMIdTagString = 0x4487;
+const int kWebMIdTagTrackUID = 0x63C5;
+const int kWebMIdTargets = 0x63C0;
+const int kWebMIdTargetType = 0x63CA;
+const int kWebMIdTargetTypeValue = 0x68CA;
+const int kWebMIdTimecode = 0xE7;
+const int kWebMIdTimecodeScale = 0x2AD7B1;
+const int kWebMIdTimeSlice = 0xE8;
+const int kWebMIdTitle = 0x7BA9;
+const int kWebMIdTrackCombinePlanes = 0xE3;
+const int kWebMIdTrackEntry = 0xAE;
+const int kWebMIdTrackJoinUID = 0xED;
+const int kWebMIdTrackNumber = 0xD7;
+const int kWebMIdTrackOperation = 0xE2;
+const int kWebMIdTrackOverlay = 0x6FAB;
+const int kWebMIdTrackPlane = 0xE4;
+const int kWebMIdTrackPlaneType = 0xE6;
+const int kWebMIdTrackPlaneUID = 0xE5;
+const int kWebMIdTracks = 0x1654AE6B;
+const int kWebMIdTrackTimecodeScale = 0x23314F;
+const int kWebMIdTrackTranslate = 0x6624;
+const int kWebMIdTrackTranslateCodec = 0x66BF;
+const int kWebMIdTrackTranslateEditionUID = 0x66FC;
+const int kWebMIdTrackTranslateTrackID = 0x66A5;
+const int kWebMIdTrackType = 0x83;
+const int kWebMIdTrackUID = 0x73C5;
+const int kWebMIdVideo = 0xE0;
+const int kWebMIdVoid = 0xEC;
+const int kWebMIdWritingApp = 0x5741;
+
+const int64 kWebMReservedId = 0x1FFFFFFF;
+const int64 kWebMUnknownSize = 0x00FFFFFFFFFFFFFFLL;
+
+const uint8 kWebMFlagKeyframe = 0x80;
+
+// Current encrypted WebM request for comments specification is here
+// http://wiki.webmproject.org/encryption/webm-encryption-rfc
+const uint8 kWebMFlagEncryptedFrame = 0x1;
+const int kWebMIvSize = 8;
+const int kWebMSignalByteSize = 1;
+
+// Current specification for WebVTT embedded in WebM
+// http://wiki.webmproject.org/webm-metadata/temporal-metadata/webvtt-in-webm
+
+const int kWebMTrackTypeVideo = 1;
+const int kWebMTrackTypeAudio = 2;
+const int kWebMTrackTypeSubtitlesOrCaptions = 0x11;
+const int kWebMTrackTypeDescriptionsOrMetadata = 0x21;
+
+MEDIA_EXPORT extern const char kWebMCodecSubtitles[];
+MEDIA_EXPORT extern const char kWebMCodecCaptions[];
+MEDIA_EXPORT extern const char kWebMCodecDescriptions[];
+MEDIA_EXPORT extern const char kWebMCodecMetadata[];
+
+} // namespace media
+
+#endif // MEDIA_FORMATS_WEBM_WEBM_CONSTANTS_H_
diff --git a/chromium/media/formats/webm/webm_content_encodings.cc b/chromium/media/formats/webm/webm_content_encodings.cc
new file mode 100644
index 00000000000..157c6ac4311
--- /dev/null
+++ b/chromium/media/formats/webm/webm_content_encodings.cc
@@ -0,0 +1,28 @@
+// Copyright 2014 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/logging.h"
+#include "media/formats/webm/webm_content_encodings.h"
+
+namespace media {
+
+ContentEncoding::ContentEncoding()
+ : order_(kOrderInvalid),
+ scope_(kScopeInvalid),
+ type_(kTypeInvalid),
+ encryption_algo_(kEncAlgoInvalid),
+ cipher_mode_(kCipherModeInvalid) {
+}
+
+ContentEncoding::~ContentEncoding() {}
+
+void ContentEncoding::SetEncryptionKeyId(const uint8* encryption_key_id,
+ int size) {
+ DCHECK(encryption_key_id);
+ DCHECK_GT(size, 0);
+ encryption_key_id_.assign(reinterpret_cast<const char*>(encryption_key_id),
+ size);
+}
+
+} // namespace media
diff --git a/chromium/media/formats/webm/webm_content_encodings.h b/chromium/media/formats/webm/webm_content_encodings.h
new file mode 100644
index 00000000000..5890ecf04f3
--- /dev/null
+++ b/chromium/media/formats/webm/webm_content_encodings.h
@@ -0,0 +1,88 @@
+// Copyright 2014 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_FORMATS_WEBM_WEBM_CONTENT_ENCODINGS_H_
+#define MEDIA_FORMATS_WEBM_WEBM_CONTENT_ENCODINGS_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+class MEDIA_EXPORT ContentEncoding {
+ public:
+ // The following enum definitions are based on the ContentEncoding element
+ // specified in the Matroska spec.
+
+ static const int kOrderInvalid = -1;
+
+ enum Scope {
+ kScopeInvalid = 0,
+ kScopeAllFrameContents = 1,
+ kScopeTrackPrivateData = 2,
+ kScopeNextContentEncodingData = 4,
+ kScopeMax = 7,
+ };
+
+ enum Type {
+ kTypeInvalid = -1,
+ kTypeCompression = 0,
+ kTypeEncryption = 1,
+ };
+
+ enum EncryptionAlgo {
+ kEncAlgoInvalid = -1,
+ kEncAlgoNotEncrypted = 0,
+ kEncAlgoDes = 1,
+ kEncAlgo3des = 2,
+ kEncAlgoTwofish = 3,
+ kEncAlgoBlowfish = 4,
+ kEncAlgoAes = 5,
+ };
+
+ enum CipherMode {
+ kCipherModeInvalid = 0,
+ kCipherModeCtr = 1,
+ };
+
+ ContentEncoding();
+ ~ContentEncoding();
+
+ int64 order() const { return order_; }
+ void set_order(int64 order) { order_ = order; }
+
+ Scope scope() const { return scope_; }
+ void set_scope(Scope scope) { scope_ = scope; }
+
+ Type type() const { return type_; }
+ void set_type(Type type) { type_ = type; }
+
+ EncryptionAlgo encryption_algo() const { return encryption_algo_; }
+ void set_encryption_algo(EncryptionAlgo encryption_algo) {
+ encryption_algo_ = encryption_algo;
+ }
+
+ const std::string& encryption_key_id() const { return encryption_key_id_; }
+ void SetEncryptionKeyId(const uint8* encryption_key_id, int size);
+
+ CipherMode cipher_mode() const { return cipher_mode_; }
+ void set_cipher_mode(CipherMode mode) { cipher_mode_ = mode; }
+
+ private:
+ int64 order_;
+ Scope scope_;
+ Type type_;
+ EncryptionAlgo encryption_algo_;
+ std::string encryption_key_id_;
+ CipherMode cipher_mode_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContentEncoding);
+};
+
+} // namespace media
+
+#endif // MEDIA_FORMATS_WEBM_WEBM_CONTENT_ENCODINGS_H_
diff --git a/chromium/media/formats/webm/webm_content_encodings_client.cc b/chromium/media/formats/webm/webm_content_encodings_client.cc
new file mode 100644
index 00000000000..f2294de7024
--- /dev/null
+++ b/chromium/media/formats/webm/webm_content_encodings_client.cc
@@ -0,0 +1,265 @@
+// Copyright 2014 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/formats/webm/webm_content_encodings_client.h"
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "media/formats/webm/webm_constants.h"
+
+namespace media {
+
+WebMContentEncodingsClient::WebMContentEncodingsClient(const LogCB& log_cb)
+ : log_cb_(log_cb),
+ content_encryption_encountered_(false),
+ content_encodings_ready_(false) {
+}
+
+WebMContentEncodingsClient::~WebMContentEncodingsClient() {
+ STLDeleteElements(&content_encodings_);
+}
+
+const ContentEncodings& WebMContentEncodingsClient::content_encodings() const {
+ DCHECK(content_encodings_ready_);
+ return content_encodings_;
+}
+
+WebMParserClient* WebMContentEncodingsClient::OnListStart(int id) {
+ if (id == kWebMIdContentEncodings) {
+ DCHECK(!cur_content_encoding_.get());
+ DCHECK(!content_encryption_encountered_);
+ STLDeleteElements(&content_encodings_);
+ content_encodings_ready_ = false;
+ return this;
+ }
+
+ if (id == kWebMIdContentEncoding) {
+ DCHECK(!cur_content_encoding_.get());
+ DCHECK(!content_encryption_encountered_);
+ cur_content_encoding_.reset(new ContentEncoding());
+ return this;
+ }
+
+ if (id == kWebMIdContentEncryption) {
+ DCHECK(cur_content_encoding_.get());
+ if (content_encryption_encountered_) {
+ MEDIA_LOG(log_cb_) << "Unexpected multiple ContentEncryption.";
+ return NULL;
+ }
+ content_encryption_encountered_ = true;
+ return this;
+ }
+
+ if (id == kWebMIdContentEncAESSettings) {
+ DCHECK(cur_content_encoding_.get());
+ return this;
+ }
+
+ // This should not happen if WebMListParser is working properly.
+ DCHECK(false);
+ return NULL;
+}
+
+// Mandatory occurrence restriction is checked in this function. Multiple
+// occurrence restriction is checked in OnUInt and OnBinary.
+bool WebMContentEncodingsClient::OnListEnd(int id) {
+ if (id == kWebMIdContentEncodings) {
+ // ContentEncoding element is mandatory. Check this!
+ if (content_encodings_.empty()) {
+ MEDIA_LOG(log_cb_) << "Missing ContentEncoding.";
+ return false;
+ }
+ content_encodings_ready_ = true;
+ return true;
+ }
+
+ if (id == kWebMIdContentEncoding) {
+ DCHECK(cur_content_encoding_.get());
+
+ //
+ // Specify default values to missing mandatory elements.
+ //
+
+ if (cur_content_encoding_->order() == ContentEncoding::kOrderInvalid) {
+ // Default value of encoding order is 0, which should only be used on the
+ // first ContentEncoding.
+ if (!content_encodings_.empty()) {
+ MEDIA_LOG(log_cb_) << "Missing ContentEncodingOrder.";
+ return false;
+ }
+ cur_content_encoding_->set_order(0);
+ }
+
+ if (cur_content_encoding_->scope() == ContentEncoding::kScopeInvalid)
+ cur_content_encoding_->set_scope(ContentEncoding::kScopeAllFrameContents);
+
+ if (cur_content_encoding_->type() == ContentEncoding::kTypeInvalid)
+ cur_content_encoding_->set_type(ContentEncoding::kTypeCompression);
+
+ // Check for elements valid in spec but not supported for now.
+ if (cur_content_encoding_->type() == ContentEncoding::kTypeCompression) {
+ MEDIA_LOG(log_cb_) << "ContentCompression not supported.";
+ return false;
+ }
+
+ // Enforce mandatory elements without default values.
+ DCHECK(cur_content_encoding_->type() == ContentEncoding::kTypeEncryption);
+ if (!content_encryption_encountered_) {
+ MEDIA_LOG(log_cb_) << "ContentEncodingType is encryption but"
+ << " ContentEncryption is missing.";
+ return false;
+ }
+
+ content_encodings_.push_back(cur_content_encoding_.release());
+ content_encryption_encountered_ = false;
+ return true;
+ }
+
+ if (id == kWebMIdContentEncryption) {
+ DCHECK(cur_content_encoding_.get());
+ // Specify default value for elements that are not present.
+ if (cur_content_encoding_->encryption_algo() ==
+ ContentEncoding::kEncAlgoInvalid) {
+ cur_content_encoding_->set_encryption_algo(
+ ContentEncoding::kEncAlgoNotEncrypted);
+ }
+ return true;
+ }
+
+ if (id == kWebMIdContentEncAESSettings) {
+ if (cur_content_encoding_->cipher_mode() ==
+ ContentEncoding::kCipherModeInvalid)
+ cur_content_encoding_->set_cipher_mode(ContentEncoding::kCipherModeCtr);
+ return true;
+ }
+
+ // This should not happen if WebMListParser is working properly.
+ DCHECK(false);
+ return false;
+}
+
+// Multiple occurrence restriction and range are checked in this function.
+// Mandatory occurrence restriction is checked in OnListEnd.
+bool WebMContentEncodingsClient::OnUInt(int id, int64 val) {
+ DCHECK(cur_content_encoding_.get());
+
+ if (id == kWebMIdContentEncodingOrder) {
+ if (cur_content_encoding_->order() != ContentEncoding::kOrderInvalid) {
+ MEDIA_LOG(log_cb_) << "Unexpected multiple ContentEncodingOrder.";
+ return false;
+ }
+
+ if (val != static_cast<int64>(content_encodings_.size())) {
+ // According to the spec, encoding order starts with 0 and counts upwards.
+ MEDIA_LOG(log_cb_) << "Unexpected ContentEncodingOrder.";
+ return false;
+ }
+
+ cur_content_encoding_->set_order(val);
+ return true;
+ }
+
+ if (id == kWebMIdContentEncodingScope) {
+ if (cur_content_encoding_->scope() != ContentEncoding::kScopeInvalid) {
+ MEDIA_LOG(log_cb_) << "Unexpected multiple ContentEncodingScope.";
+ return false;
+ }
+
+ if (val == ContentEncoding::kScopeInvalid ||
+ val > ContentEncoding::kScopeMax) {
+ MEDIA_LOG(log_cb_) << "Unexpected ContentEncodingScope.";
+ return false;
+ }
+
+ if (val & ContentEncoding::kScopeNextContentEncodingData) {
+ MEDIA_LOG(log_cb_) << "Encoded next ContentEncoding is not supported.";
+ return false;
+ }
+
+ cur_content_encoding_->set_scope(static_cast<ContentEncoding::Scope>(val));
+ return true;
+ }
+
+ if (id == kWebMIdContentEncodingType) {
+ if (cur_content_encoding_->type() != ContentEncoding::kTypeInvalid) {
+ MEDIA_LOG(log_cb_) << "Unexpected multiple ContentEncodingType.";
+ return false;
+ }
+
+ if (val == ContentEncoding::kTypeCompression) {
+ MEDIA_LOG(log_cb_) << "ContentCompression not supported.";
+ return false;
+ }
+
+ if (val != ContentEncoding::kTypeEncryption) {
+ MEDIA_LOG(log_cb_) << "Unexpected ContentEncodingType " << val << ".";
+ return false;
+ }
+
+ cur_content_encoding_->set_type(static_cast<ContentEncoding::Type>(val));
+ return true;
+ }
+
+ if (id == kWebMIdContentEncAlgo) {
+ if (cur_content_encoding_->encryption_algo() !=
+ ContentEncoding::kEncAlgoInvalid) {
+ MEDIA_LOG(log_cb_) << "Unexpected multiple ContentEncAlgo.";
+ return false;
+ }
+
+ if (val < ContentEncoding::kEncAlgoNotEncrypted ||
+ val > ContentEncoding::kEncAlgoAes) {
+ MEDIA_LOG(log_cb_) << "Unexpected ContentEncAlgo " << val << ".";
+ return false;
+ }
+
+ cur_content_encoding_->set_encryption_algo(
+ static_cast<ContentEncoding::EncryptionAlgo>(val));
+ return true;
+ }
+
+ if (id == kWebMIdAESSettingsCipherMode) {
+ if (cur_content_encoding_->cipher_mode() !=
+ ContentEncoding::kCipherModeInvalid) {
+ MEDIA_LOG(log_cb_) << "Unexpected multiple AESSettingsCipherMode.";
+ return false;
+ }
+
+ if (val != ContentEncoding::kCipherModeCtr) {
+ MEDIA_LOG(log_cb_) << "Unexpected AESSettingsCipherMode " << val << ".";
+ return false;
+ }
+
+ cur_content_encoding_->set_cipher_mode(
+ static_cast<ContentEncoding::CipherMode>(val));
+ return true;
+ }
+
+ // This should not happen if WebMListParser is working properly.
+ DCHECK(false);
+ return false;
+}
+
+// Multiple occurrence restriction is checked in this function. Mandatory
+// restriction is checked in OnListEnd.
+bool WebMContentEncodingsClient::OnBinary(int id, const uint8* data, int size) {
+ DCHECK(cur_content_encoding_.get());
+ DCHECK(data);
+ DCHECK_GT(size, 0);
+
+ if (id == kWebMIdContentEncKeyID) {
+ if (!cur_content_encoding_->encryption_key_id().empty()) {
+ MEDIA_LOG(log_cb_) << "Unexpected multiple ContentEncKeyID";
+ return false;
+ }
+ cur_content_encoding_->SetEncryptionKeyId(data, size);
+ return true;
+ }
+
+ // This should not happen if WebMListParser is working properly.
+ DCHECK(false);
+ return false;
+}
+
+} // namespace media
diff --git a/chromium/media/formats/webm/webm_content_encodings_client.h b/chromium/media/formats/webm/webm_content_encodings_client.h
new file mode 100644
index 00000000000..d00281ec569
--- /dev/null
+++ b/chromium/media/formats/webm/webm_content_encodings_client.h
@@ -0,0 +1,50 @@
+// Copyright 2014 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_FORMATS_WEBM_WEBM_CONTENT_ENCODINGS_CLIENT_H_
+#define MEDIA_FORMATS_WEBM_WEBM_CONTENT_ENCODINGS_CLIENT_H_
+
+#include <vector>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/base/media_export.h"
+#include "media/base/media_log.h"
+#include "media/formats/webm/webm_content_encodings.h"
+#include "media/formats/webm/webm_parser.h"
+
+namespace media {
+
+typedef std::vector<ContentEncoding*> ContentEncodings;
+
+// Parser for WebM ContentEncodings element.
+class MEDIA_EXPORT WebMContentEncodingsClient : public WebMParserClient {
+ public:
+ explicit WebMContentEncodingsClient(const LogCB& log_cb);
+ virtual ~WebMContentEncodingsClient();
+
+ const ContentEncodings& content_encodings() const;
+
+ // WebMParserClient methods
+ virtual WebMParserClient* OnListStart(int id) OVERRIDE;
+ virtual bool OnListEnd(int id) OVERRIDE;
+ virtual bool OnUInt(int id, int64 val) OVERRIDE;
+ virtual bool OnBinary(int id, const uint8* data, int size) OVERRIDE;
+
+ private:
+ LogCB log_cb_;
+ scoped_ptr<ContentEncoding> cur_content_encoding_;
+ bool content_encryption_encountered_;
+ ContentEncodings content_encodings_;
+
+ // |content_encodings_| is ready. For debugging purpose.
+ bool content_encodings_ready_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebMContentEncodingsClient);
+};
+
+} // namespace media
+
+#endif // MEDIA_FORMATS_WEBM_WEBM_CONTENT_ENCODINGS_CLIENT_H_
diff --git a/chromium/media/formats/webm/webm_content_encodings_client_unittest.cc b/chromium/media/formats/webm/webm_content_encodings_client_unittest.cc
new file mode 100644
index 00000000000..e124f2d883a
--- /dev/null
+++ b/chromium/media/formats/webm/webm_content_encodings_client_unittest.cc
@@ -0,0 +1,238 @@
+// Copyright 2014 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 "media/formats/webm/webm_constants.h"
+#include "media/formats/webm/webm_content_encodings_client.h"
+#include "media/formats/webm/webm_parser.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+class WebMContentEncodingsClientTest : public testing::Test {
+ public:
+ WebMContentEncodingsClientTest()
+ : client_(LogCB()),
+ parser_(kWebMIdContentEncodings, &client_) {}
+
+ void ParseAndExpectToFail(const uint8* buf, int size) {
+ int result = parser_.Parse(buf, size);
+ EXPECT_EQ(-1, result);
+ }
+
+ protected:
+ WebMContentEncodingsClient client_;
+ WebMListParser parser_;
+};
+
+TEST_F(WebMContentEncodingsClientTest, EmptyContentEncodings) {
+ const uint8 kContentEncodings[] = {
+ 0x6D, 0x80, 0x80, // ContentEncodings (size = 0)
+ };
+ int size = sizeof(kContentEncodings);
+ ParseAndExpectToFail(kContentEncodings, size);
+}
+
+TEST_F(WebMContentEncodingsClientTest, EmptyContentEncoding) {
+ const uint8 kContentEncodings[] = {
+ 0x6D, 0x80, 0x83, // ContentEncodings (size = 3)
+ 0x63, 0x40, 0x80, // ContentEncoding (size = 0)
+ };
+ int size = sizeof(kContentEncodings);
+ ParseAndExpectToFail(kContentEncodings, size);
+}
+
+TEST_F(WebMContentEncodingsClientTest, SingleContentEncoding) {
+ const uint8 kContentEncodings[] = {
+ 0x6D, 0x80, 0xA1, // ContentEncodings (size = 33)
+ 0x62, 0x40, 0x9e, // ContentEncoding (size = 30)
+ 0x50, 0x31, 0x81, 0x00, // ContentEncodingOrder (size = 1)
+ 0x50, 0x32, 0x81, 0x01, // ContentEncodingScope (size = 1)
+ 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1)
+ 0x50, 0x35, 0x8F, // ContentEncryption (size = 15)
+ 0x47, 0xE1, 0x81, 0x05, // ContentEncAlgo (size = 1)
+ 0x47, 0xE2, 0x88, // ContentEncKeyID (size = 8)
+ 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+ };
+ int size = sizeof(kContentEncodings);
+
+ int result = parser_.Parse(kContentEncodings, size);
+ ASSERT_EQ(size, result);
+ const ContentEncodings& content_encodings = client_.content_encodings();
+
+ ASSERT_EQ(1u, content_encodings.size());
+ ASSERT_TRUE(content_encodings[0]);
+ EXPECT_EQ(0, content_encodings[0]->order());
+ EXPECT_EQ(ContentEncoding::kScopeAllFrameContents,
+ content_encodings[0]->scope());
+ EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[0]->type());
+ EXPECT_EQ(ContentEncoding::kEncAlgoAes,
+ content_encodings[0]->encryption_algo());
+ EXPECT_EQ(8u, content_encodings[0]->encryption_key_id().size());
+}
+
+TEST_F(WebMContentEncodingsClientTest, MultipleContentEncoding) {
+ const uint8 kContentEncodings[] = {
+ 0x6D, 0x80, 0xC2, // ContentEncodings (size = 66)
+ 0x62, 0x40, 0x9e, // ContentEncoding (size = 30)
+ 0x50, 0x31, 0x81, 0x00, // ContentEncodingOrder (size = 1)
+ 0x50, 0x32, 0x81, 0x03, // ContentEncodingScope (size = 1)
+ 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1)
+ 0x50, 0x35, 0x8F, // ContentEncryption (size = 15)
+ 0x47, 0xE1, 0x81, 0x05, // ContentEncAlgo (size = 1)
+ 0x47, 0xE2, 0x88, // ContentEncKeyID (size = 8)
+ 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+ 0x62, 0x40, 0x9e, // ContentEncoding (size = 30)
+ 0x50, 0x31, 0x81, 0x01, // ContentEncodingOrder (size = 1)
+ 0x50, 0x32, 0x81, 0x03, // ContentEncodingScope (size = 1)
+ 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1)
+ 0x50, 0x35, 0x8F, // ContentEncryption (size = 15)
+ 0x47, 0xE1, 0x81, 0x01, // ContentEncAlgo (size = 1)
+ 0x47, 0xE2, 0x88, // ContentEncKeyID (size = 8)
+ 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
+ };
+ int size = sizeof(kContentEncodings);
+
+ int result = parser_.Parse(kContentEncodings, size);
+ ASSERT_EQ(size, result);
+ const ContentEncodings& content_encodings = client_.content_encodings();
+ ASSERT_EQ(2u, content_encodings.size());
+
+ for (int i = 0; i < 2; ++i) {
+ ASSERT_TRUE(content_encodings[i]);
+ EXPECT_EQ(i, content_encodings[i]->order());
+ EXPECT_EQ(ContentEncoding::kScopeAllFrameContents |
+ ContentEncoding::kScopeTrackPrivateData,
+ content_encodings[i]->scope());
+ EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[i]->type());
+ EXPECT_EQ(!i ? ContentEncoding::kEncAlgoAes : ContentEncoding::kEncAlgoDes,
+ content_encodings[i]->encryption_algo());
+ EXPECT_EQ(8u, content_encodings[i]->encryption_key_id().size());
+ }
+}
+
+TEST_F(WebMContentEncodingsClientTest, DefaultValues) {
+ const uint8 kContentEncodings[] = {
+ 0x6D, 0x80, 0x8A, // ContentEncodings (size = 10)
+ 0x62, 0x40, 0x87, // ContentEncoding (size = 7)
+ // ContentEncodingOrder missing
+ // ContentEncodingScope missing
+ 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1)
+ 0x50, 0x35, 0x80, // ContentEncryption (size = 0)
+ // ContentEncAlgo missing
+ };
+ int size = sizeof(kContentEncodings);
+
+ int result = parser_.Parse(kContentEncodings, size);
+ ASSERT_EQ(size, result);
+ const ContentEncodings& content_encodings = client_.content_encodings();
+
+ ASSERT_EQ(1u, content_encodings.size());
+ ASSERT_TRUE(content_encodings[0]);
+ EXPECT_EQ(0, content_encodings[0]->order());
+ EXPECT_EQ(ContentEncoding::kScopeAllFrameContents,
+ content_encodings[0]->scope());
+ EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[0]->type());
+ EXPECT_EQ(ContentEncoding::kEncAlgoNotEncrypted,
+ content_encodings[0]->encryption_algo());
+ EXPECT_TRUE(content_encodings[0]->encryption_key_id().empty());
+}
+
+TEST_F(WebMContentEncodingsClientTest, ContentEncodingsClientReuse) {
+ const uint8 kContentEncodings[] = {
+ 0x6D, 0x80, 0xA1, // ContentEncodings (size = 33)
+ 0x62, 0x40, 0x9e, // ContentEncoding (size = 30)
+ 0x50, 0x31, 0x81, 0x00, // ContentEncodingOrder (size = 1)
+ 0x50, 0x32, 0x81, 0x01, // ContentEncodingScope (size = 1)
+ 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1)
+ 0x50, 0x35, 0x8F, // ContentEncryption (size = 15)
+ 0x47, 0xE1, 0x81, 0x05, // ContentEncAlgo (size = 1)
+ 0x47, 0xE2, 0x88, // ContentEncKeyID (size = 8)
+ 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+ };
+ int size = sizeof(kContentEncodings);
+
+ // Parse for the first time.
+ int result = parser_.Parse(kContentEncodings, size);
+ ASSERT_EQ(size, result);
+
+ // Parse again.
+ parser_.Reset();
+ result = parser_.Parse(kContentEncodings, size);
+ ASSERT_EQ(size, result);
+ const ContentEncodings& content_encodings = client_.content_encodings();
+
+ ASSERT_EQ(1u, content_encodings.size());
+ ASSERT_TRUE(content_encodings[0]);
+ EXPECT_EQ(0, content_encodings[0]->order());
+ EXPECT_EQ(ContentEncoding::kScopeAllFrameContents,
+ content_encodings[0]->scope());
+ EXPECT_EQ(ContentEncoding::kTypeEncryption, content_encodings[0]->type());
+ EXPECT_EQ(ContentEncoding::kEncAlgoAes,
+ content_encodings[0]->encryption_algo());
+ EXPECT_EQ(8u, content_encodings[0]->encryption_key_id().size());
+}
+
+TEST_F(WebMContentEncodingsClientTest, InvalidContentEncodingOrder) {
+ const uint8 kContentEncodings[] = {
+ 0x6D, 0x80, 0x8E, // ContentEncodings (size = 14)
+ 0x62, 0x40, 0x8B, // ContentEncoding (size = 11)
+ 0x50, 0x31, 0x81, 0xEE, // ContentEncodingOrder (size = 1), invalid
+ 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1)
+ 0x50, 0x35, 0x80, // ContentEncryption (size = 0)
+ };
+ int size = sizeof(kContentEncodings);
+ ParseAndExpectToFail(kContentEncodings, size);
+}
+
+TEST_F(WebMContentEncodingsClientTest, InvalidContentEncodingScope) {
+ const uint8 kContentEncodings[] = {
+ 0x6D, 0x80, 0x8E, // ContentEncodings (size = 14)
+ 0x62, 0x40, 0x8B, // ContentEncoding (size = 11)
+ 0x50, 0x32, 0x81, 0xEE, // ContentEncodingScope (size = 1), invalid
+ 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1)
+ 0x50, 0x35, 0x80, // ContentEncryption (size = 0)
+ };
+ int size = sizeof(kContentEncodings);
+ ParseAndExpectToFail(kContentEncodings, size);
+}
+
+TEST_F(WebMContentEncodingsClientTest, InvalidContentEncodingType) {
+ const uint8 kContentEncodings[] = {
+ 0x6D, 0x80, 0x8E, // ContentEncodings (size = 14)
+ 0x62, 0x40, 0x8B, // ContentEncoding (size = 11)
+ 0x50, 0x33, 0x81, 0x00, // ContentEncodingType (size = 1), invalid
+ 0x50, 0x35, 0x80, // ContentEncryption (size = 0)
+ };
+ int size = sizeof(kContentEncodings);
+ ParseAndExpectToFail(kContentEncodings, size);
+}
+
+// ContentEncodingType is encryption but no ContentEncryption present.
+TEST_F(WebMContentEncodingsClientTest, MissingContentEncryption) {
+ const uint8 kContentEncodings[] = {
+ 0x6D, 0x80, 0x87, // ContentEncodings (size = 7)
+ 0x62, 0x40, 0x84, // ContentEncoding (size = 4)
+ 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1)
+ // ContentEncryption missing
+ };
+ int size = sizeof(kContentEncodings);
+ ParseAndExpectToFail(kContentEncodings, size);
+}
+
+TEST_F(WebMContentEncodingsClientTest, InvalidContentEncAlgo) {
+ const uint8 kContentEncodings[] = {
+ 0x6D, 0x80, 0x99, // ContentEncodings (size = 25)
+ 0x62, 0x40, 0x96, // ContentEncoding (size = 22)
+ 0x50, 0x33, 0x81, 0x01, // ContentEncodingType (size = 1)
+ 0x50, 0x35, 0x8F, // ContentEncryption (size = 15)
+ 0x47, 0xE1, 0x81, 0xEE, // ContentEncAlgo (size = 1), invalid
+ 0x47, 0xE2, 0x88, // ContentEncKeyID (size = 8)
+ 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+ };
+ int size = sizeof(kContentEncodings);
+ ParseAndExpectToFail(kContentEncodings, size);
+}
+
+} // namespace media
diff --git a/chromium/media/formats/webm/webm_crypto_helpers.cc b/chromium/media/formats/webm/webm_crypto_helpers.cc
new file mode 100644
index 00000000000..bd473bc23ed
--- /dev/null
+++ b/chromium/media/formats/webm/webm_crypto_helpers.cc
@@ -0,0 +1,62 @@
+// Copyright 2014 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/formats/webm/webm_crypto_helpers.h"
+
+#include "base/logging.h"
+#include "base/sys_byteorder.h"
+#include "media/base/decrypt_config.h"
+#include "media/formats/webm/webm_constants.h"
+
+namespace media {
+namespace {
+
+// Generates a 16 byte CTR counter block. The CTR counter block format is a
+// CTR IV appended with a CTR block counter. |iv| is an 8 byte CTR IV.
+// |iv_size| is the size of |iv| in btyes. Returns a string of
+// kDecryptionKeySize bytes.
+std::string GenerateWebMCounterBlock(const uint8* iv, int iv_size) {
+ std::string counter_block(reinterpret_cast<const char*>(iv), iv_size);
+ counter_block.append(DecryptConfig::kDecryptionKeySize - iv_size, 0);
+ return counter_block;
+}
+
+} // namespace anonymous
+
+bool WebMCreateDecryptConfig(const uint8* data, int data_size,
+ const uint8* key_id, int key_id_size,
+ scoped_ptr<DecryptConfig>* decrypt_config,
+ int* data_offset) {
+ if (data_size < kWebMSignalByteSize) {
+ DVLOG(1) << "Got a block from an encrypted stream with no data.";
+ return false;
+ }
+
+ uint8 signal_byte = data[0];
+ int frame_offset = sizeof(signal_byte);
+
+ // Setting the DecryptConfig object of the buffer while leaving the
+ // initialization vector empty will tell the decryptor that the frame is
+ // unencrypted.
+ std::string counter_block;
+
+ if (signal_byte & kWebMFlagEncryptedFrame) {
+ if (data_size < kWebMSignalByteSize + kWebMIvSize) {
+ DVLOG(1) << "Got an encrypted block with not enough data " << data_size;
+ return false;
+ }
+ counter_block = GenerateWebMCounterBlock(data + frame_offset, kWebMIvSize);
+ frame_offset += kWebMIvSize;
+ }
+
+ decrypt_config->reset(new DecryptConfig(
+ std::string(reinterpret_cast<const char*>(key_id), key_id_size),
+ counter_block,
+ std::vector<SubsampleEntry>()));
+ *data_offset = frame_offset;
+
+ return true;
+}
+
+} // namespace media
diff --git a/chromium/media/formats/webm/webm_crypto_helpers.h b/chromium/media/formats/webm/webm_crypto_helpers.h
new file mode 100644
index 00000000000..23095f31d3d
--- /dev/null
+++ b/chromium/media/formats/webm/webm_crypto_helpers.h
@@ -0,0 +1,33 @@
+// Copyright 2014 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_FORMATS_WEBM_WEBM_CRYPTO_HELPERS_H_
+#define MEDIA_FORMATS_WEBM_WEBM_CRYPTO_HELPERS_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/base/decoder_buffer.h"
+
+namespace media {
+
+// TODO(xhwang): Figure out the init data type appropriately once it's spec'ed.
+// See https://www.w3.org/Bugs/Public/show_bug.cgi?id=19096 for more
+// information.
+const char kWebMEncryptInitDataType[] = "video/webm";
+
+// Fills an initialized DecryptConfig, which can be sent to the Decryptor if
+// the stream has potentially encrypted frames. Also sets |data_offset| which
+// indicates where the encrypted data starts. Leaving the IV empty will tell
+// the decryptor that the frame is unencrypted. Returns true if |data| is valid,
+// false otherwise, in which case |decrypt_config| and |data_offset| will not be
+// changed. Current encrypted WebM request for comments specification is here
+// http://wiki.webmproject.org/encryption/webm-encryption-rfc
+bool WebMCreateDecryptConfig(const uint8* data, int data_size,
+ const uint8* key_id, int key_id_size,
+ scoped_ptr<DecryptConfig>* decrypt_config,
+ int* data_offset);
+
+} // namespace media
+
+#endif // MEDIA_FORMATS_WEBM_WEBM_CRYPT_HELPERS_H_
diff --git a/chromium/media/formats/webm/webm_info_parser.cc b/chromium/media/formats/webm/webm_info_parser.cc
new file mode 100644
index 00000000000..6309c21e9db
--- /dev/null
+++ b/chromium/media/formats/webm/webm_info_parser.cc
@@ -0,0 +1,103 @@
+// Copyright 2014 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/formats/webm/webm_info_parser.h"
+
+#include "base/logging.h"
+#include "media/formats/webm/webm_constants.h"
+
+namespace media {
+
+// Default timecode scale if the TimecodeScale element is
+// not specified in the INFO element.
+static const int kWebMDefaultTimecodeScale = 1000000;
+
+WebMInfoParser::WebMInfoParser()
+ : timecode_scale_(-1),
+ duration_(-1) {
+}
+
+WebMInfoParser::~WebMInfoParser() {}
+
+int WebMInfoParser::Parse(const uint8* buf, int size) {
+ timecode_scale_ = -1;
+ duration_ = -1;
+
+ WebMListParser parser(kWebMIdInfo, this);
+ int result = parser.Parse(buf, size);
+
+ if (result <= 0)
+ return result;
+
+ // For now we do all or nothing parsing.
+ return parser.IsParsingComplete() ? result : 0;
+}
+
+WebMParserClient* WebMInfoParser::OnListStart(int id) { return this; }
+
+bool WebMInfoParser::OnListEnd(int id) {
+ if (id == kWebMIdInfo && timecode_scale_ == -1) {
+ // Set timecode scale to default value if it isn't present in
+ // the Info element.
+ timecode_scale_ = kWebMDefaultTimecodeScale;
+ }
+ return true;
+}
+
+bool WebMInfoParser::OnUInt(int id, int64 val) {
+ if (id != kWebMIdTimecodeScale)
+ return true;
+
+ if (timecode_scale_ != -1) {
+ DVLOG(1) << "Multiple values for id " << std::hex << id << " specified";
+ return false;
+ }
+
+ timecode_scale_ = val;
+ return true;
+}
+
+bool WebMInfoParser::OnFloat(int id, double val) {
+ if (id != kWebMIdDuration) {
+ DVLOG(1) << "Unexpected float for id" << std::hex << id;
+ return false;
+ }
+
+ if (duration_ != -1) {
+ DVLOG(1) << "Multiple values for duration.";
+ return false;
+ }
+
+ duration_ = val;
+ return true;
+}
+
+bool WebMInfoParser::OnBinary(int id, const uint8* data, int size) {
+ if (id == kWebMIdDateUTC) {
+ if (size != 8)
+ return false;
+
+ int64 date_in_nanoseconds = 0;
+ for (int i = 0; i < size; ++i)
+ date_in_nanoseconds = (date_in_nanoseconds << 8) | data[i];
+
+ base::Time::Exploded exploded_epoch;
+ exploded_epoch.year = 2001;
+ exploded_epoch.month = 1;
+ exploded_epoch.day_of_month = 1;
+ exploded_epoch.hour = 0;
+ exploded_epoch.minute = 0;
+ exploded_epoch.second = 0;
+ exploded_epoch.millisecond = 0;
+ date_utc_ = base::Time::FromUTCExploded(exploded_epoch) +
+ base::TimeDelta::FromMicroseconds(date_in_nanoseconds / 1000);
+ }
+ return true;
+}
+
+bool WebMInfoParser::OnString(int id, const std::string& str) {
+ return true;
+}
+
+} // namespace media
diff --git a/chromium/media/formats/webm/webm_info_parser.h b/chromium/media/formats/webm/webm_info_parser.h
new file mode 100644
index 00000000000..36aac928f90
--- /dev/null
+++ b/chromium/media/formats/webm/webm_info_parser.h
@@ -0,0 +1,50 @@
+// Copyright 2014 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_FORMATS_WEBM_WEBM_INFO_PARSER_H_
+#define MEDIA_FORMATS_WEBM_WEBM_INFO_PARSER_H_
+
+#include "base/compiler_specific.h"
+#include "base/time/time.h"
+#include "media/base/media_export.h"
+#include "media/formats/webm/webm_parser.h"
+
+namespace media {
+
+// Parser for WebM Info element.
+class MEDIA_EXPORT WebMInfoParser : public WebMParserClient {
+ public:
+ WebMInfoParser();
+ virtual ~WebMInfoParser();
+
+ // Parses a WebM Info element in |buf|.
+ //
+ // Returns -1 if the parse fails.
+ // Returns 0 if more data is needed.
+ // Returns the number of bytes parsed on success.
+ int Parse(const uint8* buf, int size);
+
+ int64 timecode_scale() const { return timecode_scale_; }
+ double duration() const { return duration_; }
+ base::Time date_utc() const { return date_utc_; }
+
+ private:
+ // WebMParserClient methods
+ virtual WebMParserClient* OnListStart(int id) OVERRIDE;
+ virtual bool OnListEnd(int id) OVERRIDE;
+ virtual bool OnUInt(int id, int64 val) OVERRIDE;
+ virtual bool OnFloat(int id, double val) OVERRIDE;
+ virtual bool OnBinary(int id, const uint8* data, int size) OVERRIDE;
+ virtual bool OnString(int id, const std::string& str) OVERRIDE;
+
+ int64 timecode_scale_;
+ double duration_;
+ base::Time date_utc_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebMInfoParser);
+};
+
+} // namespace media
+
+#endif // MEDIA_FORMATS_WEBM_WEBM_INFO_PARSER_H_
diff --git a/chromium/media/formats/webm/webm_parser.cc b/chromium/media/formats/webm/webm_parser.cc
new file mode 100644
index 00000000000..1baf12b4a59
--- /dev/null
+++ b/chromium/media/formats/webm/webm_parser.cc
@@ -0,0 +1,953 @@
+// Copyright 2014 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/formats/webm/webm_parser.h"
+
+// This file contains code to parse WebM file elements. It was created
+// from information in the Matroska spec.
+// http://www.matroska.org/technical/specs/index.html
+// This file contains code for encrypted WebM. Current WebM
+// encrypted request for comments specification is here
+// http://wiki.webmproject.org/encryption/webm-encryption-rfc
+
+#include <iomanip>
+
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
+#include "media/formats/webm/webm_constants.h"
+
+namespace media {
+
+enum ElementType {
+ UNKNOWN,
+ LIST, // Referred to as Master Element in the Matroska spec.
+ UINT,
+ FLOAT,
+ BINARY,
+ STRING,
+ SKIP,
+};
+
+struct ElementIdInfo {
+ ElementType type_;
+ int id_;
+};
+
+struct ListElementInfo {
+ int id_;
+ int level_;
+ const ElementIdInfo* id_info_;
+ int id_info_count_;
+};
+
+// The following are tables indicating what IDs are valid sub-elements
+// of particular elements. If an element is encountered that doesn't
+// appear in the list, a parsing error is signalled. Some elements are
+// marked as SKIP because they are valid, but we don't care about them
+// right now.
+static const ElementIdInfo kEBMLHeaderIds[] = {
+ {UINT, kWebMIdEBMLVersion},
+ {UINT, kWebMIdEBMLReadVersion},
+ {UINT, kWebMIdEBMLMaxIDLength},
+ {UINT, kWebMIdEBMLMaxSizeLength},
+ {STRING, kWebMIdDocType},
+ {UINT, kWebMIdDocTypeVersion},
+ {UINT, kWebMIdDocTypeReadVersion},
+};
+
+static const ElementIdInfo kSegmentIds[] = {
+ {LIST, kWebMIdSeekHead},
+ {LIST, kWebMIdInfo},
+ {LIST, kWebMIdCluster},
+ {LIST, kWebMIdTracks},
+ {LIST, kWebMIdCues},
+ {LIST, kWebMIdAttachments},
+ {LIST, kWebMIdChapters},
+ {LIST, kWebMIdTags},
+};
+
+static const ElementIdInfo kSeekHeadIds[] = {
+ {LIST, kWebMIdSeek},
+};
+
+static const ElementIdInfo kSeekIds[] = {
+ {BINARY, kWebMIdSeekID},
+ {UINT, kWebMIdSeekPosition},
+};
+
+static const ElementIdInfo kInfoIds[] = {
+ {BINARY, kWebMIdSegmentUID},
+ {STRING, kWebMIdSegmentFilename},
+ {BINARY, kWebMIdPrevUID},
+ {STRING, kWebMIdPrevFilename},
+ {BINARY, kWebMIdNextUID},
+ {STRING, kWebMIdNextFilename},
+ {BINARY, kWebMIdSegmentFamily},
+ {LIST, kWebMIdChapterTranslate},
+ {UINT, kWebMIdTimecodeScale},
+ {FLOAT, kWebMIdDuration},
+ {BINARY, kWebMIdDateUTC},
+ {STRING, kWebMIdTitle},
+ {STRING, kWebMIdMuxingApp},
+ {STRING, kWebMIdWritingApp},
+};
+
+static const ElementIdInfo kChapterTranslateIds[] = {
+ {UINT, kWebMIdChapterTranslateEditionUID},
+ {UINT, kWebMIdChapterTranslateCodec},
+ {BINARY, kWebMIdChapterTranslateID},
+};
+
+static const ElementIdInfo kClusterIds[] = {
+ {BINARY, kWebMIdSimpleBlock},
+ {UINT, kWebMIdTimecode},
+ {LIST, kWebMIdSilentTracks},
+ {UINT, kWebMIdPosition},
+ {UINT, kWebMIdPrevSize},
+ {LIST, kWebMIdBlockGroup},
+};
+
+static const ElementIdInfo kSilentTracksIds[] = {
+ {UINT, kWebMIdSilentTrackNumber},
+};
+
+static const ElementIdInfo kBlockGroupIds[] = {
+ {BINARY, kWebMIdBlock},
+ {LIST, kWebMIdBlockAdditions},
+ {UINT, kWebMIdBlockDuration},
+ {UINT, kWebMIdReferencePriority},
+ {BINARY, kWebMIdReferenceBlock},
+ {BINARY, kWebMIdCodecState},
+ {BINARY, kWebMIdDiscardPadding},
+ {LIST, kWebMIdSlices},
+};
+
+static const ElementIdInfo kBlockAdditionsIds[] = {
+ {LIST, kWebMIdBlockMore},
+};
+
+static const ElementIdInfo kBlockMoreIds[] = {
+ {UINT, kWebMIdBlockAddID},
+ {BINARY, kWebMIdBlockAdditional},
+};
+
+static const ElementIdInfo kSlicesIds[] = {
+ {LIST, kWebMIdTimeSlice},
+};
+
+static const ElementIdInfo kTimeSliceIds[] = {
+ {UINT, kWebMIdLaceNumber},
+};
+
+static const ElementIdInfo kTracksIds[] = {
+ {LIST, kWebMIdTrackEntry},
+};
+
+static const ElementIdInfo kTrackEntryIds[] = {
+ {UINT, kWebMIdTrackNumber},
+ {UINT, kWebMIdTrackUID},
+ {UINT, kWebMIdTrackType},
+ {UINT, kWebMIdFlagEnabled},
+ {UINT, kWebMIdFlagDefault},
+ {UINT, kWebMIdFlagForced},
+ {UINT, kWebMIdFlagLacing},
+ {UINT, kWebMIdMinCache},
+ {UINT, kWebMIdMaxCache},
+ {UINT, kWebMIdDefaultDuration},
+ {FLOAT, kWebMIdTrackTimecodeScale},
+ {UINT, kWebMIdMaxBlockAdditionId},
+ {STRING, kWebMIdName},
+ {STRING, kWebMIdLanguage},
+ {STRING, kWebMIdCodecID},
+ {BINARY, kWebMIdCodecPrivate},
+ {STRING, kWebMIdCodecName},
+ {UINT, kWebMIdAttachmentLink},
+ {UINT, kWebMIdCodecDecodeAll},
+ {UINT, kWebMIdTrackOverlay},
+ {UINT, kWebMIdCodecDelay},
+ {UINT, kWebMIdSeekPreRoll},
+ {LIST, kWebMIdTrackTranslate},
+ {LIST, kWebMIdVideo},
+ {LIST, kWebMIdAudio},
+ {LIST, kWebMIdTrackOperation},
+ {LIST, kWebMIdContentEncodings},
+};
+
+static const ElementIdInfo kTrackTranslateIds[] = {
+ {UINT, kWebMIdTrackTranslateEditionUID},
+ {UINT, kWebMIdTrackTranslateCodec},
+ {BINARY, kWebMIdTrackTranslateTrackID},
+};
+
+static const ElementIdInfo kVideoIds[] = {
+ {UINT, kWebMIdFlagInterlaced},
+ {UINT, kWebMIdStereoMode},
+ {UINT, kWebMIdAlphaMode},
+ {UINT, kWebMIdPixelWidth},
+ {UINT, kWebMIdPixelHeight},
+ {UINT, kWebMIdPixelCropBottom},
+ {UINT, kWebMIdPixelCropTop},
+ {UINT, kWebMIdPixelCropLeft},
+ {UINT, kWebMIdPixelCropRight},
+ {UINT, kWebMIdDisplayWidth},
+ {UINT, kWebMIdDisplayHeight},
+ {UINT, kWebMIdDisplayUnit},
+ {UINT, kWebMIdAspectRatioType},
+ {BINARY, kWebMIdColorSpace},
+ {FLOAT, kWebMIdFrameRate},
+};
+
+static const ElementIdInfo kAudioIds[] = {
+ {FLOAT, kWebMIdSamplingFrequency},
+ {FLOAT, kWebMIdOutputSamplingFrequency},
+ {UINT, kWebMIdChannels},
+ {UINT, kWebMIdBitDepth},
+};
+
+static const ElementIdInfo kTrackOperationIds[] = {
+ {LIST, kWebMIdTrackCombinePlanes},
+ {LIST, kWebMIdJoinBlocks},
+};
+
+static const ElementIdInfo kTrackCombinePlanesIds[] = {
+ {LIST, kWebMIdTrackPlane},
+};
+
+static const ElementIdInfo kTrackPlaneIds[] = {
+ {UINT, kWebMIdTrackPlaneUID},
+ {UINT, kWebMIdTrackPlaneType},
+};
+
+static const ElementIdInfo kJoinBlocksIds[] = {
+ {UINT, kWebMIdTrackJoinUID},
+};
+
+static const ElementIdInfo kContentEncodingsIds[] = {
+ {LIST, kWebMIdContentEncoding},
+};
+
+static const ElementIdInfo kContentEncodingIds[] = {
+ {UINT, kWebMIdContentEncodingOrder},
+ {UINT, kWebMIdContentEncodingScope},
+ {UINT, kWebMIdContentEncodingType},
+ {LIST, kWebMIdContentCompression},
+ {LIST, kWebMIdContentEncryption},
+};
+
+static const ElementIdInfo kContentCompressionIds[] = {
+ {UINT, kWebMIdContentCompAlgo},
+ {BINARY, kWebMIdContentCompSettings},
+};
+
+static const ElementIdInfo kContentEncryptionIds[] = {
+ {LIST, kWebMIdContentEncAESSettings},
+ {UINT, kWebMIdContentEncAlgo},
+ {BINARY, kWebMIdContentEncKeyID},
+ {BINARY, kWebMIdContentSignature},
+ {BINARY, kWebMIdContentSigKeyID},
+ {UINT, kWebMIdContentSigAlgo},
+ {UINT, kWebMIdContentSigHashAlgo},
+};
+
+static const ElementIdInfo kContentEncAESSettingsIds[] = {
+ {UINT, kWebMIdAESSettingsCipherMode},
+};
+
+static const ElementIdInfo kCuesIds[] = {
+ {LIST, kWebMIdCuePoint},
+};
+
+static const ElementIdInfo kCuePointIds[] = {
+ {UINT, kWebMIdCueTime},
+ {LIST, kWebMIdCueTrackPositions},
+};
+
+static const ElementIdInfo kCueTrackPositionsIds[] = {
+ {UINT, kWebMIdCueTrack},
+ {UINT, kWebMIdCueClusterPosition},
+ {UINT, kWebMIdCueBlockNumber},
+ {UINT, kWebMIdCueCodecState},
+ {LIST, kWebMIdCueReference},
+};
+
+static const ElementIdInfo kCueReferenceIds[] = {
+ {UINT, kWebMIdCueRefTime},
+};
+
+static const ElementIdInfo kAttachmentsIds[] = {
+ {LIST, kWebMIdAttachedFile},
+};
+
+static const ElementIdInfo kAttachedFileIds[] = {
+ {STRING, kWebMIdFileDescription},
+ {STRING, kWebMIdFileName},
+ {STRING, kWebMIdFileMimeType},
+ {BINARY, kWebMIdFileData},
+ {UINT, kWebMIdFileUID},
+};
+
+static const ElementIdInfo kChaptersIds[] = {
+ {LIST, kWebMIdEditionEntry},
+};
+
+static const ElementIdInfo kEditionEntryIds[] = {
+ {UINT, kWebMIdEditionUID},
+ {UINT, kWebMIdEditionFlagHidden},
+ {UINT, kWebMIdEditionFlagDefault},
+ {UINT, kWebMIdEditionFlagOrdered},
+ {LIST, kWebMIdChapterAtom},
+};
+
+static const ElementIdInfo kChapterAtomIds[] = {
+ {UINT, kWebMIdChapterUID},
+ {UINT, kWebMIdChapterTimeStart},
+ {UINT, kWebMIdChapterTimeEnd},
+ {UINT, kWebMIdChapterFlagHidden},
+ {UINT, kWebMIdChapterFlagEnabled},
+ {BINARY, kWebMIdChapterSegmentUID},
+ {UINT, kWebMIdChapterSegmentEditionUID},
+ {UINT, kWebMIdChapterPhysicalEquiv},
+ {LIST, kWebMIdChapterTrack},
+ {LIST, kWebMIdChapterDisplay},
+ {LIST, kWebMIdChapProcess},
+};
+
+static const ElementIdInfo kChapterTrackIds[] = {
+ {UINT, kWebMIdChapterTrackNumber},
+};
+
+static const ElementIdInfo kChapterDisplayIds[] = {
+ {STRING, kWebMIdChapString},
+ {STRING, kWebMIdChapLanguage},
+ {STRING, kWebMIdChapCountry},
+};
+
+static const ElementIdInfo kChapProcessIds[] = {
+ {UINT, kWebMIdChapProcessCodecID},
+ {BINARY, kWebMIdChapProcessPrivate},
+ {LIST, kWebMIdChapProcessCommand},
+};
+
+static const ElementIdInfo kChapProcessCommandIds[] = {
+ {UINT, kWebMIdChapProcessTime},
+ {BINARY, kWebMIdChapProcessData},
+};
+
+static const ElementIdInfo kTagsIds[] = {
+ {LIST, kWebMIdTag},
+};
+
+static const ElementIdInfo kTagIds[] = {
+ {LIST, kWebMIdTargets},
+ {LIST, kWebMIdSimpleTag},
+};
+
+static const ElementIdInfo kTargetsIds[] = {
+ {UINT, kWebMIdTargetTypeValue},
+ {STRING, kWebMIdTargetType},
+ {UINT, kWebMIdTagTrackUID},
+ {UINT, kWebMIdTagEditionUID},
+ {UINT, kWebMIdTagChapterUID},
+ {UINT, kWebMIdTagAttachmentUID},
+};
+
+static const ElementIdInfo kSimpleTagIds[] = {
+ {STRING, kWebMIdTagName},
+ {STRING, kWebMIdTagLanguage},
+ {UINT, kWebMIdTagDefault},
+ {STRING, kWebMIdTagString},
+ {BINARY, kWebMIdTagBinary},
+};
+
+#define LIST_ELEMENT_INFO(id, level, id_info) \
+ { (id), (level), (id_info), arraysize(id_info) }
+
+static const ListElementInfo kListElementInfo[] = {
+ LIST_ELEMENT_INFO(kWebMIdCluster, 1, kClusterIds),
+ LIST_ELEMENT_INFO(kWebMIdEBMLHeader, 0, kEBMLHeaderIds),
+ LIST_ELEMENT_INFO(kWebMIdSegment, 0, kSegmentIds),
+ LIST_ELEMENT_INFO(kWebMIdSeekHead, 1, kSeekHeadIds),
+ LIST_ELEMENT_INFO(kWebMIdSeek, 2, kSeekIds),
+ LIST_ELEMENT_INFO(kWebMIdInfo, 1, kInfoIds),
+ LIST_ELEMENT_INFO(kWebMIdChapterTranslate, 2, kChapterTranslateIds),
+ LIST_ELEMENT_INFO(kWebMIdSilentTracks, 2, kSilentTracksIds),
+ LIST_ELEMENT_INFO(kWebMIdBlockGroup, 2, kBlockGroupIds),
+ LIST_ELEMENT_INFO(kWebMIdBlockAdditions, 3, kBlockAdditionsIds),
+ LIST_ELEMENT_INFO(kWebMIdBlockMore, 4, kBlockMoreIds),
+ LIST_ELEMENT_INFO(kWebMIdSlices, 3, kSlicesIds),
+ LIST_ELEMENT_INFO(kWebMIdTimeSlice, 4, kTimeSliceIds),
+ LIST_ELEMENT_INFO(kWebMIdTracks, 1, kTracksIds),
+ LIST_ELEMENT_INFO(kWebMIdTrackEntry, 2, kTrackEntryIds),
+ LIST_ELEMENT_INFO(kWebMIdTrackTranslate, 3, kTrackTranslateIds),
+ LIST_ELEMENT_INFO(kWebMIdVideo, 3, kVideoIds),
+ LIST_ELEMENT_INFO(kWebMIdAudio, 3, kAudioIds),
+ LIST_ELEMENT_INFO(kWebMIdTrackOperation, 3, kTrackOperationIds),
+ LIST_ELEMENT_INFO(kWebMIdTrackCombinePlanes, 4, kTrackCombinePlanesIds),
+ LIST_ELEMENT_INFO(kWebMIdTrackPlane, 5, kTrackPlaneIds),
+ LIST_ELEMENT_INFO(kWebMIdJoinBlocks, 4, kJoinBlocksIds),
+ LIST_ELEMENT_INFO(kWebMIdContentEncodings, 3, kContentEncodingsIds),
+ LIST_ELEMENT_INFO(kWebMIdContentEncoding, 4, kContentEncodingIds),
+ LIST_ELEMENT_INFO(kWebMIdContentCompression, 5, kContentCompressionIds),
+ LIST_ELEMENT_INFO(kWebMIdContentEncryption, 5, kContentEncryptionIds),
+ LIST_ELEMENT_INFO(kWebMIdContentEncAESSettings, 6, kContentEncAESSettingsIds),
+ LIST_ELEMENT_INFO(kWebMIdCues, 1, kCuesIds),
+ LIST_ELEMENT_INFO(kWebMIdCuePoint, 2, kCuePointIds),
+ LIST_ELEMENT_INFO(kWebMIdCueTrackPositions, 3, kCueTrackPositionsIds),
+ LIST_ELEMENT_INFO(kWebMIdCueReference, 4, kCueReferenceIds),
+ LIST_ELEMENT_INFO(kWebMIdAttachments, 1, kAttachmentsIds),
+ LIST_ELEMENT_INFO(kWebMIdAttachedFile, 2, kAttachedFileIds),
+ LIST_ELEMENT_INFO(kWebMIdChapters, 1, kChaptersIds),
+ LIST_ELEMENT_INFO(kWebMIdEditionEntry, 2, kEditionEntryIds),
+ LIST_ELEMENT_INFO(kWebMIdChapterAtom, 3, kChapterAtomIds),
+ LIST_ELEMENT_INFO(kWebMIdChapterTrack, 4, kChapterTrackIds),
+ LIST_ELEMENT_INFO(kWebMIdChapterDisplay, 4, kChapterDisplayIds),
+ LIST_ELEMENT_INFO(kWebMIdChapProcess, 4, kChapProcessIds),
+ LIST_ELEMENT_INFO(kWebMIdChapProcessCommand, 5, kChapProcessCommandIds),
+ LIST_ELEMENT_INFO(kWebMIdTags, 1, kTagsIds),
+ LIST_ELEMENT_INFO(kWebMIdTag, 2, kTagIds),
+ LIST_ELEMENT_INFO(kWebMIdTargets, 3, kTargetsIds),
+ LIST_ELEMENT_INFO(kWebMIdSimpleTag, 3, kSimpleTagIds),
+};
+
+// Parses an element header id or size field. These fields are variable length
+// encoded. The first byte indicates how many bytes the field occupies.
+// |buf| - The buffer to parse.
+// |size| - The number of bytes in |buf|
+// |max_bytes| - The maximum number of bytes the field can be. ID fields
+// set this to 4 & element size fields set this to 8. If the
+// first byte indicates a larger field size than this it is a
+// parser error.
+// |mask_first_byte| - For element size fields the field length encoding bits
+// need to be masked off. This parameter is true for
+// element size fields and is false for ID field values.
+//
+// Returns: The number of bytes parsed on success. -1 on error.
+static int ParseWebMElementHeaderField(const uint8* buf, int size,
+ int max_bytes, bool mask_first_byte,
+ int64* num) {
+ DCHECK(buf);
+ DCHECK(num);
+
+ if (size < 0)
+ return -1;
+
+ if (size == 0)
+ return 0;
+
+ int mask = 0x80;
+ uint8 ch = buf[0];
+ int extra_bytes = -1;
+ bool all_ones = false;
+ for (int i = 0; i < max_bytes; ++i) {
+ if ((ch & mask) != 0) {
+ mask = ~mask & 0xff;
+ *num = mask_first_byte ? ch & mask : ch;
+ all_ones = (ch & mask) == mask;
+ extra_bytes = i;
+ break;
+ }
+ mask = 0x80 | mask >> 1;
+ }
+
+ if (extra_bytes == -1)
+ return -1;
+
+ // Return 0 if we need more data.
+ if ((1 + extra_bytes) > size)
+ return 0;
+
+ int bytes_used = 1;
+
+ for (int i = 0; i < extra_bytes; ++i) {
+ ch = buf[bytes_used++];
+ all_ones &= (ch == 0xff);
+ *num = (*num << 8) | ch;
+ }
+
+ if (all_ones)
+ *num = kint64max;
+
+ return bytes_used;
+}
+
+int WebMParseElementHeader(const uint8* buf, int size,
+ int* id, int64* element_size) {
+ DCHECK(buf);
+ DCHECK_GE(size, 0);
+ DCHECK(id);
+ DCHECK(element_size);
+
+ if (size == 0)
+ return 0;
+
+ int64 tmp = 0;
+ int num_id_bytes = ParseWebMElementHeaderField(buf, size, 4, false, &tmp);
+
+ if (num_id_bytes <= 0)
+ return num_id_bytes;
+
+ if (tmp == kint64max)
+ tmp = kWebMReservedId;
+
+ *id = static_cast<int>(tmp);
+
+ int num_size_bytes = ParseWebMElementHeaderField(buf + num_id_bytes,
+ size - num_id_bytes,
+ 8, true, &tmp);
+
+ if (num_size_bytes <= 0)
+ return num_size_bytes;
+
+ if (tmp == kint64max)
+ tmp = kWebMUnknownSize;
+
+ *element_size = tmp;
+ DVLOG(3) << "WebMParseElementHeader() : id " << std::hex << *id << std::dec
+ << " size " << *element_size;
+ return num_id_bytes + num_size_bytes;
+}
+
+// Finds ElementType for a specific ID.
+static ElementType FindIdType(int id,
+ const ElementIdInfo* id_info,
+ int id_info_count) {
+
+ // Check for global element IDs that can be anywhere.
+ if (id == kWebMIdVoid || id == kWebMIdCRC32)
+ return SKIP;
+
+ for (int i = 0; i < id_info_count; ++i) {
+ if (id == id_info[i].id_)
+ return id_info[i].type_;
+ }
+
+ return UNKNOWN;
+}
+
+// Finds ListElementInfo for a specific ID.
+static const ListElementInfo* FindListInfo(int id) {
+ for (size_t i = 0; i < arraysize(kListElementInfo); ++i) {
+ if (id == kListElementInfo[i].id_)
+ return &kListElementInfo[i];
+ }
+
+ return NULL;
+}
+
+static int FindListLevel(int id) {
+ const ListElementInfo* list_info = FindListInfo(id);
+ if (list_info)
+ return list_info->level_;
+
+ return -1;
+}
+
+static int ParseUInt(const uint8* buf, int size, int id,
+ WebMParserClient* client) {
+ if ((size <= 0) || (size > 8))
+ return -1;
+
+ // Read in the big-endian integer.
+ uint64 value = 0;
+ for (int i = 0; i < size; ++i)
+ value = (value << 8) | buf[i];
+
+ // We use int64 in place of uint64 everywhere for convenience. See this bug
+ // for more details: http://crbug.com/366750#c3
+ if (!base::IsValueInRangeForNumericType<int64>(value))
+ return -1;
+
+ if (!client->OnUInt(id, value))
+ return -1;
+
+ return size;
+}
+
+static int ParseFloat(const uint8* buf, int size, int id,
+ WebMParserClient* client) {
+
+ if ((size != 4) && (size != 8))
+ return -1;
+
+ double value = -1;
+
+ // Read the bytes from big-endian form into a native endian integer.
+ int64 tmp = 0;
+ for (int i = 0; i < size; ++i)
+ tmp = (tmp << 8) | buf[i];
+
+ // Use a union to convert the integer bit pattern into a floating point
+ // number.
+ if (size == 4) {
+ union {
+ int32 src;
+ float dst;
+ } tmp2;
+ tmp2.src = static_cast<int32>(tmp);
+ value = tmp2.dst;
+ } else if (size == 8) {
+ union {
+ int64 src;
+ double dst;
+ } tmp2;
+ tmp2.src = tmp;
+ value = tmp2.dst;
+ } else {
+ return -1;
+ }
+
+ if (!client->OnFloat(id, value))
+ return -1;
+
+ return size;
+}
+
+static int ParseBinary(const uint8* buf, int size, int id,
+ WebMParserClient* client) {
+ return client->OnBinary(id, buf, size) ? size : -1;
+}
+
+static int ParseString(const uint8* buf, int size, int id,
+ WebMParserClient* client) {
+ const uint8* end = static_cast<const uint8*>(memchr(buf, '\0', size));
+ int length = (end != NULL) ? static_cast<int>(end - buf) : size;
+ std::string str(reinterpret_cast<const char*>(buf), length);
+ return client->OnString(id, str) ? size : -1;
+}
+
+static int ParseNonListElement(ElementType type, int id, int64 element_size,
+ const uint8* buf, int size,
+ WebMParserClient* client) {
+ DCHECK_GE(size, element_size);
+
+ int result = -1;
+ switch(type) {
+ case LIST:
+ NOTIMPLEMENTED();
+ result = -1;
+ break;
+ case UINT:
+ result = ParseUInt(buf, element_size, id, client);
+ break;
+ case FLOAT:
+ result = ParseFloat(buf, element_size, id, client);
+ break;
+ case BINARY:
+ result = ParseBinary(buf, element_size, id, client);
+ break;
+ case STRING:
+ result = ParseString(buf, element_size, id, client);
+ break;
+ case SKIP:
+ result = element_size;
+ break;
+ default:
+ DVLOG(1) << "Unhandled ID type " << type;
+ return -1;
+ };
+
+ DCHECK_LE(result, size);
+ return result;
+}
+
+WebMParserClient::WebMParserClient() {}
+WebMParserClient::~WebMParserClient() {}
+
+WebMParserClient* WebMParserClient::OnListStart(int id) {
+ DVLOG(1) << "Unexpected list element start with ID " << std::hex << id;
+ return NULL;
+}
+
+bool WebMParserClient::OnListEnd(int id) {
+ DVLOG(1) << "Unexpected list element end with ID " << std::hex << id;
+ return false;
+}
+
+bool WebMParserClient::OnUInt(int id, int64 val) {
+ DVLOG(1) << "Unexpected unsigned integer element with ID " << std::hex << id;
+ return false;
+}
+
+bool WebMParserClient::OnFloat(int id, double val) {
+ DVLOG(1) << "Unexpected float element with ID " << std::hex << id;
+ return false;
+}
+
+bool WebMParserClient::OnBinary(int id, const uint8* data, int size) {
+ DVLOG(1) << "Unexpected binary element with ID " << std::hex << id;
+ return false;
+}
+
+bool WebMParserClient::OnString(int id, const std::string& str) {
+ DVLOG(1) << "Unexpected string element with ID " << std::hex << id;
+ return false;
+}
+
+WebMListParser::WebMListParser(int id, WebMParserClient* client)
+ : state_(NEED_LIST_HEADER),
+ root_id_(id),
+ root_level_(FindListLevel(id)),
+ root_client_(client) {
+ DCHECK_GE(root_level_, 0);
+ DCHECK(client);
+}
+
+WebMListParser::~WebMListParser() {}
+
+void WebMListParser::Reset() {
+ ChangeState(NEED_LIST_HEADER);
+ list_state_stack_.clear();
+}
+
+int WebMListParser::Parse(const uint8* buf, int size) {
+ DCHECK(buf);
+
+ if (size < 0 || state_ == PARSE_ERROR || state_ == DONE_PARSING_LIST)
+ return -1;
+
+ if (size == 0)
+ return 0;
+
+ const uint8* cur = buf;
+ int cur_size = size;
+ int bytes_parsed = 0;
+
+ while (cur_size > 0 && state_ != PARSE_ERROR && state_ != DONE_PARSING_LIST) {
+ int element_id = 0;
+ int64 element_size = 0;
+ int result = WebMParseElementHeader(cur, cur_size, &element_id,
+ &element_size);
+
+ if (result < 0)
+ return result;
+
+ if (result == 0)
+ return bytes_parsed;
+
+ switch(state_) {
+ case NEED_LIST_HEADER: {
+ if (element_id != root_id_) {
+ ChangeState(PARSE_ERROR);
+ return -1;
+ }
+
+ // Only allow Segment & Cluster to have an unknown size.
+ if (element_size == kWebMUnknownSize &&
+ (element_id != kWebMIdSegment) &&
+ (element_id != kWebMIdCluster)) {
+ ChangeState(PARSE_ERROR);
+ return -1;
+ }
+
+ ChangeState(INSIDE_LIST);
+ if (!OnListStart(root_id_, element_size))
+ return -1;
+
+ break;
+ }
+
+ case INSIDE_LIST: {
+ int header_size = result;
+ const uint8* element_data = cur + header_size;
+ int element_data_size = cur_size - header_size;
+
+ if (element_size < element_data_size)
+ element_data_size = element_size;
+
+ result = ParseListElement(header_size, element_id, element_size,
+ element_data, element_data_size);
+
+ DCHECK_LE(result, header_size + element_data_size);
+ if (result < 0) {
+ ChangeState(PARSE_ERROR);
+ return -1;
+ }
+
+ if (result == 0)
+ return bytes_parsed;
+
+ break;
+ }
+ case DONE_PARSING_LIST:
+ case PARSE_ERROR:
+ // Shouldn't be able to get here.
+ NOTIMPLEMENTED();
+ break;
+ }
+
+ cur += result;
+ cur_size -= result;
+ bytes_parsed += result;
+ }
+
+ return (state_ == PARSE_ERROR) ? -1 : bytes_parsed;
+}
+
+bool WebMListParser::IsParsingComplete() const {
+ return state_ == DONE_PARSING_LIST;
+}
+
+void WebMListParser::ChangeState(State new_state) {
+ state_ = new_state;
+}
+
+int WebMListParser::ParseListElement(int header_size,
+ int id, int64 element_size,
+ const uint8* data, int size) {
+ DCHECK_GT(list_state_stack_.size(), 0u);
+
+ ListState& list_state = list_state_stack_.back();
+ DCHECK(list_state.element_info_);
+
+ const ListElementInfo* element_info = list_state.element_info_;
+ ElementType id_type =
+ FindIdType(id, element_info->id_info_, element_info->id_info_count_);
+
+ // Unexpected ID.
+ if (id_type == UNKNOWN) {
+ if (list_state.size_ != kWebMUnknownSize ||
+ !IsSiblingOrAncestor(list_state.id_, id)) {
+ DVLOG(1) << "No ElementType info for ID 0x" << std::hex << id;
+ return -1;
+ }
+
+ // We've reached the end of a list of unknown size. Update the size now that
+ // we know it and dispatch the end of list calls.
+ list_state.size_ = list_state.bytes_parsed_;
+
+ if (!OnListEnd())
+ return -1;
+
+ // Check to see if all open lists have ended.
+ if (list_state_stack_.size() == 0)
+ return 0;
+
+ list_state = list_state_stack_.back();
+ }
+
+ // Make sure the whole element can fit inside the current list.
+ int64 total_element_size = header_size + element_size;
+ if (list_state.size_ != kWebMUnknownSize &&
+ list_state.size_ < list_state.bytes_parsed_ + total_element_size) {
+ return -1;
+ }
+
+ if (id_type == LIST) {
+ list_state.bytes_parsed_ += header_size;
+
+ if (!OnListStart(id, element_size))
+ return -1;
+ return header_size;
+ }
+
+ // Make sure we have the entire element before trying to parse a non-list
+ // element.
+ if (size < element_size)
+ return 0;
+
+ int bytes_parsed = ParseNonListElement(id_type, id, element_size,
+ data, size, list_state.client_);
+ DCHECK_LE(bytes_parsed, size);
+
+ // Return if an error occurred or we need more data.
+ // Note: bytes_parsed is 0 for a successful parse of a size 0 element. We
+ // need to check the element_size to disambiguate the "need more data" case
+ // from a successful parse.
+ if (bytes_parsed < 0 || (bytes_parsed == 0 && element_size != 0))
+ return bytes_parsed;
+
+ int result = header_size + bytes_parsed;
+ list_state.bytes_parsed_ += result;
+
+ // See if we have reached the end of the current list.
+ if (list_state.bytes_parsed_ == list_state.size_) {
+ if (!OnListEnd())
+ return -1;
+ }
+
+ return result;
+}
+
+bool WebMListParser::OnListStart(int id, int64 size) {
+ const ListElementInfo* element_info = FindListInfo(id);
+ if (!element_info)
+ return false;
+
+ int current_level = root_level_ + list_state_stack_.size() - 1;
+ if (current_level + 1 != element_info->level_)
+ return false;
+
+ WebMParserClient* current_list_client = NULL;
+ if (!list_state_stack_.empty()) {
+ // Make sure the new list doesn't go past the end of the current list.
+ ListState current_list_state = list_state_stack_.back();
+ if (current_list_state.size_ != kWebMUnknownSize &&
+ current_list_state.size_ < current_list_state.bytes_parsed_ + size)
+ return false;
+ current_list_client = current_list_state.client_;
+ } else {
+ current_list_client = root_client_;
+ }
+
+ WebMParserClient* new_list_client = current_list_client->OnListStart(id);
+ if (!new_list_client)
+ return false;
+
+ ListState new_list_state = { id, size, 0, element_info, new_list_client };
+ list_state_stack_.push_back(new_list_state);
+
+ if (size == 0)
+ return OnListEnd();
+
+ return true;
+}
+
+bool WebMListParser::OnListEnd() {
+ int lists_ended = 0;
+ for (; !list_state_stack_.empty(); ++lists_ended) {
+ const ListState& list_state = list_state_stack_.back();
+
+ if (list_state.bytes_parsed_ != list_state.size_)
+ break;
+
+ list_state_stack_.pop_back();
+
+ int64 bytes_parsed = list_state.bytes_parsed_;
+ WebMParserClient* client = NULL;
+ if (!list_state_stack_.empty()) {
+ // Update the bytes_parsed_ for the parent element.
+ list_state_stack_.back().bytes_parsed_ += bytes_parsed;
+ client = list_state_stack_.back().client_;
+ } else {
+ client = root_client_;
+ }
+
+ if (!client->OnListEnd(list_state.id_))
+ return false;
+ }
+
+ DCHECK_GE(lists_ended, 1);
+
+ if (list_state_stack_.empty())
+ ChangeState(DONE_PARSING_LIST);
+
+ return true;
+}
+
+bool WebMListParser::IsSiblingOrAncestor(int id_a, int id_b) const {
+ DCHECK((id_a == kWebMIdSegment) || (id_a == kWebMIdCluster));
+
+ if (id_a == kWebMIdCluster) {
+ // kWebMIdCluster siblings.
+ for (size_t i = 0; i < arraysize(kSegmentIds); i++) {
+ if (kSegmentIds[i].id_ == id_b)
+ return true;
+ }
+ }
+
+ // kWebMIdSegment siblings.
+ return ((id_b == kWebMIdSegment) || (id_b == kWebMIdEBMLHeader));
+}
+
+} // namespace media
diff --git a/chromium/media/formats/webm/webm_parser.h b/chromium/media/formats/webm/webm_parser.h
new file mode 100644
index 00000000000..854db685f88
--- /dev/null
+++ b/chromium/media/formats/webm/webm_parser.h
@@ -0,0 +1,158 @@
+// Copyright 2014 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_FORMATS_WEBM_WEBM_PARSER_H_
+#define MEDIA_FORMATS_WEBM_WEBM_PARSER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// Interface for receiving WebM parser events.
+//
+// Each method is called when an element of the specified type is parsed.
+// The ID of the element that was parsed is given along with the value
+// stored in the element. List elements generate calls at the start and
+// end of the list. Any pointers passed to these methods are only guaranteed
+// to be valid for the life of that call. Each method (except for OnListStart)
+// returns a bool that indicates whether the parsed data is valid. OnListStart
+// returns a pointer to a WebMParserClient object, which should be used to
+// handle elements parsed out of the list being started. If false (or NULL by
+// OnListStart) is returned then the parse is immediately terminated and an
+// error is reported by the parser.
+class MEDIA_EXPORT WebMParserClient {
+ public:
+ virtual ~WebMParserClient();
+
+ virtual WebMParserClient* OnListStart(int id);
+ virtual bool OnListEnd(int id);
+ virtual bool OnUInt(int id, int64 val);
+ virtual bool OnFloat(int id, double val);
+ virtual bool OnBinary(int id, const uint8* data, int size);
+ virtual bool OnString(int id, const std::string& str);
+
+ protected:
+ WebMParserClient();
+
+ DISALLOW_COPY_AND_ASSIGN(WebMParserClient);
+};
+
+struct ListElementInfo;
+
+// Parses a WebM list element and all of its children. This
+// class supports incremental parsing of the list so Parse()
+// can be called multiple times with pieces of the list.
+// IsParsingComplete() will return true once the entire list has
+// been parsed.
+class MEDIA_EXPORT WebMListParser {
+ public:
+ // |id| - Element ID of the list we intend to parse.
+ // |client| - Called as different elements in the list are parsed.
+ WebMListParser(int id, WebMParserClient* client);
+ ~WebMListParser();
+
+ // Resets the state of the parser so it can start parsing a new list.
+ void Reset();
+
+ // Parses list data contained in |buf|.
+ //
+ // Returns < 0 if the parse fails.
+ // Returns 0 if more data is needed.
+ // Returning > 0 indicates success & the number of bytes parsed.
+ int Parse(const uint8* buf, int size);
+
+ // Returns true if the entire list has been parsed.
+ bool IsParsingComplete() const;
+
+ private:
+ enum State {
+ NEED_LIST_HEADER,
+ INSIDE_LIST,
+ DONE_PARSING_LIST,
+ PARSE_ERROR,
+ };
+
+ struct ListState {
+ int id_;
+ int64 size_;
+ int64 bytes_parsed_;
+ const ListElementInfo* element_info_;
+ WebMParserClient* client_;
+ };
+
+ void ChangeState(State new_state);
+
+ // Parses a single element in the current list.
+ //
+ // |header_size| - The size of the element header
+ // |id| - The ID of the element being parsed.
+ // |element_size| - The size of the element body.
+ // |data| - Pointer to the element contents.
+ // |size| - Number of bytes in |data|
+ // |client| - Client to pass the parsed data to.
+ //
+ // Returns < 0 if the parse fails.
+ // Returns 0 if more data is needed.
+ // Returning > 0 indicates success & the number of bytes parsed.
+ int ParseListElement(int header_size,
+ int id, int64 element_size,
+ const uint8* data, int size);
+
+ // Called when starting to parse a new list.
+ //
+ // |id| - The ID of the new list.
+ // |size| - The size of the new list.
+ // |client| - The client object to notify that a new list is being parsed.
+ //
+ // Returns true if this list can be started in the current context. False
+ // if starting this list causes some sort of parse error.
+ bool OnListStart(int id, int64 size);
+
+ // Called when the end of the current list has been reached. This may also
+ // signal the end of the current list's ancestors if the current list happens
+ // to be at the end of its parent.
+ //
+ // Returns true if no errors occurred while ending this list(s).
+ bool OnListEnd();
+
+ // Checks to see if |id_b| is a sibling or ancestor of |id_a|.
+ bool IsSiblingOrAncestor(int id_a, int id_b) const;
+
+ State state_;
+
+ // Element ID passed to the constructor.
+ const int root_id_;
+
+ // Element level for |root_id_|. Used to verify that elements appear at
+ // the correct level.
+ const int root_level_;
+
+ // WebMParserClient to handle the root list.
+ WebMParserClient* const root_client_;
+
+ // Stack of state for all the lists currently being parsed. Lists are
+ // added and removed from this stack as they are parsed.
+ std::vector<ListState> list_state_stack_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebMListParser);
+};
+
+// Parses an element header & returns the ID and element size.
+//
+// Returns < 0 if the parse fails.
+// Returns 0 if more data is needed.
+// Returning > 0 indicates success & the number of bytes parsed.
+// |*id| contains the element ID on success and is undefined otherwise.
+// |*element_size| contains the element size on success and is undefined
+// otherwise.
+int MEDIA_EXPORT WebMParseElementHeader(const uint8* buf, int size,
+ int* id, int64* element_size);
+
+} // namespace media
+
+#endif // MEDIA_FORMATS_WEBM_WEBM_PARSER_H_
diff --git a/chromium/media/formats/webm/webm_parser_unittest.cc b/chromium/media/formats/webm/webm_parser_unittest.cc
new file mode 100644
index 00000000000..a1249e89c42
--- /dev/null
+++ b/chromium/media/formats/webm/webm_parser_unittest.cc
@@ -0,0 +1,412 @@
+// Copyright 2014 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/formats/webm/cluster_builder.h"
+#include "media/formats/webm/webm_constants.h"
+#include "media/formats/webm/webm_parser.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::InSequence;
+using ::testing::Return;
+using ::testing::ReturnNull;
+using ::testing::StrictMock;
+using ::testing::_;
+
+namespace media {
+
+enum { kBlockCount = 5 };
+
+class MockWebMParserClient : public WebMParserClient {
+ public:
+ virtual ~MockWebMParserClient() {}
+
+ // WebMParserClient methods.
+ MOCK_METHOD1(OnListStart, WebMParserClient*(int));
+ MOCK_METHOD1(OnListEnd, bool(int));
+ MOCK_METHOD2(OnUInt, bool(int, int64));
+ MOCK_METHOD2(OnFloat, bool(int, double));
+ MOCK_METHOD3(OnBinary, bool(int, const uint8*, int));
+ MOCK_METHOD2(OnString, bool(int, const std::string&));
+};
+
+class WebMParserTest : public testing::Test {
+ protected:
+ StrictMock<MockWebMParserClient> client_;
+};
+
+static scoped_ptr<Cluster> CreateCluster(int block_count) {
+ ClusterBuilder cb;
+ cb.SetClusterTimecode(0);
+
+ for (int i = 0; i < block_count; i++) {
+ uint8 data[] = { 0x00 };
+ cb.AddSimpleBlock(0, i, 0, data, sizeof(data));
+ }
+
+ return cb.Finish();
+}
+
+static void CreateClusterExpectations(int block_count,
+ bool is_complete_cluster,
+ MockWebMParserClient* client) {
+
+ InSequence s;
+ EXPECT_CALL(*client, OnListStart(kWebMIdCluster)).WillOnce(Return(client));
+ EXPECT_CALL(*client, OnUInt(kWebMIdTimecode, 0))
+ .WillOnce(Return(true));
+
+ for (int i = 0; i < block_count; i++) {
+ EXPECT_CALL(*client, OnBinary(kWebMIdSimpleBlock, _, _))
+ .WillOnce(Return(true));
+ }
+
+ if (is_complete_cluster)
+ EXPECT_CALL(*client, OnListEnd(kWebMIdCluster)).WillOnce(Return(true));
+}
+
+TEST_F(WebMParserTest, EmptyCluster) {
+ const uint8 kEmptyCluster[] = {
+ 0x1F, 0x43, 0xB6, 0x75, 0x80 // CLUSTER (size = 0)
+ };
+ int size = sizeof(kEmptyCluster);
+
+ InSequence s;
+ EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(&client_));
+ EXPECT_CALL(client_, OnListEnd(kWebMIdCluster)).WillOnce(Return(true));
+
+ WebMListParser parser(kWebMIdCluster, &client_);
+ EXPECT_EQ(size, parser.Parse(kEmptyCluster, size));
+ EXPECT_TRUE(parser.IsParsingComplete());
+}
+
+TEST_F(WebMParserTest, EmptyClusterInSegment) {
+ const uint8 kBuffer[] = {
+ 0x18, 0x53, 0x80, 0x67, 0x85, // SEGMENT (size = 5)
+ 0x1F, 0x43, 0xB6, 0x75, 0x80, // CLUSTER (size = 0)
+ };
+ int size = sizeof(kBuffer);
+
+ InSequence s;
+ EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(&client_));
+ EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(&client_));
+ EXPECT_CALL(client_, OnListEnd(kWebMIdCluster)).WillOnce(Return(true));
+ EXPECT_CALL(client_, OnListEnd(kWebMIdSegment)).WillOnce(Return(true));
+
+ WebMListParser parser(kWebMIdSegment, &client_);
+ EXPECT_EQ(size, parser.Parse(kBuffer, size));
+ EXPECT_TRUE(parser.IsParsingComplete());
+}
+
+// Test the case where a non-list child element has a size
+// that is beyond the end of the parent.
+TEST_F(WebMParserTest, ChildNonListLargerThanParent) {
+ const uint8 kBuffer[] = {
+ 0x1F, 0x43, 0xB6, 0x75, 0x81, // CLUSTER (size = 1)
+ 0xE7, 0x81, 0x01, // Timecode (size=1, value=1)
+ };
+
+ InSequence s;
+ EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(&client_));
+
+ WebMListParser parser(kWebMIdCluster, &client_);
+ EXPECT_EQ(-1, parser.Parse(kBuffer, sizeof(kBuffer)));
+ EXPECT_FALSE(parser.IsParsingComplete());
+}
+
+// Test the case where a list child element has a size
+// that is beyond the end of the parent.
+TEST_F(WebMParserTest, ChildListLargerThanParent) {
+ const uint8 kBuffer[] = {
+ 0x18, 0x53, 0x80, 0x67, 0x85, // SEGMENT (size = 5)
+ 0x1F, 0x43, 0xB6, 0x75, 0x81, 0x11 // CLUSTER (size = 1)
+ };
+
+ InSequence s;
+ EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(&client_));
+
+ WebMListParser parser(kWebMIdSegment, &client_);
+ EXPECT_EQ(-1, parser.Parse(kBuffer, sizeof(kBuffer)));
+ EXPECT_FALSE(parser.IsParsingComplete());
+}
+
+// Expecting to parse a Cluster, but get a Segment.
+TEST_F(WebMParserTest, ListIdDoesNotMatch) {
+ const uint8 kBuffer[] = {
+ 0x18, 0x53, 0x80, 0x67, 0x80, // SEGMENT (size = 0)
+ };
+
+ WebMListParser parser(kWebMIdCluster, &client_);
+ EXPECT_EQ(-1, parser.Parse(kBuffer, sizeof(kBuffer)));
+ EXPECT_FALSE(parser.IsParsingComplete());
+}
+
+TEST_F(WebMParserTest, InvalidElementInList) {
+ const uint8 kBuffer[] = {
+ 0x18, 0x53, 0x80, 0x67, 0x82, // SEGMENT (size = 2)
+ 0xAE, 0x80, // TrackEntry (size = 0)
+ };
+
+ InSequence s;
+ EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(&client_));
+
+ WebMListParser parser(kWebMIdSegment, &client_);
+ EXPECT_EQ(-1, parser.Parse(kBuffer, sizeof(kBuffer)));
+ EXPECT_FALSE(parser.IsParsingComplete());
+}
+
+// Test specific case of InvalidElementInList to verify EBMLHEADER within
+// known-sized cluster causes parse error.
+TEST_F(WebMParserTest, InvalidEBMLHeaderInCluster) {
+ const uint8 kBuffer[] = {
+ 0x1F, 0x43, 0xB6, 0x75, 0x85, // CLUSTER (size = 5)
+ 0x1A, 0x45, 0xDF, 0xA3, 0x80, // EBMLHEADER (size = 0)
+ };
+
+ InSequence s;
+ EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(&client_));
+
+ WebMListParser parser(kWebMIdCluster, &client_);
+ EXPECT_EQ(-1, parser.Parse(kBuffer, sizeof(kBuffer)));
+ EXPECT_FALSE(parser.IsParsingComplete());
+}
+
+// Verify that EBMLHEADER ends a preceding "unknown"-sized CLUSTER.
+TEST_F(WebMParserTest, UnknownSizeClusterFollowedByEBMLHeader) {
+ const uint8 kBuffer[] = {
+ 0x1F, 0x43, 0xB6, 0x75, 0xFF, // CLUSTER (size = unknown; really 0 due to:)
+ 0x1A, 0x45, 0xDF, 0xA3, 0x80, // EBMLHEADER (size = 0)
+ };
+
+ InSequence s;
+ EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(&client_));
+ EXPECT_CALL(client_, OnListEnd(kWebMIdCluster)).WillOnce(Return(true));
+
+ WebMListParser parser(kWebMIdCluster, &client_);
+
+ // List parse should consume the CLUSTER but not the EBMLHEADER.
+ EXPECT_EQ(5, parser.Parse(kBuffer, sizeof(kBuffer)));
+ EXPECT_TRUE(parser.IsParsingComplete());
+}
+
+TEST_F(WebMParserTest, VoidAndCRC32InList) {
+ const uint8 kBuffer[] = {
+ 0x18, 0x53, 0x80, 0x67, 0x99, // SEGMENT (size = 25)
+ 0xEC, 0x83, 0x00, 0x00, 0x00, // Void (size = 3)
+ 0xBF, 0x83, 0x00, 0x00, 0x00, // CRC32 (size = 3)
+ 0x1F, 0x43, 0xB6, 0x75, 0x8A, // CLUSTER (size = 10)
+ 0xEC, 0x83, 0x00, 0x00, 0x00, // Void (size = 3)
+ 0xBF, 0x83, 0x00, 0x00, 0x00, // CRC32 (size = 3)
+ };
+ int size = sizeof(kBuffer);
+
+ InSequence s;
+ EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(&client_));
+ EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(&client_));
+ EXPECT_CALL(client_, OnListEnd(kWebMIdCluster)).WillOnce(Return(true));
+ EXPECT_CALL(client_, OnListEnd(kWebMIdSegment)).WillOnce(Return(true));
+
+ WebMListParser parser(kWebMIdSegment, &client_);
+ EXPECT_EQ(size, parser.Parse(kBuffer, size));
+ EXPECT_TRUE(parser.IsParsingComplete());
+}
+
+
+TEST_F(WebMParserTest, ParseListElementWithSingleCall) {
+ scoped_ptr<Cluster> cluster(CreateCluster(kBlockCount));
+ CreateClusterExpectations(kBlockCount, true, &client_);
+
+ WebMListParser parser(kWebMIdCluster, &client_);
+ EXPECT_EQ(cluster->size(), parser.Parse(cluster->data(), cluster->size()));
+ EXPECT_TRUE(parser.IsParsingComplete());
+}
+
+TEST_F(WebMParserTest, ParseListElementWithMultipleCalls) {
+ scoped_ptr<Cluster> cluster(CreateCluster(kBlockCount));
+ CreateClusterExpectations(kBlockCount, true, &client_);
+
+ const uint8* data = cluster->data();
+ int size = cluster->size();
+ int default_parse_size = 3;
+ WebMListParser parser(kWebMIdCluster, &client_);
+ int parse_size = std::min(default_parse_size, size);
+
+ while (size > 0) {
+ int result = parser.Parse(data, parse_size);
+ ASSERT_GE(result, 0);
+ ASSERT_LE(result, parse_size);
+
+ if (result == 0) {
+ // The parser needs more data so increase the parse_size a little.
+ EXPECT_FALSE(parser.IsParsingComplete());
+ parse_size += default_parse_size;
+ parse_size = std::min(parse_size, size);
+ continue;
+ }
+
+ parse_size = default_parse_size;
+
+ data += result;
+ size -= result;
+
+ EXPECT_EQ((size == 0), parser.IsParsingComplete());
+ }
+ EXPECT_TRUE(parser.IsParsingComplete());
+}
+
+TEST_F(WebMParserTest, Reset) {
+ InSequence s;
+ scoped_ptr<Cluster> cluster(CreateCluster(kBlockCount));
+
+ // First expect all but the last block.
+ CreateClusterExpectations(kBlockCount - 1, false, &client_);
+
+ // Now expect all blocks.
+ CreateClusterExpectations(kBlockCount, true, &client_);
+
+ WebMListParser parser(kWebMIdCluster, &client_);
+
+ // Send slightly less than the full cluster so all but the last block is
+ // parsed.
+ int result = parser.Parse(cluster->data(), cluster->size() - 1);
+ EXPECT_GT(result, 0);
+ EXPECT_LT(result, cluster->size());
+ EXPECT_FALSE(parser.IsParsingComplete());
+
+ parser.Reset();
+
+ // Now parse a whole cluster to verify that all the blocks will get parsed.
+ EXPECT_EQ(cluster->size(), parser.Parse(cluster->data(), cluster->size()));
+ EXPECT_TRUE(parser.IsParsingComplete());
+}
+
+// Test the case where multiple clients are used for different lists.
+TEST_F(WebMParserTest, MultipleClients) {
+ const uint8 kBuffer[] = {
+ 0x18, 0x53, 0x80, 0x67, 0x94, // SEGMENT (size = 20)
+ 0x16, 0x54, 0xAE, 0x6B, 0x85, // TRACKS (size = 5)
+ 0xAE, 0x83, // TRACKENTRY (size = 3)
+ 0xD7, 0x81, 0x01, // TRACKNUMBER (size = 1)
+ 0x1F, 0x43, 0xB6, 0x75, 0x85, // CLUSTER (size = 5)
+ 0xEC, 0x83, 0x00, 0x00, 0x00, // Void (size = 3)
+ };
+ int size = sizeof(kBuffer);
+
+ StrictMock<MockWebMParserClient> c1_;
+ StrictMock<MockWebMParserClient> c2_;
+ StrictMock<MockWebMParserClient> c3_;
+
+ InSequence s;
+ EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(&c1_));
+ EXPECT_CALL(c1_, OnListStart(kWebMIdTracks)).WillOnce(Return(&c2_));
+ EXPECT_CALL(c2_, OnListStart(kWebMIdTrackEntry)).WillOnce(Return(&c3_));
+ EXPECT_CALL(c3_, OnUInt(kWebMIdTrackNumber, 1)).WillOnce(Return(true));
+ EXPECT_CALL(c2_, OnListEnd(kWebMIdTrackEntry)).WillOnce(Return(true));
+ EXPECT_CALL(c1_, OnListEnd(kWebMIdTracks)).WillOnce(Return(true));
+ EXPECT_CALL(c1_, OnListStart(kWebMIdCluster)).WillOnce(Return(&c2_));
+ EXPECT_CALL(c1_, OnListEnd(kWebMIdCluster)).WillOnce(Return(true));
+ EXPECT_CALL(client_, OnListEnd(kWebMIdSegment)).WillOnce(Return(true));
+
+ WebMListParser parser(kWebMIdSegment, &client_);
+ EXPECT_EQ(size, parser.Parse(kBuffer, size));
+ EXPECT_TRUE(parser.IsParsingComplete());
+}
+
+// Test the case where multiple clients are used for different lists.
+TEST_F(WebMParserTest, InvalidClient) {
+ const uint8 kBuffer[] = {
+ 0x18, 0x53, 0x80, 0x67, 0x85, // SEGMENT (size = 20)
+ 0x16, 0x54, 0xAE, 0x6B, 0x80, // TRACKS (size = 5)
+ };
+
+ InSequence s;
+ EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(ReturnNull());
+
+ WebMListParser parser(kWebMIdSegment, &client_);
+ EXPECT_EQ(-1, parser.Parse(kBuffer, sizeof(kBuffer)));
+ EXPECT_FALSE(parser.IsParsingComplete());
+}
+
+TEST_F(WebMParserTest, ReservedIds) {
+ const uint8 k1ByteReservedId[] = { 0xFF, 0x81 };
+ const uint8 k2ByteReservedId[] = { 0x7F, 0xFF, 0x81 };
+ const uint8 k3ByteReservedId[] = { 0x3F, 0xFF, 0xFF, 0x81 };
+ const uint8 k4ByteReservedId[] = { 0x1F, 0xFF, 0xFF, 0xFF, 0x81 };
+ const uint8* kBuffers[] = {
+ k1ByteReservedId,
+ k2ByteReservedId,
+ k3ByteReservedId,
+ k4ByteReservedId
+ };
+
+ for (size_t i = 0; i < arraysize(kBuffers); i++) {
+ int id;
+ int64 element_size;
+ int buffer_size = 2 + i;
+ EXPECT_EQ(buffer_size, WebMParseElementHeader(kBuffers[i], buffer_size,
+ &id, &element_size));
+ EXPECT_EQ(id, kWebMReservedId);
+ EXPECT_EQ(element_size, 1);
+ }
+}
+
+TEST_F(WebMParserTest, ReservedSizes) {
+ const uint8 k1ByteReservedSize[] = { 0xA3, 0xFF };
+ const uint8 k2ByteReservedSize[] = { 0xA3, 0x7F, 0xFF };
+ const uint8 k3ByteReservedSize[] = { 0xA3, 0x3F, 0xFF, 0xFF };
+ const uint8 k4ByteReservedSize[] = { 0xA3, 0x1F, 0xFF, 0xFF, 0xFF };
+ const uint8 k5ByteReservedSize[] = { 0xA3, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF };
+ const uint8 k6ByteReservedSize[] = { 0xA3, 0x07, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF };
+ const uint8 k7ByteReservedSize[] = { 0xA3, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF };
+ const uint8 k8ByteReservedSize[] = { 0xA3, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF };
+ const uint8* kBuffers[] = {
+ k1ByteReservedSize,
+ k2ByteReservedSize,
+ k3ByteReservedSize,
+ k4ByteReservedSize,
+ k5ByteReservedSize,
+ k6ByteReservedSize,
+ k7ByteReservedSize,
+ k8ByteReservedSize
+ };
+
+ for (size_t i = 0; i < arraysize(kBuffers); i++) {
+ int id;
+ int64 element_size;
+ int buffer_size = 2 + i;
+ EXPECT_EQ(buffer_size, WebMParseElementHeader(kBuffers[i], buffer_size,
+ &id, &element_size));
+ EXPECT_EQ(id, 0xA3);
+ EXPECT_EQ(element_size, kWebMUnknownSize);
+ }
+}
+
+TEST_F(WebMParserTest, ZeroPaddedStrings) {
+ const uint8 kBuffer[] = {
+ 0x1A, 0x45, 0xDF, 0xA3, 0x91, // EBMLHEADER (size = 17)
+ 0x42, 0x82, 0x80, // DocType (size = 0)
+ 0x42, 0x82, 0x81, 0x00, // DocType (size = 1) ""
+ 0x42, 0x82, 0x81, 'a', // DocType (size = 1) "a"
+ 0x42, 0x82, 0x83, 'a', 0x00, 0x00 // DocType (size = 3) "a"
+ };
+ int size = sizeof(kBuffer);
+
+ InSequence s;
+ EXPECT_CALL(client_, OnListStart(kWebMIdEBMLHeader))
+ .WillOnce(Return(&client_));
+ EXPECT_CALL(client_, OnString(kWebMIdDocType, "")).WillOnce(Return(true));
+ EXPECT_CALL(client_, OnString(kWebMIdDocType, "")).WillOnce(Return(true));
+ EXPECT_CALL(client_, OnString(kWebMIdDocType, "a")).WillOnce(Return(true));
+ EXPECT_CALL(client_, OnString(kWebMIdDocType, "a")).WillOnce(Return(true));
+ EXPECT_CALL(client_, OnListEnd(kWebMIdEBMLHeader)).WillOnce(Return(true));
+
+ WebMListParser parser(kWebMIdEBMLHeader, &client_);
+ EXPECT_EQ(size, parser.Parse(kBuffer, size));
+ EXPECT_TRUE(parser.IsParsingComplete());
+}
+
+} // namespace media
diff --git a/chromium/media/formats/webm/webm_stream_parser.cc b/chromium/media/formats/webm/webm_stream_parser.cc
new file mode 100644
index 00000000000..dd200d2170f
--- /dev/null
+++ b/chromium/media/formats/webm/webm_stream_parser.cc
@@ -0,0 +1,281 @@
+// Copyright 2014 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/formats/webm/webm_stream_parser.h"
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "media/formats/webm/webm_cluster_parser.h"
+#include "media/formats/webm/webm_constants.h"
+#include "media/formats/webm/webm_content_encodings.h"
+#include "media/formats/webm/webm_crypto_helpers.h"
+#include "media/formats/webm/webm_info_parser.h"
+#include "media/formats/webm/webm_tracks_parser.h"
+
+namespace media {
+
+WebMStreamParser::WebMStreamParser()
+ : state_(kWaitingForInit),
+ unknown_segment_size_(false) {
+}
+
+WebMStreamParser::~WebMStreamParser() {
+}
+
+void WebMStreamParser::Init(const InitCB& init_cb,
+ const NewConfigCB& config_cb,
+ const NewBuffersCB& new_buffers_cb,
+ bool ignore_text_tracks,
+ const NeedKeyCB& need_key_cb,
+ const NewMediaSegmentCB& new_segment_cb,
+ const base::Closure& end_of_segment_cb,
+ const LogCB& log_cb) {
+ DCHECK_EQ(state_, kWaitingForInit);
+ DCHECK(init_cb_.is_null());
+ DCHECK(!init_cb.is_null());
+ DCHECK(!config_cb.is_null());
+ DCHECK(!new_buffers_cb.is_null());
+ DCHECK(!need_key_cb.is_null());
+ DCHECK(!new_segment_cb.is_null());
+ DCHECK(!end_of_segment_cb.is_null());
+
+ ChangeState(kParsingHeaders);
+ init_cb_ = init_cb;
+ config_cb_ = config_cb;
+ new_buffers_cb_ = new_buffers_cb;
+ ignore_text_tracks_ = ignore_text_tracks;
+ need_key_cb_ = need_key_cb;
+ new_segment_cb_ = new_segment_cb;
+ end_of_segment_cb_ = end_of_segment_cb;
+ log_cb_ = log_cb;
+}
+
+void WebMStreamParser::Flush() {
+ DCHECK_NE(state_, kWaitingForInit);
+
+ byte_queue_.Reset();
+ if (cluster_parser_)
+ cluster_parser_->Reset();
+ if (state_ == kParsingClusters) {
+ ChangeState(kParsingHeaders);
+ end_of_segment_cb_.Run();
+ }
+}
+
+bool WebMStreamParser::Parse(const uint8* buf, int size) {
+ DCHECK_NE(state_, kWaitingForInit);
+
+ if (state_ == kError)
+ return false;
+
+ byte_queue_.Push(buf, size);
+
+ int result = 0;
+ int bytes_parsed = 0;
+ const uint8* cur = NULL;
+ int cur_size = 0;
+
+ byte_queue_.Peek(&cur, &cur_size);
+ while (cur_size > 0) {
+ State oldState = state_;
+ switch (state_) {
+ case kParsingHeaders:
+ result = ParseInfoAndTracks(cur, cur_size);
+ break;
+
+ case kParsingClusters:
+ result = ParseCluster(cur, cur_size);
+ break;
+
+ case kWaitingForInit:
+ case kError:
+ return false;
+ }
+
+ if (result < 0) {
+ ChangeState(kError);
+ return false;
+ }
+
+ if (state_ == oldState && result == 0)
+ break;
+
+ DCHECK_GE(result, 0);
+ cur += result;
+ cur_size -= result;
+ bytes_parsed += result;
+ }
+
+ byte_queue_.Pop(bytes_parsed);
+ return true;
+}
+
+void WebMStreamParser::ChangeState(State new_state) {
+ DVLOG(1) << "ChangeState() : " << state_ << " -> " << new_state;
+ state_ = new_state;
+}
+
+int WebMStreamParser::ParseInfoAndTracks(const uint8* data, int size) {
+ DVLOG(2) << "ParseInfoAndTracks()";
+ DCHECK(data);
+ DCHECK_GT(size, 0);
+
+ const uint8* cur = data;
+ int cur_size = size;
+ int bytes_parsed = 0;
+
+ int id;
+ int64 element_size;
+ int result = WebMParseElementHeader(cur, cur_size, &id, &element_size);
+
+ if (result <= 0)
+ return result;
+
+ switch (id) {
+ case kWebMIdEBMLHeader:
+ case kWebMIdSeekHead:
+ case kWebMIdVoid:
+ case kWebMIdCRC32:
+ case kWebMIdCues:
+ case kWebMIdChapters:
+ // TODO(matthewjheaney): Implement support for chapters.
+ if (cur_size < (result + element_size)) {
+ // We don't have the whole element yet. Signal we need more data.
+ return 0;
+ }
+ // Skip the element.
+ return result + element_size;
+ break;
+ case kWebMIdCluster:
+ if (!cluster_parser_) {
+ MEDIA_LOG(log_cb_) << "Found Cluster element before Info.";
+ return -1;
+ }
+ ChangeState(kParsingClusters);
+ new_segment_cb_.Run();
+ return 0;
+ break;
+ case kWebMIdSegment:
+ // Segment of unknown size indicates live stream.
+ if (element_size == kWebMUnknownSize)
+ unknown_segment_size_ = true;
+ // Just consume the segment header.
+ return result;
+ break;
+ case kWebMIdInfo:
+ // We've found the element we are looking for.
+ break;
+ default: {
+ MEDIA_LOG(log_cb_) << "Unexpected element ID 0x" << std::hex << id;
+ return -1;
+ }
+ }
+
+ WebMInfoParser info_parser;
+ result = info_parser.Parse(cur, cur_size);
+
+ if (result <= 0)
+ return result;
+
+ cur += result;
+ cur_size -= result;
+ bytes_parsed += result;
+
+ WebMTracksParser tracks_parser(log_cb_, ignore_text_tracks_);
+ result = tracks_parser.Parse(cur, cur_size);
+
+ if (result <= 0)
+ return result;
+
+ bytes_parsed += result;
+
+ double timecode_scale_in_us = info_parser.timecode_scale() / 1000.0;
+ InitParameters params(kInfiniteDuration());
+
+ if (info_parser.duration() > 0) {
+ int64 duration_in_us = info_parser.duration() * timecode_scale_in_us;
+ params.duration = base::TimeDelta::FromMicroseconds(duration_in_us);
+ }
+
+ params.timeline_offset = info_parser.date_utc();
+
+ if (unknown_segment_size_ && (info_parser.duration() <= 0) &&
+ !info_parser.date_utc().is_null()) {
+ params.liveness = Demuxer::LIVENESS_LIVE;
+ } else if (info_parser.duration() >= 0) {
+ params.liveness = Demuxer::LIVENESS_RECORDED;
+ } else {
+ params.liveness = Demuxer::LIVENESS_UNKNOWN;
+ }
+
+ const AudioDecoderConfig& audio_config = tracks_parser.audio_decoder_config();
+ if (audio_config.is_encrypted())
+ FireNeedKey(tracks_parser.audio_encryption_key_id());
+
+ const VideoDecoderConfig& video_config = tracks_parser.video_decoder_config();
+ if (video_config.is_encrypted())
+ FireNeedKey(tracks_parser.video_encryption_key_id());
+
+ if (!config_cb_.Run(audio_config,
+ video_config,
+ tracks_parser.text_tracks())) {
+ DVLOG(1) << "New config data isn't allowed.";
+ return -1;
+ }
+
+ cluster_parser_.reset(new WebMClusterParser(
+ info_parser.timecode_scale(),
+ tracks_parser.audio_track_num(),
+ tracks_parser.GetAudioDefaultDuration(timecode_scale_in_us),
+ tracks_parser.video_track_num(),
+ tracks_parser.GetVideoDefaultDuration(timecode_scale_in_us),
+ tracks_parser.text_tracks(),
+ tracks_parser.ignored_tracks(),
+ tracks_parser.audio_encryption_key_id(),
+ tracks_parser.video_encryption_key_id(),
+ log_cb_));
+
+ if (!init_cb_.is_null())
+ base::ResetAndReturn(&init_cb_).Run(true, params);
+
+ return bytes_parsed;
+}
+
+int WebMStreamParser::ParseCluster(const uint8* data, int size) {
+ if (!cluster_parser_)
+ return -1;
+
+ int bytes_parsed = cluster_parser_->Parse(data, size);
+ if (bytes_parsed < 0)
+ return bytes_parsed;
+
+ const BufferQueue& audio_buffers = cluster_parser_->GetAudioBuffers();
+ const BufferQueue& video_buffers = cluster_parser_->GetVideoBuffers();
+ const TextBufferQueueMap& text_map = cluster_parser_->GetTextBuffers();
+
+ bool cluster_ended = cluster_parser_->cluster_ended();
+
+ if ((!audio_buffers.empty() || !video_buffers.empty() ||
+ !text_map.empty()) &&
+ !new_buffers_cb_.Run(audio_buffers, video_buffers, text_map)) {
+ return -1;
+ }
+
+ if (cluster_ended) {
+ ChangeState(kParsingHeaders);
+ end_of_segment_cb_.Run();
+ }
+
+ return bytes_parsed;
+}
+
+void WebMStreamParser::FireNeedKey(const std::string& key_id) {
+ std::vector<uint8> key_id_vector(key_id.begin(), key_id.end());
+ need_key_cb_.Run(kWebMEncryptInitDataType, key_id_vector);
+}
+
+} // namespace media
diff --git a/chromium/media/formats/webm/webm_stream_parser.h b/chromium/media/formats/webm/webm_stream_parser.h
new file mode 100644
index 00000000000..8a41f3744ca
--- /dev/null
+++ b/chromium/media/formats/webm/webm_stream_parser.h
@@ -0,0 +1,89 @@
+// Copyright 2014 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_FORMATS_WEBM_WEBM_STREAM_PARSER_H_
+#define MEDIA_FORMATS_WEBM_WEBM_STREAM_PARSER_H_
+
+#include "base/callback_forward.h"
+#include "base/memory/ref_counted.h"
+#include "media/base/audio_decoder_config.h"
+#include "media/base/buffers.h"
+#include "media/base/byte_queue.h"
+#include "media/base/stream_parser.h"
+#include "media/base/video_decoder_config.h"
+
+namespace media {
+
+class WebMClusterParser;
+
+class WebMStreamParser : public StreamParser {
+ public:
+ WebMStreamParser();
+ virtual ~WebMStreamParser();
+
+ // StreamParser implementation.
+ virtual void Init(const InitCB& init_cb, const NewConfigCB& config_cb,
+ const NewBuffersCB& new_buffers_cb,
+ bool ignore_text_tracks,
+ const NeedKeyCB& need_key_cb,
+ const NewMediaSegmentCB& new_segment_cb,
+ const base::Closure& end_of_segment_cb,
+ const LogCB& log_cb) OVERRIDE;
+ virtual void Flush() OVERRIDE;
+ virtual bool Parse(const uint8* buf, int size) OVERRIDE;
+
+ private:
+ enum State {
+ kWaitingForInit,
+ kParsingHeaders,
+ kParsingClusters,
+ kError
+ };
+
+ void ChangeState(State new_state);
+
+ // Parses WebM Header, Info, Tracks elements. It also skips other level 1
+ // elements that are not used right now. Once the Info & Tracks elements have
+ // been parsed, this method will transition the parser from PARSING_HEADERS to
+ // PARSING_CLUSTERS.
+ //
+ // Returns < 0 if the parse fails.
+ // Returns 0 if more data is needed.
+ // Returning > 0 indicates success & the number of bytes parsed.
+ int ParseInfoAndTracks(const uint8* data, int size);
+
+ // Incrementally parses WebM cluster elements. This method also skips
+ // CUES elements if they are encountered since we currently don't use the
+ // data in these elements.
+ //
+ // Returns < 0 if the parse fails.
+ // Returns 0 if more data is needed.
+ // Returning > 0 indicates success & the number of bytes parsed.
+ int ParseCluster(const uint8* data, int size);
+
+ // Fire needkey event through the |need_key_cb_|.
+ void FireNeedKey(const std::string& key_id);
+
+ State state_;
+ InitCB init_cb_;
+ NewConfigCB config_cb_;
+ NewBuffersCB new_buffers_cb_;
+ bool ignore_text_tracks_;
+ NeedKeyCB need_key_cb_;
+
+ NewMediaSegmentCB new_segment_cb_;
+ base::Closure end_of_segment_cb_;
+ LogCB log_cb_;
+
+ bool unknown_segment_size_;
+
+ scoped_ptr<WebMClusterParser> cluster_parser_;
+ ByteQueue byte_queue_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebMStreamParser);
+};
+
+} // namespace media
+
+#endif // MEDIA_FORMATS_WEBM_WEBM_STREAM_PARSER_H_
diff --git a/chromium/media/formats/webm/webm_tracks_parser.cc b/chromium/media/formats/webm/webm_tracks_parser.cc
new file mode 100644
index 00000000000..ed7fd521be1
--- /dev/null
+++ b/chromium/media/formats/webm/webm_tracks_parser.cc
@@ -0,0 +1,350 @@
+// Copyright 2014 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/formats/webm/webm_tracks_parser.h"
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "media/base/buffers.h"
+#include "media/formats/webm/webm_constants.h"
+#include "media/formats/webm/webm_content_encodings.h"
+
+namespace media {
+
+static TextKind CodecIdToTextKind(const std::string& codec_id) {
+ if (codec_id == kWebMCodecSubtitles)
+ return kTextSubtitles;
+
+ if (codec_id == kWebMCodecCaptions)
+ return kTextCaptions;
+
+ if (codec_id == kWebMCodecDescriptions)
+ return kTextDescriptions;
+
+ if (codec_id == kWebMCodecMetadata)
+ return kTextMetadata;
+
+ return kTextNone;
+}
+
+static base::TimeDelta PrecisionCappedDefaultDuration(
+ const double timecode_scale_in_us, const int64 duration_in_ns) {
+ if (duration_in_ns <= 0)
+ return kNoTimestamp();
+
+ int64 mult = duration_in_ns / 1000;
+ mult /= timecode_scale_in_us;
+ if (mult == 0)
+ return kNoTimestamp();
+
+ mult = static_cast<double>(mult) * timecode_scale_in_us;
+ return base::TimeDelta::FromMicroseconds(mult);
+}
+
+WebMTracksParser::WebMTracksParser(const LogCB& log_cb, bool ignore_text_tracks)
+ : track_type_(-1),
+ track_num_(-1),
+ track_uid_(-1),
+ seek_preroll_(-1),
+ codec_delay_(-1),
+ default_duration_(-1),
+ audio_track_num_(-1),
+ audio_default_duration_(-1),
+ video_track_num_(-1),
+ video_default_duration_(-1),
+ ignore_text_tracks_(ignore_text_tracks),
+ log_cb_(log_cb),
+ audio_client_(log_cb),
+ video_client_(log_cb) {
+}
+
+WebMTracksParser::~WebMTracksParser() {}
+
+int WebMTracksParser::Parse(const uint8* buf, int size) {
+ track_type_ =-1;
+ track_num_ = -1;
+ track_uid_ = -1;
+ default_duration_ = -1;
+ track_name_.clear();
+ track_language_.clear();
+ audio_track_num_ = -1;
+ audio_default_duration_ = -1;
+ audio_decoder_config_ = AudioDecoderConfig();
+ video_track_num_ = -1;
+ video_default_duration_ = -1;
+ video_decoder_config_ = VideoDecoderConfig();
+ text_tracks_.clear();
+ ignored_tracks_.clear();
+
+ WebMListParser parser(kWebMIdTracks, this);
+ int result = parser.Parse(buf, size);
+
+ if (result <= 0)
+ return result;
+
+ // For now we do all or nothing parsing.
+ return parser.IsParsingComplete() ? result : 0;
+}
+
+base::TimeDelta WebMTracksParser::GetAudioDefaultDuration(
+ const double timecode_scale_in_us) const {
+ return PrecisionCappedDefaultDuration(timecode_scale_in_us,
+ audio_default_duration_);
+}
+
+base::TimeDelta WebMTracksParser::GetVideoDefaultDuration(
+ const double timecode_scale_in_us) const {
+ return PrecisionCappedDefaultDuration(timecode_scale_in_us,
+ video_default_duration_);
+}
+
+WebMParserClient* WebMTracksParser::OnListStart(int id) {
+ if (id == kWebMIdContentEncodings) {
+ DCHECK(!track_content_encodings_client_.get());
+ track_content_encodings_client_.reset(
+ new WebMContentEncodingsClient(log_cb_));
+ return track_content_encodings_client_->OnListStart(id);
+ }
+
+ if (id == kWebMIdTrackEntry) {
+ track_type_ = -1;
+ track_num_ = -1;
+ default_duration_ = -1;
+ track_name_.clear();
+ track_language_.clear();
+ codec_id_ = "";
+ codec_private_.clear();
+ audio_client_.Reset();
+ video_client_.Reset();
+ return this;
+ }
+
+ if (id == kWebMIdAudio)
+ return &audio_client_;
+
+ if (id == kWebMIdVideo)
+ return &video_client_;
+
+ return this;
+}
+
+bool WebMTracksParser::OnListEnd(int id) {
+ if (id == kWebMIdContentEncodings) {
+ DCHECK(track_content_encodings_client_.get());
+ return track_content_encodings_client_->OnListEnd(id);
+ }
+
+ if (id == kWebMIdTrackEntry) {
+ if (track_type_ == -1 || track_num_ == -1 || track_uid_ == -1) {
+ MEDIA_LOG(log_cb_) << "Missing TrackEntry data for "
+ << " TrackType " << track_type_
+ << " TrackNum " << track_num_
+ << " TrackUID " << track_uid_;
+ return false;
+ }
+
+ if (track_type_ != kWebMTrackTypeAudio &&
+ track_type_ != kWebMTrackTypeVideo &&
+ track_type_ != kWebMTrackTypeSubtitlesOrCaptions &&
+ track_type_ != kWebMTrackTypeDescriptionsOrMetadata) {
+ MEDIA_LOG(log_cb_) << "Unexpected TrackType " << track_type_;
+ return false;
+ }
+
+ TextKind text_track_kind = kTextNone;
+ if (track_type_ == kWebMTrackTypeSubtitlesOrCaptions) {
+ text_track_kind = CodecIdToTextKind(codec_id_);
+ if (text_track_kind == kTextNone) {
+ MEDIA_LOG(log_cb_) << "Missing TrackEntry CodecID"
+ << " TrackNum " << track_num_;
+ return false;
+ }
+
+ if (text_track_kind != kTextSubtitles &&
+ text_track_kind != kTextCaptions) {
+ MEDIA_LOG(log_cb_) << "Wrong TrackEntry CodecID"
+ << " TrackNum " << track_num_;
+ return false;
+ }
+ } else if (track_type_ == kWebMTrackTypeDescriptionsOrMetadata) {
+ text_track_kind = CodecIdToTextKind(codec_id_);
+ if (text_track_kind == kTextNone) {
+ MEDIA_LOG(log_cb_) << "Missing TrackEntry CodecID"
+ << " TrackNum " << track_num_;
+ return false;
+ }
+
+ if (text_track_kind != kTextDescriptions &&
+ text_track_kind != kTextMetadata) {
+ MEDIA_LOG(log_cb_) << "Wrong TrackEntry CodecID"
+ << " TrackNum " << track_num_;
+ return false;
+ }
+ }
+
+ std::string encryption_key_id;
+ if (track_content_encodings_client_) {
+ DCHECK(!track_content_encodings_client_->content_encodings().empty());
+ // If we have multiple ContentEncoding in one track. Always choose the
+ // key id in the first ContentEncoding as the key id of the track.
+ encryption_key_id = track_content_encodings_client_->
+ content_encodings()[0]->encryption_key_id();
+ }
+
+ if (track_type_ == kWebMTrackTypeAudio) {
+ if (audio_track_num_ == -1) {
+ audio_track_num_ = track_num_;
+ audio_encryption_key_id_ = encryption_key_id;
+
+ if (default_duration_ == 0) {
+ MEDIA_LOG(log_cb_) << "Illegal 0ns audio TrackEntry DefaultDuration";
+ return false;
+ }
+ audio_default_duration_ = default_duration_;
+
+ DCHECK(!audio_decoder_config_.IsValidConfig());
+ if (!audio_client_.InitializeConfig(
+ codec_id_, codec_private_, seek_preroll_, codec_delay_,
+ !audio_encryption_key_id_.empty(), &audio_decoder_config_)) {
+ return false;
+ }
+ } else {
+ MEDIA_LOG(log_cb_) << "Ignoring audio track " << track_num_;
+ ignored_tracks_.insert(track_num_);
+ }
+ } else if (track_type_ == kWebMTrackTypeVideo) {
+ if (video_track_num_ == -1) {
+ video_track_num_ = track_num_;
+ video_encryption_key_id_ = encryption_key_id;
+
+ if (default_duration_ == 0) {
+ MEDIA_LOG(log_cb_) << "Illegal 0ns video TrackEntry DefaultDuration";
+ return false;
+ }
+ video_default_duration_ = default_duration_;
+
+ DCHECK(!video_decoder_config_.IsValidConfig());
+ if (!video_client_.InitializeConfig(
+ codec_id_, codec_private_, !video_encryption_key_id_.empty(),
+ &video_decoder_config_)) {
+ return false;
+ }
+ } else {
+ MEDIA_LOG(log_cb_) << "Ignoring video track " << track_num_;
+ ignored_tracks_.insert(track_num_);
+ }
+ } else if (track_type_ == kWebMTrackTypeSubtitlesOrCaptions ||
+ track_type_ == kWebMTrackTypeDescriptionsOrMetadata) {
+ if (ignore_text_tracks_) {
+ MEDIA_LOG(log_cb_) << "Ignoring text track " << track_num_;
+ ignored_tracks_.insert(track_num_);
+ } else {
+ std::string track_uid = base::Int64ToString(track_uid_);
+ text_tracks_[track_num_] = TextTrackConfig(text_track_kind,
+ track_name_,
+ track_language_,
+ track_uid);
+ }
+ } else {
+ MEDIA_LOG(log_cb_) << "Unexpected TrackType " << track_type_;
+ return false;
+ }
+
+ track_type_ = -1;
+ track_num_ = -1;
+ track_uid_ = -1;
+ default_duration_ = -1;
+ track_name_.clear();
+ track_language_.clear();
+ codec_id_ = "";
+ codec_private_.clear();
+ track_content_encodings_client_.reset();
+
+ audio_client_.Reset();
+ video_client_.Reset();
+ return true;
+ }
+
+ return true;
+}
+
+bool WebMTracksParser::OnUInt(int id, int64 val) {
+ int64* dst = NULL;
+
+ switch (id) {
+ case kWebMIdTrackNumber:
+ dst = &track_num_;
+ break;
+ case kWebMIdTrackType:
+ dst = &track_type_;
+ break;
+ case kWebMIdTrackUID:
+ dst = &track_uid_;
+ break;
+ case kWebMIdSeekPreRoll:
+ dst = &seek_preroll_;
+ break;
+ case kWebMIdCodecDelay:
+ dst = &codec_delay_;
+ break;
+ case kWebMIdDefaultDuration:
+ dst = &default_duration_;
+ break;
+ default:
+ return true;
+ }
+
+ if (*dst != -1) {
+ MEDIA_LOG(log_cb_) << "Multiple values for id " << std::hex << id
+ << " specified";
+ return false;
+ }
+
+ *dst = val;
+ return true;
+}
+
+bool WebMTracksParser::OnFloat(int id, double val) {
+ return true;
+}
+
+bool WebMTracksParser::OnBinary(int id, const uint8* data, int size) {
+ if (id == kWebMIdCodecPrivate) {
+ if (!codec_private_.empty()) {
+ MEDIA_LOG(log_cb_) << "Multiple CodecPrivate fields in a track.";
+ return false;
+ }
+
+ codec_private_.assign(data, data + size);
+ return true;
+ }
+ return true;
+}
+
+bool WebMTracksParser::OnString(int id, const std::string& str) {
+ if (id == kWebMIdCodecID) {
+ if (!codec_id_.empty()) {
+ MEDIA_LOG(log_cb_) << "Multiple CodecID fields in a track";
+ return false;
+ }
+
+ codec_id_ = str;
+ return true;
+ }
+
+ if (id == kWebMIdName) {
+ track_name_ = str;
+ return true;
+ }
+
+ if (id == kWebMIdLanguage) {
+ track_language_ = str;
+ return true;
+ }
+
+ return true;
+}
+
+} // namespace media
diff --git a/chromium/media/formats/webm/webm_tracks_parser.h b/chromium/media/formats/webm/webm_tracks_parser.h
new file mode 100644
index 00000000000..61d79af84fa
--- /dev/null
+++ b/chromium/media/formats/webm/webm_tracks_parser.h
@@ -0,0 +1,119 @@
+// Copyright 2014 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_FORMATS_WEBM_WEBM_TRACKS_PARSER_H_
+#define MEDIA_FORMATS_WEBM_WEBM_TRACKS_PARSER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "media/base/audio_decoder_config.h"
+#include "media/base/media_log.h"
+#include "media/base/text_track_config.h"
+#include "media/base/video_decoder_config.h"
+#include "media/formats/webm/webm_audio_client.h"
+#include "media/formats/webm/webm_content_encodings_client.h"
+#include "media/formats/webm/webm_parser.h"
+#include "media/formats/webm/webm_video_client.h"
+
+namespace media {
+
+// Parser for WebM Tracks element.
+class MEDIA_EXPORT WebMTracksParser : public WebMParserClient {
+ public:
+ explicit WebMTracksParser(const LogCB& log_cb, bool ignore_text_tracks);
+ virtual ~WebMTracksParser();
+
+ // Parses a WebM Tracks element in |buf|.
+ //
+ // Returns -1 if the parse fails.
+ // Returns 0 if more data is needed.
+ // Returns the number of bytes parsed on success.
+ int Parse(const uint8* buf, int size);
+
+ int64 audio_track_num() const { return audio_track_num_; }
+ int64 video_track_num() const { return video_track_num_; }
+
+ // If TrackEntry DefaultDuration field existed for the associated audio or
+ // video track, returns that value converted from ns to base::TimeDelta with
+ // precision not greater than |timecode_scale_in_us|. Defaults to
+ // kNoTimestamp().
+ base::TimeDelta GetAudioDefaultDuration(
+ const double timecode_scale_in_us) const;
+ base::TimeDelta GetVideoDefaultDuration(
+ const double timecode_scale_in_us) const;
+
+ const std::set<int64>& ignored_tracks() const { return ignored_tracks_; }
+
+ const std::string& audio_encryption_key_id() const {
+ return audio_encryption_key_id_;
+ }
+
+ const AudioDecoderConfig& audio_decoder_config() {
+ return audio_decoder_config_;
+ }
+
+ const std::string& video_encryption_key_id() const {
+ return video_encryption_key_id_;
+ }
+
+ const VideoDecoderConfig& video_decoder_config() {
+ return video_decoder_config_;
+ }
+
+ typedef std::map<int, TextTrackConfig> TextTracks;
+
+ const TextTracks& text_tracks() const {
+ return text_tracks_;
+ }
+
+ private:
+ // WebMParserClient implementation.
+ virtual WebMParserClient* OnListStart(int id) OVERRIDE;
+ virtual bool OnListEnd(int id) OVERRIDE;
+ virtual bool OnUInt(int id, int64 val) OVERRIDE;
+ virtual bool OnFloat(int id, double val) OVERRIDE;
+ virtual bool OnBinary(int id, const uint8* data, int size) OVERRIDE;
+ virtual bool OnString(int id, const std::string& str) OVERRIDE;
+
+ int64 track_type_;
+ int64 track_num_;
+ int64 track_uid_;
+ std::string track_name_;
+ std::string track_language_;
+ std::string codec_id_;
+ std::vector<uint8> codec_private_;
+ int64 seek_preroll_;
+ int64 codec_delay_;
+ int64 default_duration_;
+ scoped_ptr<WebMContentEncodingsClient> track_content_encodings_client_;
+
+ int64 audio_track_num_;
+ int64 audio_default_duration_;
+ int64 video_track_num_;
+ int64 video_default_duration_;
+ bool ignore_text_tracks_;
+ TextTracks text_tracks_;
+ std::set<int64> ignored_tracks_;
+ std::string audio_encryption_key_id_;
+ std::string video_encryption_key_id_;
+ LogCB log_cb_;
+
+ WebMAudioClient audio_client_;
+ AudioDecoderConfig audio_decoder_config_;
+
+ WebMVideoClient video_client_;
+ VideoDecoderConfig video_decoder_config_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebMTracksParser);
+};
+
+} // namespace media
+
+#endif // MEDIA_FORMATS_WEBM_WEBM_TRACKS_PARSER_H_
diff --git a/chromium/media/formats/webm/webm_tracks_parser_unittest.cc b/chromium/media/formats/webm/webm_tracks_parser_unittest.cc
new file mode 100644
index 00000000000..b81aa2173f0
--- /dev/null
+++ b/chromium/media/formats/webm/webm_tracks_parser_unittest.cc
@@ -0,0 +1,185 @@
+// Copyright 2014 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/logging.h"
+#include "media/base/channel_layout.h"
+#include "media/formats/webm/tracks_builder.h"
+#include "media/formats/webm/webm_constants.h"
+#include "media/formats/webm/webm_tracks_parser.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::InSequence;
+using ::testing::Return;
+using ::testing::_;
+
+namespace media {
+
+static const double kDefaultTimecodeScaleInUs = 1000.0; // 1 ms resolution
+
+class WebMTracksParserTest : public testing::Test {
+ public:
+ WebMTracksParserTest() {}
+};
+
+static void VerifyTextTrackInfo(const uint8* buffer,
+ int buffer_size,
+ TextKind text_kind,
+ const std::string& name,
+ const std::string& language) {
+ scoped_ptr<WebMTracksParser> parser(new WebMTracksParser(LogCB(), false));
+
+ int result = parser->Parse(buffer, buffer_size);
+ EXPECT_GT(result, 0);
+ EXPECT_EQ(result, buffer_size);
+
+ const WebMTracksParser::TextTracks& text_tracks = parser->text_tracks();
+ EXPECT_EQ(text_tracks.size(), WebMTracksParser::TextTracks::size_type(1));
+
+ const WebMTracksParser::TextTracks::const_iterator itr = text_tracks.begin();
+ EXPECT_EQ(itr->first, 1); // track num
+
+ const TextTrackConfig& config = itr->second;
+ EXPECT_EQ(config.kind(), text_kind);
+ EXPECT_TRUE(config.label() == name);
+ EXPECT_TRUE(config.language() == language);
+}
+
+TEST_F(WebMTracksParserTest, SubtitleNoNameNoLang) {
+ InSequence s;
+
+ TracksBuilder tb;
+ tb.AddTextTrack(1, 1, kWebMCodecSubtitles, "", "");
+
+ const std::vector<uint8> buf = tb.Finish();
+ VerifyTextTrackInfo(&buf[0], buf.size(), kTextSubtitles, "", "");
+}
+
+TEST_F(WebMTracksParserTest, SubtitleYesNameNoLang) {
+ InSequence s;
+
+ TracksBuilder tb;
+ tb.AddTextTrack(1, 1, kWebMCodecSubtitles, "Spock", "");
+
+ const std::vector<uint8> buf = tb.Finish();
+ VerifyTextTrackInfo(&buf[0], buf.size(), kTextSubtitles, "Spock", "");
+}
+
+TEST_F(WebMTracksParserTest, SubtitleNoNameYesLang) {
+ InSequence s;
+
+ TracksBuilder tb;
+ tb.AddTextTrack(1, 1, kWebMCodecSubtitles, "", "eng");
+
+ const std::vector<uint8> buf = tb.Finish();
+ VerifyTextTrackInfo(&buf[0], buf.size(), kTextSubtitles, "", "eng");
+}
+
+TEST_F(WebMTracksParserTest, SubtitleYesNameYesLang) {
+ InSequence s;
+
+ TracksBuilder tb;
+ tb.AddTextTrack(1, 1, kWebMCodecSubtitles, "Picard", "fre");
+
+ const std::vector<uint8> buf = tb.Finish();
+ VerifyTextTrackInfo(&buf[0], buf.size(), kTextSubtitles, "Picard", "fre");
+}
+
+TEST_F(WebMTracksParserTest, IgnoringTextTracks) {
+ InSequence s;
+
+ TracksBuilder tb;
+ tb.AddTextTrack(1, 1, kWebMCodecSubtitles, "Subtitles", "fre");
+ tb.AddTextTrack(2, 2, kWebMCodecSubtitles, "Commentary", "fre");
+
+ const std::vector<uint8> buf = tb.Finish();
+ scoped_ptr<WebMTracksParser> parser(new WebMTracksParser(LogCB(), true));
+
+ int result = parser->Parse(&buf[0], buf.size());
+ EXPECT_GT(result, 0);
+ EXPECT_EQ(result, static_cast<int>(buf.size()));
+
+ EXPECT_EQ(parser->text_tracks().size(), 0u);
+
+ const std::set<int64>& ignored_tracks = parser->ignored_tracks();
+ EXPECT_TRUE(ignored_tracks.find(1) != ignored_tracks.end());
+ EXPECT_TRUE(ignored_tracks.find(2) != ignored_tracks.end());
+
+ // Test again w/o ignoring the test tracks.
+ parser.reset(new WebMTracksParser(LogCB(), false));
+
+ result = parser->Parse(&buf[0], buf.size());
+ EXPECT_GT(result, 0);
+
+ EXPECT_EQ(parser->ignored_tracks().size(), 0u);
+ EXPECT_EQ(parser->text_tracks().size(), 2u);
+}
+
+TEST_F(WebMTracksParserTest, AudioVideoDefaultDurationUnset) {
+ // Other audio/video decoder config fields are necessary in the test
+ // audio/video TrackEntry configurations. This method does only very minimal
+ // verification of their inclusion and parsing; the goal is to confirm
+ // TrackEntry DefaultDuration defaults to -1 if not included in audio or
+ // video TrackEntry.
+ TracksBuilder tb;
+ tb.AddAudioTrack(1, 1, "A_VORBIS", "audio", "", -1, 2, 8000);
+ tb.AddVideoTrack(2, 2, "V_VP8", "video", "", -1, 320, 240);
+ const std::vector<uint8> buf = tb.Finish();
+
+ scoped_ptr<WebMTracksParser> parser(new WebMTracksParser(LogCB(), true));
+ int result = parser->Parse(&buf[0], buf.size());
+ EXPECT_LE(0, result);
+ EXPECT_EQ(static_cast<int>(buf.size()), result);
+
+ EXPECT_EQ(kNoTimestamp(),
+ parser->GetAudioDefaultDuration(kDefaultTimecodeScaleInUs));
+ EXPECT_EQ(kNoTimestamp(),
+ parser->GetVideoDefaultDuration(kDefaultTimecodeScaleInUs));
+
+ const VideoDecoderConfig& video_config = parser->video_decoder_config();
+ EXPECT_TRUE(video_config.IsValidConfig());
+ EXPECT_EQ(320, video_config.coded_size().width());
+ EXPECT_EQ(240, video_config.coded_size().height());
+
+ const AudioDecoderConfig& audio_config = parser->audio_decoder_config();
+ EXPECT_TRUE(audio_config.IsValidConfig());
+ EXPECT_EQ(CHANNEL_LAYOUT_STEREO, audio_config.channel_layout());
+ EXPECT_EQ(8000, audio_config.samples_per_second());
+}
+
+TEST_F(WebMTracksParserTest, AudioVideoDefaultDurationSet) {
+ // Confirm audio or video TrackEntry DefaultDuration values are parsed, if
+ // present.
+ TracksBuilder tb;
+ tb.AddAudioTrack(1, 1, "A_VORBIS", "audio", "", 12345678, 2, 8000);
+ tb.AddVideoTrack(2, 2, "V_VP8", "video", "", 987654321, 320, 240);
+ const std::vector<uint8> buf = tb.Finish();
+
+ scoped_ptr<WebMTracksParser> parser(new WebMTracksParser(LogCB(), true));
+ int result = parser->Parse(&buf[0], buf.size());
+ EXPECT_LE(0, result);
+ EXPECT_EQ(static_cast<int>(buf.size()), result);
+
+ EXPECT_EQ(base::TimeDelta::FromMicroseconds(12000),
+ parser->GetAudioDefaultDuration(kDefaultTimecodeScaleInUs));
+ EXPECT_EQ(base::TimeDelta::FromMicroseconds(985000),
+ parser->GetVideoDefaultDuration(5000.0)); // 5 ms resolution
+ EXPECT_EQ(kNoTimestamp(), parser->GetAudioDefaultDuration(12346.0));
+ EXPECT_EQ(base::TimeDelta::FromMicroseconds(12345),
+ parser->GetAudioDefaultDuration(12345.0));
+ EXPECT_EQ(base::TimeDelta::FromMicroseconds(12003),
+ parser->GetAudioDefaultDuration(1000.3)); // 1.0003 ms resolution
+}
+
+TEST_F(WebMTracksParserTest, InvalidZeroDefaultDurationSet) {
+ // Confirm parse error if TrackEntry DefaultDuration is present, but is 0ns.
+ TracksBuilder tb(true);
+ tb.AddAudioTrack(1, 1, "A_VORBIS", "audio", "", 0, 2, 8000);
+ const std::vector<uint8> buf = tb.Finish();
+
+ scoped_ptr<WebMTracksParser> parser(new WebMTracksParser(LogCB(), true));
+ EXPECT_EQ(-1, parser->Parse(&buf[0], buf.size()));
+}
+
+} // namespace media
diff --git a/chromium/media/formats/webm/webm_video_client.cc b/chromium/media/formats/webm/webm_video_client.cc
new file mode 100644
index 00000000000..bda78efafac
--- /dev/null
+++ b/chromium/media/formats/webm/webm_video_client.cc
@@ -0,0 +1,161 @@
+// Copyright 2014 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/formats/webm/webm_video_client.h"
+
+#include "media/base/video_decoder_config.h"
+#include "media/formats/webm/webm_constants.h"
+
+namespace media {
+
+WebMVideoClient::WebMVideoClient(const LogCB& log_cb)
+ : log_cb_(log_cb) {
+ Reset();
+}
+
+WebMVideoClient::~WebMVideoClient() {
+}
+
+void WebMVideoClient::Reset() {
+ pixel_width_ = -1;
+ pixel_height_ = -1;
+ crop_bottom_ = -1;
+ crop_top_ = -1;
+ crop_left_ = -1;
+ crop_right_ = -1;
+ display_width_ = -1;
+ display_height_ = -1;
+ display_unit_ = -1;
+ alpha_mode_ = -1;
+}
+
+bool WebMVideoClient::InitializeConfig(
+ const std::string& codec_id, const std::vector<uint8>& codec_private,
+ bool is_encrypted, VideoDecoderConfig* config) {
+ DCHECK(config);
+
+ VideoCodec video_codec = kUnknownVideoCodec;
+ VideoCodecProfile profile = VIDEO_CODEC_PROFILE_UNKNOWN;
+ if (codec_id == "V_VP8") {
+ video_codec = kCodecVP8;
+ profile = VP8PROFILE_MAIN;
+ } else if (codec_id == "V_VP9") {
+ video_codec = kCodecVP9;
+ profile = VP9PROFILE_MAIN;
+ } else {
+ MEDIA_LOG(log_cb_) << "Unsupported video codec_id " << codec_id;
+ return false;
+ }
+
+ VideoFrame::Format format =
+ (alpha_mode_ == 1) ? VideoFrame::YV12A : VideoFrame::YV12;
+
+ if (pixel_width_ <= 0 || pixel_height_ <= 0)
+ return false;
+
+ // Set crop and display unit defaults if these elements are not present.
+ if (crop_bottom_ == -1)
+ crop_bottom_ = 0;
+
+ if (crop_top_ == -1)
+ crop_top_ = 0;
+
+ if (crop_left_ == -1)
+ crop_left_ = 0;
+
+ if (crop_right_ == -1)
+ crop_right_ = 0;
+
+ if (display_unit_ == -1)
+ display_unit_ = 0;
+
+ gfx::Size coded_size(pixel_width_, pixel_height_);
+ gfx::Rect visible_rect(crop_top_, crop_left_,
+ pixel_width_ - (crop_left_ + crop_right_),
+ pixel_height_ - (crop_top_ + crop_bottom_));
+ if (display_unit_ == 0) {
+ if (display_width_ <= 0)
+ display_width_ = visible_rect.width();
+ if (display_height_ <= 0)
+ display_height_ = visible_rect.height();
+ } else if (display_unit_ == 3) {
+ if (display_width_ <= 0 || display_height_ <= 0)
+ return false;
+ } else {
+ MEDIA_LOG(log_cb_) << "Unsupported display unit type " << display_unit_;
+ return false;
+ }
+ gfx::Size natural_size = gfx::Size(display_width_, display_height_);
+ const uint8* extra_data = NULL;
+ size_t extra_data_size = 0;
+ if (codec_private.size() > 0) {
+ extra_data = &codec_private[0];
+ extra_data_size = codec_private.size();
+ }
+
+ config->Initialize(
+ video_codec, profile, format, coded_size, visible_rect, natural_size,
+ extra_data, extra_data_size, is_encrypted, true);
+ return config->IsValidConfig();
+}
+
+bool WebMVideoClient::OnUInt(int id, int64 val) {
+ int64* dst = NULL;
+
+ switch (id) {
+ case kWebMIdPixelWidth:
+ dst = &pixel_width_;
+ break;
+ case kWebMIdPixelHeight:
+ dst = &pixel_height_;
+ break;
+ case kWebMIdPixelCropTop:
+ dst = &crop_top_;
+ break;
+ case kWebMIdPixelCropBottom:
+ dst = &crop_bottom_;
+ break;
+ case kWebMIdPixelCropLeft:
+ dst = &crop_left_;
+ break;
+ case kWebMIdPixelCropRight:
+ dst = &crop_right_;
+ break;
+ case kWebMIdDisplayWidth:
+ dst = &display_width_;
+ break;
+ case kWebMIdDisplayHeight:
+ dst = &display_height_;
+ break;
+ case kWebMIdDisplayUnit:
+ dst = &display_unit_;
+ break;
+ case kWebMIdAlphaMode:
+ dst = &alpha_mode_;
+ break;
+ default:
+ return true;
+ }
+
+ if (*dst != -1) {
+ MEDIA_LOG(log_cb_) << "Multiple values for id " << std::hex << id
+ << " specified (" << *dst << " and " << val << ")";
+ return false;
+ }
+
+ *dst = val;
+ return true;
+}
+
+bool WebMVideoClient::OnBinary(int id, const uint8* data, int size) {
+ // Accept binary fields we don't care about for now.
+ return true;
+}
+
+bool WebMVideoClient::OnFloat(int id, double val) {
+ // Accept float fields we don't care about for now.
+ return true;
+}
+
+} // namespace media
diff --git a/chromium/media/formats/webm/webm_video_client.h b/chromium/media/formats/webm/webm_video_client.h
new file mode 100644
index 00000000000..5545e0a203e
--- /dev/null
+++ b/chromium/media/formats/webm/webm_video_client.h
@@ -0,0 +1,61 @@
+// Copyright 2014 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_FORMATS_WEBM_WEBM_VIDEO_CLIENT_H_
+#define MEDIA_FORMATS_WEBM_WEBM_VIDEO_CLIENT_H_
+
+#include <string>
+#include <vector>
+
+#include "media/base/media_log.h"
+#include "media/formats/webm/webm_parser.h"
+
+namespace media {
+class VideoDecoderConfig;
+
+// Helper class used to parse a Video element inside a TrackEntry element.
+class WebMVideoClient : public WebMParserClient {
+ public:
+ explicit WebMVideoClient(const LogCB& log_cb);
+ virtual ~WebMVideoClient();
+
+ // Reset this object's state so it can process a new video track element.
+ void Reset();
+
+ // Initialize |config| with the data in |codec_id|, |codec_private|,
+ // |is_encrypted| and the fields parsed from the last video track element this
+ // object was used to parse.
+ // Returns true if |config| was successfully initialized.
+ // Returns false if there was unexpected values in the provided parameters or
+ // video track element fields. The contents of |config| are undefined in this
+ // case and should not be relied upon.
+ bool InitializeConfig(const std::string& codec_id,
+ const std::vector<uint8>& codec_private,
+ bool is_encrypted,
+ VideoDecoderConfig* config);
+
+ private:
+ // WebMParserClient implementation.
+ virtual bool OnUInt(int id, int64 val) OVERRIDE;
+ virtual bool OnBinary(int id, const uint8* data, int size) OVERRIDE;
+ virtual bool OnFloat(int id, double val) OVERRIDE;
+
+ LogCB log_cb_;
+ int64 pixel_width_;
+ int64 pixel_height_;
+ int64 crop_bottom_;
+ int64 crop_top_;
+ int64 crop_left_;
+ int64 crop_right_;
+ int64 display_width_;
+ int64 display_height_;
+ int64 display_unit_;
+ int64 alpha_mode_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebMVideoClient);
+};
+
+} // namespace media
+
+#endif // MEDIA_FORMATS_WEBM_WEBM_VIDEO_CLIENT_H_
diff --git a/chromium/media/formats/webm/webm_webvtt_parser.cc b/chromium/media/formats/webm/webm_webvtt_parser.cc
new file mode 100644
index 00000000000..64de1ef4434
--- /dev/null
+++ b/chromium/media/formats/webm/webm_webvtt_parser.cc
@@ -0,0 +1,78 @@
+// Copyright 2014 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/formats/webm/webm_webvtt_parser.h"
+
+namespace media {
+
+void WebMWebVTTParser::Parse(const uint8* payload, int payload_size,
+ std::string* id,
+ std::string* settings,
+ std::string* content) {
+ WebMWebVTTParser parser(payload, payload_size);
+ parser.Parse(id, settings, content);
+}
+
+WebMWebVTTParser::WebMWebVTTParser(const uint8* payload, int payload_size)
+ : ptr_(payload),
+ ptr_end_(payload + payload_size) {
+}
+
+void WebMWebVTTParser::Parse(std::string* id,
+ std::string* settings,
+ std::string* content) {
+ ParseLine(id);
+ ParseLine(settings);
+ content->assign(ptr_, ptr_end_);
+}
+
+bool WebMWebVTTParser::GetByte(uint8* byte) {
+ if (ptr_ >= ptr_end_)
+ return false; // indicates end-of-stream
+
+ *byte = *ptr_++;
+ return true;
+}
+
+void WebMWebVTTParser::UngetByte() {
+ --ptr_;
+}
+
+void WebMWebVTTParser::ParseLine(std::string* line) {
+ line->clear();
+
+ // Consume characters from the stream, until we reach end-of-line.
+
+ // The WebVTT spec states that lines may be terminated in any of the following
+ // three ways:
+ // LF
+ // CR
+ // CR LF
+
+ // The spec is here:
+ // http://wiki.webmproject.org/webm-metadata/temporal-metadata/webvtt-in-webm
+
+ enum {
+ kLF = '\x0A',
+ kCR = '\x0D'
+ };
+
+ for (;;) {
+ uint8 byte;
+
+ if (!GetByte(&byte) || byte == kLF)
+ return;
+
+ if (byte == kCR) {
+ if (GetByte(&byte) && byte != kLF)
+ UngetByte();
+
+ return;
+ }
+
+ line->push_back(byte);
+ }
+}
+
+} // namespace media
diff --git a/chromium/media/formats/webm/webm_webvtt_parser.h b/chromium/media/formats/webm/webm_webvtt_parser.h
new file mode 100644
index 00000000000..12bbbd4dd29
--- /dev/null
+++ b/chromium/media/formats/webm/webm_webvtt_parser.h
@@ -0,0 +1,49 @@
+// Copyright 2014 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_FORMATS_WEBM_WEBM_WEBVTT_PARSER_H_
+#define MEDIA_FORMATS_WEBM_WEBM_WEBVTT_PARSER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+class MEDIA_EXPORT WebMWebVTTParser {
+ public:
+ // Utility function to parse the WebVTT cue from a byte stream.
+ static void Parse(const uint8* payload, int payload_size,
+ std::string* id,
+ std::string* settings,
+ std::string* content);
+
+ private:
+ // The payload is the embedded WebVTT cue, stored in a WebM block.
+ // The parser treats this as a UTF-8 byte stream.
+ WebMWebVTTParser(const uint8* payload, int payload_size);
+
+ // Parse the cue identifier, settings, and content from the stream.
+ void Parse(std::string* id, std::string* settings, std::string* content);
+ // Remove a byte from the stream, advancing the stream pointer.
+ // Returns true if a character was returned; false means "end of stream".
+ bool GetByte(uint8* byte);
+
+ // Backup the stream pointer.
+ void UngetByte();
+
+ // Parse a line of text from the stream.
+ void ParseLine(std::string* line);
+
+ // Represents the portion of the stream that has not been consumed yet.
+ const uint8* ptr_;
+ const uint8* const ptr_end_;
+
+ DISALLOW_COPY_AND_ASSIGN(WebMWebVTTParser);
+};
+
+} // namespace media
+
+#endif // MEDIA_FORMATS_WEBM_WEBM_WEBVTT_PARSER_H_
diff --git a/chromium/media/formats/webm/webm_webvtt_parser_unittest.cc b/chromium/media/formats/webm/webm_webvtt_parser_unittest.cc
new file mode 100644
index 00000000000..ecdabd42975
--- /dev/null
+++ b/chromium/media/formats/webm/webm_webvtt_parser_unittest.cc
@@ -0,0 +1,105 @@
+// Copyright 2014 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/formats/webm/webm_webvtt_parser.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::InSequence;
+
+namespace media {
+
+typedef std::vector<uint8> Cue;
+
+static Cue EncodeCue(const std::string& id,
+ const std::string& settings,
+ const std::string& content) {
+ const std::string result = id + '\n' + settings + '\n' + content;
+ const uint8* const buf = reinterpret_cast<const uint8*>(result.data());
+ return Cue(buf, buf + result.length());
+}
+
+static void DecodeCue(const Cue& cue,
+ std::string* id,
+ std::string* settings,
+ std::string* content) {
+ WebMWebVTTParser::Parse(&cue[0], static_cast<int>(cue.size()),
+ id, settings, content);
+}
+
+class WebMWebVTTParserTest : public testing::Test {
+ public:
+ WebMWebVTTParserTest() {}
+};
+
+TEST_F(WebMWebVTTParserTest, Blank) {
+ InSequence s;
+
+ const Cue cue = EncodeCue("", "", "Subtitle");
+ std::string id, settings, content;
+
+ DecodeCue(cue, &id, &settings, &content);
+ EXPECT_EQ(id, "");
+ EXPECT_EQ(settings, "");
+ EXPECT_EQ(content, "Subtitle");
+}
+
+TEST_F(WebMWebVTTParserTest, Id) {
+ InSequence s;
+
+ for (int i = 1; i <= 9; ++i) {
+ const std::string idsrc(1, '0'+i);
+ const Cue cue = EncodeCue(idsrc, "", "Subtitle");
+ std::string id, settings, content;
+
+ DecodeCue(cue, &id, &settings, &content);
+ EXPECT_EQ(id, idsrc);
+ EXPECT_EQ(settings, "");
+ EXPECT_EQ(content, "Subtitle");
+ }
+}
+
+TEST_F(WebMWebVTTParserTest, Settings) {
+ InSequence s;
+
+ enum { kSettingsCount = 4 };
+ const char* const settings_str[kSettingsCount] = {
+ "vertical:lr",
+ "line:50%",
+ "position:42%",
+ "vertical:rl line:42% position:100%" };
+
+ for (int i = 0; i < kSettingsCount; ++i) {
+ const Cue cue = EncodeCue("", settings_str[i], "Subtitle");
+ std::string id, settings, content;
+
+ DecodeCue(cue, &id, &settings, &content);
+ EXPECT_EQ(id, "");
+ EXPECT_EQ(settings, settings_str[i]);
+ EXPECT_EQ(content, "Subtitle");
+ }
+}
+
+TEST_F(WebMWebVTTParserTest, Content) {
+ InSequence s;
+
+ enum { kContentCount = 4 };
+ const char* const content_str[kContentCount] = {
+ "Subtitle",
+ "Another Subtitle",
+ "Yet Another Subtitle",
+ "Another Subtitle\nSplit Across Two Lines" };
+
+ for (int i = 0; i < kContentCount; ++i) {
+ const Cue cue = EncodeCue("", "", content_str[i]);
+ std::string id, settings, content;
+
+ DecodeCue(cue, &id, &settings, &content);
+ EXPECT_EQ(id, "");
+ EXPECT_EQ(settings, "");
+ EXPECT_EQ(content, content_str[i]);
+ }
+}
+
+} // namespace media