VirtualBox

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

Last change on this file since 96284 was 96231, checked in by vboxsync, 3 years ago

Recording/Main: Decoupled the WebM writer class from codec dependencies. Various bugfixes [build fix]. bugref:10275

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