OLD | NEW |
---|---|
(Empty) | |
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 "libyuv/convert.h" | |
scherkus (not reviewing)
2012/07/19 21:46:03
shouldn't this be third_party/libyuv/...?
Ivan Korotkov
2012/07/20 11:10:36
libyuv.gyp adds third_party/libyuv/include/ to -D,
| |
11 #include "libyuv/video_common.h" | |
12 #include "third_party/skia/include/core/SkBitmap.h" | |
13 | |
14 extern "C" { | |
15 // Getting the right degree of C compatibility has been a constant struggle. | |
16 // - Stroustrup, C++ Report, 12(7), July/August 2000. | |
17 #define private priv | |
18 #include "third_party/libvpx/source/libvpx/libmkv/EbmlIDs.h" | |
scherkus (not reviewing)
2012/07/19 21:46:03
what's going on here?
is there a particular heade
Ivan Korotkov
2012/07/20 11:10:36
One of functions in EbmlWriter.h has argument name
scherkus (not reviewing)
2012/07/20 19:20:51
Are we planning on fixing it upstream?
Ivan Korotkov
2012/07/23 18:57:52
Not sure. This code not used anywhere but in vpxen
| |
19 #include "third_party/libvpx/source/libvpx/libmkv/EbmlWriter.h" | |
20 #undef private | |
21 } | |
22 | |
23 namespace { | |
24 | |
25 // Number of encoder threads to use. | |
26 const int kNumEncoderThreads = 2; | |
scherkus (not reviewing)
2012/07/19 21:46:03
the rest of the media codebase uses static over an
Ivan Korotkov
2012/07/20 11:10:36
Done.
| |
27 | |
28 // Need a fixed size serializer for the track ID. libmkv provides a 64 bit | |
29 // one, but not a 32 bit one. | |
30 void Ebml_SerializeUnsigned32(EbmlGlobal *ebml, | |
Nikita (slow)
2012/07/19 08:37:12
nit: EbmlGlobal* ebml
scherkus (not reviewing)
2012/07/19 21:46:03
pointers go with types
EbmlGlobal* embl
Ivan Korotkov
2012/07/20 09:09:29
Done.
| |
31 unsigned long class_id, | |
32 uint64_t value) { | |
33 unsigned char size_serialized = 4 | 0x80; | |
scherkus (not reviewing)
2012/07/19 21:46:03
prefer uint8 over unsigned keyword
Ivan Korotkov
2012/07/20 11:10:36
Done.
| |
34 Ebml_WriteID(ebml, class_id); | |
35 Ebml_Serialize(ebml, &size_serialized, sizeof(size_serialized), 1); | |
36 Ebml_Serialize(ebml, &value, sizeof(value), 4); | |
37 } | |
38 | |
39 } // namespace | |
40 | |
41 namespace media { | |
42 | |
43 namespace chromeos { | |
44 | |
45 WebmEncoder::WebmEncoder(const FilePath& output_path, | |
46 unsigned bitrate, | |
47 bool realtime) | |
48 : bitrate_(bitrate), | |
49 deadline_(realtime ? VPX_DL_REALTIME : VPX_DL_GOOD_QUALITY), | |
50 output_path_(output_path) { | |
51 ebml_writer_.write_cb = base::Bind( | |
scherkus (not reviewing)
2012/07/19 21:46:03
what happens if I use two of these classes at the
Ivan Korotkov
2012/07/20 11:10:36
What's the problem with this? Each instance would
| |
52 &WebmEncoder::EbmlWrite, base::Unretained(this)); | |
53 ebml_writer_.serialize_cb = base::Bind( | |
54 &WebmEncoder::EbmlSerialize, base::Unretained(this)); | |
55 } | |
56 | |
57 void WebmEncoder::EncodeFromSprite(const SkBitmap& sprite, | |
58 int fps_n, | |
59 int fps_d) { | |
60 DCHECK(!sprite.isNull() && !sprite.empty()); | |
scherkus (not reviewing)
2012/07/19 21:46:03
nit: separate the DCHECKs
Ivan Korotkov
2012/07/23 18:57:52
Done.
| |
61 | |
62 width_ = sprite.width(); | |
63 height_ = sprite.width(); | |
64 fps_.num = fps_n; | |
65 fps_.den = fps_d; | |
66 | |
67 size_t y_size = width_ * height_; | |
68 size_t uv_size = ((width_ + 1) / 2) * ((height_ + 1) / 2); | |
69 size_t frame_size = y_size + 2 * uv_size; | |
70 size_t frame_count = sprite.height() / width_; // Sprite is tiled vertically. | |
71 | |
72 yuv_frame_.reset(new uint8[frame_size]); | |
73 | |
74 vpx_image_t image; | |
75 vpx_img_wrap(&image, VPX_IMG_FMT_I420, width_, height_, 1, yuv_frame_.get()); | |
76 | |
77 vpx_codec_ctx_t codec; | |
78 const vpx_codec_iface_t* codec_iface = vpx_codec_vp8_cx(); | |
79 DCHECK(codec_iface); | |
80 vpx_codec_err_t ret = vpx_codec_enc_config_default(codec_iface, &config_, 0); | |
81 DCHECK_EQ(VPX_CODEC_OK, ret); | |
82 | |
83 config_.rc_target_bitrate = bitrate_; | |
84 config_.g_w = width_; | |
85 config_.g_h = height_; | |
86 config_.g_pass = VPX_RC_ONE_PASS; | |
Nikita (slow)
2012/07/19 08:37:12
nit: align comments if possible
Ivan Korotkov
2012/07/20 09:09:29
Done.
| |
87 config_.g_profile = 0; // Default profile. | |
88 config_.g_threads = kNumEncoderThreads; | |
89 config_.rc_min_quantizer = 0; | |
90 config_.rc_max_quantizer = 63; // Maximum possible range. | |
91 config_.g_timebase.num = fps_.den; | |
92 config_.g_timebase.den = fps_.num; | |
93 config_.kf_mode = VPX_KF_AUTO; // Auto key frames. | |
94 | |
95 ret = vpx_codec_enc_init(&codec, codec_iface, &config_, 0); | |
96 DCHECK_EQ(VPX_CODEC_OK, ret); | |
Nikita (slow)
2012/07/19 08:37:12
Pass error to upper level if that fails
Ivan Korotkov
2012/07/20 09:09:29
Done.
| |
97 | |
98 SkAutoLockPixels lock_sprite(sprite); | |
99 | |
100 const uint8* src = reinterpret_cast<const uint8*>(sprite.getAddr32(0, 0)); | |
101 size_t src_frame_size = sprite.getSize(); | |
102 int crop_y = 0; | |
103 | |
104 WriteWebmHeader(); | |
105 | |
106 for (size_t frame = 0; frame < frame_count; ++frame) { | |
107 int res = libyuv::ConvertToI420( | |
108 src, src_frame_size, | |
109 image.planes[VPX_PLANE_Y], image.stride[VPX_PLANE_Y], | |
110 image.planes[VPX_PLANE_U], image.stride[VPX_PLANE_U], | |
111 image.planes[VPX_PLANE_V], image.stride[VPX_PLANE_V], | |
112 0, crop_y, // src origin | |
113 width_, sprite.height(), // src size | |
114 width_, height_, // dest size | |
115 libyuv::kRotate0, | |
116 libyuv::FOURCC_ARGB); | |
117 DCHECK_EQ(0, res); | |
Nikita (slow)
2012/07/19 08:37:12
Pass error to upper level if that fails?
Ivan Korotkov
2012/07/20 09:09:29
Done.
| |
118 crop_y += height_; | |
119 | |
120 ret = vpx_codec_encode(&codec, &image, frame, 1, 0, deadline_); | |
121 DCHECK_EQ(VPX_CODEC_OK, ret); | |
Nikita (slow)
2012/07/19 08:37:12
Same here.
Ivan Korotkov
2012/07/20 09:09:29
Done.
| |
122 | |
123 vpx_codec_iter_t iter = NULL; | |
124 const vpx_codec_cx_pkt_t* packet; | |
125 while ((packet = vpx_codec_get_cx_data(&codec, &iter))) { | |
126 if (packet->kind == VPX_CODEC_CX_FRAME_PKT) | |
127 WriteWebmBlock(packet); | |
128 } | |
129 } | |
130 | |
131 vpx_codec_destroy(&codec); | |
132 | |
133 WriteWebmFooter(); | |
134 } | |
135 | |
136 void WebmEncoder::WriteWebmHeader() { | |
137 output_ = file_util::OpenFile(output_path_, "wb"); | |
138 DCHECK(output_); | |
139 | |
140 // Global header. | |
141 StartSubElement(EBML); { | |
Nikita (slow)
2012/07/19 08:37:12
How about
StartSubElement(EBML);
{
...
}
EndSub
Ivan Korotkov
2012/07/20 09:09:29
IMO that looks against Chrome's style. This one fo
| |
142 Ebml_SerializeUnsigned(&ebml_writer_, EBMLVersion, 1); | |
143 Ebml_SerializeUnsigned(&ebml_writer_, EBMLReadVersion, 1); | |
144 Ebml_SerializeUnsigned(&ebml_writer_, EBMLMaxIDLength, 4); | |
145 Ebml_SerializeUnsigned(&ebml_writer_, EBMLMaxSizeLength, 8); | |
146 Ebml_SerializeString(&ebml_writer_, DocType, "webm"); | |
147 Ebml_SerializeUnsigned(&ebml_writer_, DocTypeVersion, 2); | |
148 Ebml_SerializeUnsigned(&ebml_writer_, DocTypeReadVersion, 2); | |
149 } EndSubElement(); // EBML | |
150 // Single segment with a video track. | |
Nikita (slow)
2012/07/19 08:37:12
nit: insert extra line
Ivan Korotkov
2012/07/20 09:09:29
Done.
| |
151 StartSubElement(Segment); { | |
Nikita (slow)
2012/07/19 08:37:12
nit: fix formatting as suggested
| |
152 StartSubElement(Info); { | |
153 // All timecodes in the segment will be expressed in milliseconds. | |
154 Ebml_SerializeUnsigned(&ebml_writer_, TimecodeScale, 1000000); | |
155 } EndSubElement(); // Info | |
156 StartSubElement(Tracks); { | |
Nikita (slow)
2012/07/19 08:37:12
nit: fix formatting as suggested
| |
157 StartSubElement(TrackEntry); { | |
158 Ebml_SerializeUnsigned(&ebml_writer_, TrackNumber, 1); | |
159 Ebml_SerializeUnsigned32(&ebml_writer_, TrackUID, 1); | |
160 Ebml_SerializeUnsigned(&ebml_writer_, TrackType, 1); // Video | |
161 Ebml_SerializeString(&ebml_writer_, CodecID, "V_VP8"); | |
162 StartSubElement(Video); { | |
163 Ebml_SerializeUnsigned(&ebml_writer_, PixelWidth, width_); | |
164 Ebml_SerializeUnsigned(&ebml_writer_, PixelHeight, height_); | |
165 Ebml_SerializeUnsigned(&ebml_writer_, StereoMode, 0); // Mono | |
166 float fps = static_cast<float>(fps_.num) / fps_.den; | |
167 Ebml_SerializeFloat(&ebml_writer_, FrameRate, fps); | |
168 } EndSubElement(); // Video | |
169 } EndSubElement(); // TrackEntry | |
170 } EndSubElement(); // Tracks | |
171 StartSubElement(Cluster); { | |
172 Ebml_SerializeUnsigned(&ebml_writer_, Timecode, 0); | |
173 } // Cluster left open. | |
174 } // Segment left open. | |
175 } | |
176 | |
177 void WebmEncoder::WriteWebmBlock(const vpx_codec_cx_pkt_t* packet) { | |
178 bool is_keyframe = packet->data.frame.flags & VPX_FRAME_IS_KEY; | |
179 int64_t pts_ms = 1000 * packet->data.frame.pts * fps_.den / fps_.num; | |
180 | |
181 DVLOG(1) << "Video packet @" << pts_ms << " ms " | |
182 << packet->data.frame.sz << " bytes " | |
183 << (is_keyframe ? "K" : ""); | |
184 | |
185 Ebml_WriteID(&ebml_writer_, SimpleBlock); | |
186 | |
187 unsigned long block_length = (packet->data.frame.sz + 4) | 0x10000000; | |
scherkus (not reviewing)
2012/07/19 21:46:03
what's 0x10000000?
Ivan Korotkov
2012/07/20 11:10:36
I guess these higher bit masks (0x10000000, 0x80,
| |
188 EbmlSerializeHelper(&block_length, 4); | |
189 | |
190 unsigned char track_number = 1 | 0x80; | |
scherkus (not reviewing)
2012/07/19 21:46:03
what's 0x80?
| |
191 EbmlSerializeHelper(&track_number, 1); | |
192 | |
193 EbmlSerializeHelper(&pts_ms, 2); | |
194 | |
195 unsigned char flags = 0; | |
196 if (is_keyframe) | |
197 flags |= 0x80; | |
scherkus (not reviewing)
2012/07/19 21:46:03
are there no constants for flags?
Ivan Korotkov
2012/07/20 11:10:36
libvpx/webrtc use these as is, so I assume not.
Wh
| |
198 if (packet->data.frame.flags & VPX_FRAME_IS_INVISIBLE) | |
199 flags |= 0x08; | |
200 EbmlSerializeHelper(&flags, 1); | |
201 | |
202 EbmlWrite(packet->data.frame.buf, packet->data.frame.sz); | |
203 } | |
204 | |
205 void WebmEncoder::WriteWebmFooter() { | |
206 EndSubElement(); // Cluster | |
207 EndSubElement(); // Segment | |
208 DCHECK(ebml_sub_elements_.empty()); | |
209 file_util::CloseFile(output_); | |
210 } | |
211 | |
212 void WebmEncoder::StartSubElement(unsigned long class_id) { | |
213 Ebml_WriteID(&ebml_writer_, class_id); | |
214 ebml_sub_elements_.push(ftell(output_)); | |
215 uint64_t unknown_len = 0x01FFFFFFFFFFFFFFLLU; | |
scherkus (not reviewing)
2012/07/19 21:46:03
no constant?
Ivan Korotkov
2012/07/20 11:10:36
Same here. I think it's "-1" EBML-encoded. Made it
| |
216 Ebml_Serialize(&ebml_writer_, &unknown_len, sizeof(unknown_len), 8); | |
217 } | |
218 | |
219 void WebmEncoder::EndSubElement() { | |
220 DCHECK(!ebml_sub_elements_.empty()); | |
221 | |
222 long int end_pos = ftell(output_); | |
223 long int start_pos = ebml_sub_elements_.top(); | |
224 ebml_sub_elements_.pop(); | |
225 | |
226 uint64_t size = (end_pos - start_pos - 8) | 0x0100000000000000ULL; | |
227 // Seek to the beginning of the sub-element and patch in the calculated size. | |
228 fseek(output_, start_pos, SEEK_SET); | |
Nikita (slow)
2012/07/19 08:37:12
Check return value?
Ivan Korotkov
2012/07/20 09:09:29
Done.
| |
229 EbmlSerializeHelper(&size, 8); | |
230 | |
231 // Restore write position. | |
232 fseek(output_, end_pos, SEEK_SET); | |
Nikita (slow)
2012/07/19 08:37:12
Check return value?
Ivan Korotkov
2012/07/20 09:09:29
Done.
| |
233 } | |
234 | |
235 void WebmEncoder::EbmlWrite(const void* buffer, | |
236 unsigned long len) { | |
237 unsigned long ret = fwrite(buffer, 1, len, output_); | |
238 CHECK_EQ(len, ret); | |
Nikita (slow)
2012/07/19 08:37:12
Not sure that it makes sense to have a CHECK here.
Ivan Korotkov
2012/07/20 09:09:29
It's difficult to do with EbmlXxx interface. Maybe
| |
239 } | |
240 | |
241 template <class T> | |
242 void WebmEncoder::EbmlSerializeHelper(const T* buffer, unsigned long len) { | |
243 for (unsigned long i = len; i-- > 0; ) { | |
scherkus (not reviewing)
2012/07/19 21:46:03
nit: let's try to keep conditions as they are and
Ivan Korotkov
2012/07/20 11:10:36
Ok, I guess we can use a signed iterator since len
| |
244 char c = *buffer >> (i * CHAR_BIT); | |
245 EbmlWrite(&c, 1); | |
246 } | |
247 } | |
248 | |
249 void WebmEncoder::EbmlSerialize(const void* buffer, | |
250 int buffer_size, | |
251 unsigned long len) { | |
252 switch (buffer_size) { | |
253 case 1: | |
254 return EbmlSerializeHelper(static_cast<const int8_t*>(buffer), len); | |
255 case 2: | |
256 return EbmlSerializeHelper(static_cast<const int16_t*>(buffer), len); | |
257 case 4: | |
258 return EbmlSerializeHelper(static_cast<const int32_t*>(buffer), len); | |
259 case 8: | |
260 return EbmlSerializeHelper(static_cast<const int64_t*>(buffer), len); | |
261 default: | |
262 NOTREACHED(); | |
Nikita (slow)
2012/07/19 08:37:12
nit: Add some error message?
Ivan Korotkov
2012/07/20 09:09:29
Done.
| |
263 } | |
264 } | |
265 | |
266 } // namespace chromeos | |
267 | |
268 } // namespace media | |
OLD | NEW |