VirtualBox

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

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

Main/EbmlWriter.cpp: Pass the time stamp for writeBlockOpus(). Not used yet.

  • 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 65541 2017-01-31 13:44: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.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
281/** No flags specified. */
282#define VBOX_WEBM_BLOCK_FLAG_NONE 0
283/** Invisible block which can be skipped. */
284#define VBOX_WEBM_BLOCK_FLAG_INVISIBLE 0x08
285/** The block marks a key frame. */
286#define VBOX_WEBM_BLOCK_FLAG_KEY_FRAME 0x80
287
288class WebMWriter_Impl
289{
290 /**
291 * Structure for keeping a cue entry.
292 */
293 struct WebMCueEntry
294 {
295 WebMCueEntry(uint32_t t, uint64_t l)
296 : time(t), loc(l) {}
297
298 uint32_t time;
299 uint64_t loc;
300 };
301
302 /**
303 * Track type enumeration.
304 */
305 enum WebMTrackType
306 {
307 /** Unknown / invalid type. */
308 WebMTrackType_Invalid = 0,
309 /** Only writes audio. */
310 WebMTrackType_Audio = 1,
311 /** Only writes video. */
312 WebMTrackType_Video = 2
313 };
314
315 /**
316 * Structure for keeping a WebM track entry.
317 */
318 struct WebMTrack
319 {
320 WebMTrack(WebMTrackType a_enmType, uint8_t a_uTrack, uint64_t a_offID)
321 : enmType(a_enmType)
322 , uTrack(a_uTrack)
323 , offUUID(a_offID)
324 , cTotalClusters(0)
325 , cTotalBlocks(0)
326 {
327 uUUID = RTRandU32();
328 }
329
330 /** The type of this track. */
331 WebMTrackType enmType;
332 /** Track parameters. */
333 union
334 {
335 struct
336 {
337 /** Sample rate of input data. */
338 uint32_t uHz;
339 /** Duration of the frame in samples (per channel).
340 * Valid frame size are:
341 *
342 * ms Frame size
343 * 2.5 120
344 * 5 240
345 * 10 480
346 * 20 (Default) 960
347 * 40 1920
348 * 60 2880
349 */
350 uint16_t csFrame;
351 } Audio;
352 };
353 /** This track's track number. Also used as key in track map. */
354 uint8_t uTrack;
355 /** The track's "UUID".
356 * Needed in case this track gets mux'ed with tracks from other files. Not really unique though. */
357 uint32_t uUUID;
358 /** Absolute offset in file of track UUID.
359 * Needed to write the hash sum within the footer. */
360 uint64_t offUUID;
361 /** Total number of clusters. */
362 uint64_t cTotalClusters;
363 /** Total number of blocks. */
364 uint64_t cTotalBlocks;
365 };
366
367 /**
368 * Structure for keeping a WebM cluster entry.
369 */
370 struct WebMCluster
371 {
372 WebMCluster(void)
373 : uID(0)
374 , offCluster(0)
375 , fOpen(false)
376 , tcStart(0)
377 , tcLast(0)
378 , cBlocks(0)
379 , cbData(0) { }
380
381 /** This cluster's ID. */
382 uint64_t uID;
383 /** Absolute offset (in bytes) of current cluster.
384 * Needed for seeking info table. */
385 uint64_t offCluster;
386 /** Whether this cluster element is opened currently. */
387 bool fOpen;
388 /** Timecode when starting this cluster. */
389 uint64_t tcStart;
390 /** Timecode when this cluster was last touched. */
391 uint64_t tcLast;
392 /** Number of (simple) blocks in this cluster. */
393 uint64_t cBlocks;
394 /** Size (in bytes) of data already written. */
395 uint64_t cbData;
396 };
397
398 /**
399 * Structure for keeping a WebM segment entry.
400 *
401 * Current we're only using one segment.
402 */
403 struct WebMSegment
404 {
405 WebMSegment(void)
406 : tcStart(UINT64_MAX)
407 , tcEnd(UINT64_MAX)
408 , offStart(0)
409 , offInfo(0)
410 , offSeekInfo(0)
411 , offTracks(0)
412 , offCues(0)
413 {
414 /* This is the default for WebM -- all timecodes in the segments are expressed in ms.
415 * This allows every cluster to have blocks with positive values up to 32.767 seconds. */
416 uTimecodeScaleFactor = 1000000;
417 //m_uTimecodeScaleFactor = lround(1000000000 /* ns */ / 48000);
418
419 LogFunc(("Default timecode scale is: %RU64ns\n", uTimecodeScaleFactor));
420 }
421
422 /** The timecode scale factor of this segment.
423 * By default this is 1000000, which is 1ms per timecode unit. */
424 uint64_t uTimecodeScaleFactor;
425
426 /** Timecode when starting this segment. */
427 uint64_t tcStart;
428 /** Timecode when this segment ended. */
429 uint64_t tcEnd;
430
431 /** Absolute offset (in bytes) of CurSeg. */
432 uint64_t offStart;
433 /** Absolute offset (in bytes) of general info. */
434 uint64_t offInfo;
435 /** Absolute offset (in bytes) of seeking info. */
436 uint64_t offSeekInfo;
437 /** Absolute offset (in bytes) of tracks. */
438 uint64_t offTracks;
439 /** Absolute offset (in bytes) of cues table. */
440 uint64_t offCues;
441 /** List of cue points. Needed for seeking table. */
442 std::list<WebMCueEntry> lstCues;
443
444 /** Map of tracks.
445 * The key marks the track number (*not* the UUID!). */
446 std::map <uint8_t, WebMTrack *> mapTracks;
447
448 /** Current cluster which is being handled.
449 *
450 * Note that we don't need (and shouldn't need, as this can be a *lot* of data!) a
451 * list of all clusters. */
452 WebMCluster CurCluster;
453
454 } CurSeg;
455
456#ifdef VBOX_WITH_LIBOPUS
457# pragma pack(push)
458# pragma pack(1)
459 /** Opus codec private data.
460 * Taken from: https://wiki.xiph.org/MatroskaOpus */
461 struct OpusPrivData
462 {
463 OpusPrivData(uint32_t a_u32SampleRate, uint8_t a_u8Channels)
464 : u8Channels(a_u8Channels)
465 , u32SampleRate(a_u32SampleRate) { }
466
467 /** "OpusHead". */
468 uint8_t au8Head[8] = { 0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64 };
469 /** Must set to 1. */
470 uint8_t u8Version = 1;
471 uint8_t u8Channels = 0;
472 uint16_t u16PreSkip = 0;
473 /** Sample rate *before* encoding to Opus.
474 * Note: This rate has nothing to do with the playback rate later! */
475 uint32_t u32SampleRate = 0;
476 uint16_t u16Gain = 0;
477 /** Must stay 0 -- otherwise a mapping table must be appended
478 * right after this header. */
479 uint8_t u8MappingFamily = 0;
480 };
481# pragma pack(pop)
482#endif /* VBOX_WITH_LIBOPUS */
483
484 /** Audio codec to use. */
485 WebMWriter::AudioCodec m_enmAudioCodec;
486 /** Video codec to use. */
487 WebMWriter::VideoCodec m_enmVideoCodec;
488
489 /** Whether we're currently in the tracks section. */
490 bool m_fInTracksSection;
491
492 /** Size of timecodes in bytes. */
493 size_t m_cbTimecode;
494 /** Maximum value a timecode can have. */
495 uint32_t m_uTimecodeMax;
496
497 Ebml m_Ebml;
498
499public:
500
501 typedef std::map <uint8_t, WebMTrack *> WebMTracks;
502
503public:
504
505 WebMWriter_Impl() :
506 m_fInTracksSection(false)
507 {
508 /* Size (in bytes) of time code to write. We use 2 bytes (16 bit) by default. */
509 m_cbTimecode = 2;
510 m_uTimecodeMax = UINT16_MAX;
511 }
512
513 virtual ~WebMWriter_Impl()
514 {
515 close();
516 }
517
518 /**
519 * Adds an audio track.
520 *
521 * @returns IPRT status code.
522 * @param uHz Input sampling rate.
523 * Must be supported by the selected audio codec.
524 * @param cChannels Number of input audio channels.
525 * @param cBits Number of input bits per channel.
526 * @param puTrack Track number on successful creation. Optional.
527 */
528 int AddAudioTrack(uint16_t uHz, uint8_t cChannels, uint8_t cBits, uint8_t *puTrack)
529 {
530#ifdef VBOX_WITH_LIBOPUS
531 int rc;
532
533 /*
534 * Check if the requested codec rate is supported.
535 *
536 * Only the following values are supported by an Opus standard build
537 * -- every other rate only is supported by a custom build.
538 */
539 switch (uHz)
540 {
541 case 48000:
542 case 24000:
543 case 16000:
544 case 12000:
545 case 8000:
546 rc = VINF_SUCCESS;
547 break;
548
549 default:
550 rc = VERR_NOT_SUPPORTED;
551 break;
552 }
553
554 if (RT_FAILURE(rc))
555 return rc;
556
557 m_Ebml.subStart(MkvElem_TrackEntry);
558 m_Ebml.serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)CurSeg.mapTracks.size());
559 /** @todo Implement track's "Language" property? Currently this defaults to English ("eng"). */
560
561 uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size();
562
563 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Audio, uTrack, RTFileTell(m_Ebml.getFile()));
564
565 pTrack->Audio.uHz = uHz;
566 pTrack->Audio.csFrame = pTrack->Audio.uHz / 50; /** @todo 20 ms of audio data. Make this configurable? */
567
568 OpusPrivData opusPrivData(uHz, cChannels);
569
570 LogFunc(("Opus @ %RU16Hz (Frame size is %RU16 samples / channel))\n", pTrack->Audio.uHz, pTrack->Audio.csFrame));
571
572 m_Ebml.serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID, 4)
573 .serializeUnsignedInteger(MkvElem_TrackType, 2 /* Audio */)
574 .serializeString(MkvElem_CodecID, "A_OPUS")
575 .serializeData(MkvElem_CodecPrivate, &opusPrivData, sizeof(opusPrivData))
576 .serializeUnsignedInteger(MkvElem_CodecDelay, 0)
577 .serializeUnsignedInteger(MkvElem_SeekPreRoll, 80000000) /* 80ms in ns. */
578 .subStart(MkvElem_Audio)
579 .serializeFloat(MkvElem_SamplingFrequency, (float)uHz)
580 .serializeUnsignedInteger(MkvElem_Channels, cChannels)
581 .serializeUnsignedInteger(MkvElem_BitDepth, cBits)
582 .subEnd(MkvElem_Audio)
583 .subEnd(MkvElem_TrackEntry);
584
585 CurSeg.mapTracks[uTrack] = pTrack;
586
587 if (puTrack)
588 *puTrack = uTrack;
589
590 return VINF_SUCCESS;
591#else
592 RT_NOREF(uHz, cChannels, cBits, puTrack);
593 return VERR_NOT_SUPPORTED;
594#endif
595 }
596
597 int AddVideoTrack(uint16_t uWidth, uint16_t uHeight, double dbFPS, uint8_t *puTrack)
598 {
599#ifdef VBOX_WITH_LIBVPX
600 m_Ebml.subStart(MkvElem_TrackEntry);
601 m_Ebml.serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)CurSeg.mapTracks.size());
602
603 uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size();
604
605 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Video, uTrack, RTFileTell(m_Ebml.getFile()));
606
607 /** @todo Resolve codec type. */
608 m_Ebml.serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID /* UID */, 4)
609 .serializeUnsignedInteger(MkvElem_TrackType, 1 /* Video */)
610 .serializeString(MkvElem_CodecID, "V_VP8")
611 .subStart(MkvElem_Video)
612 .serializeUnsignedInteger(MkvElem_PixelWidth, uWidth)
613 .serializeUnsignedInteger(MkvElem_PixelHeight, uHeight)
614 .serializeFloat(MkvElem_FrameRate, dbFPS)
615 .subEnd(MkvElem_Video)
616 .subEnd(MkvElem_TrackEntry);
617
618 CurSeg.mapTracks[uTrack] = pTrack;
619
620 if (puTrack)
621 *puTrack = uTrack;
622
623 return VINF_SUCCESS;
624#else
625 RT_NOREF(uWidth, uHeight, dbFPS, puTrack);
626 return VERR_NOT_SUPPORTED;
627#endif
628 }
629
630 int writeHeader(void)
631 {
632 LogFunc(("Header @ %RU64\n", RTFileTell(m_Ebml.getFile())));
633
634 m_Ebml.subStart(MkvElem_EBML)
635 .serializeUnsignedInteger(MkvElem_EBMLVersion, 1)
636 .serializeUnsignedInteger(MkvElem_EBMLReadVersion, 1)
637 .serializeUnsignedInteger(MkvElem_EBMLMaxIDLength, 4)
638 .serializeUnsignedInteger(MkvElem_EBMLMaxSizeLength, 8)
639 .serializeString(MkvElem_DocType, "webm")
640 .serializeUnsignedInteger(MkvElem_DocTypeVersion, 2)
641 .serializeUnsignedInteger(MkvElem_DocTypeReadVersion, 2)
642 .subEnd(MkvElem_EBML);
643
644 m_Ebml.subStart(MkvElem_Segment);
645
646 /* Save offset of current segment. */
647 CurSeg.offStart = RTFileTell(m_Ebml.getFile());
648
649 writeSegSeekInfo();
650
651 /* Save offset of upcoming tracks segment. */
652 CurSeg.offTracks = RTFileTell(m_Ebml.getFile());
653
654 /* The tracks segment starts right after this header. */
655 m_Ebml.subStart(MkvElem_Tracks);
656 m_fInTracksSection = true;
657
658 return VINF_SUCCESS;
659 }
660
661 int writeSimpleBlockInternal(WebMTrack *a_pTrack, uint64_t a_uTimecode,
662 const void *a_pvData, size_t a_cbData, uint8_t a_fFlags)
663 {
664 LogFunc(("SimpleBlock @ %RU64 (T%RU8, TS=%RU64, %zu bytes)\n",
665 RTFileTell(m_Ebml.getFile()), a_pTrack->uTrack, a_uTimecode, a_cbData));
666
667 /** @todo Mask out non-valid timecode bits, e.g. the upper 48 bits for a (default) 16-bit timecode. */
668 Assert(a_uTimecode <= m_uTimecodeMax);
669
670 /* Write a "Simple Block". */
671 m_Ebml.writeClassId(MkvElem_SimpleBlock);
672 /* Block size. */
673 m_Ebml.writeUnsignedInteger(0x10000000u | ( 1 /* Track number size. */
674 + m_cbTimecode /* Timecode size .*/
675 + 1 /* Flags size. */
676 + a_cbData /* Actual frame data size. */), 4);
677 /* Track number. */
678 m_Ebml.writeSize(a_pTrack->uTrack);
679 /* Timecode (relative to cluster opening timecode). */
680 m_Ebml.writeUnsignedInteger(a_uTimecode, m_cbTimecode);
681 /* Flags. */
682 m_Ebml.writeUnsignedInteger(a_fFlags, 1);
683 /* Frame data. */
684 m_Ebml.write(a_pvData, a_cbData);
685
686 a_pTrack->cTotalBlocks++;
687
688 return VINF_SUCCESS;
689 }
690
691#ifdef VBOX_WITH_LIBVPX
692 int writeBlockVP8(WebMTrack *a_pTrack, const vpx_codec_enc_cfg_t *a_pCfg, const vpx_codec_cx_pkt_t *a_pPkt)
693 {
694 RT_NOREF(a_pTrack);
695
696 /* Calculate the PTS of this frame in milliseconds. */
697 uint64_t tcPTS = a_pPkt->data.frame.pts * 1000
698 * (uint64_t) a_pCfg->g_timebase.num / a_pCfg->g_timebase.den;
699
700 if (tcPTS <= CurSeg.CurCluster.tcLast)
701 tcPTS = CurSeg.CurCluster.tcLast + 1;
702
703 CurSeg.CurCluster.tcLast = tcPTS;
704
705 if (CurSeg.CurCluster.tcStart == UINT64_MAX)
706 CurSeg.CurCluster.tcStart = CurSeg.CurCluster.tcLast;
707
708 /* Calculate the relative time of this block. */
709 uint16_t tcBlock = 0;
710 bool fClusterStart = false;
711
712 /* Did we reach the maximum our timecode can hold? Use a new cluster then. */
713 if (tcPTS - CurSeg.CurCluster.tcStart > m_uTimecodeMax)
714 fClusterStart = true;
715 else
716 {
717 /* Calculate the block's timecode, which is relative to the current cluster's starting timecode. */
718 tcBlock = static_cast<uint16_t>(tcPTS - CurSeg.CurCluster.tcStart);
719 }
720
721 const bool fKeyframe = RT_BOOL(a_pPkt->data.frame.flags & VPX_FRAME_IS_KEY);
722
723 if ( fClusterStart
724 || fKeyframe)
725 {
726 WebMCluster &Cluster = CurSeg.CurCluster;
727
728 a_pTrack->cTotalClusters++;
729
730 if (Cluster.fOpen) /* Close current cluster first. */
731 {
732 m_Ebml.subEnd(MkvElem_Cluster);
733 Cluster.fOpen = false;
734 }
735
736 tcBlock = 0;
737
738 /* Open a new cluster. */
739 Cluster.fOpen = true;
740 Cluster.tcStart = tcPTS;
741 Cluster.offCluster = RTFileTell(m_Ebml.getFile());
742 Cluster.cBlocks = 0;
743 Cluster.cbData = 0;
744
745 LogFunc(("[C%RU64] Start @ tc=%RU64 off=%RU64\n", a_pTrack->cTotalClusters, Cluster.tcStart, Cluster.offCluster));
746
747 m_Ebml.subStart(MkvElem_Cluster)
748 .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tcStart);
749
750 /* Save a cue point if this is a keyframe. */
751 if (fKeyframe)
752 {
753 WebMCueEntry cue(Cluster.tcStart, Cluster.offCluster);
754 CurSeg.lstCues.push_back(cue);
755 }
756 }
757
758 LogFunc(("tcPTS=%RU64, s=%RU64, e=%RU64\n", tcPTS, CurSeg.CurCluster.tcStart, CurSeg.CurCluster.tcLast));
759
760 uint8_t fFlags = 0;
761 if (fKeyframe)
762 fFlags |= VBOX_WEBM_BLOCK_FLAG_KEY_FRAME;
763 if (a_pPkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE)
764 fFlags |= VBOX_WEBM_BLOCK_FLAG_INVISIBLE;
765
766 return writeSimpleBlockInternal(a_pTrack, tcBlock, a_pPkt->data.frame.buf, a_pPkt->data.frame.sz, fFlags);
767 }
768#endif /* VBOX_WITH_LIBVPX */
769
770#ifdef VBOX_WITH_LIBOPUS
771 /* Audio blocks that have same absolute timecode as video blocks SHOULD be written before the video blocks. */
772 int writeBlockOpus(WebMTrack *a_pTrack, const void *pvData, size_t cbData, uint64_t uTimeStampMs)
773 {
774 AssertPtrReturn(a_pTrack, VERR_INVALID_POINTER);
775 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
776 AssertReturn(cbData, VERR_INVALID_PARAMETER);
777
778 RT_NOREF(uTimeStampMs);
779
780 WebMCluster &Cluster = CurSeg.CurCluster;
781
782 /* Calculate the PTS. */
783 /* Make sure to round the result. This is very important! */
784 uint64_t tcPTSRaw = lround((CurSeg.uTimecodeScaleFactor * 1000 * Cluster.cbData) / a_pTrack->Audio.uHz);
785
786 /* Calculate the absolute PTS. */
787 uint64_t tcPTS = lround(tcPTSRaw / CurSeg.uTimecodeScaleFactor);
788
789 if (Cluster.tcStart == UINT64_MAX)
790 Cluster.tcStart = tcPTS;
791
792 LogFunc(("tcPTSRaw=%RU64, tcPTS=%RU64, cbData=%RU64, uTimeStampMs=%RU64\n",
793 tcPTSRaw, tcPTS, Cluster.cbData, uTimeStampMs));
794
795 Cluster.tcLast = tcPTS;
796
797 uint16_t tcBlock;
798 bool fClusterStart = false;
799
800 if (a_pTrack->cTotalBlocks == 0)
801 fClusterStart = true;
802
803 /* Did we reach the maximum our timecode can hold? Use a new cluster then. */
804 if (tcPTS - Cluster.tcStart > m_uTimecodeMax)
805 {
806 tcBlock = 0;
807
808 fClusterStart = true;
809 }
810 else
811 {
812 /* Calculate the block's timecode, which is relative to the Cluster timecode. */
813 tcBlock = static_cast<uint16_t>(tcPTS - Cluster.tcStart);
814 }
815
816 if (fClusterStart)
817 {
818 if (Cluster.fOpen) /* Close current cluster first. */
819 {
820 m_Ebml.subEnd(MkvElem_Cluster);
821 Cluster.fOpen = false;
822 }
823
824 tcBlock = 0;
825
826 Cluster.fOpen = true;
827 Cluster.tcStart = tcPTS;
828 Cluster.offCluster = RTFileTell(m_Ebml.getFile());
829 Cluster.cBlocks = 0;
830 Cluster.cbData = 0;
831
832 LogFunc(("[C%RU64] Start @ tc=%RU64 off=%RU64\n", a_pTrack->cTotalClusters, Cluster.tcStart, Cluster.offCluster));
833
834 m_Ebml.subStart(MkvElem_Cluster)
835 .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tcStart);
836
837 a_pTrack->cTotalClusters++;
838 }
839
840 LogFunc(("tcPTS=%RU64, s=%RU64, e=%RU64\n", tcPTS, CurSeg.CurCluster.tcStart, CurSeg.CurCluster.tcLast));
841
842 Cluster.cBlocks += 1;
843 Cluster.cbData += cbData;
844
845 return writeSimpleBlockInternal(a_pTrack, tcBlock, pvData, cbData, VBOX_WEBM_BLOCK_FLAG_KEY_FRAME);
846 }
847#endif /* VBOX_WITH_LIBOPUS */
848
849 int WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData)
850 {
851 RT_NOREF(pvData, cbData); /* Only needed for assertions for now. */
852
853 WebMTracks::iterator itTrack = CurSeg.mapTracks.find(uTrack);
854 if (itTrack == CurSeg.mapTracks.end())
855 return VERR_NOT_FOUND;
856
857 WebMTrack *pTrack = itTrack->second;
858 AssertPtr(pTrack);
859
860 int rc;
861
862 if (m_fInTracksSection)
863 {
864 m_Ebml.subEnd(MkvElem_Tracks);
865 m_fInTracksSection = false;
866 }
867
868 switch (pTrack->enmType)
869 {
870
871 case WebMTrackType_Audio:
872 {
873#ifdef VBOX_WITH_LIBOPUS
874 if (m_enmAudioCodec == WebMWriter::AudioCodec_Opus)
875 {
876 Assert(cbData == sizeof(WebMWriter::BlockData_Opus));
877 WebMWriter::BlockData_Opus *pData = (WebMWriter::BlockData_Opus *)pvData;
878 rc = writeBlockOpus(pTrack, pData->pvData, pData->cbData, pData->uTimestampMs);
879 }
880 else
881#endif /* VBOX_WITH_LIBOPUS */
882 rc = VERR_NOT_SUPPORTED;
883 break;
884 }
885
886 case WebMTrackType_Video:
887 {
888#ifdef VBOX_WITH_LIBVPX
889 if (m_enmVideoCodec == WebMWriter::VideoCodec_VP8)
890 {
891 Assert(cbData == sizeof(WebMWriter::BlockData_VP8));
892 WebMWriter::BlockData_VP8 *pData = (WebMWriter::BlockData_VP8 *)pvData;
893 rc = writeBlockVP8(pTrack, pData->pCfg, pData->pPkt);
894 }
895 else
896#endif /* VBOX_WITH_LIBVPX */
897 rc = VERR_NOT_SUPPORTED;
898 break;
899 }
900
901 default:
902 rc = VERR_NOT_SUPPORTED;
903 break;
904 }
905
906 return rc;
907 }
908
909 int writeFooter(void)
910 {
911 if (m_fInTracksSection)
912 {
913 m_Ebml.subEnd(MkvElem_Tracks);
914 m_fInTracksSection = false;
915 }
916
917 if (CurSeg.CurCluster.fOpen)
918 {
919 m_Ebml.subEnd(MkvElem_Cluster);
920 CurSeg.CurCluster.fOpen = false;
921 }
922
923 /*
924 * Write Cues element.
925 */
926 LogFunc(("Cues @ %RU64\n", RTFileTell(m_Ebml.getFile())));
927
928 CurSeg.offCues = RTFileTell(m_Ebml.getFile());
929
930 m_Ebml.subStart(MkvElem_Cues);
931
932 std::list<WebMCueEntry>::iterator itCuePoint = CurSeg.lstCues.begin();
933 while (itCuePoint != CurSeg.lstCues.end())
934 {
935 m_Ebml.subStart(MkvElem_CuePoint)
936 .serializeUnsignedInteger(MkvElem_CueTime, itCuePoint->time)
937 .subStart(MkvElem_CueTrackPositions)
938 .serializeUnsignedInteger(MkvElem_CueTrack, 1)
939 .serializeUnsignedInteger(MkvElem_CueClusterPosition, itCuePoint->loc - CurSeg.offStart, 8)
940 .subEnd(MkvElem_CueTrackPositions)
941 .subEnd(MkvElem_CuePoint);
942
943 itCuePoint++;
944 }
945
946 m_Ebml.subEnd(MkvElem_Cues)
947 .subEnd(MkvElem_Segment);
948
949 /*
950 * Re-Update SeekHead / Info elements.
951 */
952
953 writeSegSeekInfo();
954
955 return RTFileSeek(m_Ebml.getFile(), 0, RTFILE_SEEK_END, NULL);
956 }
957
958 int close(void)
959 {
960 WebMTracks::iterator itTrack = CurSeg.mapTracks.begin();
961 for (; itTrack != CurSeg.mapTracks.end(); ++itTrack)
962 {
963 delete itTrack->second;
964 CurSeg.mapTracks.erase(itTrack);
965 }
966
967 Assert(CurSeg.mapTracks.size() == 0);
968
969 m_Ebml.close();
970
971 return VINF_SUCCESS;
972 }
973
974 friend class Ebml;
975 friend class WebMWriter;
976
977private:
978
979 /**
980 * Writes the segment's seek information and cue points.
981 *
982 * @returns IPRT status code.
983 */
984 void writeSegSeekInfo(void)
985 {
986 if (CurSeg.offSeekInfo)
987 RTFileSeek(m_Ebml.getFile(), CurSeg.offSeekInfo, RTFILE_SEEK_BEGIN, NULL);
988 else
989 CurSeg.offSeekInfo = RTFileTell(m_Ebml.getFile());
990
991 LogFunc(("SeekHead @ %RU64\n", CurSeg.offSeekInfo));
992
993 m_Ebml.subStart(MkvElem_SeekHead)
994
995 .subStart(MkvElem_Seek)
996 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Tracks)
997 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offTracks - CurSeg.offStart, 8)
998 .subEnd(MkvElem_Seek)
999
1000 .subStart(MkvElem_Seek)
1001 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Cues)
1002 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offCues - CurSeg.offStart, 8)
1003 .subEnd(MkvElem_Seek)
1004
1005 .subStart(MkvElem_Seek)
1006 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Info)
1007 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offInfo - CurSeg.offStart, 8)
1008 .subEnd(MkvElem_Seek)
1009
1010 .subEnd(MkvElem_SeekHead);
1011
1012 //int64_t iFrameTime = (int64_t)1000 * 1 / 25; //m_Framerate.den / m_Framerate.num; /** @todo Fix this! */
1013 CurSeg.offInfo = RTFileTell(m_Ebml.getFile());
1014
1015 LogFunc(("Info @ %RU64\n", CurSeg.offInfo));
1016
1017 char szMux[64];
1018 RTStrPrintf(szMux, sizeof(szMux),
1019#ifdef VBOX_WITH_LIBVPX
1020 "vpxenc%s", vpx_codec_version_str());
1021#else
1022 "unknown");
1023#endif
1024 char szApp[64];
1025 RTStrPrintf(szApp, sizeof(szApp), VBOX_PRODUCT " %sr%u", VBOX_VERSION_STRING, RTBldCfgRevision());
1026
1027 LogFunc(("Duration=%RU64\n", CurSeg.tcEnd - CurSeg.tcStart));
1028
1029 m_Ebml.subStart(MkvElem_Info)
1030 .serializeUnsignedInteger(MkvElem_TimecodeScale, CurSeg.uTimecodeScaleFactor)
1031 .serializeFloat(MkvElem_Segment_Duration, CurSeg.tcEnd /*+ iFrameTime*/ - CurSeg.tcStart)
1032 .serializeString(MkvElem_MuxingApp, szMux)
1033 .serializeString(MkvElem_WritingApp, szApp)
1034 .subEnd(MkvElem_Info);
1035 }
1036};
1037
1038WebMWriter::WebMWriter(void) : m_pImpl(new WebMWriter_Impl()) {}
1039
1040WebMWriter::~WebMWriter(void)
1041{
1042 if (m_pImpl)
1043 {
1044 Close();
1045 delete m_pImpl;
1046 }
1047}
1048
1049int WebMWriter::Create(const char *a_pszFilename, uint64_t a_fOpen,
1050 WebMWriter::AudioCodec a_enmAudioCodec, WebMWriter::VideoCodec a_enmVideoCodec)
1051{
1052 try
1053 {
1054 m_pImpl->m_enmAudioCodec = a_enmAudioCodec;
1055 m_pImpl->m_enmVideoCodec = a_enmVideoCodec;
1056
1057 LogFunc(("Creating '%s'\n", a_pszFilename));
1058
1059 int rc = m_pImpl->m_Ebml.create(a_pszFilename, a_fOpen);
1060 if (RT_SUCCESS(rc))
1061 rc = m_pImpl->writeHeader();
1062 }
1063 catch(int rc)
1064 {
1065 return rc;
1066 }
1067 return VINF_SUCCESS;
1068}
1069
1070int WebMWriter::Close(void)
1071{
1072 if (!m_pImpl->m_Ebml.isOpen())
1073 return VINF_SUCCESS;
1074
1075 int rc = m_pImpl->writeFooter();
1076 if (RT_SUCCESS(rc))
1077 m_pImpl->close();
1078
1079 return rc;
1080}
1081
1082int WebMWriter::AddAudioTrack(uint16_t uHz, uint8_t cChannels, uint8_t cBitDepth, uint8_t *puTrack)
1083{
1084 return m_pImpl->AddAudioTrack(uHz, cChannels, cBitDepth, puTrack);
1085}
1086
1087int WebMWriter::AddVideoTrack(uint16_t uWidth, uint16_t uHeight, double dbFPS, uint8_t *puTrack)
1088{
1089 return m_pImpl->AddVideoTrack(uWidth, uHeight, dbFPS, puTrack);
1090}
1091
1092int WebMWriter::WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData)
1093{
1094 int rc;
1095
1096 try
1097 {
1098 rc = m_pImpl->WriteBlock(uTrack, pvData, cbData);
1099 }
1100 catch(int rc2)
1101 {
1102 rc = rc2;
1103 }
1104 return rc;
1105}
1106
1107uint64_t WebMWriter::GetFileSize(void)
1108{
1109 return m_pImpl->m_Ebml.getFileSize();
1110}
1111
1112uint64_t WebMWriter::GetAvailableSpace(void)
1113{
1114 return m_pImpl->m_Ebml.getAvailableSpace();
1115}
1116
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