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
18void 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) \
25for (i = len - 1; i >= 0; i--) { \
26  x = (char)(*(const s *)buffer_in >> (i * CHAR_BIT)); \
27  Ebml_Write(glob, &x, 1); \
28}
29
30void 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 */
65static 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
74static 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
83static 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
102void 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
113void 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
156void 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
213void 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
292void 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