VirtualBox

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

Last change on this file was 98103, checked in by vboxsync, 16 months ago

Copyright year updates by scm.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use