VirtualBox

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

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

OSE build fixes.

  • 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: 33.2 KB
Line 
1/* $Id: EbmlWriter.cpp 65362 2017-01-18 13:52:32Z 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#include <list>
20#include <map>
21#include <stack>
22#include <iprt/asm.h>
23#include <iprt/buildconfig.h>
24#include <iprt/cdefs.h>
25#include <iprt/err.h>
26#include <iprt/file.h>
27#include <iprt/rand.h>
28#include <iprt/string.h>
29
30#include <VBox/log.h>
31#include <VBox/version.h>
32
33#include "EbmlWriter.h"
34#include "EbmlMkvIDs.h"
35#include "Logging.h"
36
37
38class Ebml
39{
40public:
41 typedef uint32_t EbmlClassId;
42
43private:
44
45 struct EbmlSubElement
46 {
47 uint64_t offset;
48 EbmlClassId classId;
49 EbmlSubElement(uint64_t offs, EbmlClassId cid) : offset(offs), classId(cid) {}
50 };
51
52 std::stack<EbmlSubElement> m_Elements;
53 RTFILE m_File;
54
55public:
56
57 Ebml(void)
58 : m_File(NIL_RTFILE) { }
59
60 virtual ~Ebml(void) { close(); }
61
62public:
63
64 /** Creates EBML output file. */
65 inline int create(const char *a_pszFilename, uint64_t fOpen)
66 {
67 return RTFileOpen(&m_File, a_pszFilename, fOpen);
68 }
69
70 /** Returns file size. */
71 inline uint64_t getFileSize(void)
72 {
73 return RTFileTell(m_File);
74 }
75
76 /** Get reference to file descriptor */
77 inline const RTFILE &getFile(void)
78 {
79 return m_File;
80 }
81
82 /** Returns available space on storage. */
83 inline uint64_t getAvailableSpace(void)
84 {
85 RTFOFF pcbFree;
86 int rc = RTFileQueryFsSizes(m_File, NULL, &pcbFree, 0, 0);
87 return (RT_SUCCESS(rc)? (uint64_t)pcbFree : UINT64_MAX);
88 }
89
90 /** Closes the file. */
91 inline void close(void)
92 {
93 if (!isOpen())
94 return;
95
96 AssertMsg(m_Elements.size() == 0,
97 ("%zu elements are not closed yet (next element to close is 0x%x)\n",
98 m_Elements.size(), m_Elements.top().classId));
99
100 RTFileClose(m_File);
101 m_File = NIL_RTFILE;
102 }
103
104 /**
105 * Returns whether the file is open or not.
106 *
107 * @returns True if open, false if not.
108 */
109 inline bool isOpen(void)
110 {
111 return RTFileIsValid(m_File);
112 }
113
114 /** Starts an EBML sub-element. */
115 inline Ebml &subStart(EbmlClassId classId)
116 {
117 writeClassId(classId);
118 /* store the current file offset. */
119 m_Elements.push(EbmlSubElement(RTFileTell(m_File), classId));
120 /* Indicates that size of the element
121 * is unkown (as according to EBML specs).
122 */
123 writeUnsignedInteger(UINT64_C(0x01FFFFFFFFFFFFFF));
124 return *this;
125 }
126
127 /** Ends an EBML sub-element. */
128 inline Ebml &subEnd(EbmlClassId classId)
129 {
130#ifdef VBOX_STRICT
131 /* Class ID on the top of the stack should match the class ID passed
132 * to the function. Otherwise it may mean that we have a bug in the code.
133 */
134 AssertMsg(!m_Elements.empty(), ("No elements to close anymore\n"));
135 AssertMsg(m_Elements.top().classId == classId,
136 ("Ending sub element 0x%x is in wrong order (next to close is 0x%x)\n", classId, m_Elements.top().classId));
137#else
138 RT_NOREF(classId);
139#endif
140
141 uint64_t uPos = RTFileTell(m_File);
142 uint64_t uSize = uPos - m_Elements.top().offset - 8;
143 RTFileSeek(m_File, m_Elements.top().offset, RTFILE_SEEK_BEGIN, NULL);
144
145 /* Make sure that size will be serialized as uint64_t. */
146 writeUnsignedInteger(uSize | UINT64_C(0x0100000000000000));
147 RTFileSeek(m_File, uPos, RTFILE_SEEK_BEGIN, NULL);
148 m_Elements.pop();
149 return *this;
150 }
151
152 /** Serializes a null-terminated string. */
153 inline Ebml &serializeString(EbmlClassId classId, const char *str)
154 {
155 writeClassId(classId);
156 uint64_t size = strlen(str);
157 writeSize(size);
158 write(str, size);
159 return *this;
160 }
161
162 /* Serializes an UNSIGNED integer
163 * If size is zero then it will be detected automatically. */
164 inline Ebml &serializeUnsignedInteger(EbmlClassId classId, uint64_t parm, size_t size = 0)
165 {
166 writeClassId(classId);
167 if (!size) size = getSizeOfUInt(parm);
168 writeSize(size);
169 writeUnsignedInteger(parm, size);
170 return *this;
171 }
172
173 /** Serializes a floating point value.
174 *
175 * Only 8-bytes double precision values are supported
176 * by this function.
177 */
178 inline Ebml &serializeFloat(EbmlClassId classId, double value)
179 {
180 writeClassId(classId);
181 writeSize(sizeof(double));
182 writeUnsignedInteger(*reinterpret_cast<uint64_t*>(&value));
183 return *this;
184 }
185
186 /** Serializes binary data. */
187 inline Ebml &serializeData(EbmlClassId classId, const void *pvData, size_t cbData)
188 {
189 writeClassId(classId);
190 writeSize(cbData);
191 write(pvData, cbData);
192 return *this;
193 }
194
195 /** Writes raw data to file. */
196 inline int write(const void *data, size_t size)
197 {
198 return RTFileWrite(m_File, data, size, NULL);
199 }
200
201 /** Writes an unsigned integer of variable of fixed size. */
202 inline void writeUnsignedInteger(uint64_t value, size_t size = sizeof(uint64_t))
203 {
204 /* convert to big-endian */
205 value = RT_H2BE_U64(value);
206 write(reinterpret_cast<uint8_t*>(&value) + sizeof(value) - size, size);
207 }
208
209 /** Writes EBML class ID to file.
210 *
211 * EBML ID already has a UTF8-like represenation
212 * so getSizeOfUInt is used to determine
213 * the number of its bytes.
214 */
215 inline void writeClassId(EbmlClassId parm)
216 {
217 writeUnsignedInteger(parm, getSizeOfUInt(parm));
218 }
219
220 /** Writes data size value. */
221 inline void writeSize(uint64_t parm)
222 {
223 /* The following expression defines the size of the value that will be serialized
224 * as an EBML UTF-8 like integer (with trailing bits represeting its size):
225 1xxx xxxx - value 0 to 2^7-2
226 01xx xxxx xxxx xxxx - value 0 to 2^14-2
227 001x xxxx xxxx xxxx xxxx xxxx - value 0 to 2^21-2
228 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^28-2
229 0000 1xxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^35-2
230 0000 01xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^42-2
231 0000 001x xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^49-2
232 0000 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^56-2
233 */
234 size_t size = 8 - ! (parm & (UINT64_MAX << 49)) - ! (parm & (UINT64_MAX << 42)) -
235 ! (parm & (UINT64_MAX << 35)) - ! (parm & (UINT64_MAX << 28)) -
236 ! (parm & (UINT64_MAX << 21)) - ! (parm & (UINT64_MAX << 14)) -
237 ! (parm & (UINT64_MAX << 7));
238 /* One is subtracted in order to avoid loosing significant bit when size = 8. */
239 uint64_t mask = RT_BIT_64(size * 8 - 1);
240 writeUnsignedInteger((parm & (((mask << 1) - 1) >> size)) | (mask >> (size - 1)), size);
241 }
242
243 /** Size calculation for variable size UNSIGNED integer.
244 *
245 * The function defines the size of the number by trimming
246 * consequent trailing zero bytes starting from the most significant.
247 * The following statement is always true:
248 * 1 <= getSizeOfUInt(arg) <= 8.
249 *
250 * Every !(arg & (UINT64_MAX << X)) expression gives one
251 * if an only if all the bits from X to 63 are set to zero.
252 */
253 static inline size_t getSizeOfUInt(uint64_t arg)
254 {
255 return 8 - ! (arg & (UINT64_MAX << 56)) - ! (arg & (UINT64_MAX << 48)) -
256 ! (arg & (UINT64_MAX << 40)) - ! (arg & (UINT64_MAX << 32)) -
257 ! (arg & (UINT64_MAX << 24)) - ! (arg & (UINT64_MAX << 16)) -
258 ! (arg & (UINT64_MAX << 8));
259 }
260
261private:
262 void operator=(const Ebml &);
263
264};
265
266class WebMWriter_Impl
267{
268 /**
269 * Structure for keeping a cue entry.
270 */
271 struct WebMCueEntry
272 {
273 WebMCueEntry(uint32_t t, uint64_t l)
274 : time(t), loc(l) {}
275
276 uint32_t time;
277 uint64_t loc;
278 };
279
280 /**
281 * Track type enumeration.
282 */
283 enum WebMTrackType
284 {
285 /** Unknown / invalid type. */
286 WebMTrackType_Invalid = 0,
287 /** Only writes audio. */
288 WebMTrackType_Audio = 1,
289 /** Only writes video. */
290 WebMTrackType_Video = 2
291 };
292
293 /**
294 * Structure for keeping a WebM track entry.
295 */
296 struct WebMTrack
297 {
298 WebMTrack(WebMTrackType a_enmType, uint8_t a_uTrack, uint64_t a_offID)
299 : enmType(a_enmType)
300 , uTrack(a_uTrack)
301 , offUUID(a_offID)
302 , cTotalClusters(0)
303 , cTotalBlocks(0)
304 {
305 uUUID = RTRandU32();
306 }
307
308 /** The type of this track. */
309 WebMTrackType enmType;
310 /** Track parameters. */
311 union
312 {
313 struct
314 {
315 /** Sample rate of input data. */
316 uint16_t uHzIn;
317 /** Sample rate the codec is using. */
318 uint16_t uHzCodec;
319 /** Frame size (in bytes), based on the codec sample rate.
320 * Note: For now this does *not* change dynamically, in other words,
321 * we do CBR (and not VBR) here! */
322 size_t cbFrameSize;
323 } Audio;
324 };
325 /** This track's track number. Also used as key in track map. */
326 uint8_t uTrack;
327 /** The track's "UUID".
328 * Needed in case this track gets mux'ed with tracks from other files. Not really unique though. */
329 uint32_t uUUID;
330 /** Absolute offset in file of track UUID.
331 * Needed to write the hash sum within the footer. */
332 uint64_t offUUID;
333 /** Total number of clusters. */
334 uint64_t cTotalClusters;
335 /** Total number of blocks. */
336 uint64_t cTotalBlocks;
337 };
338
339 /**
340 * Structure for keeping a WebM cluster entry.
341 */
342 struct WebMCluster
343 {
344 WebMCluster(void)
345 : uID(0)
346 , offCluster(0)
347 , fOpen(false)
348 , tsStartMs(0)
349 , tsEndMs(0) { }
350
351 /** This cluster's ID. */
352 uint64_t uID;
353 /** Absolute offset (in bytes) of current cluster.
354 * Needed for seeking info table. */
355 uint64_t offCluster;
356 /** Whether this cluster element is opened currently. */
357 bool fOpen;
358 /** Timestamp (in ms) when starting this cluster. */
359 uint64_t tsStartMs;
360 /** Timestamp (in ms) when this cluster ended. */
361 uint64_t tsEndMs;
362 };
363
364 /**
365 * Structure for keeping a WebM segment entry.
366 *
367 * Current we're only using one segment.
368 */
369 struct WebMSegment
370 {
371 WebMSegment(void)
372 : offStart(0)
373 , offInfo(0)
374 , offSeekInfo(0)
375 , offTracks(0)
376 , offCues(0) { }
377
378 /** Absolute offset (in bytes) of CurSeg. */
379 uint64_t offStart;
380 /** Absolute offset (in bytes) of general info. */
381 uint64_t offInfo;
382 /** Absolute offset (in bytes) of seeking info. */
383 uint64_t offSeekInfo;
384 /** Absolute offset (in bytes) of tracks. */
385 uint64_t offTracks;
386 /** Absolute offset (in bytes) of cues table. */
387 uint64_t offCues;
388 /** List of cue points. Needed for seeking table. */
389 std::list<WebMCueEntry> lstCues;
390
391 /** Map of tracks.
392 * The key marks the track number (*not* the UUID!). */
393 std::map <uint8_t, WebMTrack *> mapTracks;
394
395 /** Current cluster which is being handled.
396 *
397 * Note that we don't need (and shouldn't need, as this can be a *lot* of data!) a
398 * list of all clusters. */
399 WebMCluster CurCluster;
400
401 } CurSeg;
402
403#ifdef VBOX_WITH_LIBOPUS
404# pragma pack(push)
405# pragma pack(1)
406 /** Opus codec private data.
407 * Taken from: https://wiki.xiph.org/MatroskaOpus */
408 struct OpusPrivData
409 {
410 OpusPrivData(uint32_t a_u32SampleRate, uint8_t a_u8Channels)
411 : u8Channels(a_u8Channels)
412 , u32SampleRate(a_u32SampleRate) { }
413
414 /** "OpusHead". */
415 uint8_t au8Head[8] = { 0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64 };
416 /** Must set to 1. */
417 uint8_t u8Version = 1;
418 uint8_t u8Channels = 0;
419 uint16_t u16PreSkip = 0;
420 /** Sample rate *before* encoding to Opus.
421 * Note: This rate has nothing to do with the playback rate later! */
422 uint32_t u32SampleRate = 0;
423 uint16_t u16Gain = 0;
424 /** Must stay 0 -- otherwise a mapping table must be appended
425 * right after this header. */
426 uint8_t u8MappingFamily = 0;
427 };
428# pragma pack(pop)
429#endif /* VBOX_WITH_LIBOPUS */
430
431 /** Audio codec to use. */
432 WebMWriter::AudioCodec m_enmAudioCodec;
433 /** Video codec to use. */
434 WebMWriter::VideoCodec m_enmVideoCodec;
435
436 /** This PTS (Presentation Time Stamp) acts as our master clock for synchronizing streams. */
437 uint64_t m_uPts;
438 /** Timestamp (in ms) of initial PTS. */
439 int64_t m_tsInitialPtsMs;
440 /** Timestamp (in ms) of last written PTS. */
441 int64_t m_tsLastPtsMs;
442
443 /** Whether we're currently in the tracks section. */
444 bool m_fInTracksSection;
445
446 /** Size of timecodes in bytes. */
447 size_t m_cbTimecode;
448 /** Maximum value a timecode can have. */
449 uint32_t m_uTimecodeMax;
450 /** The timecode scale factor. */
451 uint64_t m_uTimecodeScaleNs;
452
453 Ebml m_Ebml;
454
455public:
456
457 typedef std::map <uint8_t, WebMTrack *> WebMTracks;
458
459public:
460
461 WebMWriter_Impl() :
462 m_uPts(0)
463 , m_tsInitialPtsMs(-1)
464 , m_tsLastPtsMs(-1)
465 , m_fInTracksSection(false)
466 {
467 /* Size (in bytes) of time code to write. We use 2 bytes (16 bit) by default. */
468 m_cbTimecode = 2;
469 m_uTimecodeMax = UINT16_MAX;
470
471 /* This is the default for WebM -- all timecodes in the segments are expressed in ms.
472 * This allows every cluster to have blocks with positive values up to 32.767 seconds. */
473 m_uTimecodeScaleNs = 1000000;
474 }
475
476 virtual ~WebMWriter_Impl()
477 {
478 close();
479 }
480
481 int AddAudioTrack(uint16_t uHz, uint8_t cChannels, uint8_t cBits, uint8_t *puTrack)
482 {
483#ifdef VBOX_WITH_LIBOPUS
484 m_Ebml.subStart(MkvElem_TrackEntry);
485 m_Ebml.serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)CurSeg.mapTracks.size());
486 /** @todo Implement track's "Language" property? Currently this defaults to English ("eng"). */
487
488 uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size();
489
490 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Audio, uTrack, RTFileTell(m_Ebml.getFile()));
491
492 /* Clamp the codec rate (Hz) value if it reaches a certain threshold. */
493 if (uHz > 24000) pTrack->Audio.uHzCodec = 48000;
494 else if (uHz > 16000) pTrack->Audio.uHzCodec = 24000;
495 else if (uHz > 12000) pTrack->Audio.uHzCodec = 16000;
496 else if (uHz > 8000 ) pTrack->Audio.uHzCodec = 12000;
497 else pTrack->Audio.uHzCodec = 8000;
498
499 pTrack->Audio.uHzIn = uHz;
500
501 /** @todo 960 bytes is 20ms worth of audio data. Make this configurable. */
502 pTrack->Audio.cbFrameSize = 960 /* Bytes */ / (48000 /* Hz */ / pTrack->Audio.uHzCodec);
503
504 /** @todo Resolve codec type. */
505 OpusPrivData opusPrivData(uHz, cChannels);
506
507 m_Ebml.serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID, 4)
508 .serializeUnsignedInteger(MkvElem_TrackType, 2 /* Audio */)
509 .serializeString(MkvElem_CodecID, "A_OPUS")
510 .serializeData(MkvElem_CodecPrivate, &opusPrivData, sizeof(opusPrivData))
511 .serializeUnsignedInteger(MkvElem_CodecDelay, 0)
512 .serializeUnsignedInteger(MkvElem_SeekPreRoll, 80000000) /* 80.000ms */
513 .subStart(MkvElem_Audio)
514 .serializeFloat(MkvElem_SamplingFrequency, (float)pTrack->Audio.uHzCodec)
515 .serializeUnsignedInteger(MkvElem_Channels, cChannels)
516 .serializeUnsignedInteger(MkvElem_BitDepth, cBits)
517 .subEnd(MkvElem_Audio)
518 .subEnd(MkvElem_TrackEntry);
519
520 CurSeg.mapTracks[uTrack] = pTrack;
521
522 if (puTrack)
523 *puTrack = uTrack;
524
525 return VINF_SUCCESS;
526#else
527 RT_NOREF(uHz, cChannels, cBits, puTrack);
528 return VERR_NOT_SUPPORTED;
529#endif
530 }
531
532 int AddVideoTrack(uint16_t uWidth, uint16_t uHeight, double dbFPS, uint8_t *puTrack)
533 {
534#ifdef VBOX_WITH_LIBVPX
535 m_Ebml.subStart(MkvElem_TrackEntry);
536 m_Ebml.serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)CurSeg.mapTracks.size());
537
538 uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size();
539
540 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Video, uTrack, RTFileTell(m_Ebml.getFile()));
541
542 /** @todo Resolve codec type. */
543 m_Ebml.serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID /* UID */, 4)
544 .serializeUnsignedInteger(MkvElem_TrackType, 1 /* Video */)
545 .serializeString(MkvElem_CodecID, "V_VP8")
546 .subStart(MkvElem_Video)
547 .serializeUnsignedInteger(MkvElem_PixelWidth, uWidth)
548 .serializeUnsignedInteger(MkvElem_PixelHeight, uHeight)
549 .serializeFloat(MkvElem_FrameRate, dbFPS)
550 .subEnd(MkvElem_Video)
551 .subEnd(MkvElem_TrackEntry);
552
553 CurSeg.mapTracks[uTrack] = pTrack;
554
555 if (puTrack)
556 *puTrack = uTrack;
557
558 return VINF_SUCCESS;
559#else
560 RT_NOREF(uWidth, uHeight, dbFPS, puTrack);
561 return VERR_NOT_SUPPORTED;
562#endif
563 }
564
565 int writeHeader(void)
566 {
567 LogFunc(("Header @ %RU64\n", RTFileTell(m_Ebml.getFile())));
568
569 m_Ebml.subStart(MkvElem_EBML)
570 .serializeUnsignedInteger(MkvElem_EBMLVersion, 1)
571 .serializeUnsignedInteger(MkvElem_EBMLReadVersion, 1)
572 .serializeUnsignedInteger(MkvElem_EBMLMaxIDLength, 4)
573 .serializeUnsignedInteger(MkvElem_EBMLMaxSizeLength, 8)
574 .serializeString(MkvElem_DocType, "webm")
575 .serializeUnsignedInteger(MkvElem_DocTypeVersion, 2)
576 .serializeUnsignedInteger(MkvElem_DocTypeReadVersion, 2)
577 .subEnd(MkvElem_EBML);
578
579 m_Ebml.subStart(MkvElem_Segment);
580
581 /* Save offset of current segment. */
582 CurSeg.offStart = RTFileTell(m_Ebml.getFile());
583
584 writeSegSeekInfo();
585
586 /* Save offset of upcoming tracks segment. */
587 CurSeg.offTracks = RTFileTell(m_Ebml.getFile());
588
589 /* The tracks segment starts right after this header. */
590 m_Ebml.subStart(MkvElem_Tracks);
591 m_fInTracksSection = true;
592
593 return VINF_SUCCESS;
594 }
595
596 int writeSimpleBlockInternal(WebMTrack *a_pTrack, uint64_t a_uTimecode,
597 const void *a_pvData, size_t a_cbData, uint8_t a_fFlags)
598 {
599 LogFunc(("SimpleBlock @ %RU64 (T%RU8, TS=%RU64, %zu bytes)\n",
600 RTFileTell(m_Ebml.getFile()), a_pTrack->uTrack, a_uTimecode, a_cbData));
601
602 /** @todo Mask out non-valid timecode bits, e.g. the upper 48 bits for a (default) 16-bit timecode. */
603 Assert(a_uTimecode <= m_uTimecodeMax);
604
605 /* Write a "Simple Block". */
606 m_Ebml.writeClassId(MkvElem_SimpleBlock);
607 /* Block size. */
608 m_Ebml.writeUnsignedInteger(0x10000000u | ( 1 /* Track number size. */
609 + m_cbTimecode /* Timecode size .*/
610 + 1 /* Flags size. */
611 + a_cbData /* Actual frame data size. */), 4);
612 /* Track number. */
613 m_Ebml.writeSize(a_pTrack->uTrack);
614 /* Timecode (relative to cluster opening timecode). */
615 m_Ebml.writeUnsignedInteger(a_uTimecode, m_cbTimecode);
616 /* Flags. */
617 m_Ebml.writeUnsignedInteger(a_fFlags, 1);
618 /* Frame data. */
619 m_Ebml.write(a_pvData, a_cbData);
620
621 a_pTrack->cTotalBlocks++;
622
623 return VINF_SUCCESS;
624 }
625
626#ifdef VBOX_WITH_LIBVPX
627 int writeBlockVP8(WebMTrack *a_pTrack, const vpx_codec_enc_cfg_t *a_pCfg, const vpx_codec_cx_pkt_t *a_pPkt)
628 {
629 RT_NOREF(a_pTrack);
630
631 /* Calculate the PTS of this frame in milliseconds. */
632 int64_t tsPtsMs = a_pPkt->data.frame.pts * 1000
633 * (uint64_t) a_pCfg->g_timebase.num / a_pCfg->g_timebase.den;
634
635 if (tsPtsMs <= m_tsLastPtsMs)
636 tsPtsMs = m_tsLastPtsMs + 1;
637
638 m_tsLastPtsMs = tsPtsMs;
639
640 if (m_tsInitialPtsMs < 0)
641 m_tsInitialPtsMs = m_tsLastPtsMs;
642
643 /* Calculate the relative time of this block. */
644 uint16_t tsBlockMs = 0;
645 bool fClusterStart = false;
646
647 /* Did we reach the maximum our timecode can hold? Use a new cluster then. */
648 if (tsPtsMs - CurSeg.CurCluster.tsStartMs > m_uTimecodeMax)
649 fClusterStart = true;
650 else
651 {
652 /* Calculate the block's timecode, which is relative to the current cluster's starting timecode. */
653 tsBlockMs = static_cast<uint16_t>(tsPtsMs - CurSeg.CurCluster.tsStartMs);
654 }
655
656 const bool fKeyframe = RT_BOOL(a_pPkt->data.frame.flags & VPX_FRAME_IS_KEY);
657
658 if ( fClusterStart
659 || fKeyframe)
660 {
661 WebMCluster &Cluster = CurSeg.CurCluster;
662
663 a_pTrack->cTotalClusters++;
664
665 if (Cluster.fOpen) /* Close current cluster first. */
666 {
667 m_Ebml.subEnd(MkvElem_Cluster);
668 Cluster.fOpen = false;
669 }
670
671 tsBlockMs = 0;
672
673 /* Open a new cluster. */
674 Cluster.fOpen = true;
675 Cluster.tsStartMs = tsPtsMs;
676 Cluster.offCluster = RTFileTell(m_Ebml.getFile());
677
678 LogFunc(("Cluster @ %RU64\n", Cluster.offCluster));
679
680 m_Ebml.subStart(MkvElem_Cluster)
681 .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tsStartMs);
682
683 /* Save a cue point if this is a keyframe. */
684 if (fKeyframe)
685 {
686 WebMCueEntry cue(Cluster.tsStartMs, Cluster.offCluster);
687 CurSeg.lstCues.push_back(cue);
688 }
689 }
690
691 uint8_t fFlags = 0;
692 if (fKeyframe)
693 fFlags |= 0x80; /* Key frame. */
694 if (a_pPkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE)
695 fFlags |= 0x08; /* Invisible frame. */
696
697 return writeSimpleBlockInternal(a_pTrack, tsBlockMs, a_pPkt->data.frame.buf, a_pPkt->data.frame.sz, fFlags);
698 }
699#endif /* VBOX_WITH_LIBVPX */
700
701#ifdef VBOX_WITH_LIBOPUS
702 /* Audio blocks that have same absolute timecode as video blocks SHOULD be written before the video blocks. */
703 int writeBlockOpus(WebMTrack *a_pTrack, const void *pvData, size_t cbData)
704 {
705 AssertPtrReturn(a_pTrack, VERR_INVALID_POINTER);
706 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
707 AssertReturn(cbData, VERR_INVALID_PARAMETER);
708
709 //static uint64_t s_uTimecode = 0;
710 /* For now this is constant while encoding (CBR), but might change to VBR later. */
711 //s_uTimecode += a_pTrack->Audio.cbFrameSize * 48000 / a_pTrack->Audio.uHzCodec;
712
713 //static uint64_t s_uPts = 0;
714
715 int64_t tsPtsMs = 1000000000 * a_pTrack->cTotalBlocks / 48000;
716
717 m_tsLastPtsMs = tsPtsMs;
718
719 if (m_tsInitialPtsMs < 0)
720 m_tsInitialPtsMs = m_tsLastPtsMs;
721
722 WebMCluster &Cluster = CurSeg.CurCluster;
723
724 /* Calculate the relative time of this block. */
725 uint16_t tsBlockMs = 0;
726 bool fClusterStart = false;
727
728 if (a_pTrack->cTotalBlocks == 0)
729 fClusterStart = true;
730
731 /* Did we reach the maximum our timecode can hold? Use a new cluster then. */
732 if (tsPtsMs - Cluster.tsStartMs > m_uTimecodeMax)
733 fClusterStart = true;
734 else
735 {
736 /* Calculate the block's timecode, which is relative to the Cluster timecode. */
737 tsBlockMs = static_cast<uint16_t>(tsPtsMs - Cluster.tsStartMs);
738 }
739
740 if (fClusterStart)
741 {
742 a_pTrack->cTotalClusters++;
743
744 if (Cluster.fOpen) /* Close current cluster first. */
745 {
746 m_Ebml.subEnd(MkvElem_Cluster);
747 Cluster.fOpen = false;
748 }
749
750 tsBlockMs = 0;
751
752 Cluster.fOpen = true;
753 Cluster.tsStartMs = tsPtsMs;
754 Cluster.offCluster = RTFileTell(m_Ebml.getFile());
755
756 LogFunc(("Cluster @ %RU64\n", Cluster.offCluster));
757
758 m_Ebml.subStart(MkvElem_Cluster)
759 .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tsStartMs);
760 }
761
762 LogFunc(("Cluster %RU64 - Block %RU64: -> %RU64ms\n",
763 a_pTrack->cTotalClusters, a_pTrack->cTotalBlocks, a_pTrack->cTotalBlocks * 20));
764
765 return writeSimpleBlockInternal(a_pTrack, tsBlockMs, pvData, cbData, 0 /* Flags */);
766 }
767#endif /* VBOX_WITH_LIBOPUS */
768
769 int WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData)
770 {
771 RT_NOREF(pvData, cbData); /* Only needed for assertions for now. */
772
773 WebMTracks::iterator itTrack = CurSeg.mapTracks.find(uTrack);
774 if (itTrack == CurSeg.mapTracks.end())
775 return VERR_NOT_FOUND;
776
777 WebMTrack *pTrack = itTrack->second;
778 AssertPtr(pTrack);
779
780 int rc;
781
782 if (m_fInTracksSection)
783 {
784 m_Ebml.subEnd(MkvElem_Tracks);
785 m_fInTracksSection = false;
786 }
787
788 switch (pTrack->enmType)
789 {
790
791 case WebMTrackType_Audio:
792 {
793#ifdef VBOX_WITH_LIBOPUS
794 if (m_enmAudioCodec == WebMWriter::AudioCodec_Opus)
795 {
796 Assert(cbData == sizeof(WebMWriter::BlockData_Opus));
797 WebMWriter::BlockData_Opus *pData = (WebMWriter::BlockData_Opus *)pvData;
798 rc = writeBlockOpus(pTrack, pData->pvData, pData->cbData);
799 }
800 else
801#endif /* VBOX_WITH_LIBOPUS */
802 rc = VERR_NOT_SUPPORTED;
803 break;
804 }
805
806 case WebMTrackType_Video:
807 {
808#ifdef VBOX_WITH_LIBVPX
809 if (m_enmVideoCodec == WebMWriter::VideoCodec_VP8)
810 {
811 Assert(cbData == sizeof(WebMWriter::BlockData_VP8));
812 WebMWriter::BlockData_VP8 *pData = (WebMWriter::BlockData_VP8 *)pvData;
813 rc = writeBlockVP8(pTrack, pData->pCfg, pData->pPkt);
814 }
815 else
816#endif /* VBOX_WITH_LIBVPX */
817 rc = VERR_NOT_SUPPORTED;
818 break;
819 }
820
821 default:
822 rc = VERR_NOT_SUPPORTED;
823 break;
824 }
825
826 return rc;
827 }
828
829 int writeFooter(void)
830 {
831 if (m_fInTracksSection)
832 {
833 m_Ebml.subEnd(MkvElem_Tracks);
834 m_fInTracksSection = false;
835 }
836
837 if (CurSeg.CurCluster.fOpen)
838 {
839 m_Ebml.subEnd(MkvElem_Cluster);
840 CurSeg.CurCluster.fOpen = false;
841 }
842
843 /*
844 * Write Cues element.
845 */
846 LogFunc(("Cues @ %RU64\n", RTFileTell(m_Ebml.getFile())));
847
848 CurSeg.offCues = RTFileTell(m_Ebml.getFile());
849
850 m_Ebml.subStart(MkvElem_Cues);
851
852 std::list<WebMCueEntry>::iterator itCuePoint = CurSeg.lstCues.begin();
853 while (itCuePoint != CurSeg.lstCues.end())
854 {
855 m_Ebml.subStart(MkvElem_CuePoint)
856 .serializeUnsignedInteger(MkvElem_CueTime, itCuePoint->time)
857 .subStart(MkvElem_CueTrackPositions)
858 .serializeUnsignedInteger(MkvElem_CueTrack, 1)
859 .serializeUnsignedInteger(MkvElem_CueClusterPosition, itCuePoint->loc - CurSeg.offStart, 8)
860 .subEnd(MkvElem_CueTrackPositions)
861 .subEnd(MkvElem_CuePoint);
862
863 itCuePoint++;
864 }
865
866 m_Ebml.subEnd(MkvElem_Cues)
867 .subEnd(MkvElem_Segment);
868
869 /*
870 * Re-Update SeekHead / Info elements.
871 */
872
873 writeSegSeekInfo();
874
875 return RTFileSeek(m_Ebml.getFile(), 0, RTFILE_SEEK_END, NULL);
876 }
877
878 int close(void)
879 {
880 WebMTracks::iterator itTrack = CurSeg.mapTracks.begin();
881 for (; itTrack != CurSeg.mapTracks.end(); ++itTrack)
882 {
883 delete itTrack->second;
884 CurSeg.mapTracks.erase(itTrack);
885 }
886
887 Assert(CurSeg.mapTracks.size() == 0);
888
889 m_Ebml.close();
890
891 return VINF_SUCCESS;
892 }
893
894 friend class Ebml;
895 friend class WebMWriter;
896
897private:
898
899 /**
900 * Writes the segment's seek information and cue points.
901 *
902 * @returns IPRT status code.
903 */
904 void writeSegSeekInfo(void)
905 {
906 if (CurSeg.offSeekInfo)
907 RTFileSeek(m_Ebml.getFile(), CurSeg.offSeekInfo, RTFILE_SEEK_BEGIN, NULL);
908 else
909 CurSeg.offSeekInfo = RTFileTell(m_Ebml.getFile());
910
911 LogFunc(("SeekHead @ %RU64\n", CurSeg.offSeekInfo));
912
913 m_Ebml.subStart(MkvElem_SeekHead)
914
915 .subStart(MkvElem_Seek)
916 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Tracks)
917 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offTracks - CurSeg.offStart, 8)
918 .subEnd(MkvElem_Seek)
919
920 .subStart(MkvElem_Seek)
921 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Cues)
922 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offCues - CurSeg.offStart, 8)
923 .subEnd(MkvElem_Seek)
924
925 .subStart(MkvElem_Seek)
926 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Info)
927 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offInfo - CurSeg.offStart, 8)
928 .subEnd(MkvElem_Seek)
929
930 .subEnd(MkvElem_SeekHead);
931
932 int64_t iFrameTime = (int64_t)1000 * 1 / 25; //m_Framerate.den / m_Framerate.num; /** @todo Fix this! */
933 CurSeg.offInfo = RTFileTell(m_Ebml.getFile());
934
935 LogFunc(("Info @ %RU64\n", CurSeg.offInfo));
936
937 char szMux[64];
938 RTStrPrintf(szMux, sizeof(szMux),
939#ifdef VBOX_WITH_LIBVPX
940 "vpxenc%s", vpx_codec_version_str());
941#else
942 "unknown");
943#endif
944 char szApp[64];
945 RTStrPrintf(szApp, sizeof(szApp), VBOX_PRODUCT " %sr%u", VBOX_VERSION_STRING, RTBldCfgRevision());
946
947 m_Ebml.subStart(MkvElem_Info)
948 .serializeUnsignedInteger(MkvElem_TimecodeScale, m_uTimecodeScaleNs)
949 .serializeFloat(MkvElem_Segment_Duration, m_tsLastPtsMs + iFrameTime - m_tsInitialPtsMs)
950 .serializeString(MkvElem_MuxingApp, szMux)
951 .serializeString(MkvElem_WritingApp, szApp)
952 .subEnd(MkvElem_Info);
953 }
954};
955
956WebMWriter::WebMWriter(void) : m_pImpl(new WebMWriter_Impl()) {}
957
958WebMWriter::~WebMWriter(void)
959{
960 if (m_pImpl)
961 {
962 Close();
963 delete m_pImpl;
964 }
965}
966
967int WebMWriter::Create(const char *a_pszFilename, uint64_t a_fOpen,
968 WebMWriter::AudioCodec a_enmAudioCodec, WebMWriter::VideoCodec a_enmVideoCodec)
969{
970 try
971 {
972 m_pImpl->m_enmAudioCodec = a_enmAudioCodec;
973 m_pImpl->m_enmVideoCodec = a_enmVideoCodec;
974
975 LogFunc(("Creating '%s'\n", a_pszFilename));
976
977 int rc = m_pImpl->m_Ebml.create(a_pszFilename, a_fOpen);
978 if (RT_SUCCESS(rc))
979 rc = m_pImpl->writeHeader();
980 }
981 catch(int rc)
982 {
983 return rc;
984 }
985 return VINF_SUCCESS;
986}
987
988int WebMWriter::Close(void)
989{
990 if (!m_pImpl->m_Ebml.isOpen())
991 return VINF_SUCCESS;
992
993 int rc = m_pImpl->writeFooter();
994 if (RT_SUCCESS(rc))
995 m_pImpl->close();
996
997 return rc;
998}
999
1000int WebMWriter::AddAudioTrack(uint16_t uHz, uint8_t cChannels, uint8_t cBitDepth, uint8_t *puTrack)
1001{
1002 return m_pImpl->AddAudioTrack(uHz, cChannels, cBitDepth, puTrack);
1003}
1004
1005int WebMWriter::AddVideoTrack(uint16_t uWidth, uint16_t uHeight, double dbFPS, uint8_t *puTrack)
1006{
1007 return m_pImpl->AddVideoTrack(uWidth, uHeight, dbFPS, puTrack);
1008}
1009
1010int WebMWriter::WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData)
1011{
1012 int rc;
1013
1014 try
1015 {
1016 rc = m_pImpl->WriteBlock(uTrack, pvData, cbData);
1017 }
1018 catch(int rc2)
1019 {
1020 rc = rc2;
1021 }
1022 return rc;
1023}
1024
1025uint64_t WebMWriter::GetFileSize(void)
1026{
1027 return m_pImpl->m_Ebml.getFileSize();
1028}
1029
1030uint64_t WebMWriter::GetAvailableSpace(void)
1031{
1032 return m_pImpl->m_Ebml.getAvailableSpace();
1033}
1034
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