VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/WebMWriter.cpp@ 74781

Last change on this file since 74781 was 74781, checked in by vboxsync, 6 years ago

VideoRec/Main: Fixed a crash in WebMWriter::Close().

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 26.4 KB
Line 
1/* $Id: WebMWriter.cpp 74781 2018-10-12 08:44:00Z vboxsync $ */
2/** @file
3 * WebMWriter.cpp - WebM container handling.
4 */
5
6/*
7 * Copyright (C) 2013-2018 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 * For more information, see:
20 * - https://w3c.github.io/media-source/webm-byte-stream-format.html
21 * - https://www.webmproject.org/docs/container/#muxer-guidelines
22 */
23
24#define LOG_GROUP LOG_GROUP_MAIN_DISPLAY
25#include "LoggingNew.h"
26
27#include <list>
28#include <map>
29#include <queue>
30#include <stack>
31
32#include <math.h> /* For lround.h. */
33
34#include <iprt/asm.h>
35#include <iprt/buildconfig.h>
36#include <iprt/cdefs.h>
37#include <iprt/critsect.h>
38#include <iprt/err.h>
39#include <iprt/file.h>
40#include <iprt/rand.h>
41#include <iprt/string.h>
42
43#include <VBox/log.h>
44#include <VBox/version.h>
45
46#include "WebMWriter.h"
47#include "EBML_MKV.h"
48
49
50WebMWriter::WebMWriter(void)
51{
52 /* Size (in bytes) of time code to write. We use 2 bytes (16 bit) by default. */
53 m_cbTimecode = 2;
54 m_uTimecodeMax = UINT16_MAX;
55
56 m_fInTracksSection = false;
57}
58
59WebMWriter::~WebMWriter(void)
60{
61 Close();
62}
63
64/**
65 * Opens (creates) an output file using an already open file handle.
66 *
67 * @returns VBox status code.
68 * @param a_pszFilename Name of the file the file handle points at.
69 * @param a_phFile Pointer to open file handle to use.
70 * @param a_enmAudioCodec Audio codec to use.
71 * @param a_enmVideoCodec Video codec to use.
72 */
73int WebMWriter::OpenEx(const char *a_pszFilename, PRTFILE a_phFile,
74 WebMWriter::AudioCodec a_enmAudioCodec, WebMWriter::VideoCodec a_enmVideoCodec)
75{
76 try
77 {
78 m_enmAudioCodec = a_enmAudioCodec;
79 m_enmVideoCodec = a_enmVideoCodec;
80
81 LogFunc(("Creating '%s'\n", a_pszFilename));
82
83 int rc = createEx(a_pszFilename, a_phFile);
84 if (RT_SUCCESS(rc))
85 {
86 rc = init();
87 if (RT_SUCCESS(rc))
88 rc = writeHeader();
89 }
90 }
91 catch(int rc)
92 {
93 return rc;
94 }
95 return VINF_SUCCESS;
96}
97
98/**
99 * Opens an output file.
100 *
101 * @returns VBox status code.
102 * @param a_pszFilename Name of the file to create.
103 * @param a_fOpen File open mode of type RTFILE_O_.
104 * @param a_enmAudioCodec Audio codec to use.
105 * @param a_enmVideoCodec Video codec to use.
106 */
107int WebMWriter::Open(const char *a_pszFilename, uint64_t a_fOpen,
108 WebMWriter::AudioCodec a_enmAudioCodec, WebMWriter::VideoCodec a_enmVideoCodec)
109{
110 try
111 {
112 m_enmAudioCodec = a_enmAudioCodec;
113 m_enmVideoCodec = a_enmVideoCodec;
114
115 LogFunc(("Creating '%s'\n", a_pszFilename));
116
117 int rc = create(a_pszFilename, a_fOpen);
118 if (RT_SUCCESS(rc))
119 {
120 rc = init();
121 if (RT_SUCCESS(rc))
122 rc = writeHeader();
123 }
124 }
125 catch(int rc)
126 {
127 return rc;
128 }
129 return VINF_SUCCESS;
130}
131
132/**
133 * Closes the WebM file and drains all queues.
134 *
135 * @returns IPRT status code.
136 */
137int WebMWriter::Close(void)
138{
139 LogFlowFuncEnter();
140
141 if (!isOpen())
142 return VINF_SUCCESS;
143
144 /* Make sure to drain all queues. */
145 processQueues(&CurSeg.queueBlocks, true /* fForce */);
146
147 writeFooter();
148
149 WebMTracks::iterator itTrack = CurSeg.mapTracks.begin();
150 while (itTrack != CurSeg.mapTracks.end())
151 {
152 WebMTrack *pTrack = itTrack->second;
153 if (pTrack) /* Paranoia. */
154 delete pTrack;
155
156 CurSeg.mapTracks.erase(itTrack);
157
158 itTrack = CurSeg.mapTracks.begin();
159 }
160
161 Assert(CurSeg.queueBlocks.Map.size() == 0);
162 Assert(CurSeg.mapTracks.size() == 0);
163
164 close();
165
166 return VINF_SUCCESS;
167}
168
169/**
170 * Adds an audio track.
171 *
172 * @returns IPRT status code.
173 * @param uHz Input sampling rate.
174 * Must be supported by the selected audio codec.
175 * @param cChannels Number of input audio channels.
176 * @param cBits Number of input bits per channel.
177 * @param puTrack Track number on successful creation. Optional.
178 */
179int WebMWriter::AddAudioTrack(uint16_t uHz, uint8_t cChannels, uint8_t cBits, uint8_t *puTrack)
180{
181#ifdef VBOX_WITH_LIBOPUS
182 int rc;
183
184 /*
185 * Check if the requested codec rate is supported.
186 *
187 * Only the following values are supported by an Opus standard build
188 * -- every other rate only is supported by a custom build.
189 */
190 switch (uHz)
191 {
192 case 48000:
193 case 24000:
194 case 16000:
195 case 12000:
196 case 8000:
197 rc = VINF_SUCCESS;
198 break;
199
200 default:
201 rc = VERR_NOT_SUPPORTED;
202 break;
203 }
204
205 if (RT_FAILURE(rc))
206 return rc;
207
208 /* Some players (e.g. Firefox with Nestegg) rely on track numbers starting at 1.
209 * Using a track number 0 will show those files as being corrupted. */
210 const uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size() + 1;
211
212 subStart(MkvElem_TrackEntry);
213
214 serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack);
215 serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */);
216 serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0);
217
218 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Audio, uTrack, RTFileTell(getFile()));
219
220 pTrack->Audio.uHz = uHz;
221 pTrack->Audio.msPerBlock = 20; /** Opus uses 20ms by default. Make this configurable? */
222 pTrack->Audio.framesPerBlock = uHz / (1000 /* s in ms */ / pTrack->Audio.msPerBlock);
223
224 WEBMOPUSPRIVDATA opusPrivData(uHz, cChannels);
225
226 LogFunc(("Opus @ %RU16Hz (%RU16ms + %RU16 frames per block)\n",
227 pTrack->Audio.uHz, pTrack->Audio.msPerBlock, pTrack->Audio.framesPerBlock));
228
229 serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID, 4)
230 .serializeUnsignedInteger(MkvElem_TrackType, 2 /* Audio */)
231 .serializeString(MkvElem_CodecID, "A_OPUS")
232 .serializeData(MkvElem_CodecPrivate, &opusPrivData, sizeof(opusPrivData))
233 .serializeUnsignedInteger(MkvElem_CodecDelay, 0)
234 .serializeUnsignedInteger(MkvElem_SeekPreRoll, 80 * 1000000) /* 80ms in ns. */
235 .subStart(MkvElem_Audio)
236 .serializeFloat(MkvElem_SamplingFrequency, (float)uHz)
237 .serializeUnsignedInteger(MkvElem_Channels, cChannels)
238 .serializeUnsignedInteger(MkvElem_BitDepth, cBits)
239 .subEnd(MkvElem_Audio)
240 .subEnd(MkvElem_TrackEntry);
241
242 CurSeg.mapTracks[uTrack] = pTrack;
243
244 if (puTrack)
245 *puTrack = uTrack;
246
247 return VINF_SUCCESS;
248#else
249 RT_NOREF(uHz, cChannels, cBits, puTrack);
250 return VERR_NOT_SUPPORTED;
251#endif
252}
253
254/**
255 * Adds a video track.
256 *
257 * @returns IPRT status code.
258 * @param uWidth Width (in pixels) of the video track.
259 * @param uHeight Height (in pixels) of the video track.
260 * @param uFPS FPS (Frames Per Second) of the video track.
261 * @param puTrack Track number of the added video track on success. Optional.
262 */
263int WebMWriter::AddVideoTrack(uint16_t uWidth, uint16_t uHeight, uint32_t uFPS, uint8_t *puTrack)
264{
265#ifdef VBOX_WITH_LIBVPX
266 /* Some players (e.g. Firefox with Nestegg) rely on track numbers starting at 1.
267 * Using a track number 0 will show those files as being corrupted. */
268 const uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size() + 1;
269
270 subStart(MkvElem_TrackEntry);
271
272 serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack);
273 serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */);
274 serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0);
275
276 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Video, uTrack, RTFileTell(getFile()));
277
278 /** @todo Resolve codec type. */
279 serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID /* UID */, 4)
280 .serializeUnsignedInteger(MkvElem_TrackType, 1 /* Video */)
281 .serializeString(MkvElem_CodecID, "V_VP8")
282 .subStart(MkvElem_Video)
283 .serializeUnsignedInteger(MkvElem_PixelWidth, uWidth)
284 .serializeUnsignedInteger(MkvElem_PixelHeight, uHeight)
285 /* Some players rely on the FPS rate for timing calculations.
286 * So make sure to *always* include that. */
287 .serializeFloat (MkvElem_FrameRate, (float)uFPS)
288 .subEnd(MkvElem_Video);
289
290 subEnd(MkvElem_TrackEntry);
291
292 CurSeg.mapTracks[uTrack] = pTrack;
293
294 if (puTrack)
295 *puTrack = uTrack;
296
297 return VINF_SUCCESS;
298#else
299 RT_NOREF(uWidth, uHeight, dbFPS, puTrack);
300 return VERR_NOT_SUPPORTED;
301#endif
302}
303
304/**
305 * Gets file name.
306 *
307 * @returns File name as UTF-8 string.
308 */
309const Utf8Str& WebMWriter::GetFileName(void)
310{
311 return getFileName();
312}
313
314/**
315 * Gets current output file size.
316 *
317 * @returns File size in bytes.
318 */
319uint64_t WebMWriter::GetFileSize(void)
320{
321 return getFileSize();
322}
323
324/**
325 * Gets current free storage space available for the file.
326 *
327 * @returns Available storage free space.
328 */
329uint64_t WebMWriter::GetAvailableSpace(void)
330{
331 return getAvailableSpace();
332}
333
334/**
335 * Takes care of the initialization of the instance.
336 *
337 * @returns IPRT status code.
338 */
339int WebMWriter::init(void)
340{
341 return CurSeg.init();
342}
343
344/**
345 * Takes care of the destruction of the instance.
346 */
347void WebMWriter::destroy(void)
348{
349 CurSeg.destroy();
350}
351
352/**
353 * Writes the WebM file header.
354 *
355 * @returns IPRT status code.
356 */
357int WebMWriter::writeHeader(void)
358{
359 LogFunc(("Header @ %RU64\n", RTFileTell(getFile())));
360
361 subStart(MkvElem_EBML)
362 .serializeUnsignedInteger(MkvElem_EBMLVersion, 1)
363 .serializeUnsignedInteger(MkvElem_EBMLReadVersion, 1)
364 .serializeUnsignedInteger(MkvElem_EBMLMaxIDLength, 4)
365 .serializeUnsignedInteger(MkvElem_EBMLMaxSizeLength, 8)
366 .serializeString(MkvElem_DocType, "webm")
367 .serializeUnsignedInteger(MkvElem_DocTypeVersion, 2)
368 .serializeUnsignedInteger(MkvElem_DocTypeReadVersion, 2)
369 .subEnd(MkvElem_EBML);
370
371 subStart(MkvElem_Segment);
372
373 /* Save offset of current segment. */
374 CurSeg.offStart = RTFileTell(getFile());
375
376 writeSeekHeader();
377
378 /* Save offset of upcoming tracks segment. */
379 CurSeg.offTracks = RTFileTell(getFile());
380
381 /* The tracks segment starts right after this header. */
382 subStart(MkvElem_Tracks);
383 m_fInTracksSection = true;
384
385 return VINF_SUCCESS;
386}
387
388/**
389 * Writes a simple block into the EBML structure.
390 *
391 * @returns IPRT status code.
392 * @param a_pTrack Track the simple block is assigned to.
393 * @param a_pBlock Simple block to write.
394 */
395int WebMWriter::writeSimpleBlockEBML(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock)
396{
397#ifdef LOG_ENABLED
398 WebMCluster &Cluster = CurSeg.CurCluster;
399
400 Log3Func(("[T%RU8C%RU64] Off=%RU64, AbsPTSMs=%RU64, RelToClusterMs=%RU16, %zu bytes\n",
401 a_pTrack->uTrack, Cluster.uID, RTFileTell(getFile()),
402 a_pBlock->Data.tcAbsPTSMs, a_pBlock->Data.tcRelToClusterMs, a_pBlock->Data.cb));
403#endif
404 /*
405 * Write a "Simple Block".
406 */
407 writeClassId(MkvElem_SimpleBlock);
408 /* Block size. */
409 writeUnsignedInteger(0x10000000u | ( 1 /* Track number size. */
410 + m_cbTimecode /* Timecode size .*/
411 + 1 /* Flags size. */
412 + a_pBlock->Data.cb /* Actual frame data size. */), 4);
413 /* Track number. */
414 writeSize(a_pTrack->uTrack);
415 /* Timecode (relative to cluster opening timecode). */
416 writeUnsignedInteger(a_pBlock->Data.tcRelToClusterMs, m_cbTimecode);
417 /* Flags. */
418 writeUnsignedInteger(a_pBlock->Data.fFlags, 1);
419 /* Frame data. */
420 write(a_pBlock->Data.pv, a_pBlock->Data.cb);
421
422 return VINF_SUCCESS;
423}
424
425/**
426 * Writes a simple block and enqueues it into the segment's render queue.
427 *
428 * @returns IPRT status code.
429 * @param a_pTrack Track the simple block is assigned to.
430 * @param a_pBlock Simple block to write and enqueue.
431 */
432int WebMWriter::writeSimpleBlockQueued(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock)
433{
434 RT_NOREF(a_pTrack);
435
436 int rc = VINF_SUCCESS;
437
438 try
439 {
440 const WebMTimecodeAbs tcAbsPTS = a_pBlock->Data.tcAbsPTSMs;
441
442 /* See if we already have an entry for the specified timecode in our queue. */
443 WebMBlockMap::iterator itQueue = CurSeg.queueBlocks.Map.find(tcAbsPTS);
444 if (itQueue != CurSeg.queueBlocks.Map.end()) /* Use existing queue. */
445 {
446 WebMTimecodeBlocks &Blocks = itQueue->second;
447 Blocks.Enqueue(a_pBlock);
448 }
449 else /* Create a new timecode entry. */
450 {
451 WebMTimecodeBlocks Blocks;
452 Blocks.Enqueue(a_pBlock);
453
454 CurSeg.queueBlocks.Map[tcAbsPTS] = Blocks;
455 }
456
457 processQueues(&CurSeg.queueBlocks, false /* fForce */);
458 }
459 catch(...)
460 {
461 delete a_pBlock;
462 a_pBlock = NULL;
463
464 rc = VERR_NO_MEMORY;
465 }
466
467 return rc;
468}
469
470#ifdef VBOX_WITH_LIBVPX
471/**
472 * Writes VPX (VP8 video) simple data block.
473 *
474 * @returns IPRT status code.
475 * @param a_pTrack Track ID to write data to.
476 * @param a_pCfg VPX encoder configuration to use.
477 * @param a_pPkt VPX packet video data packet to write.
478 */
479int WebMWriter::writeSimpleBlockVP8(WebMTrack *a_pTrack, const vpx_codec_enc_cfg_t *a_pCfg, const vpx_codec_cx_pkt_t *a_pPkt)
480{
481 RT_NOREF(a_pTrack);
482
483 /* Calculate the absolute PTS of this frame (in ms). */
484 WebMTimecodeAbs tcAbsPTSMs = a_pPkt->data.frame.pts * 1000
485 * (uint64_t) a_pCfg->g_timebase.num / a_pCfg->g_timebase.den;
486 if ( tcAbsPTSMs
487 && tcAbsPTSMs <= a_pTrack->tcAbsLastWrittenMs)
488 {
489 tcAbsPTSMs = a_pTrack->tcAbsLastWrittenMs + 1;
490 }
491
492 const bool fKeyframe = RT_BOOL(a_pPkt->data.frame.flags & VPX_FRAME_IS_KEY);
493
494 uint8_t fFlags = VBOX_WEBM_BLOCK_FLAG_NONE;
495 if (fKeyframe)
496 fFlags |= VBOX_WEBM_BLOCK_FLAG_KEY_FRAME;
497 if (a_pPkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE)
498 fFlags |= VBOX_WEBM_BLOCK_FLAG_INVISIBLE;
499
500 return writeSimpleBlockQueued(a_pTrack,
501 new WebMSimpleBlock(a_pTrack,
502 tcAbsPTSMs, a_pPkt->data.frame.buf, a_pPkt->data.frame.sz, fFlags));
503}
504#endif /* VBOX_WITH_LIBVPX */
505
506#ifdef VBOX_WITH_LIBOPUS
507/**
508 * Writes an Opus (audio) simple data block.
509 *
510 * @returns IPRT status code.
511 * @param a_pTrack Track ID to write data to.
512 * @param pvData Pointer to simple data block to write.
513 * @param cbData Size (in bytes) of simple data block to write.
514 * @param tcAbsPTSMs Absolute PTS of simple data block.
515 *
516 * @remarks Audio blocks that have same absolute timecode as video blocks SHOULD be written before the video blocks.
517 */
518int WebMWriter::writeSimpleBlockOpus(WebMTrack *a_pTrack, const void *pvData, size_t cbData, WebMTimecodeAbs tcAbsPTSMs)
519{
520 AssertPtrReturn(a_pTrack, VERR_INVALID_POINTER);
521 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
522 AssertReturn(cbData, VERR_INVALID_PARAMETER);
523
524 /* Every Opus frame is a key frame. */
525 const uint8_t fFlags = VBOX_WEBM_BLOCK_FLAG_KEY_FRAME;
526
527 return writeSimpleBlockQueued(a_pTrack,
528 new WebMSimpleBlock(a_pTrack, tcAbsPTSMs, pvData, cbData, fFlags));
529}
530#endif /* VBOX_WITH_LIBOPUS */
531
532/**
533 * Writes a data block to the specified track.
534 *
535 * @returns IPRT status code.
536 * @param uTrack Track ID to write data to.
537 * @param pvData Pointer to data block to write.
538 * @param cbData Size (in bytes) of data block to write.
539 */
540int WebMWriter::WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData)
541{
542 RT_NOREF(cbData); /* Only needed for assertions for now. */
543
544 int rc = RTCritSectEnter(&CurSeg.CritSect);
545 AssertRC(rc);
546
547 WebMTracks::iterator itTrack = CurSeg.mapTracks.find(uTrack);
548 if (itTrack == CurSeg.mapTracks.end())
549 {
550 RTCritSectLeave(&CurSeg.CritSect);
551 return VERR_NOT_FOUND;
552 }
553
554 WebMTrack *pTrack = itTrack->second;
555 AssertPtr(pTrack);
556
557 if (m_fInTracksSection)
558 {
559 subEnd(MkvElem_Tracks);
560 m_fInTracksSection = false;
561 }
562
563 switch (pTrack->enmType)
564 {
565
566 case WebMTrackType_Audio:
567 {
568#ifdef VBOX_WITH_LIBOPUS
569 if (m_enmAudioCodec == WebMWriter::AudioCodec_Opus)
570 {
571 Assert(cbData == sizeof(WebMWriter::BlockData_Opus));
572 WebMWriter::BlockData_Opus *pData = (WebMWriter::BlockData_Opus *)pvData;
573 rc = writeSimpleBlockOpus(pTrack, pData->pvData, pData->cbData, pData->uPTSMs);
574 }
575 else
576#endif /* VBOX_WITH_LIBOPUS */
577 rc = VERR_NOT_SUPPORTED;
578 break;
579 }
580
581 case WebMTrackType_Video:
582 {
583#ifdef VBOX_WITH_LIBVPX
584 if (m_enmVideoCodec == WebMWriter::VideoCodec_VP8)
585 {
586 Assert(cbData == sizeof(WebMWriter::BlockData_VP8));
587 WebMWriter::BlockData_VP8 *pData = (WebMWriter::BlockData_VP8 *)pvData;
588 rc = writeSimpleBlockVP8(pTrack, pData->pCfg, pData->pPkt);
589 }
590 else
591#endif /* VBOX_WITH_LIBVPX */
592 rc = VERR_NOT_SUPPORTED;
593 break;
594 }
595
596 default:
597 rc = VERR_NOT_SUPPORTED;
598 break;
599 }
600
601 int rc2 = RTCritSectLeave(&CurSeg.CritSect);
602 AssertRC(rc2);
603
604 return rc;
605}
606
607/**
608 * Processes a render queue.
609 *
610 * @returns IPRT status code.
611 * @param pQueue Queue to process.
612 * @param fForce Whether forcing to process the render queue or not.
613 * Needed to drain the queues when terminating.
614 */
615int WebMWriter::processQueues(WebMQueue *pQueue, bool fForce)
616{
617 if (pQueue->tsLastProcessedMs == 0)
618 pQueue->tsLastProcessedMs = RTTimeMilliTS();
619
620 if (!fForce)
621 {
622 /* Only process when we reached a certain threshold. */
623 if (RTTimeMilliTS() - pQueue->tsLastProcessedMs < 5000 /* ms */ /** @todo Make this configurable */)
624 return VINF_SUCCESS;
625 }
626
627 WebMCluster &Cluster = CurSeg.CurCluster;
628
629 /* Iterate through the block map. */
630 WebMBlockMap::iterator it = pQueue->Map.begin();
631 while (it != CurSeg.queueBlocks.Map.end())
632 {
633 WebMTimecodeAbs mapAbsPTSMs = it->first;
634 WebMTimecodeBlocks mapBlocks = it->second;
635
636 /* Whether to start a new cluster or not. */
637 bool fClusterStart = false;
638
639 /* No blocks written yet? Start a new cluster. */
640 if (Cluster.cBlocks == 0)
641 fClusterStart = true;
642
643 /* Did we reach the maximum a cluster can hold? Use a new cluster then. */
644 if (mapAbsPTSMs - Cluster.tcAbsStartMs > VBOX_WEBM_CLUSTER_MAX_LEN_MS)
645 fClusterStart = true;
646
647 /* If the block map indicates that a cluster is needed for this timecode, create one. */
648 if (mapBlocks.fClusterNeeded)
649 fClusterStart = true;
650
651 if ( fClusterStart
652 && !mapBlocks.fClusterStarted)
653 {
654 if (Cluster.fOpen) /* Close current cluster first. */
655 {
656 /* Make sure that the current cluster contained some data. */
657 Assert(Cluster.offStart);
658 Assert(Cluster.cBlocks);
659
660 subEnd(MkvElem_Cluster);
661 Cluster.fOpen = false;
662 }
663
664 Cluster.fOpen = true;
665 Cluster.uID = CurSeg.cClusters;
666 Cluster.tcAbsStartMs = mapAbsPTSMs;
667 Cluster.offStart = RTFileTell(getFile());
668 Cluster.cBlocks = 0;
669
670 Log2Func(("[C%RU64] Start @ %RU64ms (map TC is %RU64) / %RU64 bytes\n",
671 Cluster.uID, Cluster.tcAbsStartMs, mapAbsPTSMs, Cluster.offStart));
672
673 if (CurSeg.cClusters)
674 AssertMsg(Cluster.tcAbsStartMs, ("[C%RU64] @ %RU64 starting timecode is 0 which is invalid\n",
675 Cluster.uID, Cluster.offStart));
676
677 WebMTracks::iterator itTrack = CurSeg.mapTracks.begin();
678 while (itTrack != CurSeg.mapTracks.end())
679 {
680 /* Insert cue points for all tracks if a new cluster has been started. */
681 WebMCuePoint cue(itTrack->second /* pTrack */, Cluster.offStart, Cluster.tcAbsStartMs);
682 CurSeg.lstCues.push_back(cue);
683
684 ++itTrack;
685 }
686
687 subStart(MkvElem_Cluster)
688 .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tcAbsStartMs);
689
690 CurSeg.cClusters++;
691
692 mapBlocks.fClusterStarted = true;
693 }
694
695 /* Iterate through all blocks related to the current timecode. */
696 while (!mapBlocks.Queue.empty())
697 {
698 WebMSimpleBlock *pBlock = mapBlocks.Queue.front();
699 AssertPtr(pBlock);
700
701 WebMTrack *pTrack = pBlock->pTrack;
702 AssertPtr(pTrack);
703
704 /* Calculate the block's relative time code to the current cluster's starting time code. */
705 Assert(pBlock->Data.tcAbsPTSMs >= Cluster.tcAbsStartMs);
706 pBlock->Data.tcRelToClusterMs = pBlock->Data.tcAbsPTSMs - Cluster.tcAbsStartMs;
707
708 int rc2 = writeSimpleBlockEBML(pTrack, pBlock);
709 AssertRC(rc2);
710
711 Cluster.cBlocks++;
712
713 pTrack->cTotalBlocks++;
714 pTrack->tcAbsLastWrittenMs = pBlock->Data.tcAbsPTSMs;
715
716 if (CurSeg.tcAbsLastWrittenMs < pTrack->tcAbsLastWrittenMs)
717 CurSeg.tcAbsLastWrittenMs = pTrack->tcAbsLastWrittenMs;
718
719 /* Save a cue point if this is a keyframe (if no new cluster has been started,
720 * as this implies that a cue point already is present. */
721 if ( !fClusterStart
722 && (pBlock->Data.fFlags & VBOX_WEBM_BLOCK_FLAG_KEY_FRAME))
723 {
724 WebMCuePoint cue(pBlock->pTrack, Cluster.offStart, Cluster.tcAbsStartMs);
725 CurSeg.lstCues.push_back(cue);
726 }
727
728 delete pBlock;
729 pBlock = NULL;
730
731 mapBlocks.Queue.pop();
732 }
733
734 Assert(mapBlocks.Queue.empty());
735
736 CurSeg.queueBlocks.Map.erase(it);
737
738 it = CurSeg.queueBlocks.Map.begin();
739 }
740
741 Assert(CurSeg.queueBlocks.Map.empty());
742
743 pQueue->tsLastProcessedMs = RTTimeMilliTS();
744
745 return VINF_SUCCESS;
746}
747
748/**
749 * Writes the WebM footer.
750 *
751 * @returns IPRT status code.
752 */
753int WebMWriter::writeFooter(void)
754{
755 AssertReturn(isOpen(), VERR_WRONG_ORDER);
756
757 if (m_fInTracksSection)
758 {
759 subEnd(MkvElem_Tracks);
760 m_fInTracksSection = false;
761 }
762
763 if (CurSeg.CurCluster.fOpen)
764 {
765 subEnd(MkvElem_Cluster);
766 CurSeg.CurCluster.fOpen = false;
767 }
768
769 /*
770 * Write Cues element.
771 */
772 CurSeg.offCues = RTFileTell(getFile());
773 LogFunc(("Cues @ %RU64\n", CurSeg.offCues));
774
775 subStart(MkvElem_Cues);
776
777 std::list<WebMCuePoint>::iterator itCuePoint = CurSeg.lstCues.begin();
778 while (itCuePoint != CurSeg.lstCues.end())
779 {
780 /* Sanity. */
781 AssertPtr(itCuePoint->pTrack);
782
783 LogFunc(("CuePoint @ %RU64: Track #%RU8 (Cluster @ %RU64, tcAbs=%RU64)\n",
784 RTFileTell(getFile()), itCuePoint->pTrack->uTrack,
785 itCuePoint->offCluster, itCuePoint->tcAbs));
786
787 subStart(MkvElem_CuePoint)
788 .serializeUnsignedInteger(MkvElem_CueTime, itCuePoint->tcAbs)
789 .subStart(MkvElem_CueTrackPositions)
790 .serializeUnsignedInteger(MkvElem_CueTrack, itCuePoint->pTrack->uTrack)
791 .serializeUnsignedInteger(MkvElem_CueClusterPosition, itCuePoint->offCluster - CurSeg.offStart, 8)
792 .subEnd(MkvElem_CueTrackPositions)
793 .subEnd(MkvElem_CuePoint);
794
795 itCuePoint++;
796 }
797
798 subEnd(MkvElem_Cues);
799 subEnd(MkvElem_Segment);
800
801 /*
802 * Re-Update seek header with final information.
803 */
804
805 writeSeekHeader();
806
807 return RTFileSeek(getFile(), 0, RTFILE_SEEK_END, NULL);
808}
809
810/**
811 * Writes the segment's seek header.
812 */
813void WebMWriter::writeSeekHeader(void)
814{
815 if (CurSeg.offSeekInfo)
816 RTFileSeek(getFile(), CurSeg.offSeekInfo, RTFILE_SEEK_BEGIN, NULL);
817 else
818 CurSeg.offSeekInfo = RTFileTell(getFile());
819
820 LogFunc(("Seek Headeder @ %RU64\n", CurSeg.offSeekInfo));
821
822 subStart(MkvElem_SeekHead);
823
824 subStart(MkvElem_Seek)
825 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Tracks)
826 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offTracks - CurSeg.offStart, 8)
827 .subEnd(MkvElem_Seek);
828
829 if (CurSeg.offCues)
830 LogFunc(("Updating Cues @ %RU64\n", CurSeg.offCues));
831
832 subStart(MkvElem_Seek)
833 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Cues)
834 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offCues - CurSeg.offStart, 8)
835 .subEnd(MkvElem_Seek);
836
837 subStart(MkvElem_Seek)
838 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Info)
839 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offInfo - CurSeg.offStart, 8)
840 .subEnd(MkvElem_Seek);
841
842 subEnd(MkvElem_SeekHead);
843
844 /*
845 * Write the segment's info element.
846 */
847
848 /* Save offset of the segment's info element. */
849 CurSeg.offInfo = RTFileTell(getFile());
850
851 LogFunc(("Info @ %RU64\n", CurSeg.offInfo));
852
853 char szMux[64];
854 RTStrPrintf(szMux, sizeof(szMux),
855#ifdef VBOX_WITH_LIBVPX
856 "vpxenc%s", vpx_codec_version_str());
857#else
858 "unknown");
859#endif
860 char szApp[64];
861 RTStrPrintf(szApp, sizeof(szApp), VBOX_PRODUCT " %sr%u", VBOX_VERSION_STRING, RTBldCfgRevision());
862
863 const WebMTimecodeAbs tcAbsDurationMs = CurSeg.tcAbsLastWrittenMs - CurSeg.tcAbsStartMs;
864
865 if (!CurSeg.lstCues.empty())
866 {
867 LogFunc(("tcAbsDurationMs=%RU64\n", tcAbsDurationMs));
868 AssertMsg(tcAbsDurationMs, ("Segment seems to be empty (duration is 0)\n"));
869 }
870
871 subStart(MkvElem_Info)
872 .serializeUnsignedInteger(MkvElem_TimecodeScale, CurSeg.uTimecodeScaleFactor)
873 .serializeFloat(MkvElem_Segment_Duration, tcAbsDurationMs)
874 .serializeString(MkvElem_MuxingApp, szMux)
875 .serializeString(MkvElem_WritingApp, szApp)
876 .subEnd(MkvElem_Info);
877}
878
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use