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.
5 #include "media/webm/chromeos/webm_encoder.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"
16 // Getting the right degree of C compatibility has been a constant struggle.
17 // - Stroustrup, C++ Report, 12(7), July/August 2000.
19 #include "third_party/libvpx/source/libvpx/libmkv/EbmlIDs.h"
20 #include "third_party/libvpx/source/libvpx/libmkv/EbmlWriter.h"
24 // Number of encoder threads to use.
25 static const int kNumEncoderThreads
= 2;
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
,
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);
38 // Wrapper functor for vpx_codec_destroy().
39 struct VpxCodecDeleter
{
40 void operator()(vpx_codec_ctx_t
* codec
) {
41 vpx_codec_destroy(codec
);
45 // Wrapper functor for vpx_img_free().
46 struct VpxImgDeleter
{
47 void operator()(vpx_image_t
* image
) {
56 WebmEncoder::WebmEncoder(const base::FilePath
& output_path
,
60 deadline_(realtime
? VPX_DL_REALTIME
: VPX_DL_GOOD_QUALITY
),
61 output_path_(output_path
),
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));
69 WebmEncoder::~WebmEncoder() {
72 bool WebmEncoder::EncodeFromSprite(const SkBitmap
& sprite
,
75 DCHECK(!sprite
.isNull());
76 DCHECK(!sprite
.empty());
79 width_
= sprite
.width();
80 height_
= sprite
.width();
84 // Sprite is tiled vertically.
85 frame_count_
= sprite
.height() / width_
;
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
);
92 const vpx_codec_iface_t
* codec_iface
= vpx_codec_vp8_cx();
94 vpx_codec_err_t ret
= vpx_codec_enc_config_default(codec_iface
, &config_
, 0);
95 DCHECK_EQ(VPX_CODEC_OK
, ret
);
97 config_
.rc_target_bitrate
= bitrate_
;
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.
109 vpx_codec_ctx_t codec
;
110 ret
= vpx_codec_enc_init(&codec
, codec_iface
, &config_
, 0);
111 if (ret
!= VPX_CODEC_OK
)
113 // Ensure that codec context is freed after return.
114 scoped_ptr
<vpx_codec_ctx_t
, VpxCodecDeleter
> codec_ptr(&codec
);
116 SkAutoLockPixels
lock_sprite(sprite
);
118 const uint8
* src
= reinterpret_cast<const uint8
*>(sprite
.getAddr32(0, 0));
119 size_t src_frame_size
= sprite
.getSize();
122 if (!WriteWebmHeader())
125 for (size_t frame
= 0; frame
< frame_count_
&& !has_errors_
; ++frame
) {
126 int res
= libyuv::ConvertToI420(
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
135 libyuv::FOURCC_ARGB
);
142 ret
= vpx_codec_encode(&codec
, &image
, frame
, 1, 0, deadline_
);
143 if (ret
!= VPX_CODEC_OK
) {
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
);
156 return WriteWebmFooter();
159 bool WebmEncoder::WriteWebmHeader() {
160 output_
= file_util::OpenFile(output_path_
, "wb");
165 StartSubElement(EBML
);
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);
175 EndSubElement(); // EBML
177 // Single segment with a video track.
178 StartSubElement(Segment
);
180 StartSubElement(Info
);
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
);
187 EndSubElement(); // Info
189 StartSubElement(Tracks
);
191 StartSubElement(TrackEntry
);
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");
198 StartSubElement(Video
);
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
);
206 EndSubElement(); // Video
208 EndSubElement(); // TrackEntry
210 EndSubElement(); // Tracks
212 StartSubElement(Cluster
); {
213 Ebml_SerializeUnsigned(&ebml_writer_
, Timecode
, 0);
214 } // Cluster left open.
215 } // Segment left open.
217 // No check for |has_errors_| here because |false| is only returned when
218 // opening file fails.
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
;
226 DVLOG(1) << "Video packet @" << pts_ms
<< " ms "
227 << packet
->data
.frame
.sz
<< " bytes "
228 << (is_keyframe
? "K" : "");
230 Ebml_WriteID(&ebml_writer_
, SimpleBlock
);
232 uint32 block_length
= (packet
->data
.frame
.sz
+ 4) | 0x10000000;
233 EbmlSerializeHelper(&block_length
, 4);
235 uint8 track_number
= 1 | 0x80;
236 EbmlSerializeHelper(&track_number
, 1);
238 EbmlSerializeHelper(&pts_ms
, 2);
243 if (packet
->data
.frame
.flags
& VPX_FRAME_IS_INVISIBLE
)
245 EbmlSerializeHelper(&flags
, 1);
247 EbmlWrite(packet
->data
.frame
.buf
, packet
->data
.frame
.sz
);
250 bool WebmEncoder::WriteWebmFooter() {
251 EndSubElement(); // Cluster
252 EndSubElement(); // Segment
253 DCHECK(ebml_sub_elements_
.empty());
254 return file_util::CloseFile(output_
) && !has_errors_
;
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);
264 void WebmEncoder::EndSubElement() {
265 DCHECK(!ebml_sub_elements_
.empty());
267 long int end_pos
= ftell(output_
);
268 long int start_pos
= ebml_sub_elements_
.top();
269 ebml_sub_elements_
.pop();
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
)) {
275 LOG(ERROR
) << "Error writing to " << output_path_
.value();
277 EbmlSerializeHelper(&size
, 8);
279 // Restore write position.
280 if (fseek(output_
, end_pos
, SEEK_SET
)) {
282 LOG(ERROR
) << "Error writing to " << output_path_
.value();
286 void WebmEncoder::EbmlWrite(const void* buffer
,
288 if (fwrite(buffer
, 1, len
, output_
) != len
) {
290 LOG(ERROR
) << "Error writing to " << output_path_
.value();
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
);
302 void WebmEncoder::EbmlSerialize(const void* buffer
,
305 switch (buffer_size
) {
307 return EbmlSerializeHelper(static_cast<const int8_t*>(buffer
), len
);
309 return EbmlSerializeHelper(static_cast<const int16_t*>(buffer
), len
);
311 return EbmlSerializeHelper(static_cast<const int32_t*>(buffer
), len
);
313 return EbmlSerializeHelper(static_cast<const int64_t*>(buffer
), len
);
315 NOTREACHED() << "Invalid EbmlSerialize length: " << len
;
319 } // namespace chromeos