VirtualBox

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

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

VideoRec: Main/EbmlWriter: More stuff for audio support.

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