Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "media/webm/chromeos/webm_encoder.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/file_util.h" | |
| 9 #include "base/logging.h" | |
| 10 #include "base/memory/scoped_generic_obj.h" | |
| 11 #include "libyuv/convert.h" | |
| 12 #include "libyuv/video_common.h" | |
| 13 #include "third_party/skia/include/core/SkBitmap.h" | |
| 14 | |
| 15 extern "C" { | |
| 16 // Getting the right degree of C compatibility has been a constant struggle. | |
| 17 // - Stroustrup, C++ Report, 12(7), July/August 2000. | |
| 18 #define private priv | |
| 19 #include "third_party/libvpx/source/libvpx/libmkv/EbmlIDs.h" | |
| 20 #include "third_party/libvpx/source/libvpx/libmkv/EbmlWriter.h" | |
| 21 #undef private | |
| 22 } | |
| 23 | |
| 24 // Number of encoder threads to use. | |
| 25 static const int kNumEncoderThreads = 2; | |
| 26 | |
| 27 // Need a fixed size serializer for the track ID. libmkv provides a 64 bit | |
| 28 // one, but not a 32 bit one. | |
| 29 static void Ebml_SerializeUnsigned32(EbmlGlobal* ebml, | |
| 30 unsigned long class_id, | |
| 31 uint64_t value) { | |
| 32 uint8 size_serialized = 4 | 0x80; | |
| 33 Ebml_WriteID(ebml, class_id); | |
| 34 Ebml_Serialize(ebml, &size_serialized, sizeof(size_serialized), 1); | |
| 35 Ebml_Serialize(ebml, &value, sizeof(value), 4); | |
| 36 } | |
| 37 | |
| 38 // Wrapper functor for vpx_codec_destroy(). | |
| 39 class VpxCodecDestroyHelper { | |
| 40 public: | |
| 41 void operator()(vpx_codec_ctx_t* codec) { | |
| 42 vpx_codec_destroy(codec); | |
| 43 } | |
| 44 }; | |
| 45 | |
| 46 namespace media { | |
| 47 | |
| 48 namespace chromeos { | |
| 49 | |
| 50 WebmEncoder::WebmEncoder(const FilePath& output_path, | |
| 51 int bitrate, | |
| 52 bool realtime) | |
| 53 : bitrate_(bitrate), | |
| 54 deadline_(realtime ? VPX_DL_REALTIME : VPX_DL_GOOD_QUALITY), | |
| 55 output_path_(output_path) { | |
| 56 ebml_writer_.write_cb = base::Bind( | |
| 57 &WebmEncoder::EbmlWrite, base::Unretained(this)); | |
| 58 ebml_writer_.serialize_cb = base::Bind( | |
| 59 &WebmEncoder::EbmlSerialize, base::Unretained(this)); | |
| 60 } | |
| 61 | |
| 62 bool WebmEncoder::EncodeFromSprite(const SkBitmap& sprite, | |
| 63 int fps_n, | |
| 64 int fps_d) { | |
| 65 DCHECK(!sprite.isNull() && !sprite.empty()); | |
|
scherkus (not reviewing)
2012/07/20 19:20:51
nit: separate the DCHECKs
Ivan Korotkov
2012/07/23 18:57:52
Done.
| |
| 66 | |
| 67 width_ = sprite.width(); | |
| 68 height_ = sprite.width(); | |
| 69 fps_.num = fps_n; | |
| 70 fps_.den = fps_d; | |
| 71 | |
| 72 size_t y_size = width_ * height_; | |
| 73 size_t uv_size = ((width_ + 1) / 2) * ((height_ + 1) / 2); | |
| 74 size_t frame_size = y_size + 2 * uv_size; | |
| 75 size_t frame_count = sprite.height() / width_; // Sprite is tiled vertically. | |
|
scherkus (not reviewing)
2012/07/20 19:20:51
do we need to do any alignment / padding for our y
Ivan Korotkov
2012/07/23 18:57:52
Right, libyuv states that 16-byte alignment is mor
| |
| 76 | |
| 77 yuv_frame_.reset(new uint8[frame_size]); | |
| 78 | |
| 79 vpx_image_t image; | |
| 80 vpx_img_wrap(&image, VPX_IMG_FMT_I420, width_, height_, 1, yuv_frame_.get()); | |
| 81 | |
| 82 vpx_codec_ctx_t codec; | |
| 83 const vpx_codec_iface_t* codec_iface = vpx_codec_vp8_cx(); | |
| 84 DCHECK(codec_iface); | |
| 85 vpx_codec_err_t ret = vpx_codec_enc_config_default(codec_iface, &config_, 0); | |
| 86 DCHECK_EQ(VPX_CODEC_OK, ret); | |
| 87 | |
| 88 config_.rc_target_bitrate = bitrate_; | |
| 89 config_.g_w = width_; | |
| 90 config_.g_h = height_; | |
| 91 config_.g_pass = VPX_RC_ONE_PASS; | |
| 92 config_.g_profile = 0; // Default profile. | |
| 93 config_.g_threads = kNumEncoderThreads; | |
| 94 config_.rc_min_quantizer = 0; | |
| 95 config_.rc_max_quantizer = 63; // Maximum possible range. | |
| 96 config_.g_timebase.num = fps_.den; | |
| 97 config_.g_timebase.den = fps_.num; | |
| 98 config_.kf_mode = VPX_KF_AUTO; // Auto key frames. | |
| 99 | |
| 100 ret = vpx_codec_enc_init(&codec, codec_iface, &config_, 0); | |
| 101 if (VPX_CODEC_OK != ret) | |
| 102 return false; | |
| 103 // Ensure that codec context is freed after return. | |
| 104 ScopedGenericObj<vpx_codec_ctx_t*, VpxCodecDestroyHelper> codec_ptr(&codec); | |
| 105 | |
| 106 SkAutoLockPixels lock_sprite(sprite); | |
| 107 | |
| 108 const uint8* src = reinterpret_cast<const uint8*>(sprite.getAddr32(0, 0)); | |
| 109 size_t src_frame_size = sprite.getSize(); | |
| 110 int crop_y = 0; | |
| 111 | |
| 112 if (!WriteWebmHeader()) | |
| 113 return false; | |
| 114 | |
| 115 for (size_t frame = 0; frame < frame_count; ++frame) { | |
| 116 int res = libyuv::ConvertToI420( | |
| 117 src, src_frame_size, | |
|
scherkus (not reviewing)
2012/07/20 19:20:51
sanity check: does libyuv have any assumptions on
| |
| 118 image.planes[VPX_PLANE_Y], image.stride[VPX_PLANE_Y], | |
| 119 image.planes[VPX_PLANE_U], image.stride[VPX_PLANE_U], | |
| 120 image.planes[VPX_PLANE_V], image.stride[VPX_PLANE_V], | |
| 121 0, crop_y, // src origin | |
| 122 width_, sprite.height(), // src size | |
| 123 width_, height_, // dest size | |
| 124 libyuv::kRotate0, | |
| 125 libyuv::FOURCC_ARGB); | |
| 126 if (res) | |
| 127 return false; | |
| 128 crop_y += height_; | |
| 129 | |
| 130 ret = vpx_codec_encode(&codec, &image, frame, 1, 0, deadline_); | |
| 131 if (VPX_CODEC_OK != ret) | |
| 132 return false; | |
| 133 | |
| 134 vpx_codec_iter_t iter = NULL; | |
| 135 const vpx_codec_cx_pkt_t* packet; | |
| 136 while ((packet = vpx_codec_get_cx_data(&codec, &iter))) { | |
| 137 if (packet->kind == VPX_CODEC_CX_FRAME_PKT) | |
| 138 WriteWebmBlock(packet); | |
| 139 } | |
| 140 } | |
| 141 | |
| 142 return WriteWebmFooter(); | |
| 143 } | |
| 144 | |
| 145 bool WebmEncoder::WriteWebmHeader() { | |
| 146 output_ = file_util::OpenFile(output_path_, "wb"); | |
| 147 if (!output_) | |
| 148 return false; | |
| 149 | |
| 150 // Global header. | |
| 151 StartSubElement(EBML); { | |
|
scherkus (not reviewing)
2012/07/20 19:20:51
we have a style guide for a reason and I don't fin
Ivan Korotkov
2012/07/23 18:57:52
Done.
| |
| 152 Ebml_SerializeUnsigned(&ebml_writer_, EBMLVersion, 1); | |
| 153 Ebml_SerializeUnsigned(&ebml_writer_, EBMLReadVersion, 1); | |
| 154 Ebml_SerializeUnsigned(&ebml_writer_, EBMLMaxIDLength, 4); | |
| 155 Ebml_SerializeUnsigned(&ebml_writer_, EBMLMaxSizeLength, 8); | |
| 156 Ebml_SerializeString(&ebml_writer_, DocType, "webm"); | |
| 157 Ebml_SerializeUnsigned(&ebml_writer_, DocTypeVersion, 2); | |
| 158 Ebml_SerializeUnsigned(&ebml_writer_, DocTypeReadVersion, 2); | |
| 159 } EndSubElement(); // EBML | |
| 160 | |
| 161 // Single segment with a video track. | |
| 162 StartSubElement(Segment); { | |
| 163 StartSubElement(Info); { | |
| 164 // All timecodes in the segment will be expressed in milliseconds. | |
| 165 Ebml_SerializeUnsigned(&ebml_writer_, TimecodeScale, 1000000); | |
| 166 } EndSubElement(); // Info | |
| 167 | |
| 168 StartSubElement(Tracks); { | |
| 169 StartSubElement(TrackEntry); { | |
| 170 Ebml_SerializeUnsigned(&ebml_writer_, TrackNumber, 1); | |
| 171 Ebml_SerializeUnsigned32(&ebml_writer_, TrackUID, 1); | |
| 172 Ebml_SerializeUnsigned(&ebml_writer_, TrackType, 1); // Video | |
| 173 Ebml_SerializeString(&ebml_writer_, CodecID, "V_VP8"); | |
| 174 | |
| 175 StartSubElement(Video); { | |
| 176 Ebml_SerializeUnsigned(&ebml_writer_, PixelWidth, width_); | |
| 177 Ebml_SerializeUnsigned(&ebml_writer_, PixelHeight, height_); | |
| 178 Ebml_SerializeUnsigned(&ebml_writer_, StereoMode, 0); // Mono | |
| 179 float fps = static_cast<float>(fps_.num) / fps_.den; | |
| 180 Ebml_SerializeFloat(&ebml_writer_, FrameRate, fps); | |
| 181 } EndSubElement(); // Video | |
| 182 } EndSubElement(); // TrackEntry | |
| 183 } EndSubElement(); // Tracks | |
| 184 | |
| 185 StartSubElement(Cluster); { | |
| 186 Ebml_SerializeUnsigned(&ebml_writer_, Timecode, 0); | |
| 187 } // Cluster left open. | |
| 188 } // Segment left open. | |
| 189 | |
| 190 return true; | |
| 191 } | |
| 192 | |
| 193 void WebmEncoder::WriteWebmBlock(const vpx_codec_cx_pkt_t* packet) { | |
| 194 bool is_keyframe = packet->data.frame.flags & VPX_FRAME_IS_KEY; | |
| 195 int64_t pts_ms = 1000 * packet->data.frame.pts * fps_.den / fps_.num; | |
| 196 | |
| 197 DVLOG(1) << "Video packet @" << pts_ms << " ms " | |
| 198 << packet->data.frame.sz << " bytes " | |
| 199 << (is_keyframe ? "K" : ""); | |
| 200 | |
| 201 Ebml_WriteID(&ebml_writer_, SimpleBlock); | |
| 202 | |
| 203 uint32 block_length = (packet->data.frame.sz + 4) | 0x10000000; | |
| 204 EbmlSerializeHelper(&block_length, 4); | |
| 205 | |
| 206 uint8 track_number = 1 | 0x80; | |
| 207 EbmlSerializeHelper(&track_number, 1); | |
| 208 | |
| 209 EbmlSerializeHelper(&pts_ms, 2); | |
| 210 | |
| 211 uint8 flags = 0; | |
| 212 if (is_keyframe) | |
| 213 flags |= 0x80; | |
| 214 if (packet->data.frame.flags & VPX_FRAME_IS_INVISIBLE) | |
| 215 flags |= 0x08; | |
| 216 EbmlSerializeHelper(&flags, 1); | |
| 217 | |
| 218 EbmlWrite(packet->data.frame.buf, packet->data.frame.sz); | |
| 219 } | |
| 220 | |
| 221 bool WebmEncoder::WriteWebmFooter() { | |
| 222 EndSubElement(); // Cluster | |
| 223 EndSubElement(); // Segment | |
| 224 DCHECK(ebml_sub_elements_.empty()); | |
| 225 return file_util::CloseFile(output_); | |
| 226 } | |
| 227 | |
| 228 void WebmEncoder::StartSubElement(unsigned long class_id) { | |
| 229 Ebml_WriteID(&ebml_writer_, class_id); | |
| 230 ebml_sub_elements_.push(ftell(output_)); | |
| 231 static const uint64_t kUnknownLen = 0x01FFFFFFFFFFFFFFLLU; | |
| 232 EbmlSerializeHelper(&kUnknownLen, 8); | |
| 233 } | |
| 234 | |
| 235 void WebmEncoder::EndSubElement() { | |
| 236 DCHECK(!ebml_sub_elements_.empty()); | |
| 237 | |
| 238 long int end_pos = ftell(output_); | |
| 239 long int start_pos = ebml_sub_elements_.top(); | |
| 240 ebml_sub_elements_.pop(); | |
| 241 | |
| 242 uint64_t size = (end_pos - start_pos - 8) | 0x0100000000000000ULL; | |
| 243 // Seek to the beginning of the sub-element and patch in the calculated size. | |
| 244 if (fseek(output_, start_pos, SEEK_SET)) | |
| 245 LOG(ERROR) << "Error writing to " << output_path_.value(); | |
| 246 EbmlSerializeHelper(&size, 8); | |
| 247 | |
| 248 // Restore write position. | |
| 249 if (fseek(output_, end_pos, SEEK_SET)) | |
| 250 LOG(ERROR) << "Error writing to " << output_path_.value(); | |
| 251 } | |
| 252 | |
| 253 void WebmEncoder::EbmlWrite(const void* buffer, | |
| 254 unsigned long len) { | |
| 255 if (fwrite(buffer, 1, len, output_) != len) | |
|
Nikita (slow)
2012/07/20 15:26:09
Please flip error bit on WebmEncoder instance if f
| |
| 256 LOG(ERROR) << "Error writing to " << output_path_.value(); | |
| 257 } | |
| 258 | |
| 259 template <class T> | |
| 260 void WebmEncoder::EbmlSerializeHelper(const T* buffer, unsigned long len) { | |
| 261 for (int i = len - 1; i >= 0; i--) { | |
| 262 uint8 c = *buffer >> (i * CHAR_BIT); | |
| 263 EbmlWrite(&c, 1); | |
| 264 } | |
| 265 } | |
| 266 | |
| 267 void WebmEncoder::EbmlSerialize(const void* buffer, | |
| 268 int buffer_size, | |
| 269 unsigned long len) { | |
| 270 switch (buffer_size) { | |
| 271 case 1: | |
| 272 return EbmlSerializeHelper(static_cast<const int8_t*>(buffer), len); | |
| 273 case 2: | |
| 274 return EbmlSerializeHelper(static_cast<const int16_t*>(buffer), len); | |
| 275 case 4: | |
| 276 return EbmlSerializeHelper(static_cast<const int32_t*>(buffer), len); | |
| 277 case 8: | |
| 278 return EbmlSerializeHelper(static_cast<const int64_t*>(buffer), len); | |
| 279 default: | |
| 280 NOTREACHED() << "Invalid EbmlSerialize length: " << len; | |
| 281 } | |
| 282 } | |
| 283 | |
| 284 } // namespace chromeos | |
| 285 | |
| 286 } // namespace media | |
| OLD | NEW |