Home | History | Annotate | Download | only in chromeos
      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/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 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_ = file_util::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 file_util::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