Index: media/webm/chromeos/webm_encoder.cc |
diff --git a/media/webm/chromeos/webm_encoder.cc b/media/webm/chromeos/webm_encoder.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ed600168f826b8bd1b9551325168eac5182518f5 |
--- /dev/null |
+++ b/media/webm/chromeos/webm_encoder.cc |
@@ -0,0 +1,268 @@ |
+// 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 "libyuv/convert.h" |
scherkus (not reviewing)
2012/07/19 21:46:03
shouldn't this be third_party/libyuv/...?
Ivan Korotkov
2012/07/20 11:10:36
libyuv.gyp adds third_party/libyuv/include/ to -D,
|
+#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" |
scherkus (not reviewing)
2012/07/19 21:46:03
what's going on here?
is there a particular heade
Ivan Korotkov
2012/07/20 11:10:36
One of functions in EbmlWriter.h has argument name
scherkus (not reviewing)
2012/07/20 19:20:51
Are we planning on fixing it upstream?
Ivan Korotkov
2012/07/23 18:57:52
Not sure. This code not used anywhere but in vpxen
|
+#include "third_party/libvpx/source/libvpx/libmkv/EbmlWriter.h" |
+#undef private |
+} |
+ |
+namespace { |
+ |
+// Number of encoder threads to use. |
+const int kNumEncoderThreads = 2; |
scherkus (not reviewing)
2012/07/19 21:46:03
the rest of the media codebase uses static over an
Ivan Korotkov
2012/07/20 11:10:36
Done.
|
+ |
+// 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, |
Nikita (slow)
2012/07/19 08:37:12
nit: EbmlGlobal* ebml
scherkus (not reviewing)
2012/07/19 21:46:03
pointers go with types
EbmlGlobal* embl
Ivan Korotkov
2012/07/20 09:09:29
Done.
|
+ unsigned long class_id, |
+ uint64_t value) { |
+ unsigned char size_serialized = 4 | 0x80; |
scherkus (not reviewing)
2012/07/19 21:46:03
prefer uint8 over unsigned keyword
Ivan Korotkov
2012/07/20 11:10:36
Done.
|
+ Ebml_WriteID(ebml, class_id); |
+ Ebml_Serialize(ebml, &size_serialized, sizeof(size_serialized), 1); |
+ Ebml_Serialize(ebml, &value, sizeof(value), 4); |
+} |
+ |
+} // namespace |
+ |
+namespace media { |
+ |
+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( |
scherkus (not reviewing)
2012/07/19 21:46:03
what happens if I use two of these classes at the
Ivan Korotkov
2012/07/20 11:10:36
What's the problem with this? Each instance would
|
+ &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()); |
scherkus (not reviewing)
2012/07/19 21:46:03
nit: separate the DCHECKs
Ivan Korotkov
2012/07/23 18:57:52
Done.
|
+ |
+ width_ = sprite.width(); |
+ height_ = sprite.width(); |
+ fps_.num = fps_n; |
+ fps_.den = fps_d; |
+ |
+ size_t y_size = width_ * height_; |
+ size_t uv_size = ((width_ + 1) / 2) * ((height_ + 1) / 2); |
+ 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]); |
+ |
+ vpx_image_t image; |
+ vpx_img_wrap(&image, VPX_IMG_FMT_I420, width_, height_, 1, yuv_frame_.get()); |
+ |
+ 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; |
Nikita (slow)
2012/07/19 08:37:12
nit: align comments if possible
Ivan Korotkov
2012/07/20 09:09:29
Done.
|
+ 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); |
Nikita (slow)
2012/07/19 08:37:12
Pass error to upper level if that fails
Ivan Korotkov
2012/07/20 09:09:29
Done.
|
+ |
+ 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, |
+ 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); |
+ DCHECK_EQ(0, res); |
Nikita (slow)
2012/07/19 08:37:12
Pass error to upper level if that fails?
Ivan Korotkov
2012/07/20 09:09:29
Done.
|
+ crop_y += height_; |
+ |
+ ret = vpx_codec_encode(&codec, &image, frame, 1, 0, deadline_); |
+ DCHECK_EQ(VPX_CODEC_OK, ret); |
Nikita (slow)
2012/07/19 08:37:12
Same here.
Ivan Korotkov
2012/07/20 09:09:29
Done.
|
+ |
+ 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); { |
Nikita (slow)
2012/07/19 08:37:12
How about
StartSubElement(EBML);
{
...
}
EndSub
Ivan Korotkov
2012/07/20 09:09:29
IMO that looks against Chrome's style. This one fo
|
+ 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. |
Nikita (slow)
2012/07/19 08:37:12
nit: insert extra line
Ivan Korotkov
2012/07/20 09:09:29
Done.
|
+ StartSubElement(Segment); { |
Nikita (slow)
2012/07/19 08:37:12
nit: fix formatting as suggested
|
+ StartSubElement(Info); { |
+ // All timecodes in the segment will be expressed in milliseconds. |
+ Ebml_SerializeUnsigned(&ebml_writer_, TimecodeScale, 1000000); |
+ } EndSubElement(); // Info |
+ StartSubElement(Tracks); { |
Nikita (slow)
2012/07/19 08:37:12
nit: fix formatting as suggested
|
+ 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 * 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); |
+ |
+ unsigned long block_length = (packet->data.frame.sz + 4) | 0x10000000; |
scherkus (not reviewing)
2012/07/19 21:46:03
what's 0x10000000?
Ivan Korotkov
2012/07/20 11:10:36
I guess these higher bit masks (0x10000000, 0x80,
|
+ EbmlSerializeHelper(&block_length, 4); |
+ |
+ unsigned char track_number = 1 | 0x80; |
scherkus (not reviewing)
2012/07/19 21:46:03
what's 0x80?
|
+ EbmlSerializeHelper(&track_number, 1); |
+ |
+ EbmlSerializeHelper(&pts_ms, 2); |
+ |
+ unsigned char flags = 0; |
+ if (is_keyframe) |
+ flags |= 0x80; |
scherkus (not reviewing)
2012/07/19 21:46:03
are there no constants for flags?
Ivan Korotkov
2012/07/20 11:10:36
libvpx/webrtc use these as is, so I assume not.
Wh
|
+ 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; |
scherkus (not reviewing)
2012/07/19 21:46:03
no constant?
Ivan Korotkov
2012/07/20 11:10:36
Same here. I think it's "-1" EBML-encoded. Made it
|
+ 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); |
Nikita (slow)
2012/07/19 08:37:12
Check return value?
Ivan Korotkov
2012/07/20 09:09:29
Done.
|
+ EbmlSerializeHelper(&size, 8); |
+ |
+ // Restore write position. |
+ fseek(output_, end_pos, SEEK_SET); |
Nikita (slow)
2012/07/19 08:37:12
Check return value?
Ivan Korotkov
2012/07/20 09:09:29
Done.
|
+} |
+ |
+void WebmEncoder::EbmlWrite(const void* buffer, |
+ unsigned long len) { |
+ unsigned long ret = fwrite(buffer, 1, len, output_); |
+ CHECK_EQ(len, ret); |
Nikita (slow)
2012/07/19 08:37:12
Not sure that it makes sense to have a CHECK here.
Ivan Korotkov
2012/07/20 09:09:29
It's difficult to do with EbmlXxx interface. Maybe
|
+} |
+ |
+template <class T> |
+void WebmEncoder::EbmlSerializeHelper(const T* buffer, unsigned long len) { |
+ for (unsigned long i = len; i-- > 0; ) { |
scherkus (not reviewing)
2012/07/19 21:46:03
nit: let's try to keep conditions as they are and
Ivan Korotkov
2012/07/20 11:10:36
Ok, I guess we can use a signed iterator since len
|
+ 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(); |
Nikita (slow)
2012/07/19 08:37:12
nit: Add some error message?
Ivan Korotkov
2012/07/20 09:09:29
Done.
|
+ } |
+} |
+ |
+} // namespace chromeos |
+ |
+} // namespace media |