diff options
author | ivankr@chromium.org <ivankr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-07-24 16:22:05 +0000 |
---|---|---|
committer | ivankr@chromium.org <ivankr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-07-24 16:22:05 +0000 |
commit | 4db586cdd3f168546c370ea0b123f306e811d6be (patch) | |
tree | dc5a9f73f19db00067d337d9aef214fd49bf07c2 /media/webm | |
parent | db9aaab153b9b0a7fd452cadaffd45b9f7fca204 (diff) | |
download | chromium_src-4db586cdd3f168546c370ea0b123f306e811d6be.zip chromium_src-4db586cdd3f168546c370ea0b123f306e811d6be.tar.gz chromium_src-4db586cdd3f168546c370ea0b123f306e811d6be.tar.bz2 |
Reland 148024 - [cros] Implement WebM encoder/muxer for animated avatar capture.
TBR=nkostylev
BUG=132423
TEST=None
Review URL: https://chromiumcodereview.appspot.com/10817022
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@148117 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/webm')
-rw-r--r-- | media/webm/chromeos/DEPS | 4 | ||||
-rw-r--r-- | media/webm/chromeos/ebml_writer.cc | 33 | ||||
-rw-r--r-- | media/webm/chromeos/ebml_writer.h | 21 | ||||
-rw-r--r-- | media/webm/chromeos/webm_encoder.cc | 321 | ||||
-rw-r--r-- | media/webm/chromeos/webm_encoder.h | 99 |
5 files changed, 478 insertions, 0 deletions
diff --git a/media/webm/chromeos/DEPS b/media/webm/chromeos/DEPS new file mode 100644 index 0000000..a4378dc --- /dev/null +++ b/media/webm/chromeos/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+libyuv", + "+third_party/libvpx", +] diff --git a/media/webm/chromeos/ebml_writer.cc b/media/webm/chromeos/ebml_writer.cc new file mode 100644 index 0000000..5c5f07d --- /dev/null +++ b/media/webm/chromeos/ebml_writer.cc @@ -0,0 +1,33 @@ +// Copyright (c) 2012 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/webm/chromeos/ebml_writer.h" + +#include "media/base/media_export.h" + +extern "C" { +#include "third_party/libvpx/source/libvpx/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/media/webm/chromeos/ebml_writer.h b/media/webm/chromeos/ebml_writer.h new file mode 100644 index 0000000..0714ebf --- /dev/null +++ b/media/webm/chromeos/ebml_writer.h @@ -0,0 +1,21 @@ +// Copyright (c) 2012 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_WEBM_CHROMEOS_EBML_WRITER_H_ +#define MEDIA_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_WEBM_CHROMEOS_EBML_WRITER_H_ diff --git a/media/webm/chromeos/webm_encoder.cc b/media/webm/chromeos/webm_encoder.cc new file mode 100644 index 0000000..0bc52d6 --- /dev/null +++ b/media/webm/chromeos/webm_encoder.cc @@ -0,0 +1,321 @@ +// Copyright (c) 2012 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/webm/chromeos/webm_encoder.h" + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/memory/scoped_generic_obj.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/libmkv/EbmlIDs.h" +#include "third_party/libvpx/source/libvpx/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(). +class VpxCodecDestroyHelper { + public: + void operator()(vpx_codec_ctx_t* codec) { + vpx_codec_destroy(codec); + } +}; + +// Wrapper functor for vpx_img_free(). +class VpxImgFreeHelper { + public: + void operator()(vpx_image_t* image) { + vpx_img_free(image); + } +}; + +namespace media { + +namespace chromeos { + +WebmEncoder::WebmEncoder(const 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. + size_t 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. + ScopedGenericObj<vpx_image_t*, VpxImgFreeHelper> 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. + ScopedGenericObj<vpx_codec_ctx_t*, VpxCodecDestroyHelper> 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_ = file_util::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); + } + 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 file_util::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/media/webm/chromeos/webm_encoder.h b/media/webm/chromeos/webm_encoder.h new file mode 100644 index 0000000..9bae5b9 --- /dev/null +++ b/media/webm/chromeos/webm_encoder.h @@ -0,0 +1,99 @@ +// Copyright (c) 2012 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_WEBM_CHROMEOS_WEBM_ENCODER_H_ +#define MEDIA_WEBM_CHROMEOS_WEBM_ENCODER_H_ + +#include <stack> +#include <stdio.h> + +#include "base/file_path.h" +#include "media/base/media_export.h" +#include "media/webm/chromeos/ebml_writer.h" + +extern "C" { +#define VPX_CODEC_DISABLE_COMPAT 1 +#include "third_party/libvpx/libvpx.h" +} + +class FilePath; +class SkBitmap; + +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 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_; + + // 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_; + + 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_WEBM_CHROMEOS_WEBM_ENCODER_H_ |