VirtualBox

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

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

Main/Recording: thin out the excessive includes so that it doesn't hide missing includes in totally unrelated code, and adjust that code to do the right thing

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