VirtualBox

source: vbox/trunk/src/VBox/Main/include/WebMWriter.h@ 96229

Last change on this file since 96229 was 96229, checked in by vboxsync, 3 years ago

Recording/Main: Decoupled the WebM writer class from codec dependencies. Various bugfixes. bugref:10275

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.4 KB
Line 
1/* $Id: WebMWriter.h 96229 2022-08-16 15:41:39Z vboxsync $ */
2/** @file
3 * WebMWriter.h - WebM container handling.
4 */
5
6/*
7 * Copyright (C) 2013-2022 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#ifndef MAIN_INCLUDED_WebMWriter_h
19#define MAIN_INCLUDED_WebMWriter_h
20#ifndef RT_WITHOUT_PRAGMA_ONCE
21# pragma once
22#endif
23
24#include <iprt/buildconfig.h>
25#include <iprt/mem.h>
26#include <iprt/rand.h>
27#include <iprt/string.h>
28
29#include "VBox/com/VirtualBox.h"
30#include <VBox/version.h>
31
32#include "EBMLWriter.h"
33#include "EBML_MKV.h"
34
35#include <queue>
36#include <map>
37#include <list>
38
39#ifdef VBOX_WITH_LIBVPX
40# ifdef _MSC_VER
41# pragma warning(push)
42# pragma warning(disable: 4668) /* vpx_codec.h(64) : warning C4668: '__GNUC__' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' */
43# include <vpx/vpx_encoder.h>
44# pragma warning(pop)
45# else
46# include <vpx/vpx_encoder.h>
47# endif
48#endif /* VBOX_WITH_LIBVPX */
49
50/** No flags specified. */
51#define VBOX_WEBM_BLOCK_FLAG_NONE 0
52/** Invisible block which can be skipped. */
53#define VBOX_WEBM_BLOCK_FLAG_INVISIBLE 0x08
54/** The block marks a key frame. */
55#define VBOX_WEBM_BLOCK_FLAG_KEY_FRAME 0x80
56
57/** The default timecode scale factor for WebM -- all timecodes in the segments are expressed in ms.
58 * This allows every cluster to have blocks with positive values up to 32.767 seconds. */
59#define VBOX_WEBM_TIMECODESCALE_FACTOR_MS 1000000
60
61/** Maximum time (in ms) a cluster can store. */
62#define VBOX_WEBM_CLUSTER_MAX_LEN_MS INT16_MAX
63
64/** Maximum time a block can store.
65 * With signed 16-bit timecodes and a default timecode scale of 1ms per unit this makes 65536ms. */
66#define VBOX_WEBM_BLOCK_MAX_LEN_MS UINT16_MAX
67
68#ifdef VBOX_WITH_LIBOPUS
69# pragma pack(push)
70# pragma pack(1)
71 /** Opus codec private data within the MKV (WEBM) container.
72 * Taken from: https://wiki.xiph.org/MatroskaOpus */
73 typedef struct WEBMOPUSPRIVDATA
74 {
75 WEBMOPUSPRIVDATA(uint32_t a_u32SampleRate, uint8_t a_u8Channels)
76 {
77 au64Head = RT_MAKE_U64_FROM_U8('O', 'p', 'u', 's', 'H', 'e', 'a', 'd');
78 u8Version = 1;
79 u8Channels = a_u8Channels;
80 u16PreSkip = 0;
81 u32SampleRate = a_u32SampleRate;
82 u16Gain = 0;
83 u8MappingFamily = 0;
84 }
85
86 uint64_t au64Head; /**< Defaults to "OpusHead". */
87 uint8_t u8Version; /**< Must be set to 1. */
88 uint8_t u8Channels;
89 uint16_t u16PreSkip;
90 /** Sample rate *before* encoding to Opus.
91 * Note: This rate has nothing to do with the playback rate later! */
92 uint32_t u32SampleRate;
93 uint16_t u16Gain;
94 /** Must stay 0 -- otherwise a mapping table must be appended
95 * right after this header. */
96 uint8_t u8MappingFamily;
97 } WEBMOPUSPRIVDATA, *PWEBMOPUSPRIVDATA;
98 AssertCompileSize(WEBMOPUSPRIVDATA, 19);
99# pragma pack(pop)
100#endif /* VBOX_WITH_LIBOPUS */
101
102#ifdef VBOX_WITH_LIBVORBIS
103# pragma pack(push)
104# pragma pack(1)
105 /** Ogg Vorbis codec private data within the MKV (WEBM) container.
106 * Taken from: https://www.matroska.org/technical/codec_specs.html */
107 typedef struct WEBMOGGVORBISPRIVDATA
108 {
109 WEBMOGGVORBISPRIVDATA(uint32_t a_cbHdrIdent, uint32_t a_cbHdrComments, uint32_t a_cbHdrSetup)
110 : cbHdrIdent(a_cbHdrIdent)
111 , cbHdrComments(a_cbHdrComments)
112 {
113 RT_NOREF(a_cbHdrSetup);
114
115 /* We supply 3 headers total: The "real" header, comments header + setup header. */
116 cHeaders = 3 /* Headers */ - 1; /* Note: Always "minus one" here. */
117
118 Assert(a_cbHdrIdent <= UINT8_MAX);
119 Assert(a_cbHdrComments <= UINT8_MAX);
120 Assert(a_cbHdrSetup <= _8K);
121 Assert(a_cbHdrIdent + a_cbHdrComments + a_cbHdrSetup <= sizeof(abHdr));
122 }
123
124 /** Number of private headers - 1. */
125 uint8_t cHeaders;
126 /** Size of identification header (in bytes). */
127 uint8_t cbHdrIdent;
128 /** < Size of comments header (in bytes). */
129 uint8_t cbHdrComments;
130 /** < Header code area. */
131 uint8_t abHdr[UINT8_MAX /* Header */ + UINT8_MAX /* Comments header */ + _8K /* Setup header */];
132
133 } WEBMOGGVORBISPRIVDATA, *PWEBMOGGVORBISPRIVDATA;
134# pragma pack(pop)
135#endif
136
137class WebMWriter : public EBMLWriter
138{
139
140public:
141
142 /** Defines an absolute WebM timecode (Block + Cluster). */
143 typedef uint64_t WebMTimecodeAbs;
144
145 /** Defines a relative WebM timecode (Block). */
146 typedef uint16_t WebMTimecodeRel;
147
148 /** Defines the WebM block flags data type. */
149 typedef uint8_t WebMBlockFlags;
150
151 /**
152 * Track type enumeration.
153 */
154 enum WebMTrackType
155 {
156 /** Unknown / invalid type. */
157 WebMTrackType_Invalid = 0,
158 /** Only writes audio. */
159 WebMTrackType_Audio = 1,
160 /** Only writes video. */
161 WebMTrackType_Video = 2
162 };
163
164 struct WebMTrack;
165
166 /**
167 * Structure for defining a WebM simple block.
168 */
169 struct WebMSimpleBlock
170 {
171 WebMSimpleBlock(WebMTrack *a_pTrack,
172 WebMTimecodeAbs a_tcAbsPTSMs, const void *a_pvData, size_t a_cbData, WebMBlockFlags a_fFlags)
173 : pTrack(a_pTrack)
174 {
175 Data.tcAbsPTSMs = a_tcAbsPTSMs;
176 Data.cb = a_cbData;
177 Data.fFlags = a_fFlags;
178
179 if (Data.cb)
180 {
181 Data.pv = RTMemDup(a_pvData, a_cbData);
182 if (!Data.pv)
183 throw;
184 }
185 }
186
187 virtual ~WebMSimpleBlock()
188 {
189 if (Data.pv)
190 {
191 Assert(Data.cb);
192 RTMemFree(Data.pv);
193 }
194 }
195
196 WebMTrack *pTrack;
197
198 /** Actual simple block data. */
199 struct
200 {
201 WebMTimecodeAbs tcAbsPTSMs;
202 WebMTimecodeRel tcRelToClusterMs;
203 void *pv;
204 size_t cb;
205 WebMBlockFlags fFlags;
206 } Data;
207 };
208
209 /** A simple block queue.*/
210 typedef std::queue<WebMSimpleBlock *> WebMSimpleBlockQueue;
211
212 /** Structure for queuing all simple blocks bound to a single timecode.
213 * This can happen if multiple tracks are being involved. */
214 struct WebMTimecodeBlocks
215 {
216 WebMTimecodeBlocks(void)
217 : fClusterNeeded(false)
218 , fClusterStarted(false) { }
219
220 /** The actual block queue for this timecode. */
221 WebMSimpleBlockQueue Queue;
222 /** Whether a new cluster is needed for this timecode or not. */
223 bool fClusterNeeded;
224 /** Whether a new cluster already has been started for this timecode or not. */
225 bool fClusterStarted;
226
227 /**
228 * Enqueues a simple block into the internal queue.
229 *
230 * @param a_pBlock Block to enqueue and take ownership of.
231 */
232 void Enqueue(WebMSimpleBlock *a_pBlock)
233 {
234 Queue.push(a_pBlock);
235
236 if (a_pBlock->Data.fFlags & VBOX_WEBM_BLOCK_FLAG_KEY_FRAME)
237 fClusterNeeded = true;
238 }
239 };
240
241 /** A block map containing all currently queued blocks.
242 * The key specifies a unique timecode, whereas the value
243 * is a queue of blocks which all correlate to the key (timecode). */
244 typedef std::map<WebMTimecodeAbs, WebMTimecodeBlocks> WebMBlockMap;
245
246 /**
247 * Structure for defining a WebM (encoding) queue.
248 */
249 struct WebMQueue
250 {
251 WebMQueue(void)
252 : tcAbsLastBlockWrittenMs(0)
253 , tsLastProcessedMs(0) { }
254
255 /** Blocks as FIFO (queue). */
256 WebMBlockMap Map;
257 /** Absolute timecode (in ms) of last written block to queue. */
258 WebMTimecodeAbs tcAbsLastBlockWrittenMs;
259 /** Time stamp (in ms) of when the queue was processed last. */
260 uint64_t tsLastProcessedMs;
261 };
262
263 /**
264 * Structure for keeping a WebM track entry.
265 */
266 struct WebMTrack
267 {
268 WebMTrack(WebMTrackType a_enmType, PRECORDINGCODEC pTheCodec, uint8_t a_uTrack, uint64_t a_offID)
269 : enmType(a_enmType)
270 , pCodec(pTheCodec)
271 , uTrack(a_uTrack)
272 , offUUID(a_offID)
273 , cTotalBlocks(0)
274 , tcAbsLastWrittenMs(0)
275 {
276 uUUID = RTRandU32();
277 }
278
279 /** The type of this track. */
280 WebMTrackType enmType;
281 /** Pointer to codec data to use. */
282 PRECORDINGCODEC pCodec;
283 /** Track parameters. */
284 union
285 {
286 struct
287 {
288 /** Sample rate of input data. */
289 uint32_t uHz;
290 /** Duration of the frame in samples (per channel).
291 * Valid frame size are:
292 *
293 * ms Frame size
294 * 2.5 120
295 * 5 240
296 * 10 480
297 * 20 (Default) 960
298 * 40 1920
299 * 60 2880
300 */
301 uint16_t framesPerBlock;
302 /** How many milliseconds (ms) one written (simple) block represents. */
303 uint16_t msPerBlock;
304 } Audio;
305 };
306 /** This track's track number. Also used as key in track map. */
307 uint8_t uTrack;
308 /** The track's "UUID".
309 * Needed in case this track gets mux'ed with tracks from other files. Not really unique though. */
310 uint32_t uUUID;
311 /** Absolute offset in file of track UUID.
312 * Needed to write the hash sum within the footer. */
313 uint64_t offUUID;
314 /** Total number of blocks. */
315 uint64_t cTotalBlocks;
316 /** Absoute timecode (in ms) of last write. */
317 WebMTimecodeAbs tcAbsLastWrittenMs;
318 };
319
320 /**
321 * Structure for a single cue point track position entry.
322 */
323 struct WebMCueTrackPosEntry
324 {
325 WebMCueTrackPosEntry(uint64_t a_offCluster)
326 : offCluster(a_offCluster) { }
327
328 /** Offset (in bytes) of the related cluster containing the given position. */
329 uint64_t offCluster;
330 };
331
332 /** Map for keeping track position entries for a single cue point.
333 * The key is the track number (*not* UUID!). */
334 typedef std::map<uint8_t, WebMCueTrackPosEntry *> WebMCueTrackPosMap;
335
336 /**
337 * Structure for keeping a cue point.
338 */
339 struct WebMCuePoint
340 {
341 WebMCuePoint(WebMTimecodeAbs a_tcAbs)
342 : tcAbs(a_tcAbs) { }
343
344 virtual ~WebMCuePoint()
345 {
346 Clear();
347 }
348
349 void Clear(void)
350 {
351 WebMCueTrackPosMap::iterator itTrackPos = Pos.begin();
352 while (itTrackPos != Pos.end())
353 {
354 WebMCueTrackPosEntry *pTrackPos = itTrackPos->second;
355 AssertPtr(pTrackPos);
356 delete pTrackPos;
357
358 Pos.erase(itTrackPos);
359 itTrackPos = Pos.begin();
360 }
361
362 Assert(Pos.empty());
363 }
364
365 /** Map containing all track positions for this specific cue point. */
366 WebMCueTrackPosMap Pos;
367 /** Absolute time code according to the segment time base. */
368 WebMTimecodeAbs tcAbs;
369 };
370
371 /** List of cue points. */
372 typedef std::list<WebMCuePoint *> WebMCuePointList;
373
374 /**
375 * Structure for keeping a WebM cluster entry.
376 */
377 struct WebMCluster
378 {
379 WebMCluster(void)
380 : uID(0)
381 , offStart(0)
382 , fOpen(false)
383 , tcAbsStartMs(0)
384 , cBlocks(0) { }
385
386 /** This cluster's ID. */
387 uint64_t uID;
388 /** Absolute offset (in bytes) of this cluster.
389 * Needed for seeking info table. */
390 uint64_t offStart;
391 /** Whether this cluster element is opened currently. */
392 bool fOpen;
393 /** Absolute timecode (in ms) when this cluster starts. */
394 WebMTimecodeAbs tcAbsStartMs;
395 /** Absolute timecode (in ms) of when last written to this cluster. */
396 WebMTimecodeAbs tcAbsLastWrittenMs;
397 /** Number of (simple) blocks in this cluster. */
398 uint64_t cBlocks;
399 };
400
401 /**
402 * Structure for keeping a WebM segment entry.
403 *
404 * Current we're only using one segment.
405 */
406 struct WebMSegment
407 {
408 WebMSegment(void)
409 : tcAbsStartMs(0)
410 , tcAbsLastWrittenMs(0)
411 , offStart(0)
412 , offInfo(0)
413 , offSeekInfo(0)
414 , offTracks(0)
415 , offCues(0)
416 , cClusters(0)
417 {
418 uTimecodeScaleFactor = VBOX_WEBM_TIMECODESCALE_FACTOR_MS;
419
420 LogFunc(("Default timecode scale is: %RU64ns\n", uTimecodeScaleFactor));
421 }
422
423 virtual ~WebMSegment()
424 {
425 uninit();
426 }
427
428 /**
429 * Initializes a segment.
430 *
431 * @returns IPRT status code.
432 */
433 int init(void)
434 {
435 return RTCritSectInit(&CritSect);
436 }
437
438 /**
439 * Uninitializes a segment.
440 */
441 void uninit(void)
442 {
443 clear();
444
445 RTCritSectDelete(&CritSect);
446 }
447
448 /**
449 * Clear the segment's data by removing (and freeing) all data.
450 */
451 void clear(void)
452 {
453 WebMCuePointList::iterator itCuePoint = lstCuePoints.begin();
454 while (itCuePoint != lstCuePoints.end())
455 {
456 WebMCuePoint *pCuePoint = (*itCuePoint);
457 AssertPtr(pCuePoint);
458 delete pCuePoint;
459
460 lstCuePoints.erase(itCuePoint);
461 itCuePoint = lstCuePoints.begin();
462 }
463
464 Assert(lstCuePoints.empty());
465 }
466
467 /** Critical section for serializing access to this segment. */
468 RTCRITSECT CritSect;
469
470 /** The timecode scale factor of this segment. */
471 uint64_t uTimecodeScaleFactor;
472
473 /** Absolute timecode (in ms) when starting this segment. */
474 WebMTimecodeAbs tcAbsStartMs;
475 /** Absolute timecode (in ms) of last write. */
476 WebMTimecodeAbs tcAbsLastWrittenMs;
477
478 /** Absolute offset (in bytes) of CurSeg. */
479 uint64_t offStart;
480 /** Absolute offset (in bytes) of general info. */
481 uint64_t offInfo;
482 /** Absolute offset (in bytes) of seeking info. */
483 uint64_t offSeekInfo;
484 /** Absolute offset (in bytes) of tracks. */
485 uint64_t offTracks;
486 /** Absolute offset (in bytes) of cues table. */
487 uint64_t offCues;
488 /** List of cue points. Needed for seeking table. */
489 WebMCuePointList lstCuePoints;
490
491 /** Total number of clusters. */
492 uint64_t cClusters;
493
494 /** Map of tracks.
495 * The key marks the track number (*not* the UUID!). */
496 std::map <uint8_t, WebMTrack *> mapTracks;
497
498 /** Current cluster which is being handled.
499 *
500 * Note that we don't need (and shouldn't need, as this can be a *lot* of data!) a
501 * list of all clusters. */
502 WebMCluster CurCluster;
503
504 WebMQueue queueBlocks;
505
506 } CurSeg;
507
508 /** Audio codec to use. */
509 RecordingAudioCodec_T m_enmAudioCodec;
510 /** Video codec to use. */
511 RecordingVideoCodec_T m_enmVideoCodec;
512
513 /** Whether we're currently in the tracks section. */
514 bool m_fInTracksSection;
515
516 /** Size of timecodes (in bytes). */
517 size_t m_cbTimecode;
518 /** Maximum value a timecode can have. */
519 uint32_t m_uTimecodeMax;
520
521#ifdef VBOX_WITH_LIBVPX
522 /**
523 * Block data for VP8-encoded video data.
524 */
525 struct BlockData_VP8
526 {
527 const vpx_codec_enc_cfg_t *pCfg;
528 const vpx_codec_cx_pkt_t *pPkt;
529 };
530#endif /* VBOX_WITH_LIBVPX */
531
532 /**
533 * Block data for encoded audio data.
534 */
535 struct BlockData_Audio
536 {
537 /** Pointer to encoded audio data. */
538 const void *pvData;
539 /** Size (in bytes) of encoded audio data. */
540 size_t cbData;
541 /** PTS (in ms) of encoded audio data. */
542 uint64_t uPTSMs;
543 };
544
545public:
546
547 WebMWriter();
548
549 virtual ~WebMWriter();
550
551public:
552
553 int OpenEx(const char *a_pszFilename, PRTFILE a_phFile,
554 RecordingAudioCodec_T a_enmAudioCodec, RecordingVideoCodec_T a_enmVideoCodec);
555
556 int Open(const char *a_pszFilename, uint64_t a_fOpen,
557 RecordingAudioCodec_T a_enmAudioCodec, RecordingVideoCodec_T a_enmVideoCodec);
558
559 int Close(void);
560
561 int AddAudioTrack(PRECORDINGCODEC pCodec, uint16_t uHz, uint8_t cChannels, uint8_t cBits, uint8_t *puTrack);
562
563 int AddVideoTrack(PRECORDINGCODEC pCodec, uint16_t uWidth, uint16_t uHeight, uint32_t uFPS, uint8_t *puTrack);
564
565 int WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData, WebMTimecodeAbs tcAbsPTSMs, WebMBlockFlags uFlags);
566
567 const com::Utf8Str& GetFileName(void);
568
569 uint64_t GetFileSize(void);
570
571 uint64_t GetAvailableSpace(void);
572
573 /**
574 * Returns the number of written WebM clusters.
575 *
576 * @returns Number of written WebM clusters; 0 when no clusters written (empty file).
577 */
578 uint64_t GetClusters(void) const { return CurSeg.cClusters; }
579
580protected:
581
582 int init(RecordingAudioCodec_T a_enmAudioCodec, RecordingVideoCodec_T a_enmVideoCodec);
583
584 void destroy(void);
585
586 int writeHeader(void);
587
588 void writeSeekHeader(void);
589
590 int writeFooter(void);
591
592 int writeSimpleBlockEBML(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock);
593
594 int writeSimpleBlockQueued(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock);
595
596 int processQueue(WebMQueue *pQueue, bool fForce);
597
598protected:
599
600 typedef std::map <uint8_t, WebMTrack *> WebMTracks;
601};
602
603#endif /* !MAIN_INCLUDED_WebMWriter_h */
Note: See TracBrowser for help on using the repository browser.

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