OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 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/formats/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_ptr.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/third_party/libmkv/EbmlIDs.h" |
| 20 #include "third_party/libvpx/source/libvpx/third_party/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 struct VpxCodecDeleter { |
| 40 void operator()(vpx_codec_ctx_t* codec) { |
| 41 vpx_codec_destroy(codec); |
| 42 } |
| 43 }; |
| 44 |
| 45 // Wrapper functor for vpx_img_free(). |
| 46 struct VpxImgDeleter { |
| 47 void operator()(vpx_image_t* image) { |
| 48 vpx_img_free(image); |
| 49 } |
| 50 }; |
| 51 |
| 52 namespace media { |
| 53 |
| 54 namespace chromeos { |
| 55 |
| 56 WebmEncoder::WebmEncoder(const base::FilePath& output_path, |
| 57 int bitrate, |
| 58 bool realtime) |
| 59 : bitrate_(bitrate), |
| 60 deadline_(realtime ? VPX_DL_REALTIME : VPX_DL_GOOD_QUALITY), |
| 61 output_path_(output_path), |
| 62 has_errors_(false) { |
| 63 ebml_writer_.write_cb = base::Bind( |
| 64 &WebmEncoder::EbmlWrite, base::Unretained(this)); |
| 65 ebml_writer_.serialize_cb = base::Bind( |
| 66 &WebmEncoder::EbmlSerialize, base::Unretained(this)); |
| 67 } |
| 68 |
| 69 WebmEncoder::~WebmEncoder() { |
| 70 } |
| 71 |
| 72 bool WebmEncoder::EncodeFromSprite(const SkBitmap& sprite, |
| 73 int fps_n, |
| 74 int fps_d) { |
| 75 DCHECK(!sprite.isNull()); |
| 76 DCHECK(!sprite.empty()); |
| 77 |
| 78 has_errors_ = false; |
| 79 width_ = sprite.width(); |
| 80 height_ = sprite.width(); |
| 81 fps_.num = fps_n; |
| 82 fps_.den = fps_d; |
| 83 |
| 84 // Sprite is tiled vertically. |
| 85 frame_count_ = sprite.height() / width_; |
| 86 |
| 87 vpx_image_t image; |
| 88 vpx_img_alloc(&image, VPX_IMG_FMT_I420, width_, height_, 16); |
| 89 // Ensure that image is freed after return. |
| 90 scoped_ptr<vpx_image_t, VpxImgDeleter> image_ptr(&image); |
| 91 |
| 92 const vpx_codec_iface_t* codec_iface = vpx_codec_vp8_cx(); |
| 93 DCHECK(codec_iface); |
| 94 vpx_codec_err_t ret = vpx_codec_enc_config_default(codec_iface, &config_, 0); |
| 95 DCHECK_EQ(VPX_CODEC_OK, ret); |
| 96 |
| 97 config_.rc_target_bitrate = bitrate_; |
| 98 config_.g_w = width_; |
| 99 config_.g_h = height_; |
| 100 config_.g_pass = VPX_RC_ONE_PASS; |
| 101 config_.g_profile = 0; // Default profile. |
| 102 config_.g_threads = kNumEncoderThreads; |
| 103 config_.rc_min_quantizer = 0; |
| 104 config_.rc_max_quantizer = 63; // Maximum possible range. |
| 105 config_.g_timebase.num = fps_.den; |
| 106 config_.g_timebase.den = fps_.num; |
| 107 config_.kf_mode = VPX_KF_AUTO; // Auto key frames. |
| 108 |
| 109 vpx_codec_ctx_t codec; |
| 110 ret = vpx_codec_enc_init(&codec, codec_iface, &config_, 0); |
| 111 if (ret != VPX_CODEC_OK) |
| 112 return false; |
| 113 // Ensure that codec context is freed after return. |
| 114 scoped_ptr<vpx_codec_ctx_t, VpxCodecDeleter> codec_ptr(&codec); |
| 115 |
| 116 SkAutoLockPixels lock_sprite(sprite); |
| 117 |
| 118 const uint8* src = reinterpret_cast<const uint8*>(sprite.getAddr32(0, 0)); |
| 119 size_t src_frame_size = sprite.getSize(); |
| 120 int crop_y = 0; |
| 121 |
| 122 if (!WriteWebmHeader()) |
| 123 return false; |
| 124 |
| 125 for (size_t frame = 0; frame < frame_count_ && !has_errors_; ++frame) { |
| 126 int res = libyuv::ConvertToI420( |
| 127 src, src_frame_size, |
| 128 image.planes[VPX_PLANE_Y], image.stride[VPX_PLANE_Y], |
| 129 image.planes[VPX_PLANE_U], image.stride[VPX_PLANE_U], |
| 130 image.planes[VPX_PLANE_V], image.stride[VPX_PLANE_V], |
| 131 0, crop_y, // src origin |
| 132 width_, sprite.height(), // src size |
| 133 width_, height_, // dest size |
| 134 libyuv::kRotate0, |
| 135 libyuv::FOURCC_ARGB); |
| 136 if (res) { |
| 137 has_errors_ = true; |
| 138 break; |
| 139 } |
| 140 crop_y += height_; |
| 141 |
| 142 ret = vpx_codec_encode(&codec, &image, frame, 1, 0, deadline_); |
| 143 if (ret != VPX_CODEC_OK) { |
| 144 has_errors_ = true; |
| 145 break; |
| 146 } |
| 147 |
| 148 vpx_codec_iter_t iter = NULL; |
| 149 const vpx_codec_cx_pkt_t* packet; |
| 150 while (!has_errors_ && (packet = vpx_codec_get_cx_data(&codec, &iter))) { |
| 151 if (packet->kind == VPX_CODEC_CX_FRAME_PKT) |
| 152 WriteWebmBlock(packet); |
| 153 } |
| 154 } |
| 155 |
| 156 return WriteWebmFooter(); |
| 157 } |
| 158 |
| 159 bool WebmEncoder::WriteWebmHeader() { |
| 160 output_ = base::OpenFile(output_path_, "wb"); |
| 161 if (!output_) |
| 162 return false; |
| 163 |
| 164 // Global header. |
| 165 StartSubElement(EBML); |
| 166 { |
| 167 Ebml_SerializeUnsigned(&ebml_writer_, EBMLVersion, 1); |
| 168 Ebml_SerializeUnsigned(&ebml_writer_, EBMLReadVersion, 1); |
| 169 Ebml_SerializeUnsigned(&ebml_writer_, EBMLMaxIDLength, 4); |
| 170 Ebml_SerializeUnsigned(&ebml_writer_, EBMLMaxSizeLength, 8); |
| 171 Ebml_SerializeString(&ebml_writer_, DocType, "webm"); |
| 172 Ebml_SerializeUnsigned(&ebml_writer_, DocTypeVersion, 2); |
| 173 Ebml_SerializeUnsigned(&ebml_writer_, DocTypeReadVersion, 2); |
| 174 } |
| 175 EndSubElement(); // EBML |
| 176 |
| 177 // Single segment with a video track. |
| 178 StartSubElement(Segment); |
| 179 { |
| 180 StartSubElement(Info); |
| 181 { |
| 182 // All timecodes in the segment will be expressed in milliseconds. |
| 183 Ebml_SerializeUnsigned(&ebml_writer_, TimecodeScale, 1000000); |
| 184 double duration = 1000. * frame_count_ * fps_.den / fps_.num; |
| 185 Ebml_SerializeFloat(&ebml_writer_, Segment_Duration, duration); |
| 186 } |
| 187 EndSubElement(); // Info |
| 188 |
| 189 StartSubElement(Tracks); |
| 190 { |
| 191 StartSubElement(TrackEntry); |
| 192 { |
| 193 Ebml_SerializeUnsigned(&ebml_writer_, TrackNumber, 1); |
| 194 Ebml_SerializeUnsigned32(&ebml_writer_, TrackUID, 1); |
| 195 Ebml_SerializeUnsigned(&ebml_writer_, TrackType, 1); // Video |
| 196 Ebml_SerializeString(&ebml_writer_, CodecID, "V_VP8"); |
| 197 |
| 198 StartSubElement(Video); |
| 199 { |
| 200 Ebml_SerializeUnsigned(&ebml_writer_, PixelWidth, width_); |
| 201 Ebml_SerializeUnsigned(&ebml_writer_, PixelHeight, height_); |
| 202 Ebml_SerializeUnsigned(&ebml_writer_, StereoMode, 0); // Mono |
| 203 float fps = static_cast<float>(fps_.num) / fps_.den; |
| 204 Ebml_SerializeFloat(&ebml_writer_, FrameRate, fps); |
| 205 } |
| 206 EndSubElement(); // Video |
| 207 } |
| 208 EndSubElement(); // TrackEntry |
| 209 } |
| 210 EndSubElement(); // Tracks |
| 211 |
| 212 StartSubElement(Cluster); { |
| 213 Ebml_SerializeUnsigned(&ebml_writer_, Timecode, 0); |
| 214 } // Cluster left open. |
| 215 } // Segment left open. |
| 216 |
| 217 // No check for |has_errors_| here because |false| is only returned when |
| 218 // opening file fails. |
| 219 return true; |
| 220 } |
| 221 |
| 222 void WebmEncoder::WriteWebmBlock(const vpx_codec_cx_pkt_t* packet) { |
| 223 bool is_keyframe = packet->data.frame.flags & VPX_FRAME_IS_KEY; |
| 224 int64_t pts_ms = 1000 * packet->data.frame.pts * fps_.den / fps_.num; |
| 225 |
| 226 DVLOG(1) << "Video packet @" << pts_ms << " ms " |
| 227 << packet->data.frame.sz << " bytes " |
| 228 << (is_keyframe ? "K" : ""); |
| 229 |
| 230 Ebml_WriteID(&ebml_writer_, SimpleBlock); |
| 231 |
| 232 uint32 block_length = (packet->data.frame.sz + 4) | 0x10000000; |
| 233 EbmlSerializeHelper(&block_length, 4); |
| 234 |
| 235 uint8 track_number = 1 | 0x80; |
| 236 EbmlSerializeHelper(&track_number, 1); |
| 237 |
| 238 EbmlSerializeHelper(&pts_ms, 2); |
| 239 |
| 240 uint8 flags = 0; |
| 241 if (is_keyframe) |
| 242 flags |= 0x80; |
| 243 if (packet->data.frame.flags & VPX_FRAME_IS_INVISIBLE) |
| 244 flags |= 0x08; |
| 245 EbmlSerializeHelper(&flags, 1); |
| 246 |
| 247 EbmlWrite(packet->data.frame.buf, packet->data.frame.sz); |
| 248 } |
| 249 |
| 250 bool WebmEncoder::WriteWebmFooter() { |
| 251 EndSubElement(); // Cluster |
| 252 EndSubElement(); // Segment |
| 253 DCHECK(ebml_sub_elements_.empty()); |
| 254 return base::CloseFile(output_) && !has_errors_; |
| 255 } |
| 256 |
| 257 void WebmEncoder::StartSubElement(unsigned long class_id) { |
| 258 Ebml_WriteID(&ebml_writer_, class_id); |
| 259 ebml_sub_elements_.push(ftell(output_)); |
| 260 static const uint64_t kUnknownLen = 0x01FFFFFFFFFFFFFFLLU; |
| 261 EbmlSerializeHelper(&kUnknownLen, 8); |
| 262 } |
| 263 |
| 264 void WebmEncoder::EndSubElement() { |
| 265 DCHECK(!ebml_sub_elements_.empty()); |
| 266 |
| 267 long int end_pos = ftell(output_); |
| 268 long int start_pos = ebml_sub_elements_.top(); |
| 269 ebml_sub_elements_.pop(); |
| 270 |
| 271 uint64_t size = (end_pos - start_pos - 8) | 0x0100000000000000ULL; |
| 272 // Seek to the beginning of the sub-element and patch in the calculated size. |
| 273 if (fseek(output_, start_pos, SEEK_SET)) { |
| 274 has_errors_ = true; |
| 275 LOG(ERROR) << "Error writing to " << output_path_.value(); |
| 276 } |
| 277 EbmlSerializeHelper(&size, 8); |
| 278 |
| 279 // Restore write position. |
| 280 if (fseek(output_, end_pos, SEEK_SET)) { |
| 281 has_errors_ = true; |
| 282 LOG(ERROR) << "Error writing to " << output_path_.value(); |
| 283 } |
| 284 } |
| 285 |
| 286 void WebmEncoder::EbmlWrite(const void* buffer, |
| 287 unsigned long len) { |
| 288 if (fwrite(buffer, 1, len, output_) != len) { |
| 289 has_errors_ = true; |
| 290 LOG(ERROR) << "Error writing to " << output_path_.value(); |
| 291 } |
| 292 } |
| 293 |
| 294 template <class T> |
| 295 void WebmEncoder::EbmlSerializeHelper(const T* buffer, unsigned long len) { |
| 296 for (int i = len - 1; i >= 0; i--) { |
| 297 uint8 c = *buffer >> (i * CHAR_BIT); |
| 298 EbmlWrite(&c, 1); |
| 299 } |
| 300 } |
| 301 |
| 302 void WebmEncoder::EbmlSerialize(const void* buffer, |
| 303 int buffer_size, |
| 304 unsigned long len) { |
| 305 switch (buffer_size) { |
| 306 case 1: |
| 307 return EbmlSerializeHelper(static_cast<const int8_t*>(buffer), len); |
| 308 case 2: |
| 309 return EbmlSerializeHelper(static_cast<const int16_t*>(buffer), len); |
| 310 case 4: |
| 311 return EbmlSerializeHelper(static_cast<const int32_t*>(buffer), len); |
| 312 case 8: |
| 313 return EbmlSerializeHelper(static_cast<const int64_t*>(buffer), len); |
| 314 default: |
| 315 NOTREACHED() << "Invalid EbmlSerialize length: " << len; |
| 316 } |
| 317 } |
| 318 |
| 319 } // namespace chromeos |
| 320 |
| 321 } // namespace media |
OLD | NEW |