VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/EbmlWriter.cpp@ 68724

Last change on this file since 68724 was 68724, checked in by vboxsync, 8 years ago

Space.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
  • Property svn:mergeinfo set to (toggle deleted branches)
    /branches/VBox-3.0/src/VBox/Frontends/VBoxHeadless/VideoCapture/EbmlWriter.cpp58652,​70973
    /branches/VBox-3.2/src/VBox/Frontends/VBoxHeadless/VideoCapture/EbmlWriter.cpp66309,​66318
    /branches/VBox-4.0/src/VBox/Frontends/VBoxHeadless/VideoCapture/EbmlWriter.cpp70873
    /branches/VBox-4.1/src/VBox/Frontends/VBoxHeadless/VideoCapture/EbmlWriter.cpp74233
    /branches/VBox-4.2/src/VBox/Main/src-client/EbmlWriter.cpp91503-91504,​91506-91508,​91510,​91514-91515,​91521
    /branches/VBox-4.3/src/VBox/Main/src-client/EbmlWriter.cpp91223
    /branches/VBox-4.3/trunk/src/VBox/Main/src-client/EbmlWriter.cpp91223
    /branches/dsen/gui/src/VBox/Frontends/VBoxHeadless/VideoCapture/EbmlWriter.cpp79076-79078,​79089,​79109-79110,​79112-79113,​79127-79130,​79134,​79141,​79151,​79155,​79157-79159,​79193,​79197
    /branches/dsen/gui2/src/VBox/Frontends/VBoxHeadless/VideoCapture/EbmlWriter.cpp79224,​79228,​79233,​79235,​79258,​79262-79263,​79273,​79341,​79345,​79354,​79357,​79387-79388,​79559-79569,​79572-79573,​79578,​79581-79582,​79590-79591,​79598-79599,​79602-79603,​79605-79606,​79632,​79635,​79637,​79644
    /branches/dsen/gui3/src/VBox/Frontends/VBoxHeadless/VideoCapture/EbmlWriter.cpp79645-79692
File size: 38.2 KB
Line 
1/* $Id: EbmlWriter.cpp 68724 2017-09-12 12:57:57Z vboxsync $ */
2/** @file
3 * EbmlWriter.cpp - EBML writer + WebM container handling.
4 */
5
6/*
7 * Copyright (C) 2013-2017 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.215389.xyz. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18/**
19 * For more information, see:
20 * - https://w3c.github.io/media-source/webm-byte-stream-format.html
21 * - https://www.webmproject.org/docs/container/#muxer-guidelines
22 */
23
24#define LOG_GROUP LOG_GROUP_MAIN_DISPLAY
25#include "LoggingNew.h"
26
27#include <list>
28#include <map>
29#include <stack>
30
31#include <math.h> /* For lround.h. */
32
33#include <iprt/asm.h>
34#include <iprt/buildconfig.h>
35#include <iprt/cdefs.h>
36#include <iprt/err.h>
37#include <iprt/file.h>
38#include <iprt/rand.h>
39#include <iprt/string.h>
40
41#include <VBox/log.h>
42#include <VBox/version.h>
43
44#include "EbmlWriter.h"
45#include "EbmlMkvIDs.h"
46
47
48class Ebml
49{
50public:
51 typedef uint32_t EbmlClassId;
52
53private:
54
55 struct EbmlSubElement
56 {
57 uint64_t offset;
58 EbmlClassId classId;
59 EbmlSubElement(uint64_t offs, EbmlClassId cid) : offset(offs), classId(cid) {}
60 };
61
62 /** Stack of EBML sub elements. */
63 std::stack<EbmlSubElement> m_Elements;
64 /** The file's handle. */
65 RTFILE m_hFile;
66 /** The file's name (path). */
67 Utf8Str m_strFile;
68
69public:
70
71 Ebml(void)
72 : m_hFile(NIL_RTFILE) { }
73
74 virtual ~Ebml(void) { close(); }
75
76public:
77
78 /** Creates EBML output file. */
79 inline int create(const char *a_pszFilename, uint64_t fOpen)
80 {
81 int rc = RTFileOpen(&m_hFile, a_pszFilename, fOpen);
82 if (RT_SUCCESS(rc))
83 {
84 m_strFile = a_pszFilename;
85 }
86
87 return rc;
88 }
89
90 /** Returns the file name. */
91 inline const Utf8Str& getFileName(void)
92 {
93 return m_strFile;
94 }
95
96 /** Returns file size. */
97 inline uint64_t getFileSize(void)
98 {
99 return RTFileTell(m_hFile);
100 }
101
102 /** Get reference to file descriptor */
103 inline const RTFILE &getFile(void)
104 {
105 return m_hFile;
106 }
107
108 /** Returns available space on storage. */
109 inline uint64_t getAvailableSpace(void)
110 {
111 RTFOFF pcbFree;
112 int rc = RTFileQueryFsSizes(m_hFile, NULL, &pcbFree, 0, 0);
113 return (RT_SUCCESS(rc)? (uint64_t)pcbFree : UINT64_MAX);
114 }
115
116 /** Closes the file. */
117 inline void close(void)
118 {
119 if (!isOpen())
120 return;
121
122 AssertMsg(m_Elements.size() == 0,
123 ("%zu elements are not closed yet (next element to close is 0x%x)\n",
124 m_Elements.size(), m_Elements.top().classId));
125
126 RTFileClose(m_hFile);
127 m_hFile = NIL_RTFILE;
128
129 m_strFile = "";
130 }
131
132 /**
133 * Returns whether the file is open or not.
134 *
135 * @returns True if open, false if not.
136 */
137 inline bool isOpen(void)
138 {
139 return RTFileIsValid(m_hFile);
140 }
141
142 /** Starts an EBML sub-element. */
143 inline Ebml &subStart(EbmlClassId classId)
144 {
145 writeClassId(classId);
146 /* store the current file offset. */
147 m_Elements.push(EbmlSubElement(RTFileTell(m_hFile), classId));
148 /* Indicates that size of the element
149 * is unkown (as according to EBML specs).
150 */
151 writeUnsignedInteger(UINT64_C(0x01FFFFFFFFFFFFFF));
152 return *this;
153 }
154
155 /** Ends an EBML sub-element. */
156 inline Ebml &subEnd(EbmlClassId classId)
157 {
158#ifdef VBOX_STRICT
159 /* Class ID on the top of the stack should match the class ID passed
160 * to the function. Otherwise it may mean that we have a bug in the code.
161 */
162 AssertMsg(!m_Elements.empty(), ("No elements to close anymore\n"));
163 AssertMsg(m_Elements.top().classId == classId,
164 ("Ending sub element 0x%x is in wrong order (next to close is 0x%x)\n", classId, m_Elements.top().classId));
165#else
166 RT_NOREF(classId);
167#endif
168
169 uint64_t uPos = RTFileTell(m_hFile);
170 uint64_t uSize = uPos - m_Elements.top().offset - 8;
171 RTFileSeek(m_hFile, m_Elements.top().offset, RTFILE_SEEK_BEGIN, NULL);
172
173 /* Make sure that size will be serialized as uint64_t. */
174 writeUnsignedInteger(uSize | UINT64_C(0x0100000000000000));
175 RTFileSeek(m_hFile, uPos, RTFILE_SEEK_BEGIN, NULL);
176 m_Elements.pop();
177 return *this;
178 }
179
180 /** Serializes a null-terminated string. */
181 inline Ebml &serializeString(EbmlClassId classId, const char *str)
182 {
183 writeClassId(classId);
184 uint64_t size = strlen(str);
185 writeSize(size);
186 write(str, size);
187 return *this;
188 }
189
190 /* Serializes an UNSIGNED integer
191 * If size is zero then it will be detected automatically. */
192 inline Ebml &serializeUnsignedInteger(EbmlClassId classId, uint64_t parm, size_t size = 0)
193 {
194 writeClassId(classId);
195 if (!size) size = getSizeOfUInt(parm);
196 writeSize(size);
197 writeUnsignedInteger(parm, size);
198 return *this;
199 }
200
201 /** Serializes a floating point value.
202 *
203 * Only 8-bytes double precision values are supported
204 * by this function.
205 */
206 inline Ebml &serializeFloat(EbmlClassId classId, float value)
207 {
208 writeClassId(classId);
209 Assert(sizeof(uint32_t) == sizeof(float));
210 writeSize(sizeof(float));
211
212 union
213 {
214 float f;
215 uint8_t u8[4];
216 } u;
217
218 u.f = value;
219
220 for (int i = 3; i >= 0; i--) /* Converts values to big endian. */
221 write(&u.u8[i], 1);
222
223 return *this;
224 }
225
226 /** Serializes binary data. */
227 inline Ebml &serializeData(EbmlClassId classId, const void *pvData, size_t cbData)
228 {
229 writeClassId(classId);
230 writeSize(cbData);
231 write(pvData, cbData);
232 return *this;
233 }
234
235 /** Writes raw data to file. */
236 inline int write(const void *data, size_t size)
237 {
238 return RTFileWrite(m_hFile, data, size, NULL);
239 }
240
241 /** Writes an unsigned integer of variable of fixed size. */
242 inline void writeUnsignedInteger(uint64_t value, size_t size = sizeof(uint64_t))
243 {
244 /* convert to big-endian */
245 value = RT_H2BE_U64(value);
246 write(reinterpret_cast<uint8_t*>(&value) + sizeof(value) - size, size);
247 }
248
249 /** Writes EBML class ID to file.
250 *
251 * EBML ID already has a UTF8-like represenation
252 * so getSizeOfUInt is used to determine
253 * the number of its bytes.
254 */
255 inline void writeClassId(EbmlClassId parm)
256 {
257 writeUnsignedInteger(parm, getSizeOfUInt(parm));
258 }
259
260 /** Writes data size value. */
261 inline void writeSize(uint64_t parm)
262 {
263 /* The following expression defines the size of the value that will be serialized
264 * as an EBML UTF-8 like integer (with trailing bits represeting its size):
265 1xxx xxxx - value 0 to 2^7-2
266 01xx xxxx xxxx xxxx - value 0 to 2^14-2
267 001x xxxx xxxx xxxx xxxx xxxx - value 0 to 2^21-2
268 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^28-2
269 0000 1xxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^35-2
270 0000 01xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^42-2
271 0000 001x xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^49-2
272 0000 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^56-2
273 */
274 size_t size = 8 - ! (parm & (UINT64_MAX << 49)) - ! (parm & (UINT64_MAX << 42)) -
275 ! (parm & (UINT64_MAX << 35)) - ! (parm & (UINT64_MAX << 28)) -
276 ! (parm & (UINT64_MAX << 21)) - ! (parm & (UINT64_MAX << 14)) -
277 ! (parm & (UINT64_MAX << 7));
278 /* One is subtracted in order to avoid loosing significant bit when size = 8. */
279 uint64_t mask = RT_BIT_64(size * 8 - 1);
280 writeUnsignedInteger((parm & (((mask << 1) - 1) >> size)) | (mask >> (size - 1)), size);
281 }
282
283 /** Size calculation for variable size UNSIGNED integer.
284 *
285 * The function defines the size of the number by trimming
286 * consequent trailing zero bytes starting from the most significant.
287 * The following statement is always true:
288 * 1 <= getSizeOfUInt(arg) <= 8.
289 *
290 * Every !(arg & (UINT64_MAX << X)) expression gives one
291 * if an only if all the bits from X to 63 are set to zero.
292 */
293 static inline size_t getSizeOfUInt(uint64_t arg)
294 {
295 return 8 - ! (arg & (UINT64_MAX << 56)) - ! (arg & (UINT64_MAX << 48)) -
296 ! (arg & (UINT64_MAX << 40)) - ! (arg & (UINT64_MAX << 32)) -
297 ! (arg & (UINT64_MAX << 24)) - ! (arg & (UINT64_MAX << 16)) -
298 ! (arg & (UINT64_MAX << 8));
299 }
300
301private:
302 void operator=(const Ebml &);
303
304};
305
306/** No flags specified. */
307#define VBOX_WEBM_BLOCK_FLAG_NONE 0
308/** Invisible block which can be skipped. */
309#define VBOX_WEBM_BLOCK_FLAG_INVISIBLE 0x08
310/** The block marks a key frame. */
311#define VBOX_WEBM_BLOCK_FLAG_KEY_FRAME 0x80
312
313/** The default timecode scale factor for WebM -- all timecodes in the segments are expressed in ms.
314 * This allows every cluster to have blocks with positive values up to 32.767 seconds. */
315#define VBOX_WEBM_TIMECODESCALE_FACTOR_MS 1000000
316
317/** Maximum time (in ms) a cluster can store. */
318#define VBOX_WEBM_CLUSTER_MAX_LEN_MS INT16_MAX
319
320/** Maximum time a block can store.
321 * With signed 16-bit timecodes and a default timecode scale of 1ms per unit this makes 65536ms. */
322#define VBOX_WEBM_BLOCK_MAX_LEN_MS UINT16_MAX
323
324class WebMWriter_Impl
325{
326 /**
327 * Track type enumeration.
328 */
329 enum WebMTrackType
330 {
331 /** Unknown / invalid type. */
332 WebMTrackType_Invalid = 0,
333 /** Only writes audio. */
334 WebMTrackType_Audio = 1,
335 /** Only writes video. */
336 WebMTrackType_Video = 2
337 };
338
339 /**
340 * Structure for keeping a WebM track entry.
341 */
342 struct WebMTrack
343 {
344 WebMTrack(WebMTrackType a_enmType, uint8_t a_uTrack, uint64_t a_offID)
345 : enmType(a_enmType)
346 , uTrack(a_uTrack)
347 , offUUID(a_offID)
348 , cTotalClusters(0)
349 , cTotalBlocks(0)
350 {
351 uUUID = RTRandU32();
352 }
353
354 /** The type of this track. */
355 WebMTrackType enmType;
356 /** Track parameters. */
357 union
358 {
359 struct
360 {
361 /** Sample rate of input data. */
362 uint32_t uHz;
363 /** Duration of the frame in samples (per channel).
364 * Valid frame size are:
365 *
366 * ms Frame size
367 * 2.5 120
368 * 5 240
369 * 10 480
370 * 20 (Default) 960
371 * 40 1920
372 * 60 2880
373 */
374 uint16_t framesPerBlock;
375 /** How many milliseconds (ms) one written (simple) block represents. */
376 uint16_t msPerBlock;
377 } Audio;
378 };
379 /** This track's track number. Also used as key in track map. */
380 uint8_t uTrack;
381 /** The track's "UUID".
382 * Needed in case this track gets mux'ed with tracks from other files. Not really unique though. */
383 uint32_t uUUID;
384 /** Absolute offset in file of track UUID.
385 * Needed to write the hash sum within the footer. */
386 uint64_t offUUID;
387 /** Total number of clusters. */
388 uint64_t cTotalClusters;
389 /** Total number of blocks. */
390 uint64_t cTotalBlocks;
391 };
392
393 /**
394 * Structure for keeping a cue point.
395 */
396 struct WebMCuePoint
397 {
398 WebMCuePoint(WebMTrack *a_pTrack, uint32_t a_tcClusterStart, uint64_t a_offClusterStart)
399 : pTrack(a_pTrack)
400 , tcClusterStart(a_tcClusterStart), offClusterStart(a_offClusterStart) {}
401
402 /** Associated track. */
403 WebMTrack *pTrack;
404 /** Start time code of the related cluster. */
405 uint32_t tcClusterStart;
406 /** Start offset of the related cluster. */
407 uint64_t offClusterStart;
408 };
409
410 /**
411 * Structure for keeping a WebM cluster entry.
412 */
413 struct WebMCluster
414 {
415 WebMCluster(void)
416 : uID(0)
417 , offCluster(0)
418 , fOpen(false)
419 , tcStartMs(0)
420 , tcEndMs(0)
421 , cBlocks(0) { }
422
423 /** This cluster's ID. */
424 uint64_t uID;
425 /** Absolute offset (in bytes) of current cluster.
426 * Needed for seeking info table. */
427 uint64_t offCluster;
428 /** Whether this cluster element is opened currently. */
429 bool fOpen;
430 /** Timecode (in ms) when starting this cluster. */
431 uint64_t tcStartMs;
432 /** Timecode (in ms) when this cluster ends. */
433 uint64_t tcEndMs;
434 /** Number of (simple) blocks in this cluster. */
435 uint64_t cBlocks;
436 };
437
438 /**
439 * Structure for keeping a WebM segment entry.
440 *
441 * Current we're only using one segment.
442 */
443 struct WebMSegment
444 {
445 WebMSegment(void)
446 : tcStart(UINT64_MAX)
447 , tcEnd(UINT64_MAX)
448 , offStart(0)
449 , offInfo(0)
450 , offSeekInfo(0)
451 , offTracks(0)
452 , offCues(0)
453 {
454 uTimecodeScaleFactor = VBOX_WEBM_TIMECODESCALE_FACTOR_MS;
455
456 LogFunc(("Default timecode scale is: %RU64ns\n", uTimecodeScaleFactor));
457 }
458
459 /** The timecode scale factor of this segment. */
460 uint64_t uTimecodeScaleFactor;
461
462 /** Timecode when starting this segment. */
463 uint64_t tcStart;
464 /** Timecode when this segment ended. */
465 uint64_t tcEnd;
466
467 /** Absolute offset (in bytes) of CurSeg. */
468 uint64_t offStart;
469 /** Absolute offset (in bytes) of general info. */
470 uint64_t offInfo;
471 /** Absolute offset (in bytes) of seeking info. */
472 uint64_t offSeekInfo;
473 /** Absolute offset (in bytes) of tracks. */
474 uint64_t offTracks;
475 /** Absolute offset (in bytes) of cues table. */
476 uint64_t offCues;
477 /** List of cue points. Needed for seeking table. */
478 std::list<WebMCuePoint> lstCues;
479
480 /** Map of tracks.
481 * The key marks the track number (*not* the UUID!). */
482 std::map <uint8_t, WebMTrack *> mapTracks;
483
484 /** Current cluster which is being handled.
485 *
486 * Note that we don't need (and shouldn't need, as this can be a *lot* of data!) a
487 * list of all clusters. */
488 WebMCluster CurCluster;
489
490 } CurSeg;
491
492#ifdef VBOX_WITH_LIBOPUS
493# pragma pack(push)
494# pragma pack(1)
495 /** Opus codec private data.
496 * Taken from: https://wiki.xiph.org/MatroskaOpus */
497 struct OpusPrivData
498 {
499 OpusPrivData(uint32_t a_u32SampleRate, uint8_t a_u8Channels)
500 {
501 au64Head = RT_MAKE_U64_FROM_U8('O', 'p', 'u', 's', 'H', 'e', 'a', 'd');
502 u8Version = 1;
503 u8Channels = a_u8Channels;
504 u16PreSkip = 0;
505 u32SampleRate = a_u32SampleRate;
506 u16Gain = 0;
507 u8MappingFamily = 0;
508 }
509
510 uint64_t au64Head; /**< Defaults to "OpusHead". */
511 uint8_t u8Version; /**< Must be set to 1. */
512 uint8_t u8Channels;
513 uint16_t u16PreSkip;
514 /** Sample rate *before* encoding to Opus.
515 * Note: This rate has nothing to do with the playback rate later! */
516 uint32_t u32SampleRate;
517 uint16_t u16Gain;
518 /** Must stay 0 -- otherwise a mapping table must be appended
519 * right after this header. */
520 uint8_t u8MappingFamily;
521 };
522 AssertCompileSize(OpusPrivData, 19);
523# pragma pack(pop)
524#endif /* VBOX_WITH_LIBOPUS */
525
526 /** Audio codec to use. */
527 WebMWriter::AudioCodec m_enmAudioCodec;
528 /** Video codec to use. */
529 WebMWriter::VideoCodec m_enmVideoCodec;
530
531 /** Whether we're currently in the tracks section. */
532 bool m_fInTracksSection;
533
534 /** Size of timecodes in bytes. */
535 size_t m_cbTimecode;
536 /** Maximum value a timecode can have. */
537 uint32_t m_uTimecodeMax;
538
539 Ebml m_Ebml;
540
541public:
542
543 typedef std::map <uint8_t, WebMTrack *> WebMTracks;
544
545public:
546
547 WebMWriter_Impl() :
548 m_fInTracksSection(false)
549 {
550 /* Size (in bytes) of time code to write. We use 2 bytes (16 bit) by default. */
551 m_cbTimecode = 2;
552 m_uTimecodeMax = UINT16_MAX;
553 }
554
555 virtual ~WebMWriter_Impl()
556 {
557 close();
558 }
559
560 /**
561 * Adds an audio track.
562 *
563 * @returns IPRT status code.
564 * @param uHz Input sampling rate.
565 * Must be supported by the selected audio codec.
566 * @param cChannels Number of input audio channels.
567 * @param cBits Number of input bits per channel.
568 * @param puTrack Track number on successful creation. Optional.
569 */
570 int AddAudioTrack(uint16_t uHz, uint8_t cChannels, uint8_t cBits, uint8_t *puTrack)
571 {
572#ifdef VBOX_WITH_LIBOPUS
573 int rc;
574
575 /*
576 * Check if the requested codec rate is supported.
577 *
578 * Only the following values are supported by an Opus standard build
579 * -- every other rate only is supported by a custom build.
580 */
581 switch (uHz)
582 {
583 case 48000:
584 case 24000:
585 case 16000:
586 case 12000:
587 case 8000:
588 rc = VINF_SUCCESS;
589 break;
590
591 default:
592 rc = VERR_NOT_SUPPORTED;
593 break;
594 }
595
596 if (RT_FAILURE(rc))
597 return rc;
598
599 const uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size();
600
601 m_Ebml.subStart(MkvElem_TrackEntry);
602
603 m_Ebml.serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack);
604 m_Ebml.serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */);
605 m_Ebml.serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0);
606
607 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Audio, uTrack, RTFileTell(m_Ebml.getFile()));
608
609 pTrack->Audio.uHz = uHz;
610 pTrack->Audio.msPerBlock = 20; /** Opus uses 20ms by default. Make this configurable? */
611 pTrack->Audio.framesPerBlock = uHz / (1000 /* s in ms */ / pTrack->Audio.msPerBlock);
612
613 OpusPrivData opusPrivData(uHz, cChannels);
614
615 LogFunc(("Opus @ %RU16Hz (%RU16ms + %RU16 frames per block)\n",
616 pTrack->Audio.uHz, pTrack->Audio.msPerBlock, pTrack->Audio.framesPerBlock));
617
618 m_Ebml.serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID, 4)
619 .serializeUnsignedInteger(MkvElem_TrackType, 2 /* Audio */)
620 .serializeString(MkvElem_CodecID, "A_OPUS")
621 .serializeData(MkvElem_CodecPrivate, &opusPrivData, sizeof(opusPrivData))
622 .serializeUnsignedInteger(MkvElem_CodecDelay, 0)
623 .serializeUnsignedInteger(MkvElem_SeekPreRoll, 80 * 1000000) /* 80ms in ns. */
624 .subStart(MkvElem_Audio)
625 .serializeFloat(MkvElem_SamplingFrequency, (float)uHz)
626 .serializeUnsignedInteger(MkvElem_Channels, cChannels)
627 .serializeUnsignedInteger(MkvElem_BitDepth, cBits)
628 .subEnd(MkvElem_Audio)
629 .subEnd(MkvElem_TrackEntry);
630
631 CurSeg.mapTracks[uTrack] = pTrack;
632
633 if (puTrack)
634 *puTrack = uTrack;
635
636 return VINF_SUCCESS;
637#else
638 RT_NOREF(uHz, cChannels, cBits, puTrack);
639 return VERR_NOT_SUPPORTED;
640#endif
641 }
642
643 int AddVideoTrack(uint16_t uWidth, uint16_t uHeight, double dbFPS, uint8_t *puTrack)
644 {
645#ifdef VBOX_WITH_LIBVPX
646 RT_NOREF(dbFPS);
647
648 const uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size();
649
650 m_Ebml.subStart(MkvElem_TrackEntry);
651
652 m_Ebml.serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack);
653 m_Ebml.serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */);
654 m_Ebml.serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0);
655
656 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Video, uTrack, RTFileTell(m_Ebml.getFile()));
657
658 /** @todo Resolve codec type. */
659 m_Ebml.serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID /* UID */, 4)
660 .serializeUnsignedInteger(MkvElem_TrackType, 1 /* Video */)
661 .serializeString(MkvElem_CodecID, "V_VP8")
662 .subStart(MkvElem_Video)
663 .serializeUnsignedInteger(MkvElem_PixelWidth, uWidth)
664 .serializeUnsignedInteger(MkvElem_PixelHeight, uHeight)
665 .subEnd(MkvElem_Video);
666
667 m_Ebml.subEnd(MkvElem_TrackEntry);
668
669 CurSeg.mapTracks[uTrack] = pTrack;
670
671 if (puTrack)
672 *puTrack = uTrack;
673
674 return VINF_SUCCESS;
675#else
676 RT_NOREF(uWidth, uHeight, dbFPS, puTrack);
677 return VERR_NOT_SUPPORTED;
678#endif
679 }
680
681 int writeHeader(void)
682 {
683 LogFunc(("Header @ %RU64\n", RTFileTell(m_Ebml.getFile())));
684
685 m_Ebml.subStart(MkvElem_EBML)
686 .serializeUnsignedInteger(MkvElem_EBMLVersion, 1)
687 .serializeUnsignedInteger(MkvElem_EBMLReadVersion, 1)
688 .serializeUnsignedInteger(MkvElem_EBMLMaxIDLength, 4)
689 .serializeUnsignedInteger(MkvElem_EBMLMaxSizeLength, 8)
690 .serializeString(MkvElem_DocType, "webm")
691 .serializeUnsignedInteger(MkvElem_DocTypeVersion, 2)
692 .serializeUnsignedInteger(MkvElem_DocTypeReadVersion, 2)
693 .subEnd(MkvElem_EBML);
694
695 m_Ebml.subStart(MkvElem_Segment);
696
697 /* Save offset of current segment. */
698 CurSeg.offStart = RTFileTell(m_Ebml.getFile());
699
700 writeSegSeekInfo();
701
702 /* Save offset of upcoming tracks segment. */
703 CurSeg.offTracks = RTFileTell(m_Ebml.getFile());
704
705 /* The tracks segment starts right after this header. */
706 m_Ebml.subStart(MkvElem_Tracks);
707 m_fInTracksSection = true;
708
709 return VINF_SUCCESS;
710 }
711
712 int writeSimpleBlockInternal(WebMTrack *a_pTrack, uint64_t a_uTimecode,
713 const void *a_pvData, size_t a_cbData, uint8_t a_fFlags)
714 {
715 Log3Func(("SimpleBlock @ %RU64 (T%RU8, TS=%RU64, %zu bytes)\n",
716 RTFileTell(m_Ebml.getFile()), a_pTrack->uTrack, a_uTimecode, a_cbData));
717
718 /** @todo Mask out non-valid timecode bits, e.g. the upper 48 bits for a (default) 16-bit timecode. */
719 Assert(a_uTimecode <= m_uTimecodeMax);
720
721 /* Write a "Simple Block". */
722 m_Ebml.writeClassId(MkvElem_SimpleBlock);
723 /* Block size. */
724 m_Ebml.writeUnsignedInteger(0x10000000u | ( 1 /* Track number size. */
725 + m_cbTimecode /* Timecode size .*/
726 + 1 /* Flags size. */
727 + a_cbData /* Actual frame data size. */), 4);
728 /* Track number. */
729 m_Ebml.writeSize(a_pTrack->uTrack);
730 /* Timecode (relative to cluster opening timecode). */
731 m_Ebml.writeUnsignedInteger(a_uTimecode, m_cbTimecode);
732 /* Flags. */
733 m_Ebml.writeUnsignedInteger(a_fFlags, 1);
734 /* Frame data. */
735 m_Ebml.write(a_pvData, a_cbData);
736
737 a_pTrack->cTotalBlocks++;
738
739 return VINF_SUCCESS;
740 }
741
742#ifdef VBOX_WITH_LIBVPX
743 int writeBlockVP8(WebMTrack *a_pTrack, const vpx_codec_enc_cfg_t *a_pCfg, const vpx_codec_cx_pkt_t *a_pPkt)
744 {
745 RT_NOREF(a_pTrack);
746
747 WebMCluster &Cluster = CurSeg.CurCluster;
748
749 /* Calculate the PTS of this frame (in ms). */
750 uint64_t tcPTSMs = a_pPkt->data.frame.pts * 1000
751 * (uint64_t) a_pCfg->g_timebase.num / a_pCfg->g_timebase.den;
752
753 if ( tcPTSMs
754 && tcPTSMs <= CurSeg.CurCluster.tcEndMs)
755 {
756 tcPTSMs = CurSeg.CurCluster.tcEndMs + 1;
757 }
758
759 /* Whether to start a new cluster or not. */
760 bool fClusterStart = false;
761
762 /* No blocks written yet? Start a new cluster. */
763 if (a_pTrack->cTotalBlocks == 0)
764 fClusterStart = true;
765
766 /* Did we reach the maximum a cluster can hold? Use a new cluster then. */
767 if (tcPTSMs > VBOX_WEBM_CLUSTER_MAX_LEN_MS)
768 {
769 tcPTSMs = 0;
770
771 fClusterStart = true;
772 }
773
774 const bool fKeyframe = RT_BOOL(a_pPkt->data.frame.flags & VPX_FRAME_IS_KEY);
775
776 if ( fClusterStart
777 || fKeyframe)
778 {
779 if (Cluster.fOpen) /* Close current cluster first. */
780 {
781 m_Ebml.subEnd(MkvElem_Cluster);
782 Cluster.fOpen = false;
783 }
784
785 Cluster.fOpen = true;
786 Cluster.uID = a_pTrack->cTotalClusters;
787 Cluster.tcStartMs = tcPTSMs;
788 Cluster.offCluster = RTFileTell(m_Ebml.getFile());
789 Cluster.cBlocks = 0;
790
791 LogFunc(("[T%RU8C%RU64] Start @ %RU64ms / %RU64 bytes\n",
792 a_pTrack->uTrack, Cluster.uID, Cluster.tcStartMs, Cluster.offCluster));
793
794 m_Ebml.subStart(MkvElem_Cluster)
795 .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tcStartMs);
796
797 /* Save a cue point if this is a keyframe. */
798 if (fKeyframe)
799 {
800 WebMCuePoint cue(a_pTrack, Cluster.tcStartMs, Cluster.offCluster);
801 CurSeg.lstCues.push_back(cue);
802 }
803
804 a_pTrack->cTotalClusters++;
805 }
806
807 Cluster.tcEndMs = tcPTSMs;
808 Cluster.cBlocks++;
809
810 /* Calculate the block's timecode, which is relative to the cluster's starting timecode. */
811 uint16_t tcBlockMs = static_cast<uint16_t>(tcPTSMs - Cluster.tcStartMs);
812
813 Log2Func(("tcPTSMs=%RU64, tcBlockMs=%RU64\n", tcPTSMs, tcBlockMs));
814
815 Log2Func(("[C%RU64] cBlocks=%RU64, tcStartMs=%RU64, tcEndMs=%RU64 (%zums)\n",
816 Cluster.uID, Cluster.cBlocks, Cluster.tcStartMs, Cluster.tcEndMs, Cluster.tcEndMs - Cluster.tcStartMs));
817
818 uint8_t fFlags = 0;
819 if (fKeyframe)
820 fFlags |= VBOX_WEBM_BLOCK_FLAG_KEY_FRAME;
821 if (a_pPkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE)
822 fFlags |= VBOX_WEBM_BLOCK_FLAG_INVISIBLE;
823
824 return writeSimpleBlockInternal(a_pTrack, tcBlockMs, a_pPkt->data.frame.buf, a_pPkt->data.frame.sz, fFlags);
825 }
826#endif /* VBOX_WITH_LIBVPX */
827
828#ifdef VBOX_WITH_LIBOPUS
829 /* Audio blocks that have same absolute timecode as video blocks SHOULD be written before the video blocks. */
830 int writeBlockOpus(WebMTrack *a_pTrack, const void *pvData, size_t cbData, uint64_t uTimeStampMs)
831 {
832 AssertPtrReturn(a_pTrack, VERR_INVALID_POINTER);
833 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
834 AssertReturn(cbData, VERR_INVALID_PARAMETER);
835
836 RT_NOREF(uTimeStampMs);
837
838 WebMCluster &Cluster = CurSeg.CurCluster;
839
840 /* Calculate the PTS of the current block:
841 *
842 * The "raw PTS" is the exact time of an object represented in nanoseconds):
843 * Raw Timecode = (Block timecode + Cluster timecode) * TimecodeScaleFactor
844 */
845 uint64_t tcPTSMs = Cluster.tcStartMs + (Cluster.cBlocks * 20 /*ms */);
846
847 /* Whether to start a new cluster or not. */
848 bool fClusterStart = false;
849
850 if (a_pTrack->cTotalBlocks == 0)
851 fClusterStart = true;
852
853 /* Did we reach the maximum a cluster can hold? Use a new cluster then. */
854 if (tcPTSMs > VBOX_WEBM_CLUSTER_MAX_LEN_MS)
855 fClusterStart = true;
856
857 if (fClusterStart)
858 {
859 if (Cluster.fOpen) /* Close current clusters first. */
860 {
861 m_Ebml.subEnd(MkvElem_Cluster);
862 Cluster.fOpen = false;
863 }
864
865 Cluster.fOpen = true;
866 Cluster.uID = a_pTrack->cTotalClusters;
867 Cluster.tcStartMs = Cluster.tcEndMs;
868 Cluster.offCluster = RTFileTell(m_Ebml.getFile());
869 Cluster.cBlocks = 0;
870
871 LogFunc(("[T%RU8C%RU64] Start @ %RU64ms / %RU64 bytes\n",
872 a_pTrack->uTrack, Cluster.uID, Cluster.tcStartMs, Cluster.offCluster));
873
874 /* As all audio frame as key frames, insert a new cue point when a new cluster starts. */
875 WebMCuePoint cue(a_pTrack, Cluster.tcStartMs, Cluster.offCluster);
876 CurSeg.lstCues.push_back(cue);
877
878 m_Ebml.subStart(MkvElem_Cluster)
879 .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tcStartMs);
880
881 a_pTrack->cTotalClusters++;
882 }
883
884 Cluster.tcEndMs = tcPTSMs;
885 Cluster.cBlocks++;
886
887 /* Calculate the block's timecode, which is relative to the cluster's starting timecode. */
888 uint16_t tcBlockMs = static_cast<uint16_t>(tcPTSMs - Cluster.tcStartMs);
889
890 Log2Func(("tcPTSMs=%RU64, tcBlockMs=%RU64\n", tcPTSMs, tcBlockMs));
891
892 Log2Func(("[C%RU64] cBlocks=%RU64, tcStartMs=%RU64, tcEndMs=%RU64 (%zums)\n",
893 Cluster.uID, Cluster.cBlocks, Cluster.tcStartMs, Cluster.tcEndMs, Cluster.tcEndMs - Cluster.tcStartMs));
894
895 return writeSimpleBlockInternal(a_pTrack, tcBlockMs,
896 pvData, cbData, VBOX_WEBM_BLOCK_FLAG_KEY_FRAME);
897 }
898#endif /* VBOX_WITH_LIBOPUS */
899
900 int WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData)
901 {
902 RT_NOREF(pvData, cbData); /* Only needed for assertions for now. */
903
904 WebMTracks::iterator itTrack = CurSeg.mapTracks.find(uTrack);
905 if (itTrack == CurSeg.mapTracks.end())
906 return VERR_NOT_FOUND;
907
908 WebMTrack *pTrack = itTrack->second;
909 AssertPtr(pTrack);
910
911 int rc;
912
913 if (m_fInTracksSection)
914 {
915 m_Ebml.subEnd(MkvElem_Tracks);
916 m_fInTracksSection = false;
917 }
918
919 switch (pTrack->enmType)
920 {
921
922 case WebMTrackType_Audio:
923 {
924#ifdef VBOX_WITH_LIBOPUS
925 if (m_enmAudioCodec == WebMWriter::AudioCodec_Opus)
926 {
927 Assert(cbData == sizeof(WebMWriter::BlockData_Opus));
928 WebMWriter::BlockData_Opus *pData = (WebMWriter::BlockData_Opus *)pvData;
929 rc = writeBlockOpus(pTrack, pData->pvData, pData->cbData, pData->uTimestampMs);
930 }
931 else
932#endif /* VBOX_WITH_LIBOPUS */
933 rc = VERR_NOT_SUPPORTED;
934 break;
935 }
936
937 case WebMTrackType_Video:
938 {
939#ifdef VBOX_WITH_LIBVPX
940 if (m_enmVideoCodec == WebMWriter::VideoCodec_VP8)
941 {
942 Assert(cbData == sizeof(WebMWriter::BlockData_VP8));
943 WebMWriter::BlockData_VP8 *pData = (WebMWriter::BlockData_VP8 *)pvData;
944 rc = writeBlockVP8(pTrack, pData->pCfg, pData->pPkt);
945 }
946 else
947#endif /* VBOX_WITH_LIBVPX */
948 rc = VERR_NOT_SUPPORTED;
949 break;
950 }
951
952 default:
953 rc = VERR_NOT_SUPPORTED;
954 break;
955 }
956
957 return rc;
958 }
959
960 int writeFooter(void)
961 {
962 if (m_fInTracksSection)
963 {
964 m_Ebml.subEnd(MkvElem_Tracks);
965 m_fInTracksSection = false;
966 }
967
968 if (CurSeg.CurCluster.fOpen)
969 {
970 m_Ebml.subEnd(MkvElem_Cluster);
971 CurSeg.CurCluster.fOpen = false;
972 }
973
974 /*
975 * Write Cues element.
976 */
977 LogFunc(("Cues @ %RU64\n", RTFileTell(m_Ebml.getFile())));
978
979 CurSeg.offCues = RTFileTell(m_Ebml.getFile());
980
981 m_Ebml.subStart(MkvElem_Cues);
982
983 std::list<WebMCuePoint>::iterator itCuePoint = CurSeg.lstCues.begin();
984 while (itCuePoint != CurSeg.lstCues.end())
985 {
986 /* Sanity. */
987 AssertPtr(itCuePoint->pTrack);
988
989 const uint64_t uClusterPos = itCuePoint->offClusterStart - CurSeg.offStart;
990
991 LogFunc(("CuePoint @ %RU64: Track #%RU8 (Time %RU64, Pos %RU64)\n",
992 RTFileTell(m_Ebml.getFile()), itCuePoint->pTrack->uTrack, itCuePoint->tcClusterStart, uClusterPos));
993
994 m_Ebml.subStart(MkvElem_CuePoint)
995 .serializeUnsignedInteger(MkvElem_CueTime, itCuePoint->tcClusterStart)
996 .subStart(MkvElem_CueTrackPositions)
997 .serializeUnsignedInteger(MkvElem_CueTrack, itCuePoint->pTrack->uTrack)
998 .serializeUnsignedInteger(MkvElem_CueClusterPosition, uClusterPos, 8)
999 .subEnd(MkvElem_CueTrackPositions)
1000 .subEnd(MkvElem_CuePoint);
1001
1002 itCuePoint++;
1003 }
1004
1005 m_Ebml.subEnd(MkvElem_Cues);
1006 m_Ebml.subEnd(MkvElem_Segment);
1007
1008 /*
1009 * Re-Update SeekHead / Info elements.
1010 */
1011
1012 writeSegSeekInfo();
1013
1014 return RTFileSeek(m_Ebml.getFile(), 0, RTFILE_SEEK_END, NULL);
1015 }
1016
1017 int close(void)
1018 {
1019 WebMTracks::iterator itTrack = CurSeg.mapTracks.begin();
1020 for (; itTrack != CurSeg.mapTracks.end(); ++itTrack)
1021 {
1022 delete itTrack->second;
1023 CurSeg.mapTracks.erase(itTrack);
1024 }
1025
1026 Assert(CurSeg.mapTracks.size() == 0);
1027
1028 m_Ebml.close();
1029
1030 return VINF_SUCCESS;
1031 }
1032
1033 friend class Ebml;
1034 friend class WebMWriter;
1035
1036private:
1037
1038 /**
1039 * Writes the segment's seek information and cue points.
1040 *
1041 * @returns IPRT status code.
1042 */
1043 void writeSegSeekInfo(void)
1044 {
1045 if (CurSeg.offSeekInfo)
1046 RTFileSeek(m_Ebml.getFile(), CurSeg.offSeekInfo, RTFILE_SEEK_BEGIN, NULL);
1047 else
1048 CurSeg.offSeekInfo = RTFileTell(m_Ebml.getFile());
1049
1050 LogFunc(("SeekHead @ %RU64\n", CurSeg.offSeekInfo));
1051
1052 m_Ebml.subStart(MkvElem_SeekHead);
1053
1054 m_Ebml.subStart(MkvElem_Seek)
1055 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Tracks)
1056 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offTracks - CurSeg.offStart, 8)
1057 .subEnd(MkvElem_Seek);
1058
1059 Assert(CurSeg.offCues - CurSeg.offStart > 0); /* Sanity. */
1060
1061 m_Ebml.subStart(MkvElem_Seek)
1062 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Cues)
1063 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offCues - CurSeg.offStart, 8)
1064 .subEnd(MkvElem_Seek);
1065
1066 m_Ebml.subStart(MkvElem_Seek)
1067 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Info)
1068 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offInfo - CurSeg.offStart, 8)
1069 .subEnd(MkvElem_Seek);
1070
1071 m_Ebml.subEnd(MkvElem_SeekHead);
1072
1073 //int64_t iFrameTime = (int64_t)1000 * 1 / 25; //m_Framerate.den / m_Framerate.num; /** @todo Fix this! */
1074 CurSeg.offInfo = RTFileTell(m_Ebml.getFile());
1075
1076 LogFunc(("Info @ %RU64\n", CurSeg.offInfo));
1077
1078 char szMux[64];
1079 RTStrPrintf(szMux, sizeof(szMux),
1080#ifdef VBOX_WITH_LIBVPX
1081 "vpxenc%s", vpx_codec_version_str());
1082#else
1083 "unknown");
1084#endif
1085 char szApp[64];
1086 RTStrPrintf(szApp, sizeof(szApp), VBOX_PRODUCT " %sr%u", VBOX_VERSION_STRING, RTBldCfgRevision());
1087
1088 LogFunc(("Duration=%RU64\n", CurSeg.tcEnd - CurSeg.tcStart));
1089
1090 m_Ebml.subStart(MkvElem_Info)
1091 .serializeUnsignedInteger(MkvElem_TimecodeScale, CurSeg.uTimecodeScaleFactor)
1092 .serializeFloat(MkvElem_Segment_Duration, CurSeg.tcEnd - CurSeg.tcStart)
1093 .serializeString(MkvElem_MuxingApp, szMux)
1094 .serializeString(MkvElem_WritingApp, szApp)
1095 .subEnd(MkvElem_Info);
1096 }
1097};
1098
1099WebMWriter::WebMWriter(void) : m_pImpl(new WebMWriter_Impl()) {}
1100
1101WebMWriter::~WebMWriter(void)
1102{
1103 if (m_pImpl)
1104 {
1105 Close();
1106 delete m_pImpl;
1107 }
1108}
1109
1110int WebMWriter::Create(const char *a_pszFilename, uint64_t a_fOpen,
1111 WebMWriter::AudioCodec a_enmAudioCodec, WebMWriter::VideoCodec a_enmVideoCodec)
1112{
1113 try
1114 {
1115 m_pImpl->m_enmAudioCodec = a_enmAudioCodec;
1116 m_pImpl->m_enmVideoCodec = a_enmVideoCodec;
1117
1118 LogFunc(("Creating '%s'\n", a_pszFilename));
1119
1120 int rc = m_pImpl->m_Ebml.create(a_pszFilename, a_fOpen);
1121 if (RT_SUCCESS(rc))
1122 rc = m_pImpl->writeHeader();
1123 }
1124 catch(int rc)
1125 {
1126 return rc;
1127 }
1128 return VINF_SUCCESS;
1129}
1130
1131int WebMWriter::Close(void)
1132{
1133 if (!m_pImpl->m_Ebml.isOpen())
1134 return VINF_SUCCESS;
1135
1136 int rc = m_pImpl->writeFooter();
1137 if (RT_SUCCESS(rc))
1138 m_pImpl->close();
1139
1140 return rc;
1141}
1142
1143int WebMWriter::AddAudioTrack(uint16_t uHz, uint8_t cChannels, uint8_t cBitDepth, uint8_t *puTrack)
1144{
1145 return m_pImpl->AddAudioTrack(uHz, cChannels, cBitDepth, puTrack);
1146}
1147
1148int WebMWriter::AddVideoTrack(uint16_t uWidth, uint16_t uHeight, double dbFPS, uint8_t *puTrack)
1149{
1150 return m_pImpl->AddVideoTrack(uWidth, uHeight, dbFPS, puTrack);
1151}
1152
1153int WebMWriter::WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData)
1154{
1155 int rc;
1156
1157 try
1158 {
1159 rc = m_pImpl->WriteBlock(uTrack, pvData, cbData);
1160 }
1161 catch(int rc2)
1162 {
1163 rc = rc2;
1164 }
1165 return rc;
1166}
1167
1168const Utf8Str& WebMWriter::GetFileName(void)
1169{
1170 return m_pImpl->m_Ebml.getFileName();
1171}
1172
1173uint64_t WebMWriter::GetFileSize(void)
1174{
1175 return m_pImpl->m_Ebml.getFileSize();
1176}
1177
1178uint64_t WebMWriter::GetAvailableSpace(void)
1179{
1180 return m_pImpl->m_Ebml.getAvailableSpace();
1181}
1182
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette