VirtualBox

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

Last change on this file since 70772 was 70623, checked in by vboxsync, 6 years ago

VideoRec/WebMWriter: Fixes required for some players (e.g latest Firefox).

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

© 2023 Oracle
ContactPrivacy policyTerms of Use