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.
Ebml_SerializeUnsigned32(EbmlGlobal * ebml,unsigned long class_id,uint64_t value)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 {
operator ()VpxCodecDeleter40 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 {
operator ()VpxImgDeleter47 void operator()(vpx_image_t* image) {
48 vpx_img_free(image);
49 }
50 };
51
52 namespace media {
53
54 namespace chromeos {
55
WebmEncoder(const base::FilePath & output_path,int bitrate,bool realtime)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
~WebmEncoder()69 WebmEncoder::~WebmEncoder() {
70 }
71
EncodeFromSprite(const SkBitmap & sprite,int fps_n,int fps_d)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
WriteWebmHeader()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
WriteWebmBlock(const vpx_codec_cx_pkt_t * packet)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
WriteWebmFooter()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
StartSubElement(unsigned long class_id)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
EndSubElement()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
EbmlWrite(const void * buffer,unsigned long len)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>
EbmlSerializeHelper(const T * buffer,unsigned long len)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
EbmlSerialize(const void * buffer,int buffer_size,unsigned long len)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