VirtualBox

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

Last change on this file since 68720 was 68720, checked in by vboxsync, 7 years ago

VideoRec: Added support for building libopus on Windows, cleaned up libopus' config.h by putting most of the stuff into our own Makefile. Misc building tweaks for getting it to build on Windows as well.

  • 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 68720 2017-09-12 10:21:01Z 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.virtualbox.org. 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 RTStrPrintf((char *)au8Head, sizeof(au8Head), "OpusHead");
502
503 u8Version = 1;
504 u8Channels = a_u8Channels;
505 u16PreSkip = 0;
506 u32SampleRate = a_u32SampleRate;
507 u16Gain = 0;
508 u8MappingFamily = 0;
509 }
510
511 uint8_t au8Head[8]; /**< Defaults to "OpusHead". */
512 uint8_t u8Version; /**< Must be set to 1. */
513 uint8_t u8Channels;
514 uint16_t u16PreSkip;
515 /** Sample rate *before* encoding to Opus.
516 * Note: This rate has nothing to do with the playback rate later! */
517 uint32_t u32SampleRate;
518 uint16_t u16Gain;
519 /** Must stay 0 -- otherwise a mapping table must be appended
520 * right after this header. */
521 uint8_t u8MappingFamily;
522 };
523 AssertCompileSize(OpusPrivData, 19);
524# pragma pack(pop)
525#endif /* VBOX_WITH_LIBOPUS */
526
527 /** Audio codec to use. */
528 WebMWriter::AudioCodec m_enmAudioCodec;
529 /** Video codec to use. */
530 WebMWriter::VideoCodec m_enmVideoCodec;
531
532 /** Whether we're currently in the tracks section. */
533 bool m_fInTracksSection;
534
535 /** Size of timecodes in bytes. */
536 size_t m_cbTimecode;
537 /** Maximum value a timecode can have. */
538 uint32_t m_uTimecodeMax;
539
540 Ebml m_Ebml;
541
542public:
543
544 typedef std::map <uint8_t, WebMTrack *> WebMTracks;
545
546public:
547
548 WebMWriter_Impl() :
549 m_fInTracksSection(false)
550 {
551 /* Size (in bytes) of time code to write. We use 2 bytes (16 bit) by default. */
552 m_cbTimecode = 2;
553 m_uTimecodeMax = UINT16_MAX;
554 }
555
556 virtual ~WebMWriter_Impl()
557 {
558 close();
559 }
560
561 /**
562 * Adds an audio track.
563 *
564 * @returns IPRT status code.
565 * @param uHz Input sampling rate.
566 * Must be supported by the selected audio codec.
567 * @param cChannels Number of input audio channels.
568 * @param cBits Number of input bits per channel.
569 * @param puTrack Track number on successful creation. Optional.
570 */
571 int AddAudioTrack(uint16_t uHz, uint8_t cChannels, uint8_t cBits, uint8_t *puTrack)
572 {
573#ifdef VBOX_WITH_LIBOPUS
574 int rc;
575
576 /*
577 * Check if the requested codec rate is supported.
578 *
579 * Only the following values are supported by an Opus standard build
580 * -- every other rate only is supported by a custom build.
581 */
582 switch (uHz)
583 {
584 case 48000:
585 case 24000:
586 case 16000:
587 case 12000:
588 case 8000:
589 rc = VINF_SUCCESS;
590 break;
591
592 default:
593 rc = VERR_NOT_SUPPORTED;
594 break;
595 }
596
597 if (RT_FAILURE(rc))
598 return rc;
599
600 const uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size();
601
602 m_Ebml.subStart(MkvElem_TrackEntry);
603
604 m_Ebml.serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack);
605 m_Ebml.serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */);
606 m_Ebml.serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0);
607
608 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Audio, uTrack, RTFileTell(m_Ebml.getFile()));
609
610 pTrack->Audio.uHz = uHz;
611 pTrack->Audio.msPerBlock = 20; /** Opus uses 20ms by default. Make this configurable? */
612 pTrack->Audio.framesPerBlock = uHz / (1000 /* s in ms */ / pTrack->Audio.msPerBlock);
613
614 OpusPrivData opusPrivData(uHz, cChannels);
615
616 LogFunc(("Opus @ %RU16Hz (%RU16ms + %RU16 frames per block)\n",
617 pTrack->Audio.uHz, pTrack->Audio.msPerBlock, pTrack->Audio.framesPerBlock));
618
619 m_Ebml.serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID, 4)
620 .serializeUnsignedInteger(MkvElem_TrackType, 2 /* Audio */)
621 .serializeString(MkvElem_CodecID, "A_OPUS")
622 .serializeData(MkvElem_CodecPrivate, &opusPrivData, sizeof(opusPrivData))
623 .serializeUnsignedInteger(MkvElem_CodecDelay, 0)
624 .serializeUnsignedInteger(MkvElem_SeekPreRoll, 80 * 1000000) /* 80ms in ns. */
625 .subStart(MkvElem_Audio)
626 .serializeFloat(MkvElem_SamplingFrequency, (float)uHz)
627 .serializeUnsignedInteger(MkvElem_Channels, cChannels)
628 .serializeUnsignedInteger(MkvElem_BitDepth, cBits)
629 .subEnd(MkvElem_Audio)
630 .subEnd(MkvElem_TrackEntry);
631
632 CurSeg.mapTracks[uTrack] = pTrack;
633
634 if (puTrack)
635 *puTrack = uTrack;
636
637 return VINF_SUCCESS;
638#else
639 RT_NOREF(uHz, cChannels, cBits, puTrack);
640 return VERR_NOT_SUPPORTED;
641#endif
642 }
643
644 int AddVideoTrack(uint16_t uWidth, uint16_t uHeight, double dbFPS, uint8_t *puTrack)
645 {
646#ifdef VBOX_WITH_LIBVPX
647 RT_NOREF(dbFPS);
648
649 const uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size();
650
651 m_Ebml.subStart(MkvElem_TrackEntry);
652
653 m_Ebml.serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack);
654 m_Ebml.serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */);
655 m_Ebml.serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0);
656
657 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Video, uTrack, RTFileTell(m_Ebml.getFile()));
658
659 /** @todo Resolve codec type. */
660 m_Ebml.serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID /* UID */, 4)
661 .serializeUnsignedInteger(MkvElem_TrackType, 1 /* Video */)
662 .serializeString(MkvElem_CodecID, "V_VP8")
663 .subStart(MkvElem_Video)
664 .serializeUnsignedInteger(MkvElem_PixelWidth, uWidth)
665 .serializeUnsignedInteger(MkvElem_PixelHeight, uHeight)
666 .subEnd(MkvElem_Video);
667
668 m_Ebml.subEnd(MkvElem_TrackEntry);
669
670 CurSeg.mapTracks[uTrack] = pTrack;
671
672 if (puTrack)
673 *puTrack = uTrack;
674
675 return VINF_SUCCESS;
676#else
677 RT_NOREF(uWidth, uHeight, dbFPS, puTrack);
678 return VERR_NOT_SUPPORTED;
679#endif
680 }
681
682 int writeHeader(void)
683 {
684 LogFunc(("Header @ %RU64\n", RTFileTell(m_Ebml.getFile())));
685
686 m_Ebml.subStart(MkvElem_EBML)
687 .serializeUnsignedInteger(MkvElem_EBMLVersion, 1)
688 .serializeUnsignedInteger(MkvElem_EBMLReadVersion, 1)
689 .serializeUnsignedInteger(MkvElem_EBMLMaxIDLength, 4)
690 .serializeUnsignedInteger(MkvElem_EBMLMaxSizeLength, 8)
691 .serializeString(MkvElem_DocType, "webm")
692 .serializeUnsignedInteger(MkvElem_DocTypeVersion, 2)
693 .serializeUnsignedInteger(MkvElem_DocTypeReadVersion, 2)
694 .subEnd(MkvElem_EBML);
695
696 m_Ebml.subStart(MkvElem_Segment);
697
698 /* Save offset of current segment. */
699 CurSeg.offStart = RTFileTell(m_Ebml.getFile());
700
701 writeSegSeekInfo();
702
703 /* Save offset of upcoming tracks segment. */
704 CurSeg.offTracks = RTFileTell(m_Ebml.getFile());
705
706 /* The tracks segment starts right after this header. */
707 m_Ebml.subStart(MkvElem_Tracks);
708 m_fInTracksSection = true;
709
710 return VINF_SUCCESS;
711 }
712
713 int writeSimpleBlockInternal(WebMTrack *a_pTrack, uint64_t a_uTimecode,
714 const void *a_pvData, size_t a_cbData, uint8_t a_fFlags)
715 {
716 Log3Func(("SimpleBlock @ %RU64 (T%RU8, TS=%RU64, %zu bytes)\n",
717 RTFileTell(m_Ebml.getFile()), a_pTrack->uTrack, a_uTimecode, a_cbData));
718
719 /** @todo Mask out non-valid timecode bits, e.g. the upper 48 bits for a (default) 16-bit timecode. */
720 Assert(a_uTimecode <= m_uTimecodeMax);
721
722 /* Write a "Simple Block". */
723 m_Ebml.writeClassId(MkvElem_SimpleBlock);
724 /* Block size. */
725 m_Ebml.writeUnsignedInteger(0x10000000u | ( 1 /* Track number size. */
726 + m_cbTimecode /* Timecode size .*/
727 + 1 /* Flags size. */
728 + a_cbData /* Actual frame data size. */), 4);
729 /* Track number. */
730 m_Ebml.writeSize(a_pTrack->uTrack);
731 /* Timecode (relative to cluster opening timecode). */
732 m_Ebml.writeUnsignedInteger(a_uTimecode, m_cbTimecode);
733 /* Flags. */
734 m_Ebml.writeUnsignedInteger(a_fFlags, 1);
735 /* Frame data. */
736 m_Ebml.write(a_pvData, a_cbData);
737
738 a_pTrack->cTotalBlocks++;
739
740 return VINF_SUCCESS;
741 }
742
743#ifdef VBOX_WITH_LIBVPX
744 int writeBlockVP8(WebMTrack *a_pTrack, const vpx_codec_enc_cfg_t *a_pCfg, const vpx_codec_cx_pkt_t *a_pPkt)
745 {
746 RT_NOREF(a_pTrack);
747
748 WebMCluster &Cluster = CurSeg.CurCluster;
749
750 /* Calculate the PTS of this frame (in ms). */
751 uint64_t tcPTSMs = a_pPkt->data.frame.pts * 1000
752 * (uint64_t) a_pCfg->g_timebase.num / a_pCfg->g_timebase.den;
753
754 if ( tcPTSMs
755 && tcPTSMs <= CurSeg.CurCluster.tcEndMs)
756 {
757 tcPTSMs = CurSeg.CurCluster.tcEndMs + 1;
758 }
759
760 /* Whether to start a new cluster or not. */
761 bool fClusterStart = false;
762
763 /* No blocks written yet? Start a new cluster. */
764 if (a_pTrack->cTotalBlocks == 0)
765 fClusterStart = true;
766
767 /* Did we reach the maximum a cluster can hold? Use a new cluster then. */
768 if (tcPTSMs > VBOX_WEBM_CLUSTER_MAX_LEN_MS)
769 {
770 tcPTSMs = 0;
771
772 fClusterStart = true;
773 }
774
775 const bool fKeyframe = RT_BOOL(a_pPkt->data.frame.flags & VPX_FRAME_IS_KEY);
776
777 if ( fClusterStart
778 || fKeyframe)
779 {
780 if (Cluster.fOpen) /* Close current cluster first. */
781 {
782 m_Ebml.subEnd(MkvElem_Cluster);
783 Cluster.fOpen = false;
784 }
785
786 Cluster.fOpen = true;
787 Cluster.uID = a_pTrack->cTotalClusters;
788 Cluster.tcStartMs = tcPTSMs;
789 Cluster.offCluster = RTFileTell(m_Ebml.getFile());
790 Cluster.cBlocks = 0;
791
792 LogFunc(("[T%RU8C%RU64] Start @ %RU64ms / %RU64 bytes\n",
793 a_pTrack->uTrack, Cluster.uID, Cluster.tcStartMs, Cluster.offCluster));
794
795 m_Ebml.subStart(MkvElem_Cluster)
796 .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tcStartMs);
797
798 /* Save a cue point if this is a keyframe. */
799 if (fKeyframe)
800 {
801 WebMCuePoint cue(a_pTrack, Cluster.tcStartMs, Cluster.offCluster);
802 CurSeg.lstCues.push_back(cue);
803 }
804
805 a_pTrack->cTotalClusters++;
806 }
807
808 Cluster.tcEndMs = tcPTSMs;
809 Cluster.cBlocks++;
810
811 /* Calculate the block's timecode, which is relative to the cluster's starting timecode. */
812 uint16_t tcBlockMs = static_cast<uint16_t>(tcPTSMs - Cluster.tcStartMs);
813
814 Log2Func(("tcPTSMs=%RU64, tcBlockMs=%RU64\n", tcPTSMs, tcBlockMs));
815
816 Log2Func(("[C%RU64] cBlocks=%RU64, tcStartMs=%RU64, tcEndMs=%RU64 (%zums)\n",
817 Cluster.uID, Cluster.cBlocks, Cluster.tcStartMs, Cluster.tcEndMs, Cluster.tcEndMs - Cluster.tcStartMs));
818
819 uint8_t fFlags = 0;
820 if (fKeyframe)
821 fFlags |= VBOX_WEBM_BLOCK_FLAG_KEY_FRAME;
822 if (a_pPkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE)
823 fFlags |= VBOX_WEBM_BLOCK_FLAG_INVISIBLE;
824
825 return writeSimpleBlockInternal(a_pTrack, tcBlockMs, a_pPkt->data.frame.buf, a_pPkt->data.frame.sz, fFlags);
826 }
827#endif /* VBOX_WITH_LIBVPX */
828
829#ifdef VBOX_WITH_LIBOPUS
830 /* Audio blocks that have same absolute timecode as video blocks SHOULD be written before the video blocks. */
831 int writeBlockOpus(WebMTrack *a_pTrack, const void *pvData, size_t cbData, uint64_t uTimeStampMs)
832 {
833 AssertPtrReturn(a_pTrack, VERR_INVALID_POINTER);
834 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
835 AssertReturn(cbData, VERR_INVALID_PARAMETER);
836
837 RT_NOREF(uTimeStampMs);
838
839 WebMCluster &Cluster = CurSeg.CurCluster;
840
841 /* Calculate the PTS of the current block:
842 *
843 * The "raw PTS" is the exact time of an object represented in nanoseconds):
844 * Raw Timecode = (Block timecode + Cluster timecode) * TimecodeScaleFactor
845 */
846 uint64_t tcPTSMs = Cluster.tcStartMs + (Cluster.cBlocks * 20 /*ms */);
847
848 /* Whether to start a new cluster or not. */
849 bool fClusterStart = false;
850
851 if (a_pTrack->cTotalBlocks == 0)
852 fClusterStart = true;
853
854 /* Did we reach the maximum a cluster can hold? Use a new cluster then. */
855 if (tcPTSMs > VBOX_WEBM_CLUSTER_MAX_LEN_MS)
856 fClusterStart = true;
857
858 if (fClusterStart)
859 {
860 if (Cluster.fOpen) /* Close current clusters first. */
861 {
862 m_Ebml.subEnd(MkvElem_Cluster);
863 Cluster.fOpen = false;
864 }
865
866 Cluster.fOpen = true;
867 Cluster.uID = a_pTrack->cTotalClusters;
868 Cluster.tcStartMs = Cluster.tcEndMs;
869 Cluster.offCluster = RTFileTell(m_Ebml.getFile());
870 Cluster.cBlocks = 0;
871
872 LogFunc(("[T%RU8C%RU64] Start @ %RU64ms / %RU64 bytes\n",
873 a_pTrack->uTrack, Cluster.uID, Cluster.tcStartMs, Cluster.offCluster));
874
875 /* As all audio frame as key frames, insert a new cue point when a new cluster starts. */
876 WebMCuePoint cue(a_pTrack, Cluster.tcStartMs, Cluster.offCluster);
877 CurSeg.lstCues.push_back(cue);
878
879 m_Ebml.subStart(MkvElem_Cluster)
880 .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tcStartMs);
881
882 a_pTrack->cTotalClusters++;
883 }
884
885 Cluster.tcEndMs = tcPTSMs;
886 Cluster.cBlocks++;
887
888 /* Calculate the block's timecode, which is relative to the cluster's starting timecode. */
889 uint16_t tcBlockMs = static_cast<uint16_t>(tcPTSMs - Cluster.tcStartMs);
890
891 Log2Func(("tcPTSMs=%RU64, tcBlockMs=%RU64\n", tcPTSMs, tcBlockMs));
892
893 Log2Func(("[C%RU64] cBlocks=%RU64, tcStartMs=%RU64, tcEndMs=%RU64 (%zums)\n",
894 Cluster.uID, Cluster.cBlocks, Cluster.tcStartMs, Cluster.tcEndMs, Cluster.tcEndMs - Cluster.tcStartMs));
895
896 return writeSimpleBlockInternal(a_pTrack, tcBlockMs,
897 pvData, cbData, VBOX_WEBM_BLOCK_FLAG_KEY_FRAME);
898 }
899#endif /* VBOX_WITH_LIBOPUS */
900
901 int WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData)
902 {
903 RT_NOREF(pvData, cbData); /* Only needed for assertions for now. */
904
905 WebMTracks::iterator itTrack = CurSeg.mapTracks.find(uTrack);
906 if (itTrack == CurSeg.mapTracks.end())
907 return VERR_NOT_FOUND;
908
909 WebMTrack *pTrack = itTrack->second;
910 AssertPtr(pTrack);
911
912 int rc;
913
914 if (m_fInTracksSection)
915 {
916 m_Ebml.subEnd(MkvElem_Tracks);
917 m_fInTracksSection = false;
918 }
919
920 switch (pTrack->enmType)
921 {
922
923 case WebMTrackType_Audio:
924 {
925#ifdef VBOX_WITH_LIBOPUS
926 if (m_enmAudioCodec == WebMWriter::AudioCodec_Opus)
927 {
928 Assert(cbData == sizeof(WebMWriter::BlockData_Opus));
929 WebMWriter::BlockData_Opus *pData = (WebMWriter::BlockData_Opus *)pvData;
930 rc = writeBlockOpus(pTrack, pData->pvData, pData->cbData, pData->uTimestampMs);
931 }
932 else
933#endif /* VBOX_WITH_LIBOPUS */
934 rc = VERR_NOT_SUPPORTED;
935 break;
936 }
937
938 case WebMTrackType_Video:
939 {
940#ifdef VBOX_WITH_LIBVPX
941 if (m_enmVideoCodec == WebMWriter::VideoCodec_VP8)
942 {
943 Assert(cbData == sizeof(WebMWriter::BlockData_VP8));
944 WebMWriter::BlockData_VP8 *pData = (WebMWriter::BlockData_VP8 *)pvData;
945 rc = writeBlockVP8(pTrack, pData->pCfg, pData->pPkt);
946 }
947 else
948#endif /* VBOX_WITH_LIBVPX */
949 rc = VERR_NOT_SUPPORTED;
950 break;
951 }
952
953 default:
954 rc = VERR_NOT_SUPPORTED;
955 break;
956 }
957
958 return rc;
959 }
960
961 int writeFooter(void)
962 {
963 if (m_fInTracksSection)
964 {
965 m_Ebml.subEnd(MkvElem_Tracks);
966 m_fInTracksSection = false;
967 }
968
969 if (CurSeg.CurCluster.fOpen)
970 {
971 m_Ebml.subEnd(MkvElem_Cluster);
972 CurSeg.CurCluster.fOpen = false;
973 }
974
975 /*
976 * Write Cues element.
977 */
978 LogFunc(("Cues @ %RU64\n", RTFileTell(m_Ebml.getFile())));
979
980 CurSeg.offCues = RTFileTell(m_Ebml.getFile());
981
982 m_Ebml.subStart(MkvElem_Cues);
983
984 std::list<WebMCuePoint>::iterator itCuePoint = CurSeg.lstCues.begin();
985 while (itCuePoint != CurSeg.lstCues.end())
986 {
987 /* Sanity. */
988 AssertPtr(itCuePoint->pTrack);
989
990 const uint64_t uClusterPos = itCuePoint->offClusterStart - CurSeg.offStart;
991
992 LogFunc(("CuePoint @ %RU64: Track #%RU8 (Time %RU64, Pos %RU64)\n",
993 RTFileTell(m_Ebml.getFile()), itCuePoint->pTrack->uTrack, itCuePoint->tcClusterStart, uClusterPos));
994
995 m_Ebml.subStart(MkvElem_CuePoint)
996 .serializeUnsignedInteger(MkvElem_CueTime, itCuePoint->tcClusterStart)
997 .subStart(MkvElem_CueTrackPositions)
998 .serializeUnsignedInteger(MkvElem_CueTrack, itCuePoint->pTrack->uTrack)
999 .serializeUnsignedInteger(MkvElem_CueClusterPosition, uClusterPos, 8)
1000 .subEnd(MkvElem_CueTrackPositions)
1001 .subEnd(MkvElem_CuePoint);
1002
1003 itCuePoint++;
1004 }
1005
1006 m_Ebml.subEnd(MkvElem_Cues);
1007 m_Ebml.subEnd(MkvElem_Segment);
1008
1009 /*
1010 * Re-Update SeekHead / Info elements.
1011 */
1012
1013 writeSegSeekInfo();
1014
1015 return RTFileSeek(m_Ebml.getFile(), 0, RTFILE_SEEK_END, NULL);
1016 }
1017
1018 int close(void)
1019 {
1020 WebMTracks::iterator itTrack = CurSeg.mapTracks.begin();
1021 for (; itTrack != CurSeg.mapTracks.end(); ++itTrack)
1022 {
1023 delete itTrack->second;
1024 CurSeg.mapTracks.erase(itTrack);
1025 }
1026
1027 Assert(CurSeg.mapTracks.size() == 0);
1028
1029 m_Ebml.close();
1030
1031 return VINF_SUCCESS;
1032 }
1033
1034 friend class Ebml;
1035 friend class WebMWriter;
1036
1037private:
1038
1039 /**
1040 * Writes the segment's seek information and cue points.
1041 *
1042 * @returns IPRT status code.
1043 */
1044 void writeSegSeekInfo(void)
1045 {
1046 if (CurSeg.offSeekInfo)
1047 RTFileSeek(m_Ebml.getFile(), CurSeg.offSeekInfo, RTFILE_SEEK_BEGIN, NULL);
1048 else
1049 CurSeg.offSeekInfo = RTFileTell(m_Ebml.getFile());
1050
1051 LogFunc(("SeekHead @ %RU64\n", CurSeg.offSeekInfo));
1052
1053 m_Ebml.subStart(MkvElem_SeekHead);
1054
1055 m_Ebml.subStart(MkvElem_Seek)
1056 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Tracks)
1057 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offTracks - CurSeg.offStart, 8)
1058 .subEnd(MkvElem_Seek);
1059
1060 Assert(CurSeg.offCues - CurSeg.offStart > 0); /* Sanity. */
1061
1062 m_Ebml.subStart(MkvElem_Seek)
1063 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Cues)
1064 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offCues - CurSeg.offStart, 8)
1065 .subEnd(MkvElem_Seek);
1066
1067 m_Ebml.subStart(MkvElem_Seek)
1068 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Info)
1069 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offInfo - CurSeg.offStart, 8)
1070 .subEnd(MkvElem_Seek);
1071
1072 m_Ebml.subEnd(MkvElem_SeekHead);
1073
1074 //int64_t iFrameTime = (int64_t)1000 * 1 / 25; //m_Framerate.den / m_Framerate.num; /** @todo Fix this! */
1075 CurSeg.offInfo = RTFileTell(m_Ebml.getFile());
1076
1077 LogFunc(("Info @ %RU64\n", CurSeg.offInfo));
1078
1079 char szMux[64];
1080 RTStrPrintf(szMux, sizeof(szMux),
1081#ifdef VBOX_WITH_LIBVPX
1082 "vpxenc%s", vpx_codec_version_str());
1083#else
1084 "unknown");
1085#endif
1086 char szApp[64];
1087 RTStrPrintf(szApp, sizeof(szApp), VBOX_PRODUCT " %sr%u", VBOX_VERSION_STRING, RTBldCfgRevision());
1088
1089 LogFunc(("Duration=%RU64\n", CurSeg.tcEnd - CurSeg.tcStart));
1090
1091 m_Ebml.subStart(MkvElem_Info)
1092 .serializeUnsignedInteger(MkvElem_TimecodeScale, CurSeg.uTimecodeScaleFactor)
1093 .serializeFloat(MkvElem_Segment_Duration, CurSeg.tcEnd - CurSeg.tcStart)
1094 .serializeString(MkvElem_MuxingApp, szMux)
1095 .serializeString(MkvElem_WritingApp, szApp)
1096 .subEnd(MkvElem_Info);
1097 }
1098};
1099
1100WebMWriter::WebMWriter(void) : m_pImpl(new WebMWriter_Impl()) {}
1101
1102WebMWriter::~WebMWriter(void)
1103{
1104 if (m_pImpl)
1105 {
1106 Close();
1107 delete m_pImpl;
1108 }
1109}
1110
1111int WebMWriter::Create(const char *a_pszFilename, uint64_t a_fOpen,
1112 WebMWriter::AudioCodec a_enmAudioCodec, WebMWriter::VideoCodec a_enmVideoCodec)
1113{
1114 try
1115 {
1116 m_pImpl->m_enmAudioCodec = a_enmAudioCodec;
1117 m_pImpl->m_enmVideoCodec = a_enmVideoCodec;
1118
1119 LogFunc(("Creating '%s'\n", a_pszFilename));
1120
1121 int rc = m_pImpl->m_Ebml.create(a_pszFilename, a_fOpen);
1122 if (RT_SUCCESS(rc))
1123 rc = m_pImpl->writeHeader();
1124 }
1125 catch(int rc)
1126 {
1127 return rc;
1128 }
1129 return VINF_SUCCESS;
1130}
1131
1132int WebMWriter::Close(void)
1133{
1134 if (!m_pImpl->m_Ebml.isOpen())
1135 return VINF_SUCCESS;
1136
1137 int rc = m_pImpl->writeFooter();
1138 if (RT_SUCCESS(rc))
1139 m_pImpl->close();
1140
1141 return rc;
1142}
1143
1144int WebMWriter::AddAudioTrack(uint16_t uHz, uint8_t cChannels, uint8_t cBitDepth, uint8_t *puTrack)
1145{
1146 return m_pImpl->AddAudioTrack(uHz, cChannels, cBitDepth, puTrack);
1147}
1148
1149int WebMWriter::AddVideoTrack(uint16_t uWidth, uint16_t uHeight, double dbFPS, uint8_t *puTrack)
1150{
1151 return m_pImpl->AddVideoTrack(uWidth, uHeight, dbFPS, puTrack);
1152}
1153
1154int WebMWriter::WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData)
1155{
1156 int rc;
1157
1158 try
1159 {
1160 rc = m_pImpl->WriteBlock(uTrack, pvData, cbData);
1161 }
1162 catch(int rc2)
1163 {
1164 rc = rc2;
1165 }
1166 return rc;
1167}
1168
1169const Utf8Str& WebMWriter::GetFileName(void)
1170{
1171 return m_pImpl->m_Ebml.getFileName();
1172}
1173
1174uint64_t WebMWriter::GetFileSize(void)
1175{
1176 return m_pImpl->m_Ebml.getFileSize();
1177}
1178
1179uint64_t WebMWriter::GetAvailableSpace(void)
1180{
1181 return m_pImpl->m_Ebml.getAvailableSpace();
1182}
1183
Note: See TracBrowser for help on using the repository browser.

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