VirtualBox

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

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

src-client: Define LOG_GROUP according to interface or similar.

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