diff options
Diffstat (limited to 'src/VBox/Main/include/WebMWriter.h')
-rw-r--r-- | src/VBox/Main/include/WebMWriter.h | 586 |
1 files changed, 586 insertions, 0 deletions
diff --git a/src/VBox/Main/include/WebMWriter.h b/src/VBox/Main/include/WebMWriter.h new file mode 100644 index 00000000..248dd691 --- /dev/null +++ b/src/VBox/Main/include/WebMWriter.h @@ -0,0 +1,586 @@ +/* $Id: WebMWriter.h $ */ +/** @file + * WebMWriter.h - WebM container handling. + */ + +/* + * Copyright (C) 2013-2020 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef MAIN_INCLUDED_WebMWriter_h +#define MAIN_INCLUDED_WebMWriter_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "EBMLWriter.h" +#include "EBML_MKV.h" + +#include <queue> +#include <map> +#include <list> + +#include <iprt/mem.h> +#include <iprt/rand.h> + +#ifdef VBOX_WITH_LIBVPX +# ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4668) /* vpx_codec.h(64) : warning C4668: '__GNUC__' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' */ +# include <vpx/vpx_encoder.h> +# pragma warning(pop) +# else +# include <vpx/vpx_encoder.h> +# endif +#endif /* VBOX_WITH_LIBVPX */ + +/** No flags specified. */ +#define VBOX_WEBM_BLOCK_FLAG_NONE 0 +/** Invisible block which can be skipped. */ +#define VBOX_WEBM_BLOCK_FLAG_INVISIBLE 0x08 +/** The block marks a key frame. */ +#define VBOX_WEBM_BLOCK_FLAG_KEY_FRAME 0x80 + +/** The default timecode scale factor for WebM -- all timecodes in the segments are expressed in ms. + * This allows every cluster to have blocks with positive values up to 32.767 seconds. */ +#define VBOX_WEBM_TIMECODESCALE_FACTOR_MS 1000000 + +/** Maximum time (in ms) a cluster can store. */ +#define VBOX_WEBM_CLUSTER_MAX_LEN_MS INT16_MAX + +/** Maximum time a block can store. + * With signed 16-bit timecodes and a default timecode scale of 1ms per unit this makes 65536ms. */ +#define VBOX_WEBM_BLOCK_MAX_LEN_MS UINT16_MAX + +#ifdef VBOX_WITH_LIBOPUS +# pragma pack(push) +# pragma pack(1) + /** Opus codec private data within the MKV (WEBM) container. + * Taken from: https://wiki.xiph.org/MatroskaOpus */ + typedef struct WEBMOPUSPRIVDATA + { + WEBMOPUSPRIVDATA(uint32_t a_u32SampleRate, uint8_t a_u8Channels) + { + au64Head = RT_MAKE_U64_FROM_U8('O', 'p', 'u', 's', 'H', 'e', 'a', 'd'); + u8Version = 1; + u8Channels = a_u8Channels; + u16PreSkip = 0; + u32SampleRate = a_u32SampleRate; + u16Gain = 0; + u8MappingFamily = 0; + } + + uint64_t au64Head; /**< Defaults to "OpusHead". */ + uint8_t u8Version; /**< Must be set to 1. */ + uint8_t u8Channels; + uint16_t u16PreSkip; + /** Sample rate *before* encoding to Opus. + * Note: This rate has nothing to do with the playback rate later! */ + uint32_t u32SampleRate; + uint16_t u16Gain; + /** Must stay 0 -- otherwise a mapping table must be appended + * right after this header. */ + uint8_t u8MappingFamily; + } WEBMOPUSPRIVDATA, *PWEBMOPUSPRIVDATA; + AssertCompileSize(WEBMOPUSPRIVDATA, 19); +# pragma pack(pop) +#endif /* VBOX_WITH_LIBOPUS */ + + +class WebMWriter : public EBMLWriter +{ + +public: + + /** Defines an absolute WebM timecode (Block + Cluster). */ + typedef uint64_t WebMTimecodeAbs; + + /** Defines a relative WebM timecode (Block). */ + typedef uint16_t WebMTimecodeRel; + + /** Defines the WebM block flags data type. */ + typedef uint8_t WebMBlockFlags; + + /** + * Supported audio codecs. + */ + enum AudioCodec + { + /** No audio codec specified. */ + AudioCodec_None = 0, + /** Opus. */ + AudioCodec_Opus = 1 + }; + + /** + * Supported video codecs. + */ + enum VideoCodec + { + /** No video codec specified. */ + VideoCodec_None = 0, + /** VP8. */ + VideoCodec_VP8 = 1 + }; + + /** + * Track type enumeration. + */ + enum WebMTrackType + { + /** Unknown / invalid type. */ + WebMTrackType_Invalid = 0, + /** Only writes audio. */ + WebMTrackType_Audio = 1, + /** Only writes video. */ + WebMTrackType_Video = 2 + }; + + struct WebMTrack; + + /** + * Structure for defining a WebM simple block. + */ + struct WebMSimpleBlock + { + WebMSimpleBlock(WebMTrack *a_pTrack, + WebMTimecodeAbs a_tcAbsPTSMs, const void *a_pvData, size_t a_cbData, WebMBlockFlags a_fFlags) + : pTrack(a_pTrack) + { + Data.tcAbsPTSMs = a_tcAbsPTSMs; + Data.cb = a_cbData; + Data.fFlags = a_fFlags; + + if (Data.cb) + { + Data.pv = RTMemDup(a_pvData, a_cbData); + if (!Data.pv) + throw; + } + } + + virtual ~WebMSimpleBlock() + { + if (Data.pv) + { + Assert(Data.cb); + RTMemFree(Data.pv); + } + } + + WebMTrack *pTrack; + + /** Actual simple block data. */ + struct + { + WebMTimecodeAbs tcAbsPTSMs; + WebMTimecodeRel tcRelToClusterMs; + void *pv; + size_t cb; + WebMBlockFlags fFlags; + } Data; + }; + + /** A simple block queue.*/ + typedef std::queue<WebMSimpleBlock *> WebMSimpleBlockQueue; + + /** Structure for queuing all simple blocks bound to a single timecode. + * This can happen if multiple tracks are being involved. */ + struct WebMTimecodeBlocks + { + WebMTimecodeBlocks(void) + : fClusterNeeded(false) + , fClusterStarted(false) { } + + /** The actual block queue for this timecode. */ + WebMSimpleBlockQueue Queue; + /** Whether a new cluster is needed for this timecode or not. */ + bool fClusterNeeded; + /** Whether a new cluster already has been started for this timecode or not. */ + bool fClusterStarted; + + /** + * Enqueues a simple block into the internal queue. + * + * @param a_pBlock Block to enqueue and take ownership of. + */ + void Enqueue(WebMSimpleBlock *a_pBlock) + { + Queue.push(a_pBlock); + + if (a_pBlock->Data.fFlags & VBOX_WEBM_BLOCK_FLAG_KEY_FRAME) + fClusterNeeded = true; + } + }; + + /** A block map containing all currently queued blocks. + * The key specifies a unique timecode, whereas the value + * is a queue of blocks which all correlate to the key (timecode). */ + typedef std::map<WebMTimecodeAbs, WebMTimecodeBlocks> WebMBlockMap; + + /** + * Structure for defining a WebM (encoding) queue. + */ + struct WebMQueue + { + WebMQueue(void) + : tcAbsLastBlockWrittenMs(0) + , tsLastProcessedMs(0) { } + + /** Blocks as FIFO (queue). */ + WebMBlockMap Map; + /** Absolute timecode (in ms) of last written block to queue. */ + WebMTimecodeAbs tcAbsLastBlockWrittenMs; + /** Time stamp (in ms) of when the queue was processed last. */ + uint64_t tsLastProcessedMs; + }; + + /** + * Structure for keeping a WebM track entry. + */ + struct WebMTrack + { + WebMTrack(WebMTrackType a_enmType, uint8_t a_uTrack, uint64_t a_offID) + : enmType(a_enmType) + , uTrack(a_uTrack) + , offUUID(a_offID) + , cTotalBlocks(0) + , tcAbsLastWrittenMs(0) + { + uUUID = RTRandU32(); + } + + /** The type of this track. */ + WebMTrackType enmType; + /** Track parameters. */ + union + { + struct + { + /** Sample rate of input data. */ + uint32_t uHz; + /** Duration of the frame in samples (per channel). + * Valid frame size are: + * + * ms Frame size + * 2.5 120 + * 5 240 + * 10 480 + * 20 (Default) 960 + * 40 1920 + * 60 2880 + */ + uint16_t framesPerBlock; + /** How many milliseconds (ms) one written (simple) block represents. */ + uint16_t msPerBlock; + } Audio; + }; + /** This track's track number. Also used as key in track map. */ + uint8_t uTrack; + /** The track's "UUID". + * Needed in case this track gets mux'ed with tracks from other files. Not really unique though. */ + uint32_t uUUID; + /** Absolute offset in file of track UUID. + * Needed to write the hash sum within the footer. */ + uint64_t offUUID; + /** Total number of blocks. */ + uint64_t cTotalBlocks; + /** Absoute timecode (in ms) of last write. */ + WebMTimecodeAbs tcAbsLastWrittenMs; + }; + + /** + * Structure for a single cue point track position entry. + */ + struct WebMCueTrackPosEntry + { + WebMCueTrackPosEntry(uint64_t a_offCluster) + : offCluster(a_offCluster) { } + + /** Offset (in bytes) of the related cluster containing the given position. */ + uint64_t offCluster; + }; + + /** Map for keeping track position entries for a single cue point. + * The key is the track number (*not* UUID!). */ + typedef std::map<uint8_t, WebMCueTrackPosEntry *> WebMCueTrackPosMap; + + /** + * Structure for keeping a cue point. + */ + struct WebMCuePoint + { + WebMCuePoint(WebMTimecodeAbs a_tcAbs) + : tcAbs(a_tcAbs) { } + + virtual ~WebMCuePoint() + { + Clear(); + } + + void Clear(void) + { + WebMCueTrackPosMap::iterator itTrackPos = Pos.begin(); + while (itTrackPos != Pos.end()) + { + WebMCueTrackPosEntry *pTrackPos = itTrackPos->second; + AssertPtr(pTrackPos); + delete pTrackPos; + + Pos.erase(itTrackPos); + itTrackPos = Pos.begin(); + } + + Assert(Pos.empty()); + } + + /** Map containing all track positions for this specific cue point. */ + WebMCueTrackPosMap Pos; + /** Absolute time code according to the segment time base. */ + WebMTimecodeAbs tcAbs; + }; + + /** List of cue points. */ + typedef std::list<WebMCuePoint *> WebMCuePointList; + + /** + * Structure for keeping a WebM cluster entry. + */ + struct WebMCluster + { + WebMCluster(void) + : uID(0) + , offStart(0) + , fOpen(false) + , tcAbsStartMs(0) + , cBlocks(0) { } + + /** This cluster's ID. */ + uint64_t uID; + /** Absolute offset (in bytes) of this cluster. + * Needed for seeking info table. */ + uint64_t offStart; + /** Whether this cluster element is opened currently. */ + bool fOpen; + /** Absolute timecode (in ms) when this cluster starts. */ + WebMTimecodeAbs tcAbsStartMs; + /** Absolute timecode (in ms) of when last written to this cluster. */ + WebMTimecodeAbs tcAbsLastWrittenMs; + /** Number of (simple) blocks in this cluster. */ + uint64_t cBlocks; + }; + + /** + * Structure for keeping a WebM segment entry. + * + * Current we're only using one segment. + */ + struct WebMSegment + { + WebMSegment(void) + : tcAbsStartMs(0) + , tcAbsLastWrittenMs(0) + , offStart(0) + , offInfo(0) + , offSeekInfo(0) + , offTracks(0) + , offCues(0) + , cClusters(0) + { + uTimecodeScaleFactor = VBOX_WEBM_TIMECODESCALE_FACTOR_MS; + + LogFunc(("Default timecode scale is: %RU64ns\n", uTimecodeScaleFactor)); + } + + virtual ~WebMSegment() + { + uninit(); + } + + /** + * Initializes a segment. + * + * @returns IPRT status code. + */ + int init(void) + { + return RTCritSectInit(&CritSect); + } + + /** + * Uninitializes a segment. + */ + void uninit(void) + { + clear(); + + RTCritSectDelete(&CritSect); + } + + /** + * Clear the segment's data by removing (and freeing) all data. + */ + void clear(void) + { + WebMCuePointList::iterator itCuePoint = lstCuePoints.begin(); + while (itCuePoint != lstCuePoints.end()) + { + WebMCuePoint *pCuePoint = (*itCuePoint); + AssertPtr(pCuePoint); + delete pCuePoint; + + lstCuePoints.erase(itCuePoint); + itCuePoint = lstCuePoints.begin(); + } + + Assert(lstCuePoints.empty()); + } + + /** Critical section for serializing access to this segment. */ + RTCRITSECT CritSect; + + /** The timecode scale factor of this segment. */ + uint64_t uTimecodeScaleFactor; + + /** Absolute timecode (in ms) when starting this segment. */ + WebMTimecodeAbs tcAbsStartMs; + /** Absolute timecode (in ms) of last write. */ + WebMTimecodeAbs tcAbsLastWrittenMs; + + /** Absolute offset (in bytes) of CurSeg. */ + uint64_t offStart; + /** Absolute offset (in bytes) of general info. */ + uint64_t offInfo; + /** Absolute offset (in bytes) of seeking info. */ + uint64_t offSeekInfo; + /** Absolute offset (in bytes) of tracks. */ + uint64_t offTracks; + /** Absolute offset (in bytes) of cues table. */ + uint64_t offCues; + /** List of cue points. Needed for seeking table. */ + WebMCuePointList lstCuePoints; + + /** Total number of clusters. */ + uint64_t cClusters; + + /** Map of tracks. + * The key marks the track number (*not* the UUID!). */ + std::map <uint8_t, WebMTrack *> mapTracks; + + /** Current cluster which is being handled. + * + * Note that we don't need (and shouldn't need, as this can be a *lot* of data!) a + * list of all clusters. */ + WebMCluster CurCluster; + + WebMQueue queueBlocks; + + } CurSeg; + + /** Audio codec to use. */ + WebMWriter::AudioCodec m_enmAudioCodec; + /** Video codec to use. */ + WebMWriter::VideoCodec m_enmVideoCodec; + + /** Whether we're currently in the tracks section. */ + bool m_fInTracksSection; + + /** Size of timecodes (in bytes). */ + size_t m_cbTimecode; + /** Maximum value a timecode can have. */ + uint32_t m_uTimecodeMax; + +#ifdef VBOX_WITH_LIBVPX + /** + * Block data for VP8-encoded video data. + */ + struct BlockData_VP8 + { + const vpx_codec_enc_cfg_t *pCfg; + const vpx_codec_cx_pkt_t *pPkt; + }; +#endif /* VBOX_WITH_LIBVPX */ + +#ifdef VBOX_WITH_LIBOPUS + /** + * Block data for Opus-encoded audio data. + */ + struct BlockData_Opus + { + /** Pointer to encoded Opus audio data. */ + const void *pvData; + /** Size (in bytes) of encoded Opus audio data. */ + size_t cbData; + /** PTS (in ms) of encoded Opus audio data. */ + uint64_t uPTSMs; + }; +#endif /* VBOX_WITH_LIBOPUS */ + +public: + + WebMWriter(); + + virtual ~WebMWriter(); + +public: + + int OpenEx(const char *a_pszFilename, PRTFILE a_phFile, + WebMWriter::AudioCodec a_enmAudioCodec, WebMWriter::VideoCodec a_enmVideoCodec); + + int Open(const char *a_pszFilename, uint64_t a_fOpen, + WebMWriter::AudioCodec a_enmAudioCodec, WebMWriter::VideoCodec a_enmVideoCodec); + + int Close(void); + + int AddAudioTrack(uint16_t uHz, uint8_t cChannels, uint8_t cBits, uint8_t *puTrack); + + int AddVideoTrack(uint16_t uWidth, uint16_t uHeight, uint32_t uFPS, uint8_t *puTrack); + + int WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData); + + const com::Utf8Str& GetFileName(void); + + uint64_t GetFileSize(void); + + uint64_t GetAvailableSpace(void); + +protected: + + int init(void); + + void destroy(void); + + int writeHeader(void); + + void writeSeekHeader(void); + + int writeFooter(void); + + int writeSimpleBlockEBML(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock); + + int writeSimpleBlockQueued(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock); + +#ifdef VBOX_WITH_LIBVPX + int writeSimpleBlockVP8(WebMTrack *a_pTrack, const vpx_codec_enc_cfg_t *a_pCfg, const vpx_codec_cx_pkt_t *a_pPkt); +#endif + +#ifdef VBOX_WITH_LIBOPUS + int writeSimpleBlockOpus(WebMTrack *a_pTrack, const void *pvData, size_t cbData, WebMTimecodeAbs tcAbsPTSMs); +#endif + + int processQueue(WebMQueue *pQueue, bool fForce); + +protected: + + typedef std::map <uint8_t, WebMTrack *> WebMTracks; +}; + +#endif /* !MAIN_INCLUDED_WebMWriter_h */ |