VirtualBox

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

Last change on this file since 70063 was 69689, checked in by vboxsync, 7 years ago

Main/WebMWriter: SVN props.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 25.1 KB
Line 
1/* $Id: WebMWriter.cpp 69689 2017-11-14 14:50:37Z vboxsync $ */
2/** @file
3 * WebMWriter.cpp - WebM container handling.
4 */
5
6/*
7 * Copyright (C) 2013-2017 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 if (!isOpen())
140 return VINF_SUCCESS;
141
142 LogFunc(("\n"));
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 const uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size();
206
207 subStart(MkvElem_TrackEntry);
208
209 serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack);
210 serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */);
211 serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0);
212
213 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Audio, uTrack, RTFileTell(getFile()));
214
215 pTrack->Audio.uHz = uHz;
216 pTrack->Audio.msPerBlock = 20; /** Opus uses 20ms by default. Make this configurable? */
217 pTrack->Audio.framesPerBlock = uHz / (1000 /* s in ms */ / pTrack->Audio.msPerBlock);
218
219 WEBMOPUSPRIVDATA opusPrivData(uHz, cChannels);
220
221 LogFunc(("Opus @ %RU16Hz (%RU16ms + %RU16 frames per block)\n",
222 pTrack->Audio.uHz, pTrack->Audio.msPerBlock, pTrack->Audio.framesPerBlock));
223
224 serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID, 4)
225 .serializeUnsignedInteger(MkvElem_TrackType, 2 /* Audio */)
226 .serializeString(MkvElem_CodecID, "A_OPUS")
227 .serializeData(MkvElem_CodecPrivate, &opusPrivData, sizeof(opusPrivData))
228 .serializeUnsignedInteger(MkvElem_CodecDelay, 0)
229 .serializeUnsignedInteger(MkvElem_SeekPreRoll, 80 * 1000000) /* 80ms in ns. */
230 .subStart(MkvElem_Audio)
231 .serializeFloat(MkvElem_SamplingFrequency, (float)uHz)
232 .serializeUnsignedInteger(MkvElem_Channels, cChannels)
233 .serializeUnsignedInteger(MkvElem_BitDepth, cBits)
234 .subEnd(MkvElem_Audio)
235 .subEnd(MkvElem_TrackEntry);
236
237 CurSeg.mapTracks[uTrack] = pTrack;
238
239 if (puTrack)
240 *puTrack = uTrack;
241
242 return VINF_SUCCESS;
243#else
244 RT_NOREF(uHz, cChannels, cBits, puTrack);
245 return VERR_NOT_SUPPORTED;
246#endif
247}
248
249/**
250 * Adds a video track.
251 *
252 * @returns IPRT status code.
253 * @param uWidth Width (in pixels) of the video track.
254 * @param uHeight Height (in pixels) of the video track.
255 * @param dbFPS FPS (Frames Per Second) of the video track.
256 * @param puTrack Track number of the added video track on success. Optional.
257 */
258int WebMWriter::AddVideoTrack(uint16_t uWidth, uint16_t uHeight, double dbFPS, uint8_t *puTrack)
259{
260#ifdef VBOX_WITH_LIBVPX
261 RT_NOREF(dbFPS);
262
263 const uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size();
264
265 subStart(MkvElem_TrackEntry);
266
267 serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack);
268 serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */);
269 serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0);
270
271 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Video, uTrack, RTFileTell(getFile()));
272
273 /** @todo Resolve codec type. */
274 serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID /* UID */, 4)
275 .serializeUnsignedInteger(MkvElem_TrackType, 1 /* Video */)
276 .serializeString(MkvElem_CodecID, "V_VP8")
277 .subStart(MkvElem_Video)
278 .serializeUnsignedInteger(MkvElem_PixelWidth, uWidth)
279 .serializeUnsignedInteger(MkvElem_PixelHeight, uHeight)
280 .subEnd(MkvElem_Video);
281
282 subEnd(MkvElem_TrackEntry);
283
284 CurSeg.mapTracks[uTrack] = pTrack;
285
286 if (puTrack)
287 *puTrack = uTrack;
288
289 return VINF_SUCCESS;
290#else
291 RT_NOREF(uWidth, uHeight, dbFPS, puTrack);
292 return VERR_NOT_SUPPORTED;
293#endif
294}
295
296/**
297 * Gets file name.
298 *
299 * @returns File name as UTF-8 string.
300 */
301const Utf8Str& WebMWriter::GetFileName(void)
302{
303 return getFileName();
304}
305
306/**
307 * Gets current output file size.
308 *
309 * @returns File size in bytes.
310 */
311uint64_t WebMWriter::GetFileSize(void)
312{
313 return getFileSize();
314}
315
316/**
317 * Gets current free storage space available for the file.
318 *
319 * @returns Available storage free space.
320 */
321uint64_t WebMWriter::GetAvailableSpace(void)
322{
323 return getAvailableSpace();
324}
325
326/**
327 * Takes care of the initialization of the instance.
328 *
329 * @returns IPRT status code.
330 */
331int WebMWriter::init(void)
332{
333 return CurSeg.init();
334}
335
336/**
337 * Takes care of the destruction of the instance.
338 */
339void WebMWriter::destroy(void)
340{
341 CurSeg.destroy();
342}
343
344/**
345 * Writes the WebM file header.
346 *
347 * @returns IPRT status code.
348 */
349int WebMWriter::writeHeader(void)
350{
351 LogFunc(("Header @ %RU64\n", RTFileTell(getFile())));
352
353 subStart(MkvElem_EBML)
354 .serializeUnsignedInteger(MkvElem_EBMLVersion, 1)
355 .serializeUnsignedInteger(MkvElem_EBMLReadVersion, 1)
356 .serializeUnsignedInteger(MkvElem_EBMLMaxIDLength, 4)
357 .serializeUnsignedInteger(MkvElem_EBMLMaxSizeLength, 8)
358 .serializeString(MkvElem_DocType, "webm")
359 .serializeUnsignedInteger(MkvElem_DocTypeVersion, 2)
360 .serializeUnsignedInteger(MkvElem_DocTypeReadVersion, 2)
361 .subEnd(MkvElem_EBML);
362
363 subStart(MkvElem_Segment);
364
365 /* Save offset of current segment. */
366 CurSeg.offStart = RTFileTell(getFile());
367
368 writeSegSeekInfo();
369
370 /* Save offset of upcoming tracks segment. */
371 CurSeg.offTracks = RTFileTell(getFile());
372
373 /* The tracks segment starts right after this header. */
374 subStart(MkvElem_Tracks);
375 m_fInTracksSection = true;
376
377 return VINF_SUCCESS;
378}
379
380/**
381 * Writes a simple block into the EBML structure.
382 *
383 * @returns IPRT status code.
384 * @param a_pTrack Track the simple block is assigned to.
385 * @param a_pBlock Simple block to write.
386 */
387int WebMWriter::writeSimpleBlockEBML(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock)
388{
389#ifdef LOG_ENABLED
390 WebMCluster &Cluster = CurSeg.CurCluster;
391
392 Log3Func(("[T%RU8C%RU64] Off=%RU64, PTS=%RU16, RelToClusterMs=%RU16, %zu bytes\n",
393 a_pTrack->uTrack, Cluster.uID, RTFileTell(getFile()),
394 a_pBlock->Data.tcPTSMs, a_pBlock->Data.tcRelToClusterMs, a_pBlock->Data.cb));
395#endif
396 /*
397 * Write a "Simple Block".
398 */
399 writeClassId(MkvElem_SimpleBlock);
400 /* Block size. */
401 writeUnsignedInteger(0x10000000u | ( 1 /* Track number size. */
402 + m_cbTimecode /* Timecode size .*/
403 + 1 /* Flags size. */
404 + a_pBlock->Data.cb /* Actual frame data size. */), 4);
405 /* Track number. */
406 writeSize(a_pTrack->uTrack);
407 /* Timecode (relative to cluster opening timecode). */
408 writeUnsignedInteger(a_pBlock->Data.tcRelToClusterMs, m_cbTimecode);
409 /* Flags. */
410 writeUnsignedInteger(a_pBlock->Data.fFlags, 1);
411 /* Frame data. */
412 write(a_pBlock->Data.pv, a_pBlock->Data.cb);
413
414 return VINF_SUCCESS;
415}
416
417/**
418 * Writes a simple block and enqueues it into the segment's render queue.
419 *
420 * @returns IPRT status code.
421 * @param a_pTrack Track the simple block is assigned to.
422 * @param a_pBlock Simple block to write and enqueue.
423 */
424int WebMWriter::writeSimpleBlockQueued(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock)
425{
426 RT_NOREF(a_pTrack);
427
428 int rc = VINF_SUCCESS;
429
430 try
431 {
432 const WebMTimecode tcMap = a_pBlock->Data.tcPTSMs;
433
434 /* See if we already have an entry for the specified timecode in our queue. */
435 WebMBlockMap::iterator itQueue = CurSeg.queueBlocks.Map.find(tcMap);
436 if (itQueue != CurSeg.queueBlocks.Map.end()) /* Use existing queue. */
437 {
438 WebMTimecodeBlocks &Blocks = itQueue->second;
439 Blocks.Enqueue(a_pBlock);
440 }
441 else /* Create a new timecode entry. */
442 {
443 WebMTimecodeBlocks Blocks;
444 Blocks.Enqueue(a_pBlock);
445
446 CurSeg.queueBlocks.Map[tcMap] = Blocks;
447 }
448
449 processQueues(&CurSeg.queueBlocks, false /* fForce */);
450 }
451 catch(...)
452 {
453 delete a_pBlock;
454 a_pBlock = NULL;
455
456 rc = VERR_NO_MEMORY;
457 }
458
459 return rc;
460}
461
462#ifdef VBOX_WITH_LIBVPX
463/**
464 * Writes VPX (VP8 video) simple data block.
465 *
466 * @returns IPRT status code.
467 * @param a_pTrack Track ID to write data to.
468 * @param a_pCfg VPX encoder configuration to use.
469 * @param a_pPkt VPX packet video data packet to write.
470 */
471int WebMWriter::writeSimpleBlockVP8(WebMTrack *a_pTrack, const vpx_codec_enc_cfg_t *a_pCfg, const vpx_codec_cx_pkt_t *a_pPkt)
472{
473 RT_NOREF(a_pTrack);
474
475 /* Calculate the PTS of this frame (in ms). */
476 WebMTimecode tcPTSMs = a_pPkt->data.frame.pts * 1000
477 * (uint64_t) a_pCfg->g_timebase.num / a_pCfg->g_timebase.den;
478
479 if ( tcPTSMs
480 && tcPTSMs <= a_pTrack->tcLastWrittenMs)
481 {
482 tcPTSMs = a_pTrack->tcLastWrittenMs + 1;
483 }
484
485 const bool fKeyframe = RT_BOOL(a_pPkt->data.frame.flags & VPX_FRAME_IS_KEY);
486
487 uint8_t fFlags = VBOX_WEBM_BLOCK_FLAG_NONE;
488 if (fKeyframe)
489 fFlags |= VBOX_WEBM_BLOCK_FLAG_KEY_FRAME;
490 if (a_pPkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE)
491 fFlags |= VBOX_WEBM_BLOCK_FLAG_INVISIBLE;
492
493 return writeSimpleBlockQueued(a_pTrack,
494 new WebMSimpleBlock(a_pTrack,
495 tcPTSMs, a_pPkt->data.frame.buf, a_pPkt->data.frame.sz, fFlags));
496}
497#endif /* VBOX_WITH_LIBVPX */
498
499#ifdef VBOX_WITH_LIBOPUS
500/**
501 * Writes an Opus (audio) simple data block.
502 *
503 * @returns IPRT status code.
504 * @param a_pTrack Track ID to write data to.
505 * @param pvData Pointer to simple data block to write.
506 * @param cbData Size (in bytes) of simple data block to write.
507 * @param uPTSMs PTS of simple data block.
508 *
509 * @remarks Audio blocks that have same absolute timecode as video blocks SHOULD be written before the video blocks.
510 */
511int WebMWriter::writeSimpleBlockOpus(WebMTrack *a_pTrack, const void *pvData, size_t cbData, WebMTimecode uPTSMs)
512{
513 AssertPtrReturn(a_pTrack, VERR_INVALID_POINTER);
514 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
515 AssertReturn(cbData, VERR_INVALID_PARAMETER);
516
517 /* Every Opus frame is a key frame. */
518 const uint8_t fFlags = VBOX_WEBM_BLOCK_FLAG_KEY_FRAME;
519
520 return writeSimpleBlockQueued(a_pTrack,
521 new WebMSimpleBlock(a_pTrack,
522 uPTSMs, pvData, cbData, fFlags));
523}
524#endif /* VBOX_WITH_LIBOPUS */
525
526/**
527 * Writes a data block to the specified track.
528 *
529 * @returns IPRT status code.
530 * @param uTrack Track ID to write data to.
531 * @param pvData Pointer to data block to write.
532 * @param cbData Size (in bytes) of data block to write.
533 */
534int WebMWriter::WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData)
535{
536 RT_NOREF(cbData); /* Only needed for assertions for now. */
537
538 int rc = RTCritSectEnter(&CurSeg.CritSect);
539 AssertRC(rc);
540
541 WebMTracks::iterator itTrack = CurSeg.mapTracks.find(uTrack);
542 if (itTrack == CurSeg.mapTracks.end())
543 {
544 RTCritSectLeave(&CurSeg.CritSect);
545 return VERR_NOT_FOUND;
546 }
547
548 WebMTrack *pTrack = itTrack->second;
549 AssertPtr(pTrack);
550
551 if (m_fInTracksSection)
552 {
553 subEnd(MkvElem_Tracks);
554 m_fInTracksSection = false;
555 }
556
557 switch (pTrack->enmType)
558 {
559
560 case WebMTrackType_Audio:
561 {
562#ifdef VBOX_WITH_LIBOPUS
563 if (m_enmAudioCodec == WebMWriter::AudioCodec_Opus)
564 {
565 Assert(cbData == sizeof(WebMWriter::BlockData_Opus));
566 WebMWriter::BlockData_Opus *pData = (WebMWriter::BlockData_Opus *)pvData;
567 rc = writeSimpleBlockOpus(pTrack, pData->pvData, pData->cbData, pData->uPTSMs);
568 }
569 else
570#endif /* VBOX_WITH_LIBOPUS */
571 rc = VERR_NOT_SUPPORTED;
572 break;
573 }
574
575 case WebMTrackType_Video:
576 {
577#ifdef VBOX_WITH_LIBVPX
578 if (m_enmVideoCodec == WebMWriter::VideoCodec_VP8)
579 {
580 Assert(cbData == sizeof(WebMWriter::BlockData_VP8));
581 WebMWriter::BlockData_VP8 *pData = (WebMWriter::BlockData_VP8 *)pvData;
582 rc = writeSimpleBlockVP8(pTrack, pData->pCfg, pData->pPkt);
583 }
584 else
585#endif /* VBOX_WITH_LIBVPX */
586 rc = VERR_NOT_SUPPORTED;
587 break;
588 }
589
590 default:
591 rc = VERR_NOT_SUPPORTED;
592 break;
593 }
594
595 int rc2 = RTCritSectLeave(&CurSeg.CritSect);
596 AssertRC(rc2);
597
598 return rc;
599}
600
601/**
602 * Processes a render queue.
603 *
604 * @returns IPRT status code.
605 * @param pQueue Queue to process.
606 * @param fForce Whether forcing to process the render queue or not.
607 * Needed to drain the queues when terminating.
608 */
609int WebMWriter::processQueues(WebMQueue *pQueue, bool fForce)
610{
611 if (pQueue->tslastProcessedMs == 0)
612 pQueue->tslastProcessedMs = RTTimeMilliTS();
613
614 if (!fForce)
615 {
616 /* Only process when we reached a certain threshold. */
617 if (RTTimeMilliTS() - pQueue->tslastProcessedMs < 5000 /* ms */ /** @todo Make this configurable */)
618 return VINF_SUCCESS;
619 }
620
621 WebMCluster &Cluster = CurSeg.CurCluster;
622
623 /* Iterate through the block map. */
624 WebMBlockMap::iterator it = pQueue->Map.begin();
625 while (it != CurSeg.queueBlocks.Map.end())
626 {
627 WebMTimecode mapTC = it->first;
628 RT_NOREF(mapTC);
629 WebMTimecodeBlocks mapBlocks = it->second;
630
631 /* Whether to start a new cluster or not. */
632 bool fClusterStart = false;
633
634 /* No blocks written yet? Start a new cluster. */
635 if (Cluster.cBlocks == 0)
636 fClusterStart = true;
637
638 /* Did we reach the maximum a cluster can hold? Use a new cluster then. */
639 if (mapTC - Cluster.tcStartMs > VBOX_WEBM_CLUSTER_MAX_LEN_MS)
640 fClusterStart = true;
641
642 /* If the block map indicates that a cluster is needed for this timecode, create one. */
643 if (mapBlocks.fClusterNeeded)
644 fClusterStart = true;
645
646 if ( fClusterStart
647 && !mapBlocks.fClusterStarted)
648 {
649 if (Cluster.fOpen) /* Close current cluster first. */
650 {
651 /* Make sure that the current cluster contained some data. */
652 Assert(Cluster.offStart);
653 Assert(Cluster.cBlocks);
654
655 subEnd(MkvElem_Cluster);
656 Cluster.fOpen = false;
657 }
658
659 Cluster.fOpen = true;
660 Cluster.uID = CurSeg.cClusters;
661 Cluster.tcStartMs = mapTC;
662 Cluster.offStart = RTFileTell(getFile());
663 Cluster.cBlocks = 0;
664
665 if (CurSeg.cClusters)
666 AssertMsg(Cluster.tcStartMs, ("[C%RU64] @ %RU64 starting timecode is 0 which is invalid\n",
667 Cluster.uID, Cluster.offStart));
668
669 Log2Func(("[C%RU64] Start @ %RU64ms (map TC is %RU64) / %RU64 bytes\n",
670 Cluster.uID, Cluster.tcStartMs, mapTC, Cluster.offStart));
671
672 subStart(MkvElem_Cluster)
673 .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tcStartMs);
674
675 CurSeg.cClusters++;
676
677 mapBlocks.fClusterStarted = true;
678 }
679
680 /* Iterate through all blocks related to the current timecode. */
681 while (!mapBlocks.Queue.empty())
682 {
683 WebMSimpleBlock *pBlock = mapBlocks.Queue.front();
684 AssertPtr(pBlock);
685
686 WebMTrack *pTrack = pBlock->pTrack;
687 AssertPtr(pTrack);
688
689 /* Calculate the block's relative time code to the current cluster's starting time code. */
690 Assert(pBlock->Data.tcPTSMs >= Cluster.tcStartMs);
691 pBlock->Data.tcRelToClusterMs = pBlock->Data.tcPTSMs - Cluster.tcStartMs;
692
693 int rc2 = writeSimpleBlockEBML(pTrack, pBlock);
694 AssertRC(rc2);
695
696 Cluster.cBlocks++;
697
698 pTrack->cTotalBlocks++;
699 pTrack->tcLastWrittenMs = pBlock->Data.tcPTSMs;
700
701 if (CurSeg.tcLastWrittenMs < pTrack->tcLastWrittenMs)
702 CurSeg.tcLastWrittenMs = pTrack->tcLastWrittenMs;
703
704 /* Save a cue point if this is a keyframe. */
705 if (pBlock->Data.fFlags & VBOX_WEBM_BLOCK_FLAG_KEY_FRAME)
706 {
707 WebMCuePoint cue(pBlock->pTrack, Cluster.offStart, pBlock->Data.tcPTSMs);
708 CurSeg.lstCues.push_back(cue);
709 }
710
711 delete pBlock;
712 pBlock = NULL;
713
714 mapBlocks.Queue.pop();
715 }
716
717 Assert(mapBlocks.Queue.empty());
718
719 CurSeg.queueBlocks.Map.erase(it);
720
721 it = CurSeg.queueBlocks.Map.begin();
722 }
723
724 Assert(CurSeg.queueBlocks.Map.empty());
725
726 pQueue->tslastProcessedMs = RTTimeMilliTS();
727
728 return VINF_SUCCESS;
729}
730
731/**
732 * Writes the WebM footer.
733 *
734 * @returns IPRT status code.
735 */
736int WebMWriter::writeFooter(void)
737{
738 AssertReturn(isOpen(), VERR_WRONG_ORDER);
739
740 if (m_fInTracksSection)
741 {
742 subEnd(MkvElem_Tracks);
743 m_fInTracksSection = false;
744 }
745
746 if (CurSeg.CurCluster.fOpen)
747 {
748 subEnd(MkvElem_Cluster);
749 CurSeg.CurCluster.fOpen = false;
750 }
751
752 /*
753 * Write Cues element.
754 */
755 LogFunc(("Cues @ %RU64\n", RTFileTell(getFile())));
756
757 CurSeg.offCues = RTFileTell(getFile());
758
759 subStart(MkvElem_Cues);
760
761 std::list<WebMCuePoint>::iterator itCuePoint = CurSeg.lstCues.begin();
762 while (itCuePoint != CurSeg.lstCues.end())
763 {
764 /* Sanity. */
765 AssertPtr(itCuePoint->pTrack);
766
767 LogFunc(("CuePoint @ %RU64: Track #%RU8 (Cluster @ %RU64, TC %RU64)\n",
768 RTFileTell(getFile()), itCuePoint->pTrack->uTrack,
769 itCuePoint->offCluster, itCuePoint->tcAbs));
770
771 subStart(MkvElem_CuePoint)
772 .serializeUnsignedInteger(MkvElem_CueTime, itCuePoint->tcAbs)
773 .subStart(MkvElem_CueTrackPositions)
774 .serializeUnsignedInteger(MkvElem_CueTrack, itCuePoint->pTrack->uTrack)
775 .serializeUnsignedInteger(MkvElem_CueClusterPosition, itCuePoint->offCluster, 8)
776 .subEnd(MkvElem_CueTrackPositions)
777 .subEnd(MkvElem_CuePoint);
778
779 itCuePoint++;
780 }
781
782 subEnd(MkvElem_Cues);
783 subEnd(MkvElem_Segment);
784
785 /*
786 * Re-Update SeekHead / Info elements.
787 */
788
789 writeSegSeekInfo();
790
791 return RTFileSeek(getFile(), 0, RTFILE_SEEK_END, NULL);
792}
793
794/**
795 * Writes the segment's seek information and cue points.
796 */
797void WebMWriter::writeSegSeekInfo(void)
798{
799 if (CurSeg.offSeekInfo)
800 RTFileSeek(getFile(), CurSeg.offSeekInfo, RTFILE_SEEK_BEGIN, NULL);
801 else
802 CurSeg.offSeekInfo = RTFileTell(getFile());
803
804 LogFunc(("SeekHead @ %RU64\n", CurSeg.offSeekInfo));
805
806 subStart(MkvElem_SeekHead);
807
808 subStart(MkvElem_Seek)
809 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Tracks)
810 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offTracks - CurSeg.offStart, 8)
811 .subEnd(MkvElem_Seek);
812
813 Assert(CurSeg.offCues - CurSeg.offStart > 0); /* Sanity. */
814
815 subStart(MkvElem_Seek)
816 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Cues)
817 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offCues - CurSeg.offStart, 8)
818 .subEnd(MkvElem_Seek);
819
820 subStart(MkvElem_Seek)
821 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Info)
822 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offInfo - CurSeg.offStart, 8)
823 .subEnd(MkvElem_Seek);
824
825 subEnd(MkvElem_SeekHead);
826
827 //int64_t iFrameTime = (int64_t)1000 * 1 / 25; //m_Framerate.den / m_Framerate.num; /** @todo Fix this! */
828 CurSeg.offInfo = RTFileTell(getFile());
829
830 LogFunc(("Info @ %RU64\n", CurSeg.offInfo));
831
832 char szMux[64];
833 RTStrPrintf(szMux, sizeof(szMux),
834#ifdef VBOX_WITH_LIBVPX
835 "vpxenc%s", vpx_codec_version_str());
836#else
837 "unknown");
838#endif
839 char szApp[64];
840 RTStrPrintf(szApp, sizeof(szApp), VBOX_PRODUCT " %sr%u", VBOX_VERSION_STRING, RTBldCfgRevision());
841
842 const WebMTimecode tcDuration = CurSeg.tcLastWrittenMs - CurSeg.tcStartMs;
843
844 if (!CurSeg.lstCues.empty())
845 {
846 LogFunc(("tcDuration=%RU64\n", tcDuration));
847 AssertMsg(tcDuration, ("Segment seems to be empty\n"));
848 }
849
850 subStart(MkvElem_Info)
851 .serializeUnsignedInteger(MkvElem_TimecodeScale, CurSeg.uTimecodeScaleFactor)
852 .serializeFloat(MkvElem_Segment_Duration, tcDuration)
853 .serializeString(MkvElem_MuxingApp, szMux)
854 .serializeString(MkvElem_WritingApp, szApp)
855 .subEnd(MkvElem_Info);
856}
857
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