VirtualBox

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

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

VideoRec: Fixed seeking in recorded files.

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