VirtualBox

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

Last change on this file since 96391 was 96324, checked in by vboxsync, 3 years ago

Recording/Main: Renaming (use m_ prefixes for class variables).

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