VirtualBox

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

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

VideoRec: More infrastructure work on making the WebM writer as much as possible independent from the actual codec(s) being used. It also now has the ability to add tracks, making it more flexible in usage (e.g. if an additional audio track is required). Work in progress.

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