VirtualBox

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

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

VideoRec/WebMWriter: Use the block map's currently processed TC (timecode) as the new cluster's starting TC.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 29.1 KB
Line 
1/* $Id: WebMWriter.cpp 75076 2018-10-25 14:42:55Z 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 processQueue(&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 Utf8Str strFileName = getFileName().c_str();
165
166 close();
167
168 int rc = VINF_SUCCESS;
169
170 /* If no clusters (= data) was written, delete the file again. */
171 if (!CurSeg.cClusters)
172 rc = RTFileDelete(strFileName.c_str());
173
174 LogFlowFuncLeaveRC(rc);
175 return rc;
176}
177
178/**
179 * Adds an audio track.
180 *
181 * @returns IPRT status code.
182 * @param uHz Input sampling rate.
183 * Must be supported by the selected audio codec.
184 * @param cChannels Number of input audio channels.
185 * @param cBits Number of input bits per channel.
186 * @param puTrack Track number on successful creation. Optional.
187 */
188int WebMWriter::AddAudioTrack(uint16_t uHz, uint8_t cChannels, uint8_t cBits, uint8_t *puTrack)
189{
190#ifdef VBOX_WITH_LIBOPUS
191 AssertReturn(uHz, VERR_INVALID_PARAMETER);
192 AssertReturn(cBits, VERR_INVALID_PARAMETER);
193 AssertReturn(cChannels, VERR_INVALID_PARAMETER);
194
195 /*
196 * Adjust the handed-in Hz rate to values which are supported by the Opus codec.
197 *
198 * Only the following values are supported by an Opus standard build
199 * -- every other rate only is supported by a custom build.
200 *
201 * See opus_encoder_create() for more information.
202 */
203 if (uHz > 24000) uHz = 48000;
204 else if (uHz > 16000) uHz = 24000;
205 else if (uHz > 12000) uHz = 16000;
206 else if (uHz > 8000 ) uHz = 12000;
207 else uHz = 8000;
208
209 /* Some players (e.g. Firefox with Nestegg) rely on track numbers starting at 1.
210 * Using a track number 0 will show those files as being corrupted. */
211 const uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size() + 1;
212
213 subStart(MkvElem_TrackEntry);
214
215 serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack);
216 serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */);
217 serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0);
218
219 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Audio, uTrack, RTFileTell(getFile()));
220
221 pTrack->Audio.uHz = uHz;
222 pTrack->Audio.msPerBlock = 20; /** Opus uses 20ms by default. Make this configurable? */
223 pTrack->Audio.framesPerBlock = uHz / (1000 /* s in ms */ / pTrack->Audio.msPerBlock);
224
225 WEBMOPUSPRIVDATA opusPrivData(uHz, cChannels);
226
227 LogFunc(("Opus @ %RU16Hz (%RU16ms + %RU16 frames per block)\n",
228 pTrack->Audio.uHz, pTrack->Audio.msPerBlock, pTrack->Audio.framesPerBlock));
229
230 serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID, 4)
231 .serializeUnsignedInteger(MkvElem_TrackType, 2 /* Audio */)
232 .serializeString(MkvElem_CodecID, "A_OPUS")
233 .serializeData(MkvElem_CodecPrivate, &opusPrivData, sizeof(opusPrivData))
234 .serializeUnsignedInteger(MkvElem_CodecDelay, 0)
235 .serializeUnsignedInteger(MkvElem_SeekPreRoll, 80 * 1000000) /* 80ms in ns. */
236 .subStart(MkvElem_Audio)
237 .serializeFloat(MkvElem_SamplingFrequency, (float)uHz)
238 .serializeUnsignedInteger(MkvElem_Channels, cChannels)
239 .serializeUnsignedInteger(MkvElem_BitDepth, cBits)
240 .subEnd(MkvElem_Audio)
241 .subEnd(MkvElem_TrackEntry);
242
243 CurSeg.mapTracks[uTrack] = pTrack;
244
245 if (puTrack)
246 *puTrack = uTrack;
247
248 return VINF_SUCCESS;
249#else
250 RT_NOREF(uHz, cChannels, cBits, puTrack);
251 return VERR_NOT_SUPPORTED;
252#endif
253}
254
255/**
256 * Adds a video track.
257 *
258 * @returns IPRT status code.
259 * @param uWidth Width (in pixels) of the video track.
260 * @param uHeight Height (in pixels) of the video track.
261 * @param uFPS FPS (Frames Per Second) of the video track.
262 * @param puTrack Track number of the added video track on success. Optional.
263 */
264int WebMWriter::AddVideoTrack(uint16_t uWidth, uint16_t uHeight, uint32_t uFPS, uint8_t *puTrack)
265{
266#ifdef VBOX_WITH_LIBVPX
267 /* Some players (e.g. Firefox with Nestegg) rely on track numbers starting at 1.
268 * Using a track number 0 will show those files as being corrupted. */
269 const uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size() + 1;
270
271 subStart(MkvElem_TrackEntry);
272
273 serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack);
274 serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */);
275 serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0);
276
277 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Video, uTrack, RTFileTell(getFile()));
278
279 /** @todo Resolve codec type. */
280 serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID /* UID */, 4)
281 .serializeUnsignedInteger(MkvElem_TrackType, 1 /* Video */)
282 .serializeString(MkvElem_CodecID, "V_VP8")
283 .subStart(MkvElem_Video)
284 .serializeUnsignedInteger(MkvElem_PixelWidth, uWidth)
285 .serializeUnsignedInteger(MkvElem_PixelHeight, uHeight)
286 /* Some players rely on the FPS rate for timing calculations.
287 * So make sure to *always* include that. */
288 .serializeFloat (MkvElem_FrameRate, (float)uFPS)
289 .subEnd(MkvElem_Video);
290
291 subEnd(MkvElem_TrackEntry);
292
293 CurSeg.mapTracks[uTrack] = pTrack;
294
295 if (puTrack)
296 *puTrack = uTrack;
297
298 return VINF_SUCCESS;
299#else
300 RT_NOREF(uWidth, uHeight, dbFPS, puTrack);
301 return VERR_NOT_SUPPORTED;
302#endif
303}
304
305/**
306 * Gets file name.
307 *
308 * @returns File name as UTF-8 string.
309 */
310const Utf8Str& WebMWriter::GetFileName(void)
311{
312 return getFileName();
313}
314
315/**
316 * Gets current output file size.
317 *
318 * @returns File size in bytes.
319 */
320uint64_t WebMWriter::GetFileSize(void)
321{
322 return getFileSize();
323}
324
325/**
326 * Gets current free storage space available for the file.
327 *
328 * @returns Available storage free space.
329 */
330uint64_t WebMWriter::GetAvailableSpace(void)
331{
332 return getAvailableSpace();
333}
334
335/**
336 * Takes care of the initialization of the instance.
337 *
338 * @returns IPRT status code.
339 */
340int WebMWriter::init(void)
341{
342 return CurSeg.init();
343}
344
345/**
346 * Takes care of the destruction of the instance.
347 */
348void WebMWriter::destroy(void)
349{
350 CurSeg.uninit();
351}
352
353/**
354 * Writes the WebM file header.
355 *
356 * @returns IPRT status code.
357 */
358int WebMWriter::writeHeader(void)
359{
360 LogFunc(("Header @ %RU64\n", RTFileTell(getFile())));
361
362 subStart(MkvElem_EBML)
363 .serializeUnsignedInteger(MkvElem_EBMLVersion, 1)
364 .serializeUnsignedInteger(MkvElem_EBMLReadVersion, 1)
365 .serializeUnsignedInteger(MkvElem_EBMLMaxIDLength, 4)
366 .serializeUnsignedInteger(MkvElem_EBMLMaxSizeLength, 8)
367 .serializeString(MkvElem_DocType, "webm")
368 .serializeUnsignedInteger(MkvElem_DocTypeVersion, 2)
369 .serializeUnsignedInteger(MkvElem_DocTypeReadVersion, 2)
370 .subEnd(MkvElem_EBML);
371
372 subStart(MkvElem_Segment);
373
374 /* Save offset of current segment. */
375 CurSeg.offStart = RTFileTell(getFile());
376
377 writeSeekHeader();
378
379 /* Save offset of upcoming tracks segment. */
380 CurSeg.offTracks = RTFileTell(getFile());
381
382 /* The tracks segment starts right after this header. */
383 subStart(MkvElem_Tracks);
384 m_fInTracksSection = true;
385
386 return VINF_SUCCESS;
387}
388
389/**
390 * Writes a simple block into the EBML structure.
391 *
392 * @returns IPRT status code.
393 * @param a_pTrack Track the simple block is assigned to.
394 * @param a_pBlock Simple block to write.
395 */
396int WebMWriter::writeSimpleBlockEBML(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock)
397{
398#ifdef LOG_ENABLED
399 WebMCluster &Cluster = CurSeg.CurCluster;
400
401 Log3Func(("[T%RU8C%RU64] Off=%RU64, AbsPTSMs=%RU64, RelToClusterMs=%RU16, %zu bytes\n",
402 a_pTrack->uTrack, Cluster.uID, RTFileTell(getFile()),
403 a_pBlock->Data.tcAbsPTSMs, a_pBlock->Data.tcRelToClusterMs, a_pBlock->Data.cb));
404#endif
405 /*
406 * Write a "Simple Block".
407 */
408 writeClassId(MkvElem_SimpleBlock);
409 /* Block size. */
410 writeUnsignedInteger(0x10000000u | ( 1 /* Track number size. */
411 + m_cbTimecode /* Timecode size .*/
412 + 1 /* Flags size. */
413 + a_pBlock->Data.cb /* Actual frame data size. */), 4);
414 /* Track number. */
415 writeSize(a_pTrack->uTrack);
416 /* Timecode (relative to cluster opening timecode). */
417 writeUnsignedInteger(a_pBlock->Data.tcRelToClusterMs, m_cbTimecode);
418 /* Flags. */
419 writeUnsignedInteger(a_pBlock->Data.fFlags, 1);
420 /* Frame data. */
421 write(a_pBlock->Data.pv, a_pBlock->Data.cb);
422
423 return VINF_SUCCESS;
424}
425
426/**
427 * Writes a simple block and enqueues it into the segment's render queue.
428 *
429 * @returns IPRT status code.
430 * @param a_pTrack Track the simple block is assigned to.
431 * @param a_pBlock Simple block to write and enqueue.
432 */
433int WebMWriter::writeSimpleBlockQueued(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock)
434{
435 RT_NOREF(a_pTrack);
436
437 int rc = VINF_SUCCESS;
438
439 try
440 {
441 const WebMTimecodeAbs tcAbsPTS = a_pBlock->Data.tcAbsPTSMs;
442
443 /* See if we already have an entry for the specified timecode in our queue. */
444 WebMBlockMap::iterator itQueue = CurSeg.queueBlocks.Map.find(tcAbsPTS);
445 if (itQueue != CurSeg.queueBlocks.Map.end()) /* Use existing queue. */
446 {
447 WebMTimecodeBlocks &Blocks = itQueue->second;
448 Blocks.Enqueue(a_pBlock);
449 }
450 else /* Create a new timecode entry. */
451 {
452 WebMTimecodeBlocks Blocks;
453 Blocks.Enqueue(a_pBlock);
454
455 CurSeg.queueBlocks.Map[tcAbsPTS] = Blocks;
456 }
457
458 rc = processQueue(&CurSeg.queueBlocks, false /* fForce */);
459 }
460 catch(...)
461 {
462 delete a_pBlock;
463 a_pBlock = NULL;
464
465 rc = VERR_NO_MEMORY;
466 }
467
468 return rc;
469}
470
471#ifdef VBOX_WITH_LIBVPX
472/**
473 * Writes VPX (VP8 video) simple data block.
474 *
475 * @returns IPRT status code.
476 * @param a_pTrack Track ID to write data to.
477 * @param a_pCfg VPX encoder configuration to use.
478 * @param a_pPkt VPX packet video data packet to write.
479 */
480int WebMWriter::writeSimpleBlockVP8(WebMTrack *a_pTrack, const vpx_codec_enc_cfg_t *a_pCfg, const vpx_codec_cx_pkt_t *a_pPkt)
481{
482 RT_NOREF(a_pTrack);
483
484 /* Calculate the absolute PTS of this frame (in ms). */
485 WebMTimecodeAbs tcAbsPTSMs = a_pPkt->data.frame.pts * 1000
486 * (uint64_t) a_pCfg->g_timebase.num / a_pCfg->g_timebase.den;
487 if ( tcAbsPTSMs
488 && tcAbsPTSMs <= a_pTrack->tcAbsLastWrittenMs)
489 {
490 AssertFailed(); /* Should never happen. */
491 tcAbsPTSMs = a_pTrack->tcAbsLastWrittenMs + 1;
492 }
493
494 const bool fKeyframe = RT_BOOL(a_pPkt->data.frame.flags & VPX_FRAME_IS_KEY);
495
496 uint8_t fFlags = VBOX_WEBM_BLOCK_FLAG_NONE;
497 if (fKeyframe)
498 fFlags |= VBOX_WEBM_BLOCK_FLAG_KEY_FRAME;
499 if (a_pPkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE)
500 fFlags |= VBOX_WEBM_BLOCK_FLAG_INVISIBLE;
501
502 return writeSimpleBlockQueued(a_pTrack,
503 new WebMSimpleBlock(a_pTrack,
504 tcAbsPTSMs, a_pPkt->data.frame.buf, a_pPkt->data.frame.sz, fFlags));
505}
506#endif /* VBOX_WITH_LIBVPX */
507
508#ifdef VBOX_WITH_LIBOPUS
509/**
510 * Writes an Opus (audio) simple data block.
511 *
512 * @returns IPRT status code.
513 * @param a_pTrack Track ID to write data to.
514 * @param pvData Pointer to simple data block to write.
515 * @param cbData Size (in bytes) of simple data block to write.
516 * @param tcAbsPTSMs Absolute PTS of simple data block.
517 *
518 * @remarks Audio blocks that have same absolute timecode as video blocks SHOULD be written before the video blocks.
519 */
520int WebMWriter::writeSimpleBlockOpus(WebMTrack *a_pTrack, const void *pvData, size_t cbData, WebMTimecodeAbs tcAbsPTSMs)
521{
522 AssertPtrReturn(a_pTrack, VERR_INVALID_POINTER);
523 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
524 AssertReturn(cbData, VERR_INVALID_PARAMETER);
525
526 /* Every Opus frame is a key frame. */
527 const uint8_t fFlags = VBOX_WEBM_BLOCK_FLAG_KEY_FRAME;
528
529 return writeSimpleBlockQueued(a_pTrack,
530 new WebMSimpleBlock(a_pTrack, tcAbsPTSMs, pvData, cbData, fFlags));
531}
532#endif /* VBOX_WITH_LIBOPUS */
533
534/**
535 * Writes a data block to the specified track.
536 *
537 * @returns IPRT status code.
538 * @param uTrack Track ID to write data to.
539 * @param pvData Pointer to data block to write.
540 * @param cbData Size (in bytes) of data block to write.
541 */
542int WebMWriter::WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData)
543{
544 RT_NOREF(cbData); /* Only needed for assertions for now. */
545
546 int rc = RTCritSectEnter(&CurSeg.CritSect);
547 AssertRC(rc);
548
549 WebMTracks::iterator itTrack = CurSeg.mapTracks.find(uTrack);
550 if (itTrack == CurSeg.mapTracks.end())
551 {
552 RTCritSectLeave(&CurSeg.CritSect);
553 return VERR_NOT_FOUND;
554 }
555
556 WebMTrack *pTrack = itTrack->second;
557 AssertPtr(pTrack);
558
559 if (m_fInTracksSection)
560 {
561 subEnd(MkvElem_Tracks);
562 m_fInTracksSection = false;
563 }
564
565 switch (pTrack->enmType)
566 {
567
568 case WebMTrackType_Audio:
569 {
570#ifdef VBOX_WITH_LIBOPUS
571 if (m_enmAudioCodec == WebMWriter::AudioCodec_Opus)
572 {
573 Assert(cbData == sizeof(WebMWriter::BlockData_Opus));
574 WebMWriter::BlockData_Opus *pData = (WebMWriter::BlockData_Opus *)pvData;
575 rc = writeSimpleBlockOpus(pTrack, pData->pvData, pData->cbData, pData->uPTSMs);
576 }
577 else
578#endif /* VBOX_WITH_LIBOPUS */
579 rc = VERR_NOT_SUPPORTED;
580 break;
581 }
582
583 case WebMTrackType_Video:
584 {
585#ifdef VBOX_WITH_LIBVPX
586 if (m_enmVideoCodec == WebMWriter::VideoCodec_VP8)
587 {
588 Assert(cbData == sizeof(WebMWriter::BlockData_VP8));
589 WebMWriter::BlockData_VP8 *pData = (WebMWriter::BlockData_VP8 *)pvData;
590 rc = writeSimpleBlockVP8(pTrack, pData->pCfg, pData->pPkt);
591 }
592 else
593#endif /* VBOX_WITH_LIBVPX */
594 rc = VERR_NOT_SUPPORTED;
595 break;
596 }
597
598 default:
599 rc = VERR_NOT_SUPPORTED;
600 break;
601 }
602
603 int rc2 = RTCritSectLeave(&CurSeg.CritSect);
604 AssertRC(rc2);
605
606 return rc;
607}
608
609/**
610 * Processes a render queue.
611 *
612 * @returns IPRT status code.
613 * @param pQueue Queue to process.
614 * @param fForce Whether forcing to process the render queue or not.
615 * Needed to drain the queues when terminating.
616 */
617int WebMWriter::processQueue(WebMQueue *pQueue, bool fForce)
618{
619 if (pQueue->tsLastProcessedMs == 0)
620 pQueue->tsLastProcessedMs = RTTimeMilliTS();
621
622 if (!fForce)
623 {
624 /* Only process when we reached a certain threshold. */
625 if (RTTimeMilliTS() - pQueue->tsLastProcessedMs < 5000 /* ms */ /** @todo Make this configurable */)
626 return VINF_SUCCESS;
627 }
628
629 WebMCluster &Cluster = CurSeg.CurCluster;
630
631 /* Iterate through the block map. */
632 WebMBlockMap::iterator it = pQueue->Map.begin();
633 while (it != CurSeg.queueBlocks.Map.end())
634 {
635 WebMTimecodeAbs mapAbsPTSMs = it->first;
636 WebMTimecodeBlocks mapBlocks = it->second;
637
638 /* Whether to start a new cluster or not. */
639 bool fClusterStart = false;
640
641 /* If the current segment does not have any clusters (yet),
642 * take the first absolute PTS as the starting point for that segment. */
643 if (CurSeg.cClusters == 0)
644 {
645 CurSeg.tcAbsStartMs = mapAbsPTSMs;
646 fClusterStart = true;
647 }
648
649 /* Determine if we need to start a new cluster. */
650 /* No blocks written yet? Start a new cluster. */
651 if ( Cluster.cBlocks == 0
652 /* Did we reach the maximum a cluster can hold? Use a new cluster then. */
653 || mapAbsPTSMs - Cluster.tcAbsStartMs > VBOX_WEBM_CLUSTER_MAX_LEN_MS
654 /* If the block map indicates that a cluster is needed for this timecode, create one. */
655 || mapBlocks.fClusterNeeded)
656 {
657 fClusterStart = true;
658 }
659
660 if ( fClusterStart
661 && !mapBlocks.fClusterStarted)
662 {
663 /* Last written timecode of the current cluster. */
664 uint64_t tcAbsClusterLastWrittenMs;
665
666 if (Cluster.fOpen) /* Close current cluster first. */
667 {
668 Log2Func(("[C%RU64] End @ %RU64ms (duration = %RU64ms)\n",
669 Cluster.uID, Cluster.tcAbsLastWrittenMs, Cluster.tcAbsLastWrittenMs - Cluster.tcAbsStartMs));
670
671 /* Make sure that the current cluster contained some data. */
672 Assert(Cluster.offStart);
673 Assert(Cluster.cBlocks);
674
675 /* Save the last written timecode of the current cluster before closing it. */
676 tcAbsClusterLastWrittenMs = Cluster.tcAbsLastWrittenMs;
677
678 subEnd(MkvElem_Cluster);
679 Cluster.fOpen = false;
680 }
681 else /* First cluster ever? Use the segment's starting timecode. */
682 tcAbsClusterLastWrittenMs = CurSeg.tcAbsStartMs;
683
684 Cluster.fOpen = true;
685 Cluster.uID = CurSeg.cClusters;
686 /* Use the block map's currently processed TC as the cluster's starting TC. */
687 Cluster.tcAbsStartMs = mapAbsPTSMs;
688 Cluster.tcAbsLastWrittenMs = Cluster.tcAbsStartMs;
689 Cluster.offStart = RTFileTell(getFile());
690 Cluster.cBlocks = 0;
691
692 AssertMsg(Cluster.tcAbsStartMs <= mapAbsPTSMs,
693 ("Cluster #%RU64 start TC (%RU64) must not bigger than the block map's currently processed TC (%RU64)\n",
694 Cluster.uID, Cluster.tcAbsStartMs, mapAbsPTSMs));
695
696 Log2Func(("[C%RU64] Start @ %RU64ms (map TC is %RU64) / %RU64 bytes\n",
697 Cluster.uID, Cluster.tcAbsStartMs, mapAbsPTSMs, Cluster.offStart));
698
699 /* Insert cue points for all tracks if a new cluster has been started. */
700 WebMCuePoint *pCuePoint = new WebMCuePoint(Cluster.tcAbsStartMs);
701
702 WebMTracks::iterator itTrack = CurSeg.mapTracks.begin();
703 while (itTrack != CurSeg.mapTracks.end())
704 {
705 pCuePoint->Pos[itTrack->first] = new WebMCueTrackPosEntry(Cluster.offStart);
706 ++itTrack;
707 }
708
709 CurSeg.lstCuePoints.push_back(pCuePoint);
710
711 subStart(MkvElem_Cluster)
712 .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tcAbsStartMs - CurSeg.tcAbsStartMs);
713
714 CurSeg.cClusters++;
715
716 mapBlocks.fClusterStarted = true;
717 }
718
719 Log2Func(("[C%RU64] SegTcAbsStartMs=%RU64, ClusterTcAbsStartMs=%RU64, ClusterTcAbsLastWrittenMs=%RU64, mapAbsPTSMs=%RU64\n",
720 Cluster.uID, CurSeg.tcAbsStartMs, Cluster.tcAbsStartMs, Cluster.tcAbsLastWrittenMs, mapAbsPTSMs));
721
722 /* Iterate through all blocks related to the current timecode. */
723 while (!mapBlocks.Queue.empty())
724 {
725 WebMSimpleBlock *pBlock = mapBlocks.Queue.front();
726 AssertPtr(pBlock);
727
728 WebMTrack *pTrack = pBlock->pTrack;
729 AssertPtr(pTrack);
730
731 /* Calculate the block's relative time code to the current cluster's starting time code. */
732 Assert(pBlock->Data.tcAbsPTSMs >= Cluster.tcAbsStartMs);
733 pBlock->Data.tcRelToClusterMs = pBlock->Data.tcAbsPTSMs - Cluster.tcAbsStartMs;
734
735 int rc2 = writeSimpleBlockEBML(pTrack, pBlock);
736 AssertRC(rc2);
737
738 Cluster.cBlocks++;
739 Cluster.tcAbsLastWrittenMs = pBlock->Data.tcAbsPTSMs;
740
741 pTrack->cTotalBlocks++;
742 pTrack->tcAbsLastWrittenMs = Cluster.tcAbsLastWrittenMs;
743
744 if (CurSeg.tcAbsLastWrittenMs < pTrack->tcAbsLastWrittenMs)
745 CurSeg.tcAbsLastWrittenMs = pTrack->tcAbsLastWrittenMs;
746
747 /* Save a cue point if this is a keyframe (if no new cluster has been started,
748 * as this implies that a cue point already is present. */
749 if ( !fClusterStart
750 && (pBlock->Data.fFlags & VBOX_WEBM_BLOCK_FLAG_KEY_FRAME))
751 {
752 /* Insert cue points for all tracks if a new cluster has been started. */
753 WebMCuePoint *pCuePoint = new WebMCuePoint(Cluster.tcAbsLastWrittenMs);
754
755 WebMTracks::iterator itTrack = CurSeg.mapTracks.begin();
756 while (itTrack != CurSeg.mapTracks.end())
757 {
758 pCuePoint->Pos[itTrack->first] = new WebMCueTrackPosEntry(Cluster.offStart);
759 ++itTrack;
760 }
761
762 CurSeg.lstCuePoints.push_back(pCuePoint);
763 }
764
765 delete pBlock;
766 pBlock = NULL;
767
768 mapBlocks.Queue.pop();
769 }
770
771 Assert(mapBlocks.Queue.empty());
772
773 CurSeg.queueBlocks.Map.erase(it);
774
775 it = CurSeg.queueBlocks.Map.begin();
776 }
777
778 Assert(CurSeg.queueBlocks.Map.empty());
779
780 pQueue->tsLastProcessedMs = RTTimeMilliTS();
781
782 return VINF_SUCCESS;
783}
784
785/**
786 * Writes the WebM footer.
787 *
788 * @returns IPRT status code.
789 */
790int WebMWriter::writeFooter(void)
791{
792 AssertReturn(isOpen(), VERR_WRONG_ORDER);
793
794 if (m_fInTracksSection)
795 {
796 subEnd(MkvElem_Tracks);
797 m_fInTracksSection = false;
798 }
799
800 if (CurSeg.CurCluster.fOpen)
801 {
802 subEnd(MkvElem_Cluster);
803 CurSeg.CurCluster.fOpen = false;
804 }
805
806 /*
807 * Write Cues element.
808 */
809 CurSeg.offCues = RTFileTell(getFile());
810 LogFunc(("Cues @ %RU64\n", CurSeg.offCues));
811
812 subStart(MkvElem_Cues);
813
814 WebMCuePointList::iterator itCuePoint = CurSeg.lstCuePoints.begin();
815 while (itCuePoint != CurSeg.lstCuePoints.end())
816 {
817 WebMCuePoint *pCuePoint = (*itCuePoint);
818 AssertPtr(pCuePoint);
819
820 LogFunc(("CuePoint @ %RU64: %zu tracks, tcAbs=%RU64)\n",
821 RTFileTell(getFile()), pCuePoint->Pos.size(), pCuePoint->tcAbs));
822
823 subStart(MkvElem_CuePoint)
824 .serializeUnsignedInteger(MkvElem_CueTime, pCuePoint->tcAbs);
825
826 WebMCueTrackPosMap::iterator itTrackPos = pCuePoint->Pos.begin();
827 while (itTrackPos != pCuePoint->Pos.end())
828 {
829 WebMCueTrackPosEntry *pTrackPos = itTrackPos->second;
830 AssertPtr(pTrackPos);
831
832 LogFunc(("TrackPos (track #%RU32) @ %RU64, offCluster=%RU64)\n",
833 itTrackPos->first, RTFileTell(getFile()), pTrackPos->offCluster));
834
835 subStart(MkvElem_CueTrackPositions)
836 .serializeUnsignedInteger(MkvElem_CueTrack, itTrackPos->first)
837 .serializeUnsignedInteger(MkvElem_CueClusterPosition, pTrackPos->offCluster - CurSeg.offStart, 8)
838 .subEnd(MkvElem_CueTrackPositions);
839
840 ++itTrackPos;
841 }
842
843 subEnd(MkvElem_CuePoint);
844
845 ++itCuePoint;
846 }
847
848 subEnd(MkvElem_Cues);
849 subEnd(MkvElem_Segment);
850
851 /*
852 * Re-Update seek header with final information.
853 */
854
855 writeSeekHeader();
856
857 return RTFileSeek(getFile(), 0, RTFILE_SEEK_END, NULL);
858}
859
860/**
861 * Writes the segment's seek header.
862 */
863void WebMWriter::writeSeekHeader(void)
864{
865 if (CurSeg.offSeekInfo)
866 RTFileSeek(getFile(), CurSeg.offSeekInfo, RTFILE_SEEK_BEGIN, NULL);
867 else
868 CurSeg.offSeekInfo = RTFileTell(getFile());
869
870 LogFunc(("Seek Header @ %RU64\n", CurSeg.offSeekInfo));
871
872 subStart(MkvElem_SeekHead);
873
874 subStart(MkvElem_Seek)
875 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Tracks)
876 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offTracks - CurSeg.offStart, 8)
877 .subEnd(MkvElem_Seek);
878
879 if (CurSeg.offCues)
880 LogFunc(("Updating Cues @ %RU64\n", CurSeg.offCues));
881
882 subStart(MkvElem_Seek)
883 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Cues)
884 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offCues - CurSeg.offStart, 8)
885 .subEnd(MkvElem_Seek);
886
887 subStart(MkvElem_Seek)
888 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Info)
889 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offInfo - CurSeg.offStart, 8)
890 .subEnd(MkvElem_Seek);
891
892 subEnd(MkvElem_SeekHead);
893
894 /*
895 * Write the segment's info element.
896 */
897
898 /* Save offset of the segment's info element. */
899 CurSeg.offInfo = RTFileTell(getFile());
900
901 LogFunc(("Info @ %RU64\n", CurSeg.offInfo));
902
903 char szMux[64];
904 RTStrPrintf(szMux, sizeof(szMux),
905#ifdef VBOX_WITH_LIBVPX
906 "vpxenc%s", vpx_codec_version_str());
907#else
908 "unknown");
909#endif
910 char szApp[64];
911 RTStrPrintf(szApp, sizeof(szApp), VBOX_PRODUCT " %sr%u", VBOX_VERSION_STRING, RTBldCfgRevision());
912
913 const WebMTimecodeAbs tcAbsDurationMs = CurSeg.tcAbsLastWrittenMs - CurSeg.tcAbsStartMs;
914
915 if (!CurSeg.lstCuePoints.empty())
916 {
917 LogFunc(("tcAbsDurationMs=%RU64\n", tcAbsDurationMs));
918 AssertMsg(tcAbsDurationMs, ("Segment seems to be empty (duration is 0)\n"));
919 }
920
921 subStart(MkvElem_Info)
922 .serializeUnsignedInteger(MkvElem_TimecodeScale, CurSeg.uTimecodeScaleFactor)
923 .serializeFloat(MkvElem_Segment_Duration, tcAbsDurationMs)
924 .serializeString(MkvElem_MuxingApp, szMux)
925 .serializeString(MkvElem_WritingApp, szApp)
926 .subEnd(MkvElem_Info);
927}
928
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