VirtualBox

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

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

VideoRec: Made uHz 32-bit.

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