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_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 322