Chromium Code Reviews| Index: chrome/browser/chromeos/login/webm_encoder.cc |
| diff --git a/chrome/browser/chromeos/login/webm_encoder.cc b/chrome/browser/chromeos/login/webm_encoder.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..2ee1fcc42cc15e6d3a9068a7bec76dea2ff7c204 |
| --- /dev/null |
| +++ b/chrome/browser/chromeos/login/webm_encoder.cc |
| @@ -0,0 +1,283 @@ |
| +// 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 "chrome/browser/chromeos/login/webm_encoder.h" |
| + |
| +#include "base/bind.h" |
| +#include "base/file_util.h" |
| +#include "base/logging.h" |
| +#include "content/public/browser/browser_thread.h" |
| +#include "libyuv/convert.h" |
| +#include "libyuv/video_common.h" |
| +#include "third_party/skia/include/core/SkBitmap.h" |
| +#include "ui/gfx/image/image_skia.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 |
| +} |
| + |
| +using content::BrowserThread; |
| + |
| +namespace { |
| + |
| +// Number of encoder threads to use. |
| +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. |
| +void Ebml_SerializeUnsigned32(EbmlGlobal *ebml, |
| + unsigned long class_id, |
| + uint64_t value) { |
| + unsigned char 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); |
| +} |
| + |
| +} // namespace |
| + |
| +namespace chromeos { |
| + |
| +WebmEncoder::WebmEncoder(const FilePath& output_path, |
| + unsigned bitrate, |
| + bool realtime) |
| + : bitrate_(bitrate), |
| + deadline_(realtime ? VPX_DL_REALTIME : VPX_DL_GOOD_QUALITY), |
| + output_path_(output_path) { |
| + ebml_writer_.write_cb = base::Bind( |
| + &WebmEncoder::EbmlWrite, base::Unretained(this)); |
| + ebml_writer_.serialize_cb = base::Bind( |
| + &WebmEncoder::EbmlSerialize, base::Unretained(this)); |
| +} |
| + |
| +void WebmEncoder::EncodeFromSprite(const SkBitmap& sprite, |
| + int fps_n, |
| + int fps_d) { |
| + DCHECK(!sprite.isNull() && !sprite.empty()); |
| + |
| + width_ = sprite.width(); |
| + height_ = sprite.width(); |
| + fps_.num = fps_n; |
| + fps_.den = fps_d; |
| + |
| + size_t uv_stride = (width_ + 1) / 2; |
| + size_t y_size = width_ * height_; |
| + size_t uv_size = uv_stride * uv_stride; |
| + size_t frame_size = y_size + 2 * uv_size; |
| + size_t frame_count = sprite.height() / width_; // Sprite is tiled vertically. |
| + |
| + yuv_frame_.reset(new uint8[frame_size]); |
| + uint8* y = yuv_frame_.get(); |
| + uint8* u = y + y_size; |
| + uint8* v = y + y_size + uv_size; |
| + |
| + vpx_image_t image; |
|
jkoleszar
2012/07/17 18:33:34
Can simplify this block to:
vpx_img_wrap(&image
Ivan Korotkov
2012/07/18 12:35:10
Cool, thanks.
|
| + image.fmt = VPX_IMG_FMT_I420; |
| + image.w = width_; |
| + image.d_w = width_; |
| + image.h = height_; |
| + image.d_h = height_; |
| + image.planes[VPX_PLANE_Y] = y; |
| + image.stride[VPX_PLANE_Y] = width_; |
| + image.planes[VPX_PLANE_U] = u; |
| + image.stride[VPX_PLANE_U] = uv_stride; |
| + image.planes[VPX_PLANE_V] = v; |
| + image.stride[VPX_PLANE_V] = uv_stride; |
| + |
| + vpx_codec_ctx_t codec; |
| + 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. |
| + |
| + ret = vpx_codec_enc_init(&codec, codec_iface, &config_, 0); |
| + DCHECK_EQ(VPX_CODEC_OK, ret); |
| + |
| + 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; |
| + |
| + WriteWebmHeader(); |
| + |
| + for (size_t frame = 0; frame < frame_count; ++frame) { |
| + int res = libyuv::ConvertToI420( |
| + src, src_frame_size, |
| + y, width_, |
|
jkoleszar
2012/07/17 18:33:34
if you want, you can remove all these variables an
Ivan Korotkov
2012/07/18 12:35:10
Makes sense.
|
| + u, uv_stride, |
| + v, uv_stride, |
| + 0, crop_y, // src origin |
| + width_, sprite.height(), // src size |
| + width_, height_, // dest size |
| + libyuv::kRotate0, |
| + libyuv::FOURCC_ARGB); |
| + DCHECK_EQ(0, res); |
| + crop_y += height_; |
| + |
| + ret = vpx_codec_encode(&codec, &image, frame, 1, 0, deadline_); |
| + DCHECK_EQ(VPX_CODEC_OK, ret); |
| + |
| + vpx_codec_iter_t iter = NULL; |
| + const vpx_codec_cx_pkt_t* packet; |
| + while ((packet = vpx_codec_get_cx_data(&codec, &iter))) { |
| + if (packet->kind == VPX_CODEC_CX_FRAME_PKT) |
| + WriteWebmBlock(packet); |
| + } |
| + } |
| + |
| + vpx_codec_destroy(&codec); |
| + |
| + WriteWebmFooter(); |
| +} |
| + |
| +void WebmEncoder::WriteWebmHeader() { |
| + output_ = file_util::OpenFile(output_path_, "wb"); |
| + DCHECK(output_); |
| + |
| + // 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. |
| +} |
| + |
| +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 * |
| + static_cast<uint64_t>(fps_.den) / fps_.num * packet->data.frame.pts; |
|
jkoleszar
2012/07/17 18:33:34
multiply by pts before the divide.
Ivan Korotkov
2012/07/18 12:35:10
Done.
|
| + |
| + DVLOG(1) << "Video packet @" << pts_ms << " ms " |
| + << packet->data.frame.sz << " bytes " |
| + << (is_keyframe ? "K" : ""); |
| + |
| + Ebml_WriteID(&ebml_writer_, SimpleBlock); |
| + |
| + unsigned long block_length = (packet->data.frame.sz + 4) | 0x10000000; |
| + EbmlSerializeHelper(&block_length, 4); |
| + |
| + unsigned char track_number = 1 | 0x80; |
| + EbmlSerializeHelper(&track_number, 1); |
| + |
| + EbmlSerializeHelper(&pts_ms, 2); |
| + |
| + unsigned char 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); |
| +} |
| + |
| +void WebmEncoder::WriteWebmFooter() { |
| + EndSubElement(); // Cluster |
| + EndSubElement(); // Segment |
| + DCHECK(ebml_sub_elements_.empty()); |
| + file_util::CloseFile(output_); |
| +} |
| + |
| +void WebmEncoder::StartSubElement(unsigned long class_id) { |
| + Ebml_WriteID(&ebml_writer_, class_id); |
| + ebml_sub_elements_.push(ftell(output_)); |
| + uint64_t unknown_len = 0x01FFFFFFFFFFFFFFLLU; |
| + Ebml_Serialize(&ebml_writer_, &unknown_len, sizeof(unknown_len), 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. |
| + fseek(output_, start_pos, SEEK_SET); |
| + EbmlSerializeHelper(&size, 8); |
| + |
| + // Restore write position. |
| + fseek(output_, end_pos, SEEK_SET); |
| +} |
| + |
| +void WebmEncoder::EbmlWrite(const void* buffer, |
| + unsigned long len) { |
| + unsigned long ret = fwrite(buffer, 1, len, output_); |
| + CHECK_EQ(len, ret); |
| +} |
| + |
| +template <class T> |
| +void WebmEncoder::EbmlSerializeHelper(const T* buffer, unsigned long len) { |
| + for (unsigned long i = len; i-- > 0; ) { |
| + char 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(); |
| + } |
| +} |
| + |
| +} // namespace chromeos |