VirtualBox

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

Last change on this file was 107683, checked in by vboxsync, 6 weeks ago

src/VBox/Main/src-client/WebMWriter.cpp: Fixed warnings found by Parfait (unused assignment + unread variable). jiraref:VBP-1424

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