VirtualBox

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

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

Build fix (no C++11 on Solaris).

  • 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: 28.3 KB
Line 
1/* $Id: EbmlWriter.cpp 65332 2017-01-16 13:55:14Z 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#include <iprt/asm.h>
23#include <iprt/buildconfig.h>
24#include <iprt/cdefs.h>
25#include <iprt/err.h>
26#include <iprt/file.h>
27#include <iprt/rand.h>
28#include <iprt/string.h>
29
30#include <VBox/log.h>
31#include <VBox/version.h>
32
33#include "EbmlWriter.h"
34#include "EbmlIDs.h"
35#include "Logging.h"
36
37
38class Ebml
39{
40public:
41 typedef uint32_t EbmlClassId;
42
43private:
44
45 struct EbmlSubElement
46 {
47 uint64_t offset;
48 EbmlClassId classId;
49 EbmlSubElement(uint64_t offs, EbmlClassId cid) : offset(offs), classId(cid) {}
50 };
51
52 std::stack<EbmlSubElement> m_Elements;
53 RTFILE m_File;
54
55public:
56
57 Ebml(void)
58 : m_File(NIL_RTFILE) { }
59
60 virtual ~Ebml(void) { close(); }
61
62public:
63
64 /** Creates EBML output file. */
65 inline int create(const char *a_pszFilename, uint64_t fOpen)
66 {
67 return RTFileOpen(&m_File, a_pszFilename, fOpen);
68 }
69
70 /** Returns file size. */
71 inline uint64_t getFileSize(void)
72 {
73 return RTFileTell(m_File);
74 }
75
76 /** Get reference to file descriptor */
77 inline const RTFILE &getFile(void)
78 {
79 return m_File;
80 }
81
82 /** Returns available space on storage. */
83 inline uint64_t getAvailableSpace(void)
84 {
85 RTFOFF pcbFree;
86 int rc = RTFileQueryFsSizes(m_File, NULL, &pcbFree, 0, 0);
87 return (RT_SUCCESS(rc)? (uint64_t)pcbFree : UINT64_MAX);
88 }
89
90 /** Closes the file. */
91 inline void close(void)
92 {
93 if (!isOpen())
94 return;
95
96 AssertMsg(m_Elements.size() == 0,
97 ("%zu elements are not closed yet (next element to close is 0x%x)\n",
98 m_Elements.size(), m_Elements.top().classId));
99
100 RTFileClose(m_File);
101 m_File = NIL_RTFILE;
102 }
103
104 /**
105 * Returns whether the file is open or not.
106 *
107 * @returns True if open, false if not.
108 */
109 inline bool isOpen(void)
110 {
111 return RTFileIsValid(m_File);
112 }
113
114 /** Starts an EBML sub-element. */
115 inline Ebml &subStart(EbmlClassId classId)
116 {
117 writeClassId(classId);
118 /* store the current file offset. */
119 m_Elements.push(EbmlSubElement(RTFileTell(m_File), classId));
120 /* Indicates that size of the element
121 * is unkown (as according to EBML specs).
122 */
123 writeUnsignedInteger(UINT64_C(0x01FFFFFFFFFFFFFF));
124 return *this;
125 }
126
127 /** Ends an EBML sub-element. */
128 inline Ebml &subEnd(EbmlClassId classId)
129 {
130#ifdef VBOX_STRICT
131 /* Class ID on the top of the stack should match the class ID passed
132 * to the function. Otherwise it may mean that we have a bug in the code.
133 */
134 AssertMsg(!m_Elements.empty(), ("No elements to close anymore\n"));
135 AssertMsg(m_Elements.top().classId == classId,
136 ("Ending sub element 0x%x is in wrong order (next to close is 0x%x)\n", classId, m_Elements.top().classId));
137#else
138 RT_NOREF(classId);
139#endif
140
141 uint64_t uPos = RTFileTell(m_File);
142 uint64_t uSize = uPos - m_Elements.top().offset - 8;
143 RTFileSeek(m_File, m_Elements.top().offset, RTFILE_SEEK_BEGIN, NULL);
144
145 /* make sure that size will be serialized as uint64 */
146 writeUnsignedInteger(uSize | UINT64_C(0x0100000000000000));
147 RTFileSeek(m_File, uPos, RTFILE_SEEK_BEGIN, NULL);
148 m_Elements.pop();
149 return *this;
150 }
151
152 /** Serializes a null-terminated string. */
153 inline Ebml &serializeString(EbmlClassId classId, const char *str)
154 {
155 writeClassId(classId);
156 uint64_t size = strlen(str);
157 writeSize(size);
158 write(str, size);
159 return *this;
160 }
161
162 /* Serializes an UNSIGNED integer
163 * If size is zero then it will be detected automatically. */
164 inline Ebml &serializeUnsignedInteger(EbmlClassId classId, uint64_t parm, size_t size = 0)
165 {
166 writeClassId(classId);
167 if (!size) size = getSizeOfUInt(parm);
168 writeSize(size);
169 writeUnsignedInteger(parm, size);
170 return *this;
171 }
172
173 /** Serializes a floating point value.
174 *
175 * Only 8-bytes double precision values are supported
176 * by this function.
177 */
178 inline Ebml &serializeFloat(EbmlClassId classId, double value)
179 {
180 writeClassId(classId);
181 writeSize(sizeof(double));
182 writeUnsignedInteger(*reinterpret_cast<uint64_t*>(&value));
183 return *this;
184 }
185
186 /** Serializes binary data. */
187 inline Ebml &serializeData(EbmlClassId classId, const void *pvData, size_t cbData)
188 {
189 writeClassId(classId);
190 writeSize(cbData);
191 write(pvData, cbData);
192 return *this;
193 }
194
195 /** Writes raw data to file. */
196 inline int write(const void *data, size_t size)
197 {
198 return RTFileWrite(m_File, data, size, NULL);
199 }
200
201 /** Writes an unsigned integer of variable of fixed size. */
202 inline void writeUnsignedInteger(uint64_t value, size_t size = sizeof(uint64_t))
203 {
204 /* convert to big-endian */
205 value = RT_H2BE_U64(value);
206 write(reinterpret_cast<uint8_t*>(&value) + sizeof(value) - size, size);
207 }
208
209 /** Writes EBML class ID to file.
210 *
211 * EBML ID already has a UTF8-like represenation
212 * so getSizeOfUInt is used to determine
213 * the number of its bytes.
214 */
215 inline void writeClassId(EbmlClassId parm)
216 {
217 writeUnsignedInteger(parm, getSizeOfUInt(parm));
218 }
219
220 /** Writes data size value. */
221 inline void writeSize(uint64_t parm)
222 {
223 /* The following expression defines the size of the value that will be serialized
224 * as an EBML UTF-8 like integer (with trailing bits represeting its size):
225 1xxx xxxx - value 0 to 2^7-2
226 01xx xxxx xxxx xxxx - value 0 to 2^14-2
227 001x xxxx xxxx xxxx xxxx xxxx - value 0 to 2^21-2
228 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^28-2
229 0000 1xxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^35-2
230 0000 01xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^42-2
231 0000 001x xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^49-2
232 0000 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^56-2
233 */
234 size_t size = 8 - ! (parm & (UINT64_MAX << 49)) - ! (parm & (UINT64_MAX << 42)) -
235 ! (parm & (UINT64_MAX << 35)) - ! (parm & (UINT64_MAX << 28)) -
236 ! (parm & (UINT64_MAX << 21)) - ! (parm & (UINT64_MAX << 14)) -
237 ! (parm & (UINT64_MAX << 7));
238 /* One is subtracted in order to avoid loosing significant bit when size = 8. */
239 uint64_t mask = RT_BIT_64(size * 8 - 1);
240 writeUnsignedInteger((parm & (((mask << 1) - 1) >> size)) | (mask >> (size - 1)), size);
241 }
242
243 /** Size calculation for variable size UNSIGNED integer.
244 *
245 * The function defines the size of the number by trimming
246 * consequent trailing zero bytes starting from the most significant.
247 * The following statement is always true:
248 * 1 <= getSizeOfUInt(arg) <= 8.
249 *
250 * Every !(arg & (UINT64_MAX << X)) expression gives one
251 * if an only if all the bits from X to 63 are set to zero.
252 */
253 static inline size_t getSizeOfUInt(uint64_t arg)
254 {
255 return 8 - ! (arg & (UINT64_MAX << 56)) - ! (arg & (UINT64_MAX << 48)) -
256 ! (arg & (UINT64_MAX << 40)) - ! (arg & (UINT64_MAX << 32)) -
257 ! (arg & (UINT64_MAX << 24)) - ! (arg & (UINT64_MAX << 16)) -
258 ! (arg & (UINT64_MAX << 8));
259 }
260
261private:
262 void operator=(const Ebml &);
263
264};
265
266class WebMWriter_Impl
267{
268 /**
269 * Structure for keeping a cue entry.
270 */
271 struct WebMCueEntry
272 {
273 WebMCueEntry(uint32_t t, uint64_t l)
274 : time(t), loc(l) {}
275
276 uint32_t time;
277 uint64_t loc;
278 };
279
280 /**
281 * Track type enumeration.
282 */
283 enum WebMTrackType
284 {
285 /** Unknown / invalid type. */
286 WebMTrackType_Invalid = 0,
287 /** Only writes audio. */
288 WebMTrackType_Audio = 1,
289 /** Only writes video. */
290 WebMTrackType_Video = 2
291 };
292
293 /**
294 * Structure for keeping a track entry.
295 */
296 struct WebMTrack
297 {
298 WebMTrack(WebMTrackType a_enmType, uint8_t a_uTrack, uint64_t a_offID)
299 : enmType(a_enmType)
300 , uTrack(a_uTrack)
301 , offID(a_offID)
302 {
303 uUUID = RTRandU32();
304 }
305
306 /** The type of this track. */
307 WebMTrackType enmType;
308 /** Track parameters. */
309 union
310 {
311 struct
312 {
313 /** Sample rate of input data. */
314 uint16_t uHzIn;
315 /** Sample rate the codec is using. */
316 uint16_t uHzCodec;
317 /** Frame size (in bytes), based on the codec sample rate. */
318 size_t cbFrameSize;
319 } Audio;
320 };
321 /** This track's track number. Also used as key in track map. */
322 uint8_t uTrack;
323 /** The track's "UUID".
324 * Needed in case this track gets mux'ed with tracks from other files. Not really unique though. */
325 uint32_t uUUID;
326 /** Absolute offset in file of track ID.
327 * Needed to write the hash sum within the footer. */
328 uint64_t offID;
329 };
330
331#ifdef VBOX_WITH_LIBOPUS
332# pragma pack(push)
333# pragma pack(1)
334 /** Opus codec private data.
335 * Taken from: https://wiki.xiph.org/MatroskaOpus */
336 struct OpusPrivData
337 {
338 OpusPrivData(uint32_t a_u32SampleRate, uint8_t a_u8Channels)
339 : u8Channels(a_u8Channels)
340 , u32SampleRate(a_u32SampleRate) { }
341
342 /** "OpusHead". */
343 uint8_t au8Head[8] = { 0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64 };
344 /** Must set to 1. */
345 uint8_t u8Version = 1;
346 uint8_t u8Channels = 0;
347 uint16_t u16PreSkip = 0;
348 /** Sample rate *before* encoding to Opus.
349 * Note: This rate has nothing to do with the playback rate later! */
350 uint32_t u32SampleRate = 0;
351 uint16_t u16Gain = 0;
352 /** Must stay 0 -- otherwise a mapping table must be appended
353 * right after this header. */
354 uint8_t u8MappingFamily = 0;
355 };
356# pragma pack(pop)
357#endif /* VBOX_WITH_LIBOPUS */
358
359 /** Audio codec to use. */
360 WebMWriter::AudioCodec m_enmAudioCodec;
361 /** Video codec to use. */
362 WebMWriter::VideoCodec m_enmVideoCodec;
363
364 /** This PTS (Presentation Time Stamp) acts as our master clock for synchronizing streams. */
365 uint64_t m_uPts;
366 /** Timestamp (in ms) of initial PTS. */
367 int64_t m_tsInitialPtsMs;
368 /** Timestamp (in ms) of last written PTS. */
369 int64_t m_tsLastPtsMs;
370
371 /** Start offset (in bytes) of current segment. */
372 uint64_t m_offSegCurStart;
373
374 /** Start offset (in bytes) of seeking info segment. */
375 uint64_t m_offSegSeekInfoStart;
376 /** Start offset (in bytes) of general info segment. */
377 uint64_t m_offSegInfoStart;
378
379 /** Start offset (in bytes) of tracks segment. */
380 uint64_t m_offSegTracksStart;
381
382 /** Absolute position of cue segment. */
383 uint64_t m_offSegCuesStart;
384 /** List of cue points. Needed for seeking table. */
385 std::list<WebMCueEntry> m_lstCue;
386
387 /** Map of tracks.
388 * The key marks the track number (*not* the UID!). */
389 std::map <uint8_t, WebMTrack *> m_mapTracks;
390 /** Whether we're currently in an opened tracks segment. */
391 bool m_fTracksOpen;
392
393 /** Timestamp (in ms) when the current cluster has been opened. */
394 uint32_t m_tsClusterOpenMs;
395 /** Whether we're currently in an opened cluster segment. */
396 bool m_fClusterOpen;
397 /** Absolute position (in bytes) of current cluster within file.
398 * Needed for seeking info table. */
399 uint64_t m_offSegClusterStart;
400
401 Ebml m_Ebml;
402
403public:
404
405 typedef std::map <uint8_t, WebMTrack *> WebMTracks;
406
407public:
408
409 WebMWriter_Impl() :
410 m_uPts(0)
411 , m_tsInitialPtsMs(-1)
412 , m_tsLastPtsMs(-1)
413 , m_offSegCurStart(0)
414 , m_offSegSeekInfoStart(0)
415 , m_offSegInfoStart(0)
416 , m_offSegTracksStart(0)
417 , m_offSegCuesStart(0)
418 , m_fTracksOpen(false)
419 , m_tsClusterOpenMs(0)
420 , m_fClusterOpen(false)
421 , m_offSegClusterStart(0) {}
422
423 virtual ~WebMWriter_Impl()
424 {
425 close();
426 }
427
428 int AddAudioTrack(uint16_t uHz, uint8_t cChannels, uint8_t cBits, uint8_t *puTrack)
429 {
430#ifdef VBOX_WITH_LIBOPUS
431 m_Ebml.subStart(TrackEntry);
432 m_Ebml.serializeUnsignedInteger(TrackNumber, (uint8_t)m_mapTracks.size());
433 /** @todo Implement track's "Language" property? Currently this defaults to English ("eng"). */
434
435 uint8_t uTrack = (uint8_t)m_mapTracks.size();
436
437 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Audio, uTrack, RTFileTell(m_Ebml.getFile()));
438
439 /* Clamp the codec rate value if it reaches a certain threshold. */
440 if (uHz > 24000) pTrack->Audio.uHzCodec = 48000;
441 else if (uHz > 16000) pTrack->Audio.uHzCodec = 24000;
442 else if (uHz > 12000) pTrack->Audio.uHzCodec = 16000;
443 else if (uHz > 8000 ) pTrack->Audio.uHzCodec = 12000;
444 else pTrack->Audio.uHzCodec = 8000;
445
446 pTrack->Audio.uHzIn = uHz;
447
448 /** @todo 1920 bytes is 40ms worth of audio data. Make this configurable. */
449 pTrack->Audio.cbFrameSize = 1920 / (48000 / pTrack->Audio.uHzCodec);
450
451 /** @todo Resolve codec type. */
452 OpusPrivData opusPrivData(uHz, cChannels);
453
454 m_Ebml.serializeUnsignedInteger(TrackUID, pTrack->uUUID, 4)
455 .serializeUnsignedInteger(TrackType, 2 /* Audio */)
456 .serializeString(CodecID, "A_OPUS")
457 .serializeData(CodecPrivate, &opusPrivData, sizeof(opusPrivData))
458 .serializeUnsignedInteger(CodecDelay, 0)
459 .serializeUnsignedInteger(SeekPreRoll, 80000000)
460 .subStart(Audio)
461 .serializeFloat(SamplingFrequency, (float)pTrack->Audio.uHzCodec)
462 .serializeUnsignedInteger(Channels, cChannels)
463 .serializeUnsignedInteger(BitDepth, cBits)
464 .subEnd(Audio)
465 .subEnd(TrackEntry);
466
467 m_mapTracks[uTrack] = pTrack;
468
469 if (puTrack)
470 *puTrack = uTrack;
471
472 return VINF_SUCCESS;
473#else
474 RT_NOREF(uHz, cChannels, cBits, puTrack);
475 return VERR_NOT_SUPPORTED;
476#endif
477 }
478
479 int AddVideoTrack(uint16_t uWidth, uint16_t uHeight, double dbFPS, uint8_t *puTrack)
480 {
481 m_Ebml.subStart(TrackEntry);
482 m_Ebml.serializeUnsignedInteger(TrackNumber, (uint8_t)m_mapTracks.size());
483
484 uint8_t uTrack = (uint8_t)m_mapTracks.size();
485
486 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Video, uTrack, RTFileTell(m_Ebml.getFile()));
487
488 /** @todo Resolve codec type. */
489 m_Ebml.serializeUnsignedInteger(TrackUID, pTrack->uUUID /* UID */, 4)
490 .serializeUnsignedInteger(TrackType, 1 /* Video */)
491 .serializeString(CodecID, "V_VP8")
492 .subStart(Video)
493 .serializeUnsignedInteger(PixelWidth, uWidth)
494 .serializeUnsignedInteger(PixelHeight, uHeight)
495 .serializeFloat(FrameRate, dbFPS)
496 .subEnd(Video)
497 .subEnd(TrackEntry);
498
499 m_mapTracks[uTrack] = pTrack;
500
501 if (puTrack)
502 *puTrack = uTrack;
503
504 return VINF_SUCCESS;
505 }
506
507 int writeHeader(void)
508 {
509 LogFunc(("Header @ %RU64\n", RTFileTell(m_Ebml.getFile())));
510
511 m_Ebml.subStart(EBML)
512 .serializeUnsignedInteger(EBMLVersion, 1)
513 .serializeUnsignedInteger(EBMLReadVersion, 1)
514 .serializeUnsignedInteger(EBMLMaxIDLength, 4)
515 .serializeUnsignedInteger(EBMLMaxSizeLength, 8)
516 .serializeString(DocType, "webm")
517 .serializeUnsignedInteger(DocTypeVersion, 2)
518 .serializeUnsignedInteger(DocTypeReadVersion, 2)
519 .subEnd(EBML);
520
521 m_Ebml.subStart(Segment);
522
523 /* Save offset of current segment. */
524 m_offSegCurStart = RTFileTell(m_Ebml.getFile());
525
526 writeSeekInfo();
527
528 /* Save offset of upcoming tracks segment. */
529 m_offSegTracksStart = RTFileTell(m_Ebml.getFile());
530
531 /* The tracks segment starts right after this header. */
532 m_Ebml.subStart(Tracks);
533 m_fTracksOpen = true;
534
535 return VINF_SUCCESS;
536 }
537
538 int writeSimpleBlockInternal(uint8_t uTrackID, uint16_t uTimecode, const void *pvData, size_t cbData, uint8_t fFlags)
539 {
540 LogFunc(("SimpleBlock @ %RU64 (T%RU8, TS=%RU16, %zu bytes)\n", RTFileTell(m_Ebml.getFile()), uTrackID, uTimecode, cbData));
541
542 /* Write a "Simple Block". */
543 m_Ebml.writeClassId(SimpleBlock);
544 /* Block size. */
545 m_Ebml.writeUnsignedInteger(0x10000000u | (1 /* Track number. */
546 + 2 /* Timecode .*/
547 + 1 /* Flags. */
548 + cbData /* Actual frame data. */), 4);
549 /* Track number. */
550 m_Ebml.writeSize(uTrackID);
551 /* Timecode (relative to cluster opening timecode). */
552 m_Ebml.writeUnsignedInteger(uTimecode, 2);
553 /* Flags. */
554 m_Ebml.writeUnsignedInteger(fFlags, 1);
555 /* Frame data. */
556 m_Ebml.write(pvData, cbData);
557
558 return VINF_SUCCESS;
559 }
560
561 int writeBlockVP8(const WebMTrack *a_pTrack, const vpx_codec_enc_cfg_t *a_pCfg, const vpx_codec_cx_pkt_t *a_pPkt)
562 {
563 RT_NOREF(a_pTrack);
564
565 /* Calculate the PTS of this frame in milliseconds. */
566 int64_t tsPtsMs = a_pPkt->data.frame.pts * 1000
567 * (uint64_t) a_pCfg->g_timebase.num / a_pCfg->g_timebase.den;
568
569 if (tsPtsMs <= m_tsLastPtsMs)
570 tsPtsMs = m_tsLastPtsMs + 1;
571
572 m_tsLastPtsMs = tsPtsMs;
573
574 if (m_tsInitialPtsMs < 0)
575 m_tsInitialPtsMs = m_tsLastPtsMs;
576
577 /* Calculate the relative time of this block. */
578 uint16_t tsBlockMs = 0;
579 bool fClusterStart = false;
580
581 if (tsPtsMs - m_tsClusterOpenMs > 65536)
582 fClusterStart = true;
583 else
584 {
585 /* Calculate the block's timecode, which is relative to the Cluster timecode. */
586 tsBlockMs = static_cast<uint16_t>(tsPtsMs - m_tsClusterOpenMs);
587 }
588
589 const bool fKeyframe = RT_BOOL(a_pPkt->data.frame.flags & VPX_FRAME_IS_KEY);
590
591 if ( fClusterStart
592 || fKeyframe)
593 {
594 if (m_fClusterOpen) /* Close current cluster first. */
595 m_Ebml.subEnd(Cluster);
596
597 tsBlockMs = 0;
598
599 /* Open a new cluster. */
600 m_fClusterOpen = true;
601 m_tsClusterOpenMs = (uint32_t)tsPtsMs;
602 m_offSegClusterStart = RTFileTell(m_Ebml.getFile());
603
604 LogFunc(("Cluster @ %RU64\n", m_offSegClusterStart));
605
606 m_Ebml.subStart(Cluster)
607 .serializeUnsignedInteger(Timecode, m_tsClusterOpenMs);
608
609 /* Save a cue point if this is a keyframe. */
610 if (fKeyframe)
611 {
612 WebMCueEntry cue(m_tsClusterOpenMs, m_offSegClusterStart);
613 m_lstCue.push_back(cue);
614 }
615 }
616
617 uint8_t fFlags = 0;
618 if (fKeyframe)
619 fFlags |= 0x80; /* Key frame. */
620 if (a_pPkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE)
621 fFlags |= 0x08; /* Invisible frame. */
622
623 return writeSimpleBlockInternal(a_pTrack->uTrack, tsBlockMs, a_pPkt->data.frame.buf, a_pPkt->data.frame.sz, fFlags);
624 }
625
626#ifdef VBOX_WITH_LIBOPUS
627 /* Audio blocks that have same absolute timecode as video blocks SHOULD be written before the video blocks. */
628 int writeBlockOpus(const WebMTrack *a_pTrack, const void *pvData, size_t cbData)
629 {
630 AssertPtrReturn(a_pTrack, VERR_INVALID_POINTER);
631 AssertReturn(a_pTrack->Audio.cbFrameSize == cbData, VERR_INVALID_PARAMETER);
632
633static uint16_t s_uTimecode = 0;
634
635 if (!m_fClusterOpen)
636 {
637 m_Ebml.subStart(Cluster)
638 .serializeUnsignedInteger(Timecode, 0);
639 m_fClusterOpen = true;
640 }
641
642 uint64_t ts = (s_uTimecode * 1000 * 1000 * 1000) / 48000;
643
644 LogFunc(("ts=%RU64 (timecode %RU16)\n", ts, s_uTimecode));
645
646 s_uTimecode += a_pTrack->Audio.cbFrameSize;
647
648 return writeSimpleBlockInternal(a_pTrack->uTrack, ts, pvData, cbData, 0 /* Flags */);
649 }
650#endif
651
652 int WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData)
653 {
654 RT_NOREF(cbData); /* Only needed for assertions for now. */
655
656 WebMTracks::const_iterator itTrack = m_mapTracks.find(uTrack);
657 if (itTrack == m_mapTracks.end())
658 return VERR_NOT_FOUND;
659
660 const WebMTrack *pTrack = itTrack->second;
661 AssertPtr(pTrack);
662
663 int rc;
664
665 if (m_fTracksOpen)
666 {
667 m_Ebml.subEnd(Tracks);
668 m_fTracksOpen = false;
669 }
670
671 switch (pTrack->enmType)
672 {
673
674 case WebMTrackType_Audio:
675 {
676#ifdef VBOX_WITH_LIBOPUS
677 if (m_enmAudioCodec == WebMWriter::AudioCodec_Opus)
678 {
679 Assert(cbData == sizeof(WebMWriter::BlockData_Opus));
680 WebMWriter::BlockData_Opus *pData = (WebMWriter::BlockData_Opus *)pvData;
681 rc = writeBlockOpus(pTrack, pData->pvData, pData->cbData);
682 }
683 else
684#endif /* VBOX_WITH_LIBOPUS */
685 rc = VERR_NOT_SUPPORTED;
686 break;
687 }
688
689 case WebMTrackType_Video:
690 {
691 if (m_enmVideoCodec == WebMWriter::VideoCodec_VP8)
692 {
693 Assert(cbData == sizeof(WebMWriter::BlockData_VP8));
694 WebMWriter::BlockData_VP8 *pData = (WebMWriter::BlockData_VP8 *)pvData;
695 rc = writeBlockVP8(pTrack, pData->pCfg, pData->pPkt);
696 }
697 else
698 rc = VERR_NOT_SUPPORTED;
699 break;
700 }
701
702 default:
703 rc = VERR_NOT_SUPPORTED;
704 break;
705 }
706
707 return rc;
708 }
709
710 int writeFooter(void)
711 {
712 if (m_fTracksOpen)
713 {
714 m_Ebml.subEnd(Tracks);
715 m_fTracksOpen = false;
716 }
717
718 if (m_fClusterOpen)
719 {
720 m_Ebml.subEnd(Cluster);
721 m_fClusterOpen = false;
722 }
723
724 /*
725 * Write Cues segment.
726 */
727 LogFunc(("Cues @ %RU64\n", RTFileTell(m_Ebml.getFile())));
728
729 m_offSegCuesStart = RTFileTell(m_Ebml.getFile());
730
731 m_Ebml.subStart(Cues);
732
733 for (std::list<WebMCueEntry>::iterator it = m_lstCue.begin(); it != m_lstCue.end(); ++it)
734 {
735 m_Ebml.subStart(CuePoint)
736 .serializeUnsignedInteger(CueTime, it->time)
737 .subStart(CueTrackPositions)
738 .serializeUnsignedInteger(CueTrack, 1)
739 .serializeUnsignedInteger(CueClusterPosition, it->loc - m_offSegCurStart, 8)
740 .subEnd(CueTrackPositions)
741 .subEnd(CuePoint);
742 }
743
744 m_Ebml.subEnd(Cues)
745 .subEnd(Segment);
746
747 /*
748 * Update SeekHead / Info segment.
749 */
750
751 writeSeekInfo();
752
753 return RTFileSeek(m_Ebml.getFile(), 0, RTFILE_SEEK_END, NULL);
754 }
755
756 int close(void)
757 {
758 WebMTracks::iterator itTrack = m_mapTracks.begin();
759 for (; itTrack != m_mapTracks.end(); ++itTrack)
760 {
761 delete itTrack->second;
762 m_mapTracks.erase(itTrack);
763
764 itTrack = m_mapTracks.begin();
765 }
766
767 Assert(m_mapTracks.size() == 0);
768
769 m_Ebml.close();
770
771 return VINF_SUCCESS;
772 }
773
774 friend class Ebml;
775 friend class WebMWriter;
776
777private:
778
779 void writeSeekInfo(void)
780 {
781 if (m_offSegSeekInfoStart)
782 RTFileSeek(m_Ebml.getFile(), m_offSegSeekInfoStart, RTFILE_SEEK_BEGIN, NULL);
783 else
784 m_offSegSeekInfoStart = RTFileTell(m_Ebml.getFile());
785
786 LogFunc(("SeekHead @ %RU64\n", m_offSegSeekInfoStart));
787
788 m_Ebml.subStart(SeekHead)
789
790 .subStart(Seek)
791 .serializeUnsignedInteger(SeekID, Tracks)
792 .serializeUnsignedInteger(SeekPosition, m_offSegTracksStart - m_offSegCurStart, 8)
793 .subEnd(Seek)
794
795 .subStart(Seek)
796 .serializeUnsignedInteger(SeekID, Cues)
797 .serializeUnsignedInteger(SeekPosition, m_offSegCuesStart - m_offSegCurStart, 8)
798 .subEnd(Seek)
799
800 .subStart(Seek)
801 .serializeUnsignedInteger(SeekID, Info)
802 .serializeUnsignedInteger(SeekPosition, m_offSegInfoStart - m_offSegCurStart, 8)
803 .subEnd(Seek)
804
805 .subEnd(SeekHead);
806
807 int64_t iFrameTime = (int64_t)1000 * 1 / 25; //m_Framerate.den / m_Framerate.num; /** @todo Fix this! */
808 m_offSegInfoStart = RTFileTell(m_Ebml.getFile());
809
810 LogFunc(("Info @ %RU64\n", m_offSegInfoStart));
811
812 char szMux[64];
813 RTStrPrintf(szMux, sizeof(szMux), "vpxenc%s", vpx_codec_version_str());
814
815 char szApp[64];
816 RTStrPrintf(szApp, sizeof(szApp), VBOX_PRODUCT " %sr%u", VBOX_VERSION_STRING, RTBldCfgRevision());
817
818 m_Ebml.subStart(Info)
819 .serializeUnsignedInteger(TimecodeScale, 1000000)
820 .serializeFloat(Segment_Duration, m_tsLastPtsMs + iFrameTime - m_tsInitialPtsMs)
821 .serializeString(MuxingApp, szMux)
822 .serializeString(WritingApp, szApp)
823 .subEnd(Info);
824 }
825};
826
827WebMWriter::WebMWriter(void) : m_pImpl(new WebMWriter_Impl()) {}
828
829WebMWriter::~WebMWriter(void)
830{
831 if (m_pImpl)
832 {
833 Close();
834 delete m_pImpl;
835 }
836}
837
838int WebMWriter::Create(const char *a_pszFilename, uint64_t a_fOpen,
839 WebMWriter::AudioCodec a_enmAudioCodec, WebMWriter::VideoCodec a_enmVideoCodec)
840{
841 try
842 {
843 m_pImpl->m_enmAudioCodec = a_enmAudioCodec;
844 m_pImpl->m_enmVideoCodec = a_enmVideoCodec;
845
846 LogFunc(("Creating '%s'\n", a_pszFilename));
847
848 int rc = m_pImpl->m_Ebml.create(a_pszFilename, a_fOpen);
849 if (RT_SUCCESS(rc))
850 rc = m_pImpl->writeHeader();
851 }
852 catch(int rc)
853 {
854 return rc;
855 }
856 return VINF_SUCCESS;
857}
858
859int WebMWriter::Close(void)
860{
861 if (!m_pImpl->m_Ebml.isOpen())
862 return VINF_SUCCESS;
863
864 int rc = m_pImpl->writeFooter();
865 if (RT_SUCCESS(rc))
866 m_pImpl->close();
867
868 return rc;
869}
870
871int WebMWriter::AddAudioTrack(uint16_t uHz, uint8_t cChannels, uint8_t cBitDepth, uint8_t *puTrack)
872{
873 return m_pImpl->AddAudioTrack(uHz, cChannels, cBitDepth, puTrack);
874}
875
876int WebMWriter::AddVideoTrack(uint16_t uWidth, uint16_t uHeight, double dbFPS, uint8_t *puTrack)
877{
878 return m_pImpl->AddVideoTrack(uWidth, uHeight, dbFPS, puTrack);
879}
880
881int WebMWriter::WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData)
882{
883 int rc;
884
885 try
886 {
887 rc = m_pImpl->WriteBlock(uTrack, pvData, cbData);
888 }
889 catch(int rc2)
890 {
891 rc = rc2;
892 }
893 return rc;
894}
895
896uint64_t WebMWriter::GetFileSize(void)
897{
898 return m_pImpl->m_Ebml.getFileSize();
899}
900
901uint64_t WebMWriter::GetAvailableSpace(void)
902{
903 return m_pImpl->m_Ebml.getAvailableSpace();
904}
905
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