VirtualBox

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

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

VideoRec: Update on syncing with audio data.

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette