VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/EBMLWriter.cpp@ 69350

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

Main/EBML: Renamed EbmlMkvIDs.h -> EBML_MKV.h

  • 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: 47.4 KB
Line 
1/* $Id: EBMLWriter.cpp 69195 2017-10-24 09:29:34Z 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 <queue>
30#include <stack>
31
32#include <math.h> /* For lround.h. */
33
34#include <iprt/asm.h>
35#include <iprt/buildconfig.h>
36#include <iprt/cdefs.h>
37#include <iprt/critsect.h>
38#include <iprt/err.h>
39#include <iprt/file.h>
40#include <iprt/rand.h>
41#include <iprt/string.h>
42
43#include <VBox/log.h>
44#include <VBox/version.h>
45
46#include "EBMLWriter.h"
47#include "EBML_MKV.h"
48
49/** No flags set. */
50#define VBOX_EBMLWRITER_FLAG_NONE 0
51/** The file handle was inherited. */
52#define VBOX_EBMLWRITER_FLAG_HANDLE_INHERITED RT_BIT(0)
53
54class EBMLWriter
55{
56public:
57 typedef uint32_t EbmlClassId;
58
59private:
60
61 struct EbmlSubElement
62 {
63 uint64_t offset;
64 EbmlClassId classId;
65 EbmlSubElement(uint64_t offs, EbmlClassId cid) : offset(offs), classId(cid) {}
66 };
67
68 /** Stack of EBML sub elements. */
69 std::stack<EbmlSubElement> m_Elements;
70 /** The file's handle. */
71 RTFILE m_hFile;
72 /** The file's name (path). */
73 Utf8Str m_strFile;
74 /** Flags. */
75 uint32_t m_fFlags;
76
77public:
78
79 EBMLWriter(void)
80 : m_hFile(NIL_RTFILE)
81 , m_fFlags(VBOX_EBMLWRITER_FLAG_NONE) { }
82
83 virtual ~EBMLWriter(void) { close(); }
84
85public:
86
87 /** Creates an EBML output file using an existing, open file handle. */
88 inline int createEx(const char *a_pszFile, PRTFILE phFile)
89 {
90 AssertPtrReturn(phFile, VERR_INVALID_POINTER);
91
92 m_hFile = *phFile;
93 m_fFlags |= VBOX_EBMLWRITER_FLAG_HANDLE_INHERITED;
94 m_strFile = a_pszFile;
95
96 return VINF_SUCCESS;
97 }
98
99 /** Creates an EBML output file using a file name. */
100 inline int create(const char *a_pszFile, uint64_t fOpen)
101 {
102 int rc = RTFileOpen(&m_hFile, a_pszFile, fOpen);
103 if (RT_SUCCESS(rc))
104 m_strFile = a_pszFile;
105
106 return rc;
107 }
108
109 /** Returns the file name. */
110 inline const Utf8Str& getFileName(void)
111 {
112 return m_strFile;
113 }
114
115 /** Returns file size. */
116 inline uint64_t getFileSize(void)
117 {
118 return RTFileTell(m_hFile);
119 }
120
121 /** Get reference to file descriptor */
122 inline const RTFILE &getFile(void)
123 {
124 return m_hFile;
125 }
126
127 /** Returns available space on storage. */
128 inline uint64_t getAvailableSpace(void)
129 {
130 RTFOFF pcbFree;
131 int rc = RTFileQueryFsSizes(m_hFile, NULL, &pcbFree, 0, 0);
132 return (RT_SUCCESS(rc)? (uint64_t)pcbFree : UINT64_MAX);
133 }
134
135 /** Closes the file. */
136 inline void close(void)
137 {
138 if (!isOpen())
139 return;
140
141 AssertMsg(m_Elements.size() == 0,
142 ("%zu elements are not closed yet (next element to close is 0x%x)\n",
143 m_Elements.size(), m_Elements.top().classId));
144
145 if (!(m_fFlags & VBOX_EBMLWRITER_FLAG_HANDLE_INHERITED))
146 {
147 RTFileClose(m_hFile);
148 m_hFile = NIL_RTFILE;
149 }
150
151 m_fFlags = VBOX_EBMLWRITER_FLAG_NONE;
152 m_strFile = "";
153 }
154
155 /**
156 * Returns whether the file is open or not.
157 *
158 * @returns True if open, false if not.
159 */
160 inline bool isOpen(void)
161 {
162 return RTFileIsValid(m_hFile);
163 }
164
165 /** Starts an EBML sub-element. */
166 inline EBMLWriter &subStart(EbmlClassId classId)
167 {
168 writeClassId(classId);
169 /* store the current file offset. */
170 m_Elements.push(EbmlSubElement(RTFileTell(m_hFile), classId));
171 /* Indicates that size of the element
172 * is unkown (as according to EBML specs).
173 */
174 writeUnsignedInteger(UINT64_C(0x01FFFFFFFFFFFFFF));
175 return *this;
176 }
177
178 /** Ends an EBML sub-element. */
179 inline EBMLWriter &subEnd(EbmlClassId classId)
180 {
181#ifdef VBOX_STRICT
182 /* Class ID on the top of the stack should match the class ID passed
183 * to the function. Otherwise it may mean that we have a bug in the code.
184 */
185 AssertMsg(!m_Elements.empty(), ("No elements to close anymore\n"));
186 AssertMsg(m_Elements.top().classId == classId,
187 ("Ending sub element 0x%x is in wrong order (next to close is 0x%x)\n", classId, m_Elements.top().classId));
188#else
189 RT_NOREF(classId);
190#endif
191
192 uint64_t uPos = RTFileTell(m_hFile);
193 uint64_t uSize = uPos - m_Elements.top().offset - 8;
194 RTFileSeek(m_hFile, m_Elements.top().offset, RTFILE_SEEK_BEGIN, NULL);
195
196 /* Make sure that size will be serialized as uint64_t. */
197 writeUnsignedInteger(uSize | UINT64_C(0x0100000000000000));
198 RTFileSeek(m_hFile, uPos, RTFILE_SEEK_BEGIN, NULL);
199 m_Elements.pop();
200 return *this;
201 }
202
203 /** Serializes a null-terminated string. */
204 inline EBMLWriter &serializeString(EbmlClassId classId, const char *str)
205 {
206 writeClassId(classId);
207 uint64_t size = strlen(str);
208 writeSize(size);
209 write(str, size);
210 return *this;
211 }
212
213 /* Serializes an UNSIGNED integer
214 * If size is zero then it will be detected automatically. */
215 inline EBMLWriter &serializeUnsignedInteger(EbmlClassId classId, uint64_t parm, size_t size = 0)
216 {
217 writeClassId(classId);
218 if (!size) size = getSizeOfUInt(parm);
219 writeSize(size);
220 writeUnsignedInteger(parm, size);
221 return *this;
222 }
223
224 /** Serializes a floating point value.
225 *
226 * Only 8-bytes double precision values are supported
227 * by this function.
228 */
229 inline EBMLWriter &serializeFloat(EbmlClassId classId, float value)
230 {
231 writeClassId(classId);
232 Assert(sizeof(uint32_t) == sizeof(float));
233 writeSize(sizeof(float));
234
235 union
236 {
237 float f;
238 uint8_t u8[4];
239 } u;
240
241 u.f = value;
242
243 for (int i = 3; i >= 0; i--) /* Converts values to big endian. */
244 write(&u.u8[i], 1);
245
246 return *this;
247 }
248
249 /** Serializes binary data. */
250 inline EBMLWriter &serializeData(EbmlClassId classId, const void *pvData, size_t cbData)
251 {
252 writeClassId(classId);
253 writeSize(cbData);
254 write(pvData, cbData);
255 return *this;
256 }
257
258 /** Writes raw data to file. */
259 inline int write(const void *data, size_t size)
260 {
261 return RTFileWrite(m_hFile, data, size, NULL);
262 }
263
264 /** Writes an unsigned integer of variable of fixed size. */
265 inline void writeUnsignedInteger(uint64_t value, size_t size = sizeof(uint64_t))
266 {
267 /* convert to big-endian */
268 value = RT_H2BE_U64(value);
269 write(reinterpret_cast<uint8_t*>(&value) + sizeof(value) - size, size);
270 }
271
272 /** Writes EBML class ID to file.
273 *
274 * EBML ID already has a UTF8-like represenation
275 * so getSizeOfUInt is used to determine
276 * the number of its bytes.
277 */
278 inline void writeClassId(EbmlClassId parm)
279 {
280 writeUnsignedInteger(parm, getSizeOfUInt(parm));
281 }
282
283 /** Writes data size value. */
284 inline void writeSize(uint64_t parm)
285 {
286 /* The following expression defines the size of the value that will be serialized
287 * as an EBML UTF-8 like integer (with trailing bits represeting its size):
288 1xxx xxxx - value 0 to 2^7-2
289 01xx xxxx xxxx xxxx - value 0 to 2^14-2
290 001x xxxx xxxx xxxx xxxx xxxx - value 0 to 2^21-2
291 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^28-2
292 0000 1xxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^35-2
293 0000 01xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^42-2
294 0000 001x xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^49-2
295 0000 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^56-2
296 */
297 size_t size = 8 - ! (parm & (UINT64_MAX << 49)) - ! (parm & (UINT64_MAX << 42)) -
298 ! (parm & (UINT64_MAX << 35)) - ! (parm & (UINT64_MAX << 28)) -
299 ! (parm & (UINT64_MAX << 21)) - ! (parm & (UINT64_MAX << 14)) -
300 ! (parm & (UINT64_MAX << 7));
301 /* One is subtracted in order to avoid loosing significant bit when size = 8. */
302 uint64_t mask = RT_BIT_64(size * 8 - 1);
303 writeUnsignedInteger((parm & (((mask << 1) - 1) >> size)) | (mask >> (size - 1)), size);
304 }
305
306 /** Size calculation for variable size UNSIGNED integer.
307 *
308 * The function defines the size of the number by trimming
309 * consequent trailing zero bytes starting from the most significant.
310 * The following statement is always true:
311 * 1 <= getSizeOfUInt(arg) <= 8.
312 *
313 * Every !(arg & (UINT64_MAX << X)) expression gives one
314 * if an only if all the bits from X to 63 are set to zero.
315 */
316 static inline size_t getSizeOfUInt(uint64_t arg)
317 {
318 return 8 - ! (arg & (UINT64_MAX << 56)) - ! (arg & (UINT64_MAX << 48)) -
319 ! (arg & (UINT64_MAX << 40)) - ! (arg & (UINT64_MAX << 32)) -
320 ! (arg & (UINT64_MAX << 24)) - ! (arg & (UINT64_MAX << 16)) -
321 ! (arg & (UINT64_MAX << 8));
322 }
323
324private:
325 void operator=(const EBMLWriter &);
326
327};
328
329/** No flags specified. */
330#define VBOX_WEBM_BLOCK_FLAG_NONE 0
331/** Invisible block which can be skipped. */
332#define VBOX_WEBM_BLOCK_FLAG_INVISIBLE 0x08
333/** The block marks a key frame. */
334#define VBOX_WEBM_BLOCK_FLAG_KEY_FRAME 0x80
335
336/** The default timecode scale factor for WebM -- all timecodes in the segments are expressed in ms.
337 * This allows every cluster to have blocks with positive values up to 32.767 seconds. */
338#define VBOX_WEBM_TIMECODESCALE_FACTOR_MS 1000000
339
340/** Maximum time (in ms) a cluster can store. */
341#define VBOX_WEBM_CLUSTER_MAX_LEN_MS INT16_MAX
342
343/** Maximum time a block can store.
344 * With signed 16-bit timecodes and a default timecode scale of 1ms per unit this makes 65536ms. */
345#define VBOX_WEBM_BLOCK_MAX_LEN_MS UINT16_MAX
346
347#ifdef VBOX_WITH_LIBOPUS
348# pragma pack(push)
349# pragma pack(1)
350 /** Opus codec private data within the MKV (WEBM) container.
351 * Taken from: https://wiki.xiph.org/MatroskaOpus */
352 typedef struct WEBMOPUSPRIVDATA
353 {
354 WEBMOPUSPRIVDATA(uint32_t a_u32SampleRate, uint8_t a_u8Channels)
355 {
356 au64Head = RT_MAKE_U64_FROM_U8('O', 'p', 'u', 's', 'H', 'e', 'a', 'd');
357 u8Version = 1;
358 u8Channels = a_u8Channels;
359 u16PreSkip = 0;
360 u32SampleRate = a_u32SampleRate;
361 u16Gain = 0;
362 u8MappingFamily = 0;
363 }
364
365 uint64_t au64Head; /**< Defaults to "OpusHead". */
366 uint8_t u8Version; /**< Must be set to 1. */
367 uint8_t u8Channels;
368 uint16_t u16PreSkip;
369 /** Sample rate *before* encoding to Opus.
370 * Note: This rate has nothing to do with the playback rate later! */
371 uint32_t u32SampleRate;
372 uint16_t u16Gain;
373 /** Must stay 0 -- otherwise a mapping table must be appended
374 * right after this header. */
375 uint8_t u8MappingFamily;
376 } WEBMOPUSPRIVDATA, *PWEBMOPUSPRIVDATA;
377 AssertCompileSize(WEBMOPUSPRIVDATA, 19);
378# pragma pack(pop)
379#endif /* VBOX_WITH_LIBOPUS */
380
381class WebMWriter_Impl
382{
383 /** Defines a WebM timecode. */
384 typedef uint16_t WebMTimecode;
385
386 /** Defines the WebM block flags data type. */
387 typedef uint8_t WebMBlockFlags;
388
389 /**
390 * Track type enumeration.
391 */
392 enum WebMTrackType
393 {
394 /** Unknown / invalid type. */
395 WebMTrackType_Invalid = 0,
396 /** Only writes audio. */
397 WebMTrackType_Audio = 1,
398 /** Only writes video. */
399 WebMTrackType_Video = 2
400 };
401
402 struct WebMTrack;
403
404 /**
405 * Structure for defining a WebM simple block.
406 */
407 struct WebMSimpleBlock
408 {
409 WebMSimpleBlock(WebMTrack *a_pTrack,
410 WebMTimecode a_tcPTSMs, const void *a_pvData, size_t a_cbData, WebMBlockFlags a_fFlags)
411 : pTrack(a_pTrack)
412 {
413 Data.tcPTSMs = a_tcPTSMs;
414 Data.cb = a_cbData;
415 Data.fFlags = a_fFlags;
416
417 if (Data.cb)
418 {
419 Data.pv = RTMemDup(a_pvData, a_cbData);
420 if (!Data.pv)
421 throw;
422 }
423 }
424
425 virtual ~WebMSimpleBlock()
426 {
427 if (Data.pv)
428 {
429 Assert(Data.cb);
430 RTMemFree(Data.pv);
431 }
432 }
433
434 WebMTrack *pTrack;
435
436 /** Actual simple block data. */
437 struct
438 {
439 WebMTimecode tcPTSMs;
440 WebMTimecode tcRelToClusterMs;
441 void *pv;
442 size_t cb;
443 WebMBlockFlags fFlags;
444 } Data;
445 };
446
447 /** A simple block queue.*/
448 typedef std::queue<WebMSimpleBlock *> WebMSimpleBlockQueue;
449
450 /** Structure for queuing all simple blocks bound to a single timecode.
451 * This can happen if multiple tracks are being involved. */
452 struct WebMTimecodeBlocks
453 {
454 WebMTimecodeBlocks(void)
455 : fClusterNeeded(false)
456 , fClusterStarted(false) { }
457
458 /** The actual block queue for this timecode. */
459 WebMSimpleBlockQueue Queue;
460 /** Whether a new cluster is needed for this timecode or not. */
461 bool fClusterNeeded;
462 /** Whether a new cluster already has been started for this timecode or not. */
463 bool fClusterStarted;
464
465 /**
466 * Enqueues a simple block into the internal queue.
467 *
468 * @param a_pBlock Block to enqueue and take ownership of.
469 */
470 void Enqueue(WebMSimpleBlock *a_pBlock)
471 {
472 Queue.push(a_pBlock);
473
474 if (a_pBlock->Data.fFlags & VBOX_WEBM_BLOCK_FLAG_KEY_FRAME)
475 fClusterNeeded = true;
476 }
477 };
478
479 /** A block map containing all currently queued blocks.
480 * The key specifies a unique timecode, whereas the value
481 * is a queue of blocks which all correlate to the key (timecode). */
482 typedef std::map<WebMTimecode, WebMTimecodeBlocks> WebMBlockMap;
483
484 /**
485 * Structure for defining a WebM (encoding) queue.
486 */
487 struct WebMQueue
488 {
489 WebMQueue(void)
490 : tcLastBlockWrittenMs(0)
491 , tslastProcessedMs(0) { }
492
493 /** Blocks as FIFO (queue). */
494 WebMBlockMap Map;
495 /** Timecode (in ms) of last written block to queue. */
496 WebMTimecode tcLastBlockWrittenMs;
497 /** Time stamp (in ms) of when the queue was processed last. */
498 uint64_t tslastProcessedMs;
499 };
500
501 /**
502 * Structure for keeping a WebM track entry.
503 */
504 struct WebMTrack
505 {
506 WebMTrack(WebMTrackType a_enmType, uint8_t a_uTrack, uint64_t a_offID)
507 : enmType(a_enmType)
508 , uTrack(a_uTrack)
509 , offUUID(a_offID)
510 , cTotalBlocks(0)
511 , tcLastWrittenMs(0)
512 {
513 uUUID = RTRandU32();
514 }
515
516 /** The type of this track. */
517 WebMTrackType enmType;
518 /** Track parameters. */
519 union
520 {
521 struct
522 {
523 /** Sample rate of input data. */
524 uint32_t uHz;
525 /** Duration of the frame in samples (per channel).
526 * Valid frame size are:
527 *
528 * ms Frame size
529 * 2.5 120
530 * 5 240
531 * 10 480
532 * 20 (Default) 960
533 * 40 1920
534 * 60 2880
535 */
536 uint16_t framesPerBlock;
537 /** How many milliseconds (ms) one written (simple) block represents. */
538 uint16_t msPerBlock;
539 } Audio;
540 };
541 /** This track's track number. Also used as key in track map. */
542 uint8_t uTrack;
543 /** The track's "UUID".
544 * Needed in case this track gets mux'ed with tracks from other files. Not really unique though. */
545 uint32_t uUUID;
546 /** Absolute offset in file of track UUID.
547 * Needed to write the hash sum within the footer. */
548 uint64_t offUUID;
549 /** Total number of blocks. */
550 uint64_t cTotalBlocks;
551 /** Timecode (in ms) of last write. */
552 WebMTimecode tcLastWrittenMs;
553 };
554
555 /**
556 * Structure for keeping a cue point.
557 */
558 struct WebMCuePoint
559 {
560 WebMCuePoint(WebMTrack *a_pTrack, uint64_t a_offCluster, WebMTimecode a_tcAbs)
561 : pTrack(a_pTrack)
562 , offCluster(a_offCluster), tcAbs(a_tcAbs) { }
563
564 /** Associated track. */
565 WebMTrack *pTrack;
566 /** Offset (in bytes) of the related cluster containing the given position. */
567 uint64_t offCluster;
568 /** Time code (absolute) of this cue point. */
569 WebMTimecode tcAbs;
570 };
571
572 /**
573 * Structure for keeping a WebM cluster entry.
574 */
575 struct WebMCluster
576 {
577 WebMCluster(void)
578 : uID(0)
579 , offStart(0)
580 , fOpen(false)
581 , tcStartMs(0)
582 , cBlocks(0) { }
583
584 /** This cluster's ID. */
585 uint64_t uID;
586 /** Absolute offset (in bytes) of this cluster.
587 * Needed for seeking info table. */
588 uint64_t offStart;
589 /** Whether this cluster element is opened currently. */
590 bool fOpen;
591 /** Timecode (in ms) when this cluster starts. */
592 WebMTimecode tcStartMs;
593 /** Number of (simple) blocks in this cluster. */
594 uint64_t cBlocks;
595 };
596
597 /**
598 * Structure for keeping a WebM segment entry.
599 *
600 * Current we're only using one segment.
601 */
602 struct WebMSegment
603 {
604 WebMSegment(void)
605 : tcStartMs(0)
606 , tcLastWrittenMs(0)
607 , offStart(0)
608 , offInfo(0)
609 , offSeekInfo(0)
610 , offTracks(0)
611 , offCues(0)
612 {
613 uTimecodeScaleFactor = VBOX_WEBM_TIMECODESCALE_FACTOR_MS;
614
615 LogFunc(("Default timecode scale is: %RU64ns\n", uTimecodeScaleFactor));
616 }
617
618 int init(void)
619 {
620 return RTCritSectInit(&CritSect);
621 }
622
623 void destroy(void)
624 {
625 RTCritSectDelete(&CritSect);
626 }
627
628 /** Critical section for serializing access to this segment. */
629 RTCRITSECT CritSect;
630
631 /** The timecode scale factor of this segment. */
632 uint64_t uTimecodeScaleFactor;
633
634 /** Timecode (in ms) when starting this segment. */
635 WebMTimecode tcStartMs;
636 /** Timecode (in ms) of last write. */
637 WebMTimecode tcLastWrittenMs;
638
639 /** Absolute offset (in bytes) of CurSeg. */
640 uint64_t offStart;
641 /** Absolute offset (in bytes) of general info. */
642 uint64_t offInfo;
643 /** Absolute offset (in bytes) of seeking info. */
644 uint64_t offSeekInfo;
645 /** Absolute offset (in bytes) of tracks. */
646 uint64_t offTracks;
647 /** Absolute offset (in bytes) of cues table. */
648 uint64_t offCues;
649 /** List of cue points. Needed for seeking table. */
650 std::list<WebMCuePoint> lstCues;
651
652 /** Total number of clusters. */
653 uint64_t cClusters;
654
655 /** Map of tracks.
656 * The key marks the track number (*not* the UUID!). */
657 std::map <uint8_t, WebMTrack *> mapTracks;
658
659 /** Current cluster which is being handled.
660 *
661 * Note that we don't need (and shouldn't need, as this can be a *lot* of data!) a
662 * list of all clusters. */
663 WebMCluster CurCluster;
664
665 WebMQueue queueBlocks;
666
667 } CurSeg;
668
669 /** Audio codec to use. */
670 WebMWriter::AudioCodec m_enmAudioCodec;
671 /** Video codec to use. */
672 WebMWriter::VideoCodec m_enmVideoCodec;
673
674 /** Whether we're currently in the tracks section. */
675 bool m_fInTracksSection;
676
677 /** Size of timecodes in bytes. */
678 size_t m_cbTimecode;
679 /** Maximum value a timecode can have. */
680 uint32_t m_uTimecodeMax;
681
682 EBMLWriter m_Ebml;
683
684public:
685
686 typedef std::map <uint8_t, WebMTrack *> WebMTracks;
687
688public:
689
690 WebMWriter_Impl() :
691 m_fInTracksSection(false)
692 {
693 /* Size (in bytes) of time code to write. We use 2 bytes (16 bit) by default. */
694 m_cbTimecode = 2;
695 m_uTimecodeMax = UINT16_MAX;
696 }
697
698 virtual ~WebMWriter_Impl()
699 {
700 close();
701 }
702
703 int init(void)
704 {
705 return CurSeg.init();
706 }
707
708 void destroy(void)
709 {
710 CurSeg.destroy();
711 }
712
713 /**
714 * Adds an audio track.
715 *
716 * @returns IPRT status code.
717 * @param uHz Input sampling rate.
718 * Must be supported by the selected audio codec.
719 * @param cChannels Number of input audio channels.
720 * @param cBits Number of input bits per channel.
721 * @param puTrack Track number on successful creation. Optional.
722 */
723 int AddAudioTrack(uint16_t uHz, uint8_t cChannels, uint8_t cBits, uint8_t *puTrack)
724 {
725#ifdef VBOX_WITH_LIBOPUS
726 int rc;
727
728 /*
729 * Check if the requested codec rate is supported.
730 *
731 * Only the following values are supported by an Opus standard build
732 * -- every other rate only is supported by a custom build.
733 */
734 switch (uHz)
735 {
736 case 48000:
737 case 24000:
738 case 16000:
739 case 12000:
740 case 8000:
741 rc = VINF_SUCCESS;
742 break;
743
744 default:
745 rc = VERR_NOT_SUPPORTED;
746 break;
747 }
748
749 if (RT_FAILURE(rc))
750 return rc;
751
752 const uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size();
753
754 m_Ebml.subStart(MkvElem_TrackEntry);
755
756 m_Ebml.serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack);
757 m_Ebml.serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */);
758 m_Ebml.serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0);
759
760 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Audio, uTrack, RTFileTell(m_Ebml.getFile()));
761
762 pTrack->Audio.uHz = uHz;
763 pTrack->Audio.msPerBlock = 20; /** Opus uses 20ms by default. Make this configurable? */
764 pTrack->Audio.framesPerBlock = uHz / (1000 /* s in ms */ / pTrack->Audio.msPerBlock);
765
766 WEBMOPUSPRIVDATA opusPrivData(uHz, cChannels);
767
768 LogFunc(("Opus @ %RU16Hz (%RU16ms + %RU16 frames per block)\n",
769 pTrack->Audio.uHz, pTrack->Audio.msPerBlock, pTrack->Audio.framesPerBlock));
770
771 m_Ebml.serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID, 4)
772 .serializeUnsignedInteger(MkvElem_TrackType, 2 /* Audio */)
773 .serializeString(MkvElem_CodecID, "A_OPUS")
774 .serializeData(MkvElem_CodecPrivate, &opusPrivData, sizeof(opusPrivData))
775 .serializeUnsignedInteger(MkvElem_CodecDelay, 0)
776 .serializeUnsignedInteger(MkvElem_SeekPreRoll, 80 * 1000000) /* 80ms in ns. */
777 .subStart(MkvElem_Audio)
778 .serializeFloat(MkvElem_SamplingFrequency, (float)uHz)
779 .serializeUnsignedInteger(MkvElem_Channels, cChannels)
780 .serializeUnsignedInteger(MkvElem_BitDepth, cBits)
781 .subEnd(MkvElem_Audio)
782 .subEnd(MkvElem_TrackEntry);
783
784 CurSeg.mapTracks[uTrack] = pTrack;
785
786 if (puTrack)
787 *puTrack = uTrack;
788
789 return VINF_SUCCESS;
790#else
791 RT_NOREF(uHz, cChannels, cBits, puTrack);
792 return VERR_NOT_SUPPORTED;
793#endif
794 }
795
796 int AddVideoTrack(uint16_t uWidth, uint16_t uHeight, double dbFPS, uint8_t *puTrack)
797 {
798#ifdef VBOX_WITH_LIBVPX
799 RT_NOREF(dbFPS);
800
801 const uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size();
802
803 m_Ebml.subStart(MkvElem_TrackEntry);
804
805 m_Ebml.serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack);
806 m_Ebml.serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */);
807 m_Ebml.serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0);
808
809 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Video, uTrack, RTFileTell(m_Ebml.getFile()));
810
811 /** @todo Resolve codec type. */
812 m_Ebml.serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID /* UID */, 4)
813 .serializeUnsignedInteger(MkvElem_TrackType, 1 /* Video */)
814 .serializeString(MkvElem_CodecID, "V_VP8")
815 .subStart(MkvElem_Video)
816 .serializeUnsignedInteger(MkvElem_PixelWidth, uWidth)
817 .serializeUnsignedInteger(MkvElem_PixelHeight, uHeight)
818 .subEnd(MkvElem_Video);
819
820 m_Ebml.subEnd(MkvElem_TrackEntry);
821
822 CurSeg.mapTracks[uTrack] = pTrack;
823
824 if (puTrack)
825 *puTrack = uTrack;
826
827 return VINF_SUCCESS;
828#else
829 RT_NOREF(uWidth, uHeight, dbFPS, puTrack);
830 return VERR_NOT_SUPPORTED;
831#endif
832 }
833
834 /**
835 * Writes the WebM file header.
836 *
837 * @returns IPRT status code.
838 */
839 int writeHeader(void)
840 {
841 LogFunc(("Header @ %RU64\n", RTFileTell(m_Ebml.getFile())));
842
843 m_Ebml.subStart(MkvElem_EBML)
844 .serializeUnsignedInteger(MkvElem_EBMLVersion, 1)
845 .serializeUnsignedInteger(MkvElem_EBMLReadVersion, 1)
846 .serializeUnsignedInteger(MkvElem_EBMLMaxIDLength, 4)
847 .serializeUnsignedInteger(MkvElem_EBMLMaxSizeLength, 8)
848 .serializeString(MkvElem_DocType, "webm")
849 .serializeUnsignedInteger(MkvElem_DocTypeVersion, 2)
850 .serializeUnsignedInteger(MkvElem_DocTypeReadVersion, 2)
851 .subEnd(MkvElem_EBML);
852
853 m_Ebml.subStart(MkvElem_Segment);
854
855 /* Save offset of current segment. */
856 CurSeg.offStart = RTFileTell(m_Ebml.getFile());
857
858 writeSegSeekInfo();
859
860 /* Save offset of upcoming tracks segment. */
861 CurSeg.offTracks = RTFileTell(m_Ebml.getFile());
862
863 /* The tracks segment starts right after this header. */
864 m_Ebml.subStart(MkvElem_Tracks);
865 m_fInTracksSection = true;
866
867 return VINF_SUCCESS;
868 }
869
870 /**
871 * Writes a simple block into the EBML structure.
872 *
873 * @returns IPRT status code.
874 * @param a_pTrack Track the simple block is assigned to.
875 * @param a_pBlock Simple block to write.
876 */
877 int writeSimpleBlockEBML(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock)
878 {
879#ifdef LOG_ENABLED
880 WebMCluster &Cluster = CurSeg.CurCluster;
881
882 Log3Func(("[T%RU8C%RU64] Off=%RU64, PTS=%RU16, RelToClusterMs=%RU16, %zu bytes\n",
883 a_pTrack->uTrack, Cluster.uID, RTFileTell(m_Ebml.getFile()),
884 a_pBlock->Data.tcPTSMs, a_pBlock->Data.tcRelToClusterMs, a_pBlock->Data.cb));
885#endif
886 /*
887 * Write a "Simple Block".
888 */
889 m_Ebml.writeClassId(MkvElem_SimpleBlock);
890 /* Block size. */
891 m_Ebml.writeUnsignedInteger(0x10000000u | ( 1 /* Track number size. */
892 + m_cbTimecode /* Timecode size .*/
893 + 1 /* Flags size. */
894 + a_pBlock->Data.cb /* Actual frame data size. */), 4);
895 /* Track number. */
896 m_Ebml.writeSize(a_pTrack->uTrack);
897 /* Timecode (relative to cluster opening timecode). */
898 m_Ebml.writeUnsignedInteger(a_pBlock->Data.tcRelToClusterMs, m_cbTimecode);
899 /* Flags. */
900 m_Ebml.writeUnsignedInteger(a_pBlock->Data.fFlags, 1);
901 /* Frame data. */
902 m_Ebml.write(a_pBlock->Data.pv, a_pBlock->Data.cb);
903
904 return VINF_SUCCESS;
905 }
906
907 /**
908 * Writes a simple block and enqueues it into the segment's render queue.
909 *
910 * @returns IPRT status code.
911 * @param a_pTrack Track the simple block is assigned to.
912 * @param a_pBlock Simple block to write and enqueue.
913 */
914 int writeSimpleBlockQueued(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock)
915 {
916 RT_NOREF(a_pTrack);
917
918 int rc = VINF_SUCCESS;
919
920 try
921 {
922 const WebMTimecode tcMap = a_pBlock->Data.tcPTSMs;
923
924 /* See if we already have an entry for the specified timecode in our queue. */
925 WebMBlockMap::iterator itQueue = CurSeg.queueBlocks.Map.find(tcMap);
926 if (itQueue != CurSeg.queueBlocks.Map.end()) /* Use existing queue. */
927 {
928 WebMTimecodeBlocks &Blocks = itQueue->second;
929 Blocks.Enqueue(a_pBlock);
930 }
931 else /* Create a new timecode entry. */
932 {
933 WebMTimecodeBlocks Blocks;
934 Blocks.Enqueue(a_pBlock);
935
936 CurSeg.queueBlocks.Map[tcMap] = Blocks;
937 }
938
939 processQueues(&CurSeg.queueBlocks, false /* fForce */);
940 }
941 catch(...)
942 {
943 delete a_pBlock;
944 a_pBlock = NULL;
945
946 rc = VERR_NO_MEMORY;
947 }
948
949 return rc;
950 }
951
952#ifdef VBOX_WITH_LIBVPX
953 /**
954 * Writes VPX (VP8 video) simple data block.
955 *
956 * @returns IPRT status code.
957 * @param a_pTrack Track ID to write data to.
958 * @param a_pCfg VPX encoder configuration to use.
959 * @param a_pPkt VPX packet video data packet to write.
960 */
961 int writeSimpleBlockVP8(WebMTrack *a_pTrack, const vpx_codec_enc_cfg_t *a_pCfg, const vpx_codec_cx_pkt_t *a_pPkt)
962 {
963 RT_NOREF(a_pTrack);
964
965 /* Calculate the PTS of this frame (in ms). */
966 WebMTimecode tcPTSMs = a_pPkt->data.frame.pts * 1000
967 * (uint64_t) a_pCfg->g_timebase.num / a_pCfg->g_timebase.den;
968
969 if ( tcPTSMs
970 && tcPTSMs <= a_pTrack->tcLastWrittenMs)
971 {
972 tcPTSMs = a_pTrack->tcLastWrittenMs + 1;
973 }
974
975 const bool fKeyframe = RT_BOOL(a_pPkt->data.frame.flags & VPX_FRAME_IS_KEY);
976
977 uint8_t fFlags = VBOX_WEBM_BLOCK_FLAG_NONE;
978 if (fKeyframe)
979 fFlags |= VBOX_WEBM_BLOCK_FLAG_KEY_FRAME;
980 if (a_pPkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE)
981 fFlags |= VBOX_WEBM_BLOCK_FLAG_INVISIBLE;
982
983 return writeSimpleBlockQueued(a_pTrack,
984 new WebMSimpleBlock(a_pTrack,
985 tcPTSMs, a_pPkt->data.frame.buf, a_pPkt->data.frame.sz, fFlags));
986 }
987#endif /* VBOX_WITH_LIBVPX */
988
989#ifdef VBOX_WITH_LIBOPUS
990 /**
991 * Writes an Opus (audio) simple data block.
992 *
993 * @returns IPRT status code.
994 * @param a_pTrack Track ID to write data to.
995 * @param pvData Pointer to simple data block to write.
996 * @param cbData Size (in bytes) of simple data block to write.
997 * @param uPTSMs PTS of simple data block.
998 *
999 * @remarks Audio blocks that have same absolute timecode as video blocks SHOULD be written before the video blocks.
1000 */
1001 int writeSimpleBlockOpus(WebMTrack *a_pTrack, const void *pvData, size_t cbData, WebMTimecode uPTSMs)
1002 {
1003 AssertPtrReturn(a_pTrack, VERR_INVALID_POINTER);
1004 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
1005 AssertReturn(cbData, VERR_INVALID_PARAMETER);
1006
1007 /* Every Opus frame is a key frame. */
1008 const uint8_t fFlags = VBOX_WEBM_BLOCK_FLAG_KEY_FRAME;
1009
1010 return writeSimpleBlockQueued(a_pTrack,
1011 new WebMSimpleBlock(a_pTrack,
1012 uPTSMs, pvData, cbData, fFlags));
1013 }
1014#endif /* VBOX_WITH_LIBOPUS */
1015
1016 /**
1017 * Writes a data block to the specified track.
1018 *
1019 * @returns IPRT status code.
1020 * @param uTrack Track ID to write data to.
1021 * @param pvData Pointer to data block to write.
1022 * @param cbData Size (in bytes) of data block to write.
1023 */
1024 int WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData)
1025 {
1026 RT_NOREF(cbData); /* Only needed for assertions for now. */
1027
1028 int rc = RTCritSectEnter(&CurSeg.CritSect);
1029 AssertRC(rc);
1030
1031 WebMTracks::iterator itTrack = CurSeg.mapTracks.find(uTrack);
1032 if (itTrack == CurSeg.mapTracks.end())
1033 {
1034 RTCritSectLeave(&CurSeg.CritSect);
1035 return VERR_NOT_FOUND;
1036 }
1037
1038 WebMTrack *pTrack = itTrack->second;
1039 AssertPtr(pTrack);
1040
1041 if (m_fInTracksSection)
1042 {
1043 m_Ebml.subEnd(MkvElem_Tracks);
1044 m_fInTracksSection = false;
1045 }
1046
1047 switch (pTrack->enmType)
1048 {
1049
1050 case WebMTrackType_Audio:
1051 {
1052#ifdef VBOX_WITH_LIBOPUS
1053 if (m_enmAudioCodec == WebMWriter::AudioCodec_Opus)
1054 {
1055 Assert(cbData == sizeof(WebMWriter::BlockData_Opus));
1056 WebMWriter::BlockData_Opus *pData = (WebMWriter::BlockData_Opus *)pvData;
1057 rc = writeSimpleBlockOpus(pTrack, pData->pvData, pData->cbData, pData->uPTSMs);
1058 }
1059 else
1060#endif /* VBOX_WITH_LIBOPUS */
1061 rc = VERR_NOT_SUPPORTED;
1062 break;
1063 }
1064
1065 case WebMTrackType_Video:
1066 {
1067#ifdef VBOX_WITH_LIBVPX
1068 if (m_enmVideoCodec == WebMWriter::VideoCodec_VP8)
1069 {
1070 Assert(cbData == sizeof(WebMWriter::BlockData_VP8));
1071 WebMWriter::BlockData_VP8 *pData = (WebMWriter::BlockData_VP8 *)pvData;
1072 rc = writeSimpleBlockVP8(pTrack, pData->pCfg, pData->pPkt);
1073 }
1074 else
1075#endif /* VBOX_WITH_LIBVPX */
1076 rc = VERR_NOT_SUPPORTED;
1077 break;
1078 }
1079
1080 default:
1081 rc = VERR_NOT_SUPPORTED;
1082 break;
1083 }
1084
1085 int rc2 = RTCritSectLeave(&CurSeg.CritSect);
1086 AssertRC(rc2);
1087
1088 return rc;
1089 }
1090
1091 /**
1092 * Processes a render queue.
1093 *
1094 * @returns IPRT status code.
1095 * @param pQueue Queue to process.
1096 * @param fForce Whether forcing to process the render queue or not.
1097 * Needed to drain the queues when terminating.
1098 */
1099 int processQueues(WebMQueue *pQueue, bool fForce)
1100 {
1101 if (pQueue->tslastProcessedMs == 0)
1102 pQueue->tslastProcessedMs = RTTimeMilliTS();
1103
1104 if (!fForce)
1105 {
1106 /* Only process when we reached a certain threshold. */
1107 if (RTTimeMilliTS() - pQueue->tslastProcessedMs < 5000 /* ms */ /** @todo Make this configurable */)
1108 return VINF_SUCCESS;
1109 }
1110
1111 WebMCluster &Cluster = CurSeg.CurCluster;
1112
1113 /* Iterate through the block map. */
1114 WebMBlockMap::iterator it = pQueue->Map.begin();
1115 while (it != CurSeg.queueBlocks.Map.end())
1116 {
1117 WebMTimecode mapTC = it->first;
1118 RT_NOREF(mapTC);
1119 WebMTimecodeBlocks mapBlocks = it->second;
1120
1121 /* Whether to start a new cluster or not. */
1122 bool fClusterStart = false;
1123
1124 /* No blocks written yet? Start a new cluster. */
1125 if (Cluster.cBlocks == 0)
1126 fClusterStart = true;
1127
1128 /* Did we reach the maximum a cluster can hold? Use a new cluster then. */
1129 if (mapTC - Cluster.tcStartMs > VBOX_WEBM_CLUSTER_MAX_LEN_MS)
1130 fClusterStart = true;
1131
1132 /* If the block map indicates that a cluster is needed for this timecode, create one. */
1133 if (mapBlocks.fClusterNeeded)
1134 fClusterStart = true;
1135
1136 if ( fClusterStart
1137 && !mapBlocks.fClusterStarted)
1138 {
1139 if (Cluster.fOpen) /* Close current cluster first. */
1140 {
1141 /* Make sure that the current cluster contained some data. */
1142 Assert(Cluster.offStart);
1143 Assert(Cluster.cBlocks);
1144
1145 m_Ebml.subEnd(MkvElem_Cluster);
1146 Cluster.fOpen = false;
1147 }
1148
1149 Cluster.fOpen = true;
1150 Cluster.uID = CurSeg.cClusters;
1151 Cluster.tcStartMs = mapTC;
1152 Cluster.offStart = RTFileTell(m_Ebml.getFile());
1153 Cluster.cBlocks = 0;
1154
1155 if (CurSeg.cClusters)
1156 AssertMsg(Cluster.tcStartMs, ("[C%RU64] @ %RU64 starting timecode is 0 which is invalid\n",
1157 Cluster.uID, Cluster.offStart));
1158
1159 Log2Func(("[C%RU64] Start @ %RU64ms (map TC is %RU64) / %RU64 bytes\n",
1160 Cluster.uID, Cluster.tcStartMs, mapTC, Cluster.offStart));
1161
1162 m_Ebml.subStart(MkvElem_Cluster)
1163 .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tcStartMs);
1164
1165 CurSeg.cClusters++;
1166
1167 mapBlocks.fClusterStarted = true;
1168 }
1169
1170 /* Iterate through all blocks related to the current timecode. */
1171 while (!mapBlocks.Queue.empty())
1172 {
1173 WebMSimpleBlock *pBlock = mapBlocks.Queue.front();
1174 AssertPtr(pBlock);
1175
1176 WebMTrack *pTrack = pBlock->pTrack;
1177 AssertPtr(pTrack);
1178
1179 /* Calculate the block's relative time code to the current cluster's starting time code. */
1180 Assert(pBlock->Data.tcPTSMs >= Cluster.tcStartMs);
1181 pBlock->Data.tcRelToClusterMs = pBlock->Data.tcPTSMs - Cluster.tcStartMs;
1182
1183 int rc2 = writeSimpleBlockEBML(pTrack, pBlock);
1184 AssertRC(rc2);
1185
1186 Cluster.cBlocks++;
1187
1188 pTrack->cTotalBlocks++;
1189 pTrack->tcLastWrittenMs = pBlock->Data.tcPTSMs;
1190
1191 if (CurSeg.tcLastWrittenMs < pTrack->tcLastWrittenMs)
1192 CurSeg.tcLastWrittenMs = pTrack->tcLastWrittenMs;
1193
1194 /* Save a cue point if this is a keyframe. */
1195 if (pBlock->Data.fFlags & VBOX_WEBM_BLOCK_FLAG_KEY_FRAME)
1196 {
1197 WebMCuePoint cue(pBlock->pTrack, Cluster.offStart, pBlock->Data.tcPTSMs);
1198 CurSeg.lstCues.push_back(cue);
1199 }
1200
1201 delete pBlock;
1202 pBlock = NULL;
1203
1204 mapBlocks.Queue.pop();
1205 }
1206
1207 Assert(mapBlocks.Queue.empty());
1208
1209 CurSeg.queueBlocks.Map.erase(it);
1210
1211 it = CurSeg.queueBlocks.Map.begin();
1212 }
1213
1214 Assert(CurSeg.queueBlocks.Map.empty());
1215
1216 pQueue->tslastProcessedMs = RTTimeMilliTS();
1217
1218 return VINF_SUCCESS;
1219 }
1220
1221 /**
1222 * Writes the WebM footer.
1223 *
1224 * @returns IPRT status code.
1225 */
1226 int writeFooter(void)
1227 {
1228 AssertReturn(m_Ebml.isOpen(), VERR_WRONG_ORDER);
1229
1230 if (m_fInTracksSection)
1231 {
1232 m_Ebml.subEnd(MkvElem_Tracks);
1233 m_fInTracksSection = false;
1234 }
1235
1236 if (CurSeg.CurCluster.fOpen)
1237 {
1238 m_Ebml.subEnd(MkvElem_Cluster);
1239 CurSeg.CurCluster.fOpen = false;
1240 }
1241
1242 /*
1243 * Write Cues element.
1244 */
1245 LogFunc(("Cues @ %RU64\n", RTFileTell(m_Ebml.getFile())));
1246
1247 CurSeg.offCues = RTFileTell(m_Ebml.getFile());
1248
1249 m_Ebml.subStart(MkvElem_Cues);
1250
1251 std::list<WebMCuePoint>::iterator itCuePoint = CurSeg.lstCues.begin();
1252 while (itCuePoint != CurSeg.lstCues.end())
1253 {
1254 /* Sanity. */
1255 AssertPtr(itCuePoint->pTrack);
1256
1257 LogFunc(("CuePoint @ %RU64: Track #%RU8 (Cluster @ %RU64, TC %RU64)\n",
1258 RTFileTell(m_Ebml.getFile()), itCuePoint->pTrack->uTrack,
1259 itCuePoint->offCluster, itCuePoint->tcAbs));
1260
1261 m_Ebml.subStart(MkvElem_CuePoint)
1262 .serializeUnsignedInteger(MkvElem_CueTime, itCuePoint->tcAbs)
1263 .subStart(MkvElem_CueTrackPositions)
1264 .serializeUnsignedInteger(MkvElem_CueTrack, itCuePoint->pTrack->uTrack)
1265 .serializeUnsignedInteger(MkvElem_CueClusterPosition, itCuePoint->offCluster, 8)
1266 .subEnd(MkvElem_CueTrackPositions)
1267 .subEnd(MkvElem_CuePoint);
1268
1269 itCuePoint++;
1270 }
1271
1272 m_Ebml.subEnd(MkvElem_Cues);
1273 m_Ebml.subEnd(MkvElem_Segment);
1274
1275 /*
1276 * Re-Update SeekHead / Info elements.
1277 */
1278
1279 writeSegSeekInfo();
1280
1281 return RTFileSeek(m_Ebml.getFile(), 0, RTFILE_SEEK_END, NULL);
1282 }
1283
1284 /**
1285 * Closes the WebM file and drains all queues.
1286 *
1287 * @returns IPRT status code.
1288 */
1289 int close(void)
1290 {
1291 if (!m_Ebml.isOpen())
1292 return VINF_SUCCESS;
1293
1294 LogFunc(("\n"));
1295
1296 /* Make sure to drain all queues. */
1297 processQueues(&CurSeg.queueBlocks, true /* fForce */);
1298
1299 writeFooter();
1300
1301 WebMTracks::iterator itTrack = CurSeg.mapTracks.begin();
1302 for (; itTrack != CurSeg.mapTracks.end(); ++itTrack)
1303 {
1304 WebMTrack *pTrack = itTrack->second;
1305
1306 delete pTrack;
1307 CurSeg.mapTracks.erase(itTrack);
1308 }
1309
1310 Assert(CurSeg.queueBlocks.Map.size() == 0);
1311 Assert(CurSeg.mapTracks.size() == 0);
1312
1313 m_Ebml.close();
1314
1315 return VINF_SUCCESS;
1316 }
1317
1318 friend class Ebml;
1319 friend class WebMWriter;
1320
1321private:
1322
1323 /**
1324 * Writes the segment's seek information and cue points.
1325 */
1326 void writeSegSeekInfo(void)
1327 {
1328 if (CurSeg.offSeekInfo)
1329 RTFileSeek(m_Ebml.getFile(), CurSeg.offSeekInfo, RTFILE_SEEK_BEGIN, NULL);
1330 else
1331 CurSeg.offSeekInfo = RTFileTell(m_Ebml.getFile());
1332
1333 LogFunc(("SeekHead @ %RU64\n", CurSeg.offSeekInfo));
1334
1335 m_Ebml.subStart(MkvElem_SeekHead);
1336
1337 m_Ebml.subStart(MkvElem_Seek)
1338 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Tracks)
1339 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offTracks - CurSeg.offStart, 8)
1340 .subEnd(MkvElem_Seek);
1341
1342 Assert(CurSeg.offCues - CurSeg.offStart > 0); /* Sanity. */
1343
1344 m_Ebml.subStart(MkvElem_Seek)
1345 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Cues)
1346 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offCues - CurSeg.offStart, 8)
1347 .subEnd(MkvElem_Seek);
1348
1349 m_Ebml.subStart(MkvElem_Seek)
1350 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Info)
1351 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offInfo - CurSeg.offStart, 8)
1352 .subEnd(MkvElem_Seek);
1353
1354 m_Ebml.subEnd(MkvElem_SeekHead);
1355
1356 //int64_t iFrameTime = (int64_t)1000 * 1 / 25; //m_Framerate.den / m_Framerate.num; /** @todo Fix this! */
1357 CurSeg.offInfo = RTFileTell(m_Ebml.getFile());
1358
1359 LogFunc(("Info @ %RU64\n", CurSeg.offInfo));
1360
1361 char szMux[64];
1362 RTStrPrintf(szMux, sizeof(szMux),
1363#ifdef VBOX_WITH_LIBVPX
1364 "vpxenc%s", vpx_codec_version_str());
1365#else
1366 "unknown");
1367#endif
1368 char szApp[64];
1369 RTStrPrintf(szApp, sizeof(szApp), VBOX_PRODUCT " %sr%u", VBOX_VERSION_STRING, RTBldCfgRevision());
1370
1371 const WebMTimecode tcDuration = CurSeg.tcLastWrittenMs - CurSeg.tcStartMs;
1372
1373 if (!CurSeg.lstCues.empty())
1374 {
1375 LogFunc(("tcDuration=%RU64\n", tcDuration));
1376 AssertMsg(tcDuration, ("Segment seems to be empty\n"));
1377 }
1378
1379 m_Ebml.subStart(MkvElem_Info)
1380 .serializeUnsignedInteger(MkvElem_TimecodeScale, CurSeg.uTimecodeScaleFactor)
1381 .serializeFloat(MkvElem_Segment_Duration, tcDuration)
1382 .serializeString(MkvElem_MuxingApp, szMux)
1383 .serializeString(MkvElem_WritingApp, szApp)
1384 .subEnd(MkvElem_Info);
1385 }
1386};
1387
1388WebMWriter::WebMWriter(void)
1389 : m_pImpl(new WebMWriter_Impl()) {}
1390
1391WebMWriter::~WebMWriter(void)
1392{
1393 if (m_pImpl)
1394 {
1395 m_pImpl->destroy();
1396
1397 delete m_pImpl;
1398 }
1399}
1400
1401int WebMWriter::OpenEx(const char *a_pszFilename, PRTFILE a_phFile,
1402 WebMWriter::AudioCodec a_enmAudioCodec, WebMWriter::VideoCodec a_enmVideoCodec)
1403{
1404 try
1405 {
1406 m_pImpl->m_enmAudioCodec = a_enmAudioCodec;
1407 m_pImpl->m_enmVideoCodec = a_enmVideoCodec;
1408
1409 LogFunc(("Creating '%s'\n", a_pszFilename));
1410
1411 int rc = m_pImpl->m_Ebml.createEx(a_pszFilename, a_phFile);
1412 if (RT_SUCCESS(rc))
1413 {
1414 rc = m_pImpl->init();
1415 if (RT_SUCCESS(rc))
1416 rc = m_pImpl->writeHeader();
1417 }
1418 }
1419 catch(int rc)
1420 {
1421 return rc;
1422 }
1423 return VINF_SUCCESS;
1424}
1425
1426int WebMWriter::Open(const char *a_pszFilename, uint64_t a_fOpen,
1427 WebMWriter::AudioCodec a_enmAudioCodec, WebMWriter::VideoCodec a_enmVideoCodec)
1428{
1429 try
1430 {
1431 m_pImpl->m_enmAudioCodec = a_enmAudioCodec;
1432 m_pImpl->m_enmVideoCodec = a_enmVideoCodec;
1433
1434 LogFunc(("Creating '%s'\n", a_pszFilename));
1435
1436 int rc = m_pImpl->m_Ebml.create(a_pszFilename, a_fOpen);
1437 if (RT_SUCCESS(rc))
1438 {
1439 rc = m_pImpl->init();
1440 if (RT_SUCCESS(rc))
1441 rc = m_pImpl->writeHeader();
1442 }
1443 }
1444 catch(int rc)
1445 {
1446 return rc;
1447 }
1448 return VINF_SUCCESS;
1449}
1450
1451int WebMWriter::Close(void)
1452{
1453 return m_pImpl->close();
1454}
1455
1456int WebMWriter::AddAudioTrack(uint16_t uHz, uint8_t cChannels, uint8_t cBitDepth, uint8_t *puTrack)
1457{
1458 return m_pImpl->AddAudioTrack(uHz, cChannels, cBitDepth, puTrack);
1459}
1460
1461int WebMWriter::AddVideoTrack(uint16_t uWidth, uint16_t uHeight, double dbFPS, uint8_t *puTrack)
1462{
1463 return m_pImpl->AddVideoTrack(uWidth, uHeight, dbFPS, puTrack);
1464}
1465
1466int WebMWriter::WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData)
1467{
1468 int rc;
1469
1470 try
1471 {
1472 rc = m_pImpl->WriteBlock(uTrack, pvData, cbData);
1473 }
1474 catch(int rc2)
1475 {
1476 rc = rc2;
1477 }
1478 return rc;
1479}
1480
1481const Utf8Str& WebMWriter::GetFileName(void)
1482{
1483 return m_pImpl->m_Ebml.getFileName();
1484}
1485
1486uint64_t WebMWriter::GetFileSize(void)
1487{
1488 return m_pImpl->m_Ebml.getFileSize();
1489}
1490
1491uint64_t WebMWriter::GetAvailableSpace(void)
1492{
1493 return m_pImpl->m_Ebml.getAvailableSpace();
1494}
1495
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