VirtualBox

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

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

VideoRec/EbmlWriter: Added support for using inherited file handles.

  • 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.6 KB
Line 
1/* $Id: EbmlWriter.cpp 68796 2017-09-20 10:25:19Z 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(UINT64_MAX)
502 , tcEnd(UINT64_MAX)
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 /* Calculate the block's timecode, which is relative to the cluster's starting timecode. */
832 uint16_t tcBlockMs = static_cast<uint16_t>(tcPTSMs - Cluster.tcStartMs);
833
834 Log2Func(("tcPTSMs=%RU64, tcBlockMs=%RU64\n", tcPTSMs, tcBlockMs));
835
836 Log2Func(("[C%RU64] cBlocks=%RU64, tcStartMs=%RU64, tcEndMs=%RU64 (%zums)\n",
837 Cluster.uID, Cluster.cBlocks, Cluster.tcStartMs, Cluster.tcEndMs, Cluster.tcEndMs - Cluster.tcStartMs));
838
839 uint8_t fFlags = 0;
840 if (fKeyframe)
841 fFlags |= VBOX_WEBM_BLOCK_FLAG_KEY_FRAME;
842 if (a_pPkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE)
843 fFlags |= VBOX_WEBM_BLOCK_FLAG_INVISIBLE;
844
845 return writeSimpleBlockInternal(a_pTrack, tcBlockMs, a_pPkt->data.frame.buf, a_pPkt->data.frame.sz, fFlags);
846 }
847#endif /* VBOX_WITH_LIBVPX */
848
849#ifdef VBOX_WITH_LIBOPUS
850 /* Audio blocks that have same absolute timecode as video blocks SHOULD be written before the video blocks. */
851 int writeBlockOpus(WebMTrack *a_pTrack, const void *pvData, size_t cbData, uint64_t uTimeStampMs)
852 {
853 AssertPtrReturn(a_pTrack, VERR_INVALID_POINTER);
854 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
855 AssertReturn(cbData, VERR_INVALID_PARAMETER);
856
857 RT_NOREF(uTimeStampMs);
858
859 WebMCluster &Cluster = CurSeg.CurCluster;
860
861 /* Calculate the PTS of the current block:
862 *
863 * The "raw PTS" is the exact time of an object represented in nanoseconds):
864 * Raw Timecode = (Block timecode + Cluster timecode) * TimecodeScaleFactor
865 */
866 uint64_t tcPTSMs = Cluster.tcStartMs + (Cluster.cBlocks * 20 /*ms */);
867
868 /* Whether to start a new cluster or not. */
869 bool fClusterStart = false;
870
871 if (a_pTrack->cTotalBlocks == 0)
872 fClusterStart = true;
873
874 /* Did we reach the maximum a cluster can hold? Use a new cluster then. */
875 if (tcPTSMs > VBOX_WEBM_CLUSTER_MAX_LEN_MS)
876 fClusterStart = true;
877
878 if (fClusterStart)
879 {
880 if (Cluster.fOpen) /* Close current clusters first. */
881 {
882 m_Ebml.subEnd(MkvElem_Cluster);
883 Cluster.fOpen = false;
884 }
885
886 Cluster.fOpen = true;
887 Cluster.uID = a_pTrack->cTotalClusters;
888 Cluster.tcStartMs = Cluster.tcEndMs;
889 Cluster.offCluster = RTFileTell(m_Ebml.getFile());
890 Cluster.cBlocks = 0;
891
892 LogFunc(("[T%RU8C%RU64] Start @ %RU64ms / %RU64 bytes\n",
893 a_pTrack->uTrack, Cluster.uID, Cluster.tcStartMs, Cluster.offCluster));
894
895 /* As all audio frame as key frames, insert a new cue point when a new cluster starts. */
896 WebMCuePoint cue(a_pTrack, Cluster.tcStartMs, Cluster.offCluster);
897 CurSeg.lstCues.push_back(cue);
898
899 m_Ebml.subStart(MkvElem_Cluster)
900 .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tcStartMs);
901
902 a_pTrack->cTotalClusters++;
903 }
904
905 Cluster.tcEndMs = tcPTSMs;
906 Cluster.cBlocks++;
907
908 /* Calculate the block's timecode, which is relative to the cluster's starting timecode. */
909 uint16_t tcBlockMs = static_cast<uint16_t>(tcPTSMs - Cluster.tcStartMs);
910
911 Log2Func(("tcPTSMs=%RU64, tcBlockMs=%RU64\n", tcPTSMs, tcBlockMs));
912
913 Log2Func(("[C%RU64] cBlocks=%RU64, tcStartMs=%RU64, tcEndMs=%RU64 (%zums)\n",
914 Cluster.uID, Cluster.cBlocks, Cluster.tcStartMs, Cluster.tcEndMs, Cluster.tcEndMs - Cluster.tcStartMs));
915
916 return writeSimpleBlockInternal(a_pTrack, tcBlockMs,
917 pvData, cbData, VBOX_WEBM_BLOCK_FLAG_KEY_FRAME);
918 }
919#endif /* VBOX_WITH_LIBOPUS */
920
921 int WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData)
922 {
923 RT_NOREF(pvData, cbData); /* Only needed for assertions for now. */
924
925 WebMTracks::iterator itTrack = CurSeg.mapTracks.find(uTrack);
926 if (itTrack == CurSeg.mapTracks.end())
927 return VERR_NOT_FOUND;
928
929 WebMTrack *pTrack = itTrack->second;
930 AssertPtr(pTrack);
931
932 int rc;
933
934 if (m_fInTracksSection)
935 {
936 m_Ebml.subEnd(MkvElem_Tracks);
937 m_fInTracksSection = false;
938 }
939
940 switch (pTrack->enmType)
941 {
942
943 case WebMTrackType_Audio:
944 {
945#ifdef VBOX_WITH_LIBOPUS
946 if (m_enmAudioCodec == WebMWriter::AudioCodec_Opus)
947 {
948 Assert(cbData == sizeof(WebMWriter::BlockData_Opus));
949 WebMWriter::BlockData_Opus *pData = (WebMWriter::BlockData_Opus *)pvData;
950 rc = writeBlockOpus(pTrack, pData->pvData, pData->cbData, pData->uTimestampMs);
951 }
952 else
953#endif /* VBOX_WITH_LIBOPUS */
954 rc = VERR_NOT_SUPPORTED;
955 break;
956 }
957
958 case WebMTrackType_Video:
959 {
960#ifdef VBOX_WITH_LIBVPX
961 if (m_enmVideoCodec == WebMWriter::VideoCodec_VP8)
962 {
963 Assert(cbData == sizeof(WebMWriter::BlockData_VP8));
964 WebMWriter::BlockData_VP8 *pData = (WebMWriter::BlockData_VP8 *)pvData;
965 rc = writeBlockVP8(pTrack, pData->pCfg, pData->pPkt);
966 }
967 else
968#endif /* VBOX_WITH_LIBVPX */
969 rc = VERR_NOT_SUPPORTED;
970 break;
971 }
972
973 default:
974 rc = VERR_NOT_SUPPORTED;
975 break;
976 }
977
978 return rc;
979 }
980
981 int writeFooter(void)
982 {
983 if (m_fInTracksSection)
984 {
985 m_Ebml.subEnd(MkvElem_Tracks);
986 m_fInTracksSection = false;
987 }
988
989 if (CurSeg.CurCluster.fOpen)
990 {
991 m_Ebml.subEnd(MkvElem_Cluster);
992 CurSeg.CurCluster.fOpen = false;
993 }
994
995 /*
996 * Write Cues element.
997 */
998 LogFunc(("Cues @ %RU64\n", RTFileTell(m_Ebml.getFile())));
999
1000 CurSeg.offCues = RTFileTell(m_Ebml.getFile());
1001
1002 m_Ebml.subStart(MkvElem_Cues);
1003
1004 std::list<WebMCuePoint>::iterator itCuePoint = CurSeg.lstCues.begin();
1005 while (itCuePoint != CurSeg.lstCues.end())
1006 {
1007 /* Sanity. */
1008 AssertPtr(itCuePoint->pTrack);
1009
1010 const uint64_t uClusterPos = itCuePoint->offClusterStart - CurSeg.offStart;
1011
1012 LogFunc(("CuePoint @ %RU64: Track #%RU8 (Time %RU64, Pos %RU64)\n",
1013 RTFileTell(m_Ebml.getFile()), itCuePoint->pTrack->uTrack, itCuePoint->tcClusterStart, uClusterPos));
1014
1015 m_Ebml.subStart(MkvElem_CuePoint)
1016 .serializeUnsignedInteger(MkvElem_CueTime, itCuePoint->tcClusterStart)
1017 .subStart(MkvElem_CueTrackPositions)
1018 .serializeUnsignedInteger(MkvElem_CueTrack, itCuePoint->pTrack->uTrack)
1019 .serializeUnsignedInteger(MkvElem_CueClusterPosition, uClusterPos, 8)
1020 .subEnd(MkvElem_CueTrackPositions)
1021 .subEnd(MkvElem_CuePoint);
1022
1023 itCuePoint++;
1024 }
1025
1026 m_Ebml.subEnd(MkvElem_Cues);
1027 m_Ebml.subEnd(MkvElem_Segment);
1028
1029 /*
1030 * Re-Update SeekHead / Info elements.
1031 */
1032
1033 writeSegSeekInfo();
1034
1035 return RTFileSeek(m_Ebml.getFile(), 0, RTFILE_SEEK_END, NULL);
1036 }
1037
1038 int close(void)
1039 {
1040 WebMTracks::iterator itTrack = CurSeg.mapTracks.begin();
1041 for (; itTrack != CurSeg.mapTracks.end(); ++itTrack)
1042 {
1043 delete itTrack->second;
1044 CurSeg.mapTracks.erase(itTrack);
1045 }
1046
1047 Assert(CurSeg.mapTracks.size() == 0);
1048
1049 m_Ebml.close();
1050
1051 return VINF_SUCCESS;
1052 }
1053
1054 friend class Ebml;
1055 friend class WebMWriter;
1056
1057private:
1058
1059 /**
1060 * Writes the segment's seek information and cue points.
1061 *
1062 * @returns IPRT status code.
1063 */
1064 void writeSegSeekInfo(void)
1065 {
1066 if (CurSeg.offSeekInfo)
1067 RTFileSeek(m_Ebml.getFile(), CurSeg.offSeekInfo, RTFILE_SEEK_BEGIN, NULL);
1068 else
1069 CurSeg.offSeekInfo = RTFileTell(m_Ebml.getFile());
1070
1071 LogFunc(("SeekHead @ %RU64\n", CurSeg.offSeekInfo));
1072
1073 m_Ebml.subStart(MkvElem_SeekHead);
1074
1075 m_Ebml.subStart(MkvElem_Seek)
1076 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Tracks)
1077 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offTracks - CurSeg.offStart, 8)
1078 .subEnd(MkvElem_Seek);
1079
1080 Assert(CurSeg.offCues - CurSeg.offStart > 0); /* Sanity. */
1081
1082 m_Ebml.subStart(MkvElem_Seek)
1083 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Cues)
1084 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offCues - CurSeg.offStart, 8)
1085 .subEnd(MkvElem_Seek);
1086
1087 m_Ebml.subStart(MkvElem_Seek)
1088 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Info)
1089 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offInfo - CurSeg.offStart, 8)
1090 .subEnd(MkvElem_Seek);
1091
1092 m_Ebml.subEnd(MkvElem_SeekHead);
1093
1094 //int64_t iFrameTime = (int64_t)1000 * 1 / 25; //m_Framerate.den / m_Framerate.num; /** @todo Fix this! */
1095 CurSeg.offInfo = RTFileTell(m_Ebml.getFile());
1096
1097 LogFunc(("Info @ %RU64\n", CurSeg.offInfo));
1098
1099 char szMux[64];
1100 RTStrPrintf(szMux, sizeof(szMux),
1101#ifdef VBOX_WITH_LIBVPX
1102 "vpxenc%s", vpx_codec_version_str());
1103#else
1104 "unknown");
1105#endif
1106 char szApp[64];
1107 RTStrPrintf(szApp, sizeof(szApp), VBOX_PRODUCT " %sr%u", VBOX_VERSION_STRING, RTBldCfgRevision());
1108
1109 LogFunc(("Duration=%RU64\n", CurSeg.tcEnd - CurSeg.tcStart));
1110
1111 m_Ebml.subStart(MkvElem_Info)
1112 .serializeUnsignedInteger(MkvElem_TimecodeScale, CurSeg.uTimecodeScaleFactor)
1113 .serializeFloat(MkvElem_Segment_Duration, CurSeg.tcEnd - CurSeg.tcStart)
1114 .serializeString(MkvElem_MuxingApp, szMux)
1115 .serializeString(MkvElem_WritingApp, szApp)
1116 .subEnd(MkvElem_Info);
1117 }
1118};
1119
1120WebMWriter::WebMWriter(void)
1121 : m_pImpl(new WebMWriter_Impl()) {}
1122
1123WebMWriter::~WebMWriter(void)
1124{
1125 if (m_pImpl)
1126 {
1127 Close();
1128 delete m_pImpl;
1129 }
1130}
1131
1132int WebMWriter::CreateEx(const char *a_pszFilename, PRTFILE a_phFile,
1133 WebMWriter::AudioCodec a_enmAudioCodec, WebMWriter::VideoCodec a_enmVideoCodec)
1134{
1135 try
1136 {
1137 m_pImpl->m_enmAudioCodec = a_enmAudioCodec;
1138 m_pImpl->m_enmVideoCodec = a_enmVideoCodec;
1139
1140 LogFunc(("Creating '%s'\n", a_pszFilename));
1141
1142 int rc = m_pImpl->m_Ebml.createEx(a_pszFilename, a_phFile);
1143 if (RT_SUCCESS(rc))
1144 rc = m_pImpl->writeHeader();
1145 }
1146 catch(int rc)
1147 {
1148 return rc;
1149 }
1150 return VINF_SUCCESS;
1151}
1152
1153int WebMWriter::Create(const char *a_pszFilename, uint64_t a_fOpen,
1154 WebMWriter::AudioCodec a_enmAudioCodec, WebMWriter::VideoCodec a_enmVideoCodec)
1155{
1156 try
1157 {
1158 m_pImpl->m_enmAudioCodec = a_enmAudioCodec;
1159 m_pImpl->m_enmVideoCodec = a_enmVideoCodec;
1160
1161 LogFunc(("Creating '%s'\n", a_pszFilename));
1162
1163 int rc = m_pImpl->m_Ebml.create(a_pszFilename, a_fOpen);
1164 if (RT_SUCCESS(rc))
1165 rc = m_pImpl->writeHeader();
1166 }
1167 catch(int rc)
1168 {
1169 return rc;
1170 }
1171 return VINF_SUCCESS;
1172}
1173
1174int WebMWriter::Close(void)
1175{
1176 if (!m_pImpl->m_Ebml.isOpen())
1177 return VINF_SUCCESS;
1178
1179 int rc = m_pImpl->writeFooter();
1180 if (RT_SUCCESS(rc))
1181 m_pImpl->close();
1182
1183 return rc;
1184}
1185
1186int WebMWriter::AddAudioTrack(uint16_t uHz, uint8_t cChannels, uint8_t cBitDepth, uint8_t *puTrack)
1187{
1188 return m_pImpl->AddAudioTrack(uHz, cChannels, cBitDepth, puTrack);
1189}
1190
1191int WebMWriter::AddVideoTrack(uint16_t uWidth, uint16_t uHeight, double dbFPS, uint8_t *puTrack)
1192{
1193 return m_pImpl->AddVideoTrack(uWidth, uHeight, dbFPS, puTrack);
1194}
1195
1196int WebMWriter::WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData)
1197{
1198 int rc;
1199
1200 try
1201 {
1202 rc = m_pImpl->WriteBlock(uTrack, pvData, cbData);
1203 }
1204 catch(int rc2)
1205 {
1206 rc = rc2;
1207 }
1208 return rc;
1209}
1210
1211const Utf8Str& WebMWriter::GetFileName(void)
1212{
1213 return m_pImpl->m_Ebml.getFileName();
1214}
1215
1216uint64_t WebMWriter::GetFileSize(void)
1217{
1218 return m_pImpl->m_Ebml.getFileSize();
1219}
1220
1221uint64_t WebMWriter::GetAvailableSpace(void)
1222{
1223 return m_pImpl->m_Ebml.getAvailableSpace();
1224}
1225
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