1 /*
2  *  Copyright (c) 2013 The WebM project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 #include "webmenc.h"
11 
12 #include <limits.h>
13 #include <string.h>
14 
15 #include "third_party/libmkv/EbmlWriter.h"
16 #include "third_party/libmkv/EbmlIDs.h"
17 
Ebml_Write(struct EbmlGlobal * glob,const void * buffer_in,unsigned long len)18 void Ebml_Write(struct EbmlGlobal *glob,
19                 const void *buffer_in,
20                 unsigned long len) {
21   (void) fwrite(buffer_in, 1, len, glob->stream);
22 }
23 
24 #define WRITE_BUFFER(s) \
25 for (i = len - 1; i >= 0; i--) { \
26   x = (char)(*(const s *)buffer_in >> (i * CHAR_BIT)); \
27   Ebml_Write(glob, &x, 1); \
28 }
29 
Ebml_Serialize(struct EbmlGlobal * glob,const void * buffer_in,int buffer_size,unsigned long len)30 void Ebml_Serialize(struct EbmlGlobal *glob,
31                     const void *buffer_in,
32                     int buffer_size,
33                     unsigned long len) {
34   char x;
35   int i;
36 
37   /* buffer_size:
38    * 1 - int8_t;
39    * 2 - int16_t;
40    * 3 - int32_t;
41    * 4 - int64_t;
42    */
43   switch (buffer_size) {
44     case 1:
45       WRITE_BUFFER(int8_t)
46       break;
47     case 2:
48       WRITE_BUFFER(int16_t)
49       break;
50     case 4:
51       WRITE_BUFFER(int32_t)
52       break;
53     case 8:
54       WRITE_BUFFER(int64_t)
55       break;
56     default:
57       break;
58   }
59 }
60 #undef WRITE_BUFFER
61 
62 /* Need a fixed size serializer for the track ID. libmkv provides a 64 bit
63  * one, but not a 32 bit one.
64  */
Ebml_SerializeUnsigned32(struct EbmlGlobal * glob,unsigned int class_id,uint64_t ui)65 static void Ebml_SerializeUnsigned32(struct EbmlGlobal *glob,
66                                      unsigned int class_id,
67                                      uint64_t ui) {
68   const unsigned char sizeSerialized = 4 | 0x80;
69   Ebml_WriteID(glob, class_id);
70   Ebml_Serialize(glob, &sizeSerialized, sizeof(sizeSerialized), 1);
71   Ebml_Serialize(glob, &ui, sizeof(ui), 4);
72 }
73 
Ebml_StartSubElement(struct EbmlGlobal * glob,EbmlLoc * ebmlLoc,unsigned int class_id)74 static void Ebml_StartSubElement(struct EbmlGlobal *glob,
75                                  EbmlLoc *ebmlLoc,
76                                  unsigned int class_id) {
77   const uint64_t kEbmlUnknownLength = LITERALU64(0x01FFFFFF, 0xFFFFFFFF);
78   Ebml_WriteID(glob, class_id);
79   *ebmlLoc = ftello(glob->stream);
80   Ebml_Serialize(glob, &kEbmlUnknownLength, sizeof(kEbmlUnknownLength), 8);
81 }
82 
Ebml_EndSubElement(struct EbmlGlobal * glob,EbmlLoc * ebmlLoc)83 static void Ebml_EndSubElement(struct EbmlGlobal *glob, EbmlLoc *ebmlLoc) {
84   off_t pos;
85   uint64_t size;
86 
87   /* Save the current stream pointer. */
88   pos = ftello(glob->stream);
89 
90   /* Calculate the size of this element. */
91   size = pos - *ebmlLoc - 8;
92   size |= LITERALU64(0x01000000, 0x00000000);
93 
94   /* Seek back to the beginning of the element and write the new size. */
95   fseeko(glob->stream, *ebmlLoc, SEEK_SET);
96   Ebml_Serialize(glob, &size, sizeof(size), 8);
97 
98   /* Reset the stream pointer. */
99   fseeko(glob->stream, pos, SEEK_SET);
100 }
101 
write_webm_seek_element(struct EbmlGlobal * ebml,unsigned int id,off_t pos)102 void write_webm_seek_element(struct EbmlGlobal *ebml,
103                              unsigned int id,
104                              off_t pos) {
105   uint64_t offset = pos - ebml->position_reference;
106   EbmlLoc start;
107   Ebml_StartSubElement(ebml, &start, Seek);
108   Ebml_SerializeBinary(ebml, SeekID, id);
109   Ebml_SerializeUnsigned64(ebml, SeekPosition, offset);
110   Ebml_EndSubElement(ebml, &start);
111 }
112 
write_webm_seek_info(struct EbmlGlobal * ebml)113 void write_webm_seek_info(struct EbmlGlobal *ebml) {
114   off_t pos;
115   EbmlLoc start;
116   EbmlLoc startInfo;
117   uint64_t frame_time;
118   char version_string[64];
119 
120   /* Save the current stream pointer. */
121   pos = ftello(ebml->stream);
122 
123   if (ebml->seek_info_pos)
124     fseeko(ebml->stream, ebml->seek_info_pos, SEEK_SET);
125   else
126     ebml->seek_info_pos = pos;
127 
128   Ebml_StartSubElement(ebml, &start, SeekHead);
129   write_webm_seek_element(ebml, Tracks, ebml->track_pos);
130   write_webm_seek_element(ebml, Cues, ebml->cue_pos);
131   write_webm_seek_element(ebml, Info, ebml->segment_info_pos);
132   Ebml_EndSubElement(ebml, &start);
133 
134   /* Create and write the Segment Info. */
135   if (ebml->debug) {
136     strcpy(version_string, "vpxenc");
137   } else {
138     strcpy(version_string, "vpxenc ");
139     strncat(version_string,
140             vpx_codec_version_str(),
141             sizeof(version_string) - 1 - strlen(version_string));
142   }
143 
144   frame_time = (uint64_t)1000 * ebml->framerate.den
145                / ebml->framerate.num;
146   ebml->segment_info_pos = ftello(ebml->stream);
147   Ebml_StartSubElement(ebml, &startInfo, Info);
148   Ebml_SerializeUnsigned(ebml, TimecodeScale, 1000000);
149   Ebml_SerializeFloat(ebml, Segment_Duration,
150                       (double)(ebml->last_pts_ms + frame_time));
151   Ebml_SerializeString(ebml, 0x4D80, version_string);
152   Ebml_SerializeString(ebml, 0x5741, version_string);
153   Ebml_EndSubElement(ebml, &startInfo);
154 }
155 
write_webm_file_header(struct EbmlGlobal * glob,const vpx_codec_enc_cfg_t * cfg,const struct vpx_rational * fps,stereo_format_t stereo_fmt,unsigned int fourcc)156 void write_webm_file_header(struct EbmlGlobal *glob,
157                             const vpx_codec_enc_cfg_t *cfg,
158                             const struct vpx_rational *fps,
159                             stereo_format_t stereo_fmt,
160                             unsigned int fourcc) {
161   EbmlLoc start;
162   EbmlLoc trackStart;
163   EbmlLoc videoStart;
164   unsigned int trackNumber = 1;
165   uint64_t trackID = 0;
166   unsigned int pixelWidth = cfg->g_w;
167   unsigned int pixelHeight = cfg->g_h;
168 
169   /* Write the EBML header. */
170   Ebml_StartSubElement(glob, &start, EBML);
171   Ebml_SerializeUnsigned(glob, EBMLVersion, 1);
172   Ebml_SerializeUnsigned(glob, EBMLReadVersion, 1);
173   Ebml_SerializeUnsigned(glob, EBMLMaxIDLength, 4);
174   Ebml_SerializeUnsigned(glob, EBMLMaxSizeLength, 8);
175   Ebml_SerializeString(glob, DocType, "webm");
176   Ebml_SerializeUnsigned(glob, DocTypeVersion, 2);
177   Ebml_SerializeUnsigned(glob, DocTypeReadVersion, 2);
178   Ebml_EndSubElement(glob, &start);
179 
180   /* Open and begin writing the segment element. */
181   Ebml_StartSubElement(glob, &glob->startSegment, Segment);
182   glob->position_reference = ftello(glob->stream);
183   glob->framerate = *fps;
184   write_webm_seek_info(glob);
185 
186   /* Open and write the Tracks element. */
187   glob->track_pos = ftello(glob->stream);
188   Ebml_StartSubElement(glob, &trackStart, Tracks);
189 
190   /* Open and write the Track entry. */
191   Ebml_StartSubElement(glob, &start, TrackEntry);
192   Ebml_SerializeUnsigned(glob, TrackNumber, trackNumber);
193   glob->track_id_pos = ftello(glob->stream);
194   Ebml_SerializeUnsigned32(glob, TrackUID, trackID);
195   Ebml_SerializeUnsigned(glob, TrackType, 1);
196   Ebml_SerializeString(glob, CodecID,
197                        fourcc == VP8_FOURCC ? "V_VP8" : "V_VP9");
198   Ebml_StartSubElement(glob, &videoStart, Video);
199   Ebml_SerializeUnsigned(glob, PixelWidth, pixelWidth);
200   Ebml_SerializeUnsigned(glob, PixelHeight, pixelHeight);
201   Ebml_SerializeUnsigned(glob, StereoMode, stereo_fmt);
202   Ebml_EndSubElement(glob, &videoStart);
203 
204   /* Close Track entry. */
205   Ebml_EndSubElement(glob, &start);
206 
207   /* Close Tracks element. */
208   Ebml_EndSubElement(glob, &trackStart);
209 
210   /* Segment element remains open. */
211 }
212 
write_webm_block(struct EbmlGlobal * glob,const vpx_codec_enc_cfg_t * cfg,const vpx_codec_cx_pkt_t * pkt)213 void write_webm_block(struct EbmlGlobal *glob,
214                       const vpx_codec_enc_cfg_t *cfg,
215                       const vpx_codec_cx_pkt_t *pkt) {
216   unsigned int block_length;
217   unsigned char track_number;
218   uint16_t block_timecode = 0;
219   unsigned char flags;
220   int64_t pts_ms;
221   int start_cluster = 0, is_keyframe;
222 
223   /* Calculate the PTS of this frame in milliseconds. */
224   pts_ms = pkt->data.frame.pts * 1000
225            * (uint64_t)cfg->g_timebase.num / (uint64_t)cfg->g_timebase.den;
226 
227   if (pts_ms <= glob->last_pts_ms)
228     pts_ms = glob->last_pts_ms + 1;
229 
230   glob->last_pts_ms = pts_ms;
231 
232   /* Calculate the relative time of this block. */
233   if (pts_ms - glob->cluster_timecode > SHRT_MAX)
234     start_cluster = 1;
235   else
236     block_timecode = (uint16_t)pts_ms - glob->cluster_timecode;
237 
238   is_keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY);
239   if (start_cluster || is_keyframe) {
240     if (glob->cluster_open)
241       Ebml_EndSubElement(glob, &glob->startCluster);
242 
243     /* Open the new cluster. */
244     block_timecode = 0;
245     glob->cluster_open = 1;
246     glob->cluster_timecode = (uint32_t)pts_ms;
247     glob->cluster_pos = ftello(glob->stream);
248     Ebml_StartSubElement(glob, &glob->startCluster, Cluster);
249     Ebml_SerializeUnsigned(glob, Timecode, glob->cluster_timecode);
250 
251     /* Save a cue point if this is a keyframe. */
252     if (is_keyframe) {
253       struct cue_entry *cue, *new_cue_list;
254 
255       new_cue_list = realloc(glob->cue_list,
256                              (glob->cues + 1) * sizeof(struct cue_entry));
257       if (new_cue_list)
258         glob->cue_list = new_cue_list;
259       else
260         fatal("Failed to realloc cue list.");
261 
262       cue = &glob->cue_list[glob->cues];
263       cue->time = glob->cluster_timecode;
264       cue->loc = glob->cluster_pos;
265       glob->cues++;
266     }
267   }
268 
269   /* Write the Simple Block. */
270   Ebml_WriteID(glob, SimpleBlock);
271 
272   block_length = (unsigned int)pkt->data.frame.sz + 4;
273   block_length |= 0x10000000;
274   Ebml_Serialize(glob, &block_length, sizeof(block_length), 4);
275 
276   track_number = 1;
277   track_number |= 0x80;
278   Ebml_Write(glob, &track_number, 1);
279 
280   Ebml_Serialize(glob, &block_timecode, sizeof(block_timecode), 2);
281 
282   flags = 0;
283   if (is_keyframe)
284     flags |= 0x80;
285   if (pkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE)
286     flags |= 0x08;
287   Ebml_Write(glob, &flags, 1);
288 
289   Ebml_Write(glob, pkt->data.frame.buf, (unsigned int)pkt->data.frame.sz);
290 }
291 
write_webm_file_footer(struct EbmlGlobal * glob,int hash)292 void write_webm_file_footer(struct EbmlGlobal *glob, int hash) {
293   EbmlLoc start_cues;
294   EbmlLoc start_cue_point;
295   EbmlLoc start_cue_tracks;
296   unsigned int i;
297 
298   if (glob->cluster_open)
299     Ebml_EndSubElement(glob, &glob->startCluster);
300 
301   glob->cue_pos = ftello(glob->stream);
302   Ebml_StartSubElement(glob, &start_cues, Cues);
303 
304   for (i = 0; i < glob->cues; i++) {
305     struct cue_entry *cue = &glob->cue_list[i];
306     Ebml_StartSubElement(glob, &start_cue_point, CuePoint);
307     Ebml_SerializeUnsigned(glob, CueTime, cue->time);
308 
309     Ebml_StartSubElement(glob, &start_cue_tracks, CueTrackPositions);
310     Ebml_SerializeUnsigned(glob, CueTrack, 1);
311     Ebml_SerializeUnsigned64(glob, CueClusterPosition,
312                              cue->loc - glob->position_reference);
313     Ebml_EndSubElement(glob, &start_cue_tracks);
314 
315     Ebml_EndSubElement(glob, &start_cue_point);
316   }
317 
318   Ebml_EndSubElement(glob, &start_cues);
319 
320   /* Close the Segment. */
321   Ebml_EndSubElement(glob, &glob->startSegment);
322 
323   /* Patch up the seek info block. */
324   write_webm_seek_info(glob);
325 
326   /* Patch up the track id. */
327   fseeko(glob->stream, glob->track_id_pos, SEEK_SET);
328   Ebml_SerializeUnsigned32(glob, TrackUID, glob->debug ? 0xDEADBEEF : hash);
329 
330   fseeko(glob->stream, 0, SEEK_END);
331 }
332