VirtualBox

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

Last change on this file since 96407 was 96407, checked in by vboxsync, 22 months ago

scm copyright and license note update

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

© 2023 Oracle
ContactPrivacy policyTerms of Use