VirtualBox

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

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

Build fix.Build fix.

  • 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.6 KB
Line 
1/* $Id: EbmlWriter.cpp 65259 2017-01-12 11:29:34Z 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.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 <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#ifdef VBOX_WITH_AUDIO_VIDEOREC
386 m_Ebml.subStart(TrackEntry);
387 m_Ebml.serializeUnsignedInteger(TrackNumber, (uint8_t)m_lstTracks.size());
388 /** @todo Implement track's "Language" property? Currently this defaults to English ("eng"). */
389
390 WebMTrack TrackAudio(WebMTrackType_Audio, RTFileTell(m_Ebml.getFile()));
391 m_lstTracks.push_back(TrackAudio);
392
393 /** @todo Resolve codec type. */
394 OpusPrivData opusPrivData;
395
396 m_Ebml.serializeUnsignedInteger(TrackUID, TrackAudio.uID, 4)
397 .serializeUnsignedInteger(TrackType, 2 /* Audio */)
398 .serializeString(CodecID, "A_OPUS")
399 .serializeData(CodecPrivate, &opusPrivData, sizeof(opusPrivData))
400 .subStart(Audio)
401 .serializeFloat(SamplingFrequency, fSamplingHz)
402 .serializeFloat(OutputSamplingFrequency, fOutputHz)
403 .serializeUnsignedInteger(Channels, cChannels)
404 .serializeUnsignedInteger(BitDepth, cBitDepth)
405 .subEnd(Audio)
406 .subEnd(TrackEntry);
407
408 return VINF_SUCCESS;
409#else
410 RT_NOREF(fSamplingHz, fOutputHz, cChannels, cBitDepth);
411 return VERR_NOT_SUPPORTED;
412#endif
413 }
414
415 int AddVideoTrack(uint16_t uWidth, uint16_t uHeight, double dbFPS)
416 {
417 m_Ebml.subStart(TrackEntry);
418 m_Ebml.serializeUnsignedInteger(TrackNumber, (uint8_t)m_lstTracks.size());
419
420 WebMTrack TrackVideo(WebMTrackType_Video, RTFileTell(m_Ebml.getFile()));
421 m_lstTracks.push_back(TrackVideo);
422
423 /** @todo Resolve codec type. */
424
425 m_Ebml.serializeUnsignedInteger(TrackUID, TrackVideo.uID /* UID */, 4)
426 .serializeUnsignedInteger(TrackType, 1 /* Video */)
427 .serializeString(CodecID, "V_VP8")
428 .subStart(Video)
429 .serializeUnsignedInteger(PixelWidth, uWidth)
430 .serializeUnsignedInteger(PixelHeight, uHeight)
431 .serializeFloat(FrameRate, dbFPS)
432 .subEnd(Video)
433 .subEnd(TrackEntry);
434
435 return VINF_SUCCESS;
436 }
437
438 int writeHeader(void)
439 {
440 LogFunc(("Header @ %RU64\n", RTFileTell(m_Ebml.getFile())));
441
442 m_Ebml.subStart(EBML)
443 .serializeUnsignedInteger(EBMLVersion, 1)
444 .serializeUnsignedInteger(EBMLReadVersion, 1)
445 .serializeUnsignedInteger(EBMLMaxIDLength, 4)
446 .serializeUnsignedInteger(EBMLMaxSizeLength, 8)
447 .serializeString(DocType, "webm")
448 .serializeUnsignedInteger(DocTypeVersion, 2)
449 .serializeUnsignedInteger(DocTypeReadVersion, 2)
450 .subEnd(EBML);
451
452 m_Ebml.subStart(Segment);
453
454 /* Save offset of current segment. */
455 m_offSegCurStart = RTFileTell(m_Ebml.getFile());
456
457 writeSeekInfo();
458
459 /* Save offset of upcoming tracks segment. */
460 m_offSegTracksStart = RTFileTell(m_Ebml.getFile());
461
462 /* The tracks segment starts right after this header. */
463 m_Ebml.subStart(Tracks);
464 m_fTracksOpen = true;
465
466 return VINF_SUCCESS;
467 }
468
469 int writeSimpleBlockInternal(uint8_t uTrackID, uint16_t uTimecode, const void *pvData, size_t cbData, uint8_t fFlags)
470 {
471 LogFunc(("SimpleBlock @ %RU64 (T%RU8, TS=%RU16, %zu bytes)\n", RTFileTell(m_Ebml.getFile()), uTrackID, uTimecode, cbData));
472
473 /* Write a "Simple Block". */
474 m_Ebml.writeClassId(SimpleBlock);
475 /* Block size. */
476 m_Ebml.writeUnsignedInteger(0x10000000u | (1 /* Track number. */
477 + 2 /* Timecode .*/
478 + 1 /* Flags. */
479 + cbData /* Actual frame data. */), 4);
480 /* Track number. */
481 m_Ebml.writeSize(uTrackID);
482 /* Timecode (relative to cluster opening timecode). */
483 m_Ebml.writeUnsignedInteger(uTimecode, 2);
484 /* Flags. */
485 m_Ebml.writeUnsignedInteger(fFlags, 1);
486 /* Frame data. */
487 m_Ebml.write(pvData, cbData);
488
489 return VINF_SUCCESS;
490 }
491
492 int writeBlockVP8(const vpx_codec_enc_cfg_t *a_pCfg, const vpx_codec_cx_pkt_t *a_pPkt)
493 {
494 /* Calculate the PTS of this frame in milliseconds. */
495 int64_t tsPtsMs = a_pPkt->data.frame.pts * 1000
496 * (uint64_t) a_pCfg->g_timebase.num / a_pCfg->g_timebase.den;
497
498 if (tsPtsMs <= m_tsLastPtsMs)
499 tsPtsMs = m_tsLastPtsMs + 1;
500
501 m_tsLastPtsMs = tsPtsMs;
502
503 if (m_tsInitialPtsMs < 0)
504 m_tsInitialPtsMs = m_tsLastPtsMs;
505
506 /* Calculate the relative time of this block. */
507 uint16_t tsBlockMs = 0;
508 bool fClusterStart = false;
509
510 if (tsPtsMs - m_tsClusterOpenMs > 65536)
511 fClusterStart = true;
512 else
513 {
514 /* Calculate the block's timecode, which is relative to the Cluster timecode. */
515 tsBlockMs = static_cast<uint16_t>(tsPtsMs - m_tsClusterOpenMs);
516 }
517
518 const bool fKeyframe = RT_BOOL(a_pPkt->data.frame.flags & VPX_FRAME_IS_KEY);
519
520 if ( fClusterStart
521 || fKeyframe)
522 {
523 if (m_fClusterOpen) /* Close current cluster first. */
524 m_Ebml.subEnd(Cluster);
525
526 tsBlockMs = 0;
527
528 /* Open a new cluster. */
529 m_fClusterOpen = true;
530 m_tsClusterOpenMs = (uint32_t)tsPtsMs;
531 m_offSegClusterStart = RTFileTell(m_Ebml.getFile());
532
533 LogFunc(("Cluster @ %RU64\n", m_offSegClusterStart));
534
535 m_Ebml.subStart(Cluster)
536 .serializeUnsignedInteger(Timecode, m_tsClusterOpenMs);
537
538 /* Save a cue point if this is a keyframe. */
539 if (fKeyframe)
540 {
541 WebMCueEntry cue(m_tsClusterOpenMs, m_offSegClusterStart);
542 m_lstCue.push_back(cue);
543 }
544 }
545
546 uint8_t fFlags = 0;
547 if (fKeyframe)
548 fFlags |= 0x80; /* Key frame. */
549 if (a_pPkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE)
550 fFlags |= 0x08; /* Invisible frame. */
551
552 return writeSimpleBlockInternal(0 /** @todo FIX! */, tsBlockMs, a_pPkt->data.frame.buf, a_pPkt->data.frame.sz, fFlags);
553 }
554
555#ifdef VBOX_WITH_AUDIO_VIDEOREC
556 /* Audio blocks that have same absolute timecode as video blocks SHOULD be written before the video blocks. */
557 int writeBlockOpus(const void *pvData, size_t cbData)
558 {
559static uint16_t s_uTimecode = 0;
560
561 return writeSimpleBlockInternal(1 /** @todo FIX! */, s_uTimecode++, pvData, cbData, 0 /* Flags */);
562 }
563#endif
564
565 int WriteBlock(WebMWriter::BlockType blockType, const void *pvData, size_t cbData)
566 {
567 int rc;
568
569 if (m_fTracksOpen)
570 {
571 m_Ebml.subEnd(Tracks);
572 m_fTracksOpen = false;
573 }
574
575 switch (blockType)
576 {
577#ifdef VBOX_WITH_AUDIO_VIDEOREC
578 case WebMWriter::BlockType_Audio:
579 {
580 if (m_enmAudioCodec == WebMWriter::AudioCodec_Opus)
581 {
582 Assert(cbData == sizeof(WebMWriter::BlockData_Opus));
583 WebMWriter::BlockData_Opus *pData = (WebMWriter::BlockData_Opus *)pvData;
584 rc = writeBlockOpus(pData->pvData, pData->cbData);
585 }
586 else
587 rc = VERR_NOT_SUPPORTED;
588 break;
589 }
590#endif
591 case WebMWriter::BlockType_Video:
592 {
593 if (m_enmVideoCodec == WebMWriter::VideoCodec_VP8)
594 {
595 Assert(cbData == sizeof(WebMWriter::BlockData_VP8));
596 WebMWriter::BlockData_VP8 *pData = (WebMWriter::BlockData_VP8 *)pvData;
597 rc = writeBlockVP8(pData->pCfg, pData->pPkt);
598 }
599 else
600 rc = VERR_NOT_SUPPORTED;
601 break;
602 }
603
604 default:
605 rc = VERR_NOT_SUPPORTED;
606 break;
607 }
608
609 return rc;
610 }
611
612 int writeFooter(void)
613 {
614 if (m_fTracksOpen)
615 {
616 m_Ebml.subEnd(Tracks);
617 m_fTracksOpen = false;
618 }
619
620 if (m_fClusterOpen)
621 {
622 m_Ebml.subEnd(Cluster);
623 m_fClusterOpen = false;
624 }
625
626 /*
627 * Write Cues segment.
628 */
629 LogFunc(("Cues @ %RU64\n", RTFileTell(m_Ebml.getFile())));
630
631 m_offSegCuesStart = RTFileTell(m_Ebml.getFile());
632
633 m_Ebml.subStart(Cues);
634
635 for (std::list<WebMCueEntry>::iterator it = m_lstCue.begin(); it != m_lstCue.end(); ++it)
636 {
637 m_Ebml.subStart(CuePoint)
638 .serializeUnsignedInteger(CueTime, it->time)
639 .subStart(CueTrackPositions)
640 .serializeUnsignedInteger(CueTrack, 1)
641 .serializeUnsignedInteger(CueClusterPosition, it->loc - m_offSegCurStart, 8)
642 .subEnd(CueTrackPositions)
643 .subEnd(CuePoint);
644 }
645
646 m_Ebml.subEnd(Cues)
647 .subEnd(Segment);
648
649 /*
650 * Update SeekHead / Info segment.
651 */
652
653 writeSeekInfo();
654
655 return RTFileSeek(m_Ebml.getFile(), 0, RTFILE_SEEK_END, NULL);
656 }
657
658 friend class Ebml;
659 friend class WebMWriter;
660
661private:
662
663 void writeSeekInfo(void)
664 {
665 if (m_offSegSeekInfoStart)
666 RTFileSeek(m_Ebml.getFile(), m_offSegSeekInfoStart, RTFILE_SEEK_BEGIN, NULL);
667 else
668 m_offSegSeekInfoStart = RTFileTell(m_Ebml.getFile());
669
670 LogFunc(("SeekHead @ %RU64\n", m_offSegSeekInfoStart));
671
672 m_Ebml.subStart(SeekHead)
673
674 .subStart(Seek)
675 .serializeUnsignedInteger(SeekID, Tracks)
676 .serializeUnsignedInteger(SeekPosition, m_offSegTracksStart - m_offSegCurStart, 8)
677 .subEnd(Seek)
678
679 .subStart(Seek)
680 .serializeUnsignedInteger(SeekID, Cues)
681 .serializeUnsignedInteger(SeekPosition, m_offSegCuesStart - m_offSegCurStart, 8)
682 .subEnd(Seek)
683
684 .subStart(Seek)
685 .serializeUnsignedInteger(SeekID, Info)
686 .serializeUnsignedInteger(SeekPosition, m_offSegInfoStart - m_offSegCurStart, 8)
687 .subEnd(Seek)
688
689 .subEnd(SeekHead);
690
691 int64_t iFrameTime = (int64_t)1000 * 1 / 25; //m_Framerate.den / m_Framerate.num; /** @todo Fix this! */
692 m_offSegInfoStart = RTFileTell(m_Ebml.getFile());
693
694 LogFunc(("Info @ %RU64\n", m_offSegInfoStart));
695
696 char szVersion[64];
697 RTStrPrintf(szVersion, sizeof(szVersion), "vpxenc%s", vpx_codec_version_str()); /** @todo Make this configurable? */
698
699 m_Ebml.subStart(Info)
700 .serializeUnsignedInteger(TimecodeScale, 1000000)
701 .serializeFloat(Segment_Duration, m_tsLastPtsMs + iFrameTime - m_tsInitialPtsMs)
702 .serializeString(MuxingApp, szVersion)
703 .serializeString(WritingApp, szVersion)
704 .subEnd(Info);
705 }
706};
707
708WebMWriter::WebMWriter(void) : m_pImpl(new WebMWriter_Impl()) {}
709
710WebMWriter::~WebMWriter(void)
711{
712 if (m_pImpl)
713 {
714 Close();
715 delete m_pImpl;
716 }
717}
718
719int WebMWriter::Create(const char *a_pszFilename, uint64_t a_fOpen,
720 WebMWriter::AudioCodec a_enmAudioCodec, WebMWriter::VideoCodec a_enmVideoCodec)
721{
722 try
723 {
724 m_pImpl->m_enmAudioCodec = a_enmAudioCodec;
725 m_pImpl->m_enmVideoCodec = a_enmVideoCodec;
726
727 LogFunc(("Creating '%s'\n", a_pszFilename));
728
729 int rc = m_pImpl->m_Ebml.create(a_pszFilename, a_fOpen);
730 if (RT_SUCCESS(rc))
731 rc = m_pImpl->writeHeader();
732 }
733 catch(int rc)
734 {
735 return rc;
736 }
737 return VINF_SUCCESS;
738}
739
740int WebMWriter::Close(void)
741{
742 if (!m_pImpl->m_Ebml.isOpen())
743 return VINF_SUCCESS;
744
745 int rc = m_pImpl->writeFooter();
746
747 /* Close the file in any case. */
748 m_pImpl->m_Ebml.close();
749
750 return rc;
751}
752
753int WebMWriter::AddAudioTrack(float fSamplingHz, float fOutputHz, uint8_t cChannels, uint8_t cBitDepth)
754{
755 return m_pImpl->AddAudioTrack(fSamplingHz, fOutputHz, cChannels, cBitDepth);
756}
757
758int WebMWriter::AddVideoTrack(uint16_t uWidth, uint16_t uHeight, double dbFPS)
759{
760 return m_pImpl->AddVideoTrack(uWidth, uHeight, dbFPS);
761}
762
763int WebMWriter::WriteBlock(WebMWriter::BlockType blockType, const void *pvData, size_t cbData)
764{
765 int rc;
766
767 try
768 {
769 rc = m_pImpl->WriteBlock(blockType, pvData, cbData);
770 }
771 catch(int rc2)
772 {
773 rc = rc2;
774 }
775 return rc;
776}
777
778uint64_t WebMWriter::GetFileSize(void)
779{
780 return m_pImpl->m_Ebml.getFileSize();
781}
782
783uint64_t WebMWriter::GetAvailableSpace(void)
784{
785 return m_pImpl->m_Ebml.getAvailableSpace();
786}
787
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