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 |