- Timestamp:
- Nov 14, 2017 11:09:16 AM (7 years ago)
- Location:
- trunk/src/VBox/Main
- Files:
-
- 5 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/Main/Makefile.kmk
r69241 r69683 782 782 ifdef VBOX_WITH_VIDEOREC 783 783 VBoxC_SOURCES += \ 784 src-client/EBMLWriter.cpp 784 src-client/EBMLWriter.cpp \ 785 src-client/WebMWriter.cpp 785 786 ifdef VBOX_WITH_LIBVPX 786 787 VBoxC_SDKS += VBOX_VPX -
trunk/src/VBox/Main/src-client/DrvAudioVideoRec.cpp
r69240 r69683 90 90 91 91 #include "../../Devices/Audio/DrvAudio.h" 92 #include " EBMLWriter.h"92 #include "WebMWriter.h" 93 93 94 94 #include <iprt/mem.h> -
trunk/src/VBox/Main/src-client/EBMLWriter.cpp
r69195 r69683 1 1 /* $Id$ */ 2 2 /** @file 3 * E bmlWriter.cpp - EBML writer + WebM container handling.3 * EBMLWriter.cpp - EBML writer implementation. 4 4 */ 5 5 … … 22 22 */ 23 23 24 #ifdef LOG_GROUP 25 # undef LOG_GROUP 26 #endif 24 27 #define LOG_GROUP LOG_GROUP_MAIN_DISPLAY 25 28 #include "LoggingNew.h" … … 52 55 #define VBOX_EBMLWRITER_FLAG_HANDLE_INHERITED RT_BIT(0) 53 56 54 class EBMLWriter 55 { 56 public: 57 typedef uint32_t EbmlClassId; 58 59 private: 60 61 struct EbmlSubElement 57 /** Creates an EBML output file using an existing, open file handle. */ 58 int EBMLWriter::createEx(const char *a_pszFile, PRTFILE phFile) 59 { 60 AssertPtrReturn(phFile, VERR_INVALID_POINTER); 61 62 m_hFile = *phFile; 63 m_fFlags |= VBOX_EBMLWRITER_FLAG_HANDLE_INHERITED; 64 m_strFile = a_pszFile; 65 66 return VINF_SUCCESS; 67 } 68 69 /** Creates an EBML output file using a file name. */ 70 int EBMLWriter::create(const char *a_pszFile, uint64_t fOpen) 71 { 72 int rc = RTFileOpen(&m_hFile, a_pszFile, fOpen); 73 if (RT_SUCCESS(rc)) 74 m_strFile = a_pszFile; 75 76 return rc; 77 } 78 79 /** Returns available space on storage. */ 80 uint64_t EBMLWriter::getAvailableSpace(void) 81 { 82 RTFOFF pcbFree; 83 int rc = RTFileQueryFsSizes(m_hFile, NULL, &pcbFree, 0, 0); 84 return (RT_SUCCESS(rc)? (uint64_t)pcbFree : UINT64_MAX); 85 } 86 87 /** Closes the file. */ 88 void EBMLWriter::close(void) 89 { 90 if (!isOpen()) 91 return; 92 93 AssertMsg(m_Elements.size() == 0, 94 ("%zu elements are not closed yet (next element to close is 0x%x)\n", 95 m_Elements.size(), m_Elements.top().classId)); 96 97 if (!(m_fFlags & VBOX_EBMLWRITER_FLAG_HANDLE_INHERITED)) 62 98 { 63 uint64_t offset; 64 EbmlClassId classId; 65 EbmlSubElement(uint64_t offs, EbmlClassId cid) : offset(offs), classId(cid) {} 66 }; 67 68 /** Stack of EBML sub elements. */ 69 std::stack<EbmlSubElement> m_Elements; 70 /** The file's handle. */ 71 RTFILE m_hFile; 72 /** The file's name (path). */ 73 Utf8Str m_strFile; 74 /** Flags. */ 75 uint32_t m_fFlags; 76 77 public: 78 79 EBMLWriter(void) 80 : m_hFile(NIL_RTFILE) 81 , m_fFlags(VBOX_EBMLWRITER_FLAG_NONE) { } 82 83 virtual ~EBMLWriter(void) { close(); } 84 85 public: 86 87 /** Creates an EBML output file using an existing, open file handle. */ 88 inline int createEx(const char *a_pszFile, PRTFILE phFile) 99 RTFileClose(m_hFile); 100 m_hFile = NIL_RTFILE; 101 } 102 103 m_fFlags = VBOX_EBMLWRITER_FLAG_NONE; 104 m_strFile = ""; 105 } 106 107 /** Starts an EBML sub-element. */ 108 EBMLWriter& EBMLWriter::subStart(EbmlClassId classId) 109 { 110 writeClassId(classId); 111 /* store the current file offset. */ 112 m_Elements.push(EbmlSubElement(RTFileTell(m_hFile), classId)); 113 /* Indicates that size of the element 114 * is unkown (as according to EBML specs). 115 */ 116 writeUnsignedInteger(UINT64_C(0x01FFFFFFFFFFFFFF)); 117 return *this; 118 } 119 120 /** Ends an EBML sub-element. */ 121 EBMLWriter& EBMLWriter::subEnd(EbmlClassId classId) 122 { 123 #ifdef VBOX_STRICT 124 /* Class ID on the top of the stack should match the class ID passed 125 * to the function. Otherwise it may mean that we have a bug in the code. 126 */ 127 AssertMsg(!m_Elements.empty(), ("No elements to close anymore\n")); 128 AssertMsg(m_Elements.top().classId == classId, 129 ("Ending sub element 0x%x is in wrong order (next to close is 0x%x)\n", classId, m_Elements.top().classId)); 130 #else 131 RT_NOREF(classId); 132 #endif 133 134 uint64_t uPos = RTFileTell(m_hFile); 135 uint64_t uSize = uPos - m_Elements.top().offset - 8; 136 RTFileSeek(m_hFile, m_Elements.top().offset, RTFILE_SEEK_BEGIN, NULL); 137 138 /* Make sure that size will be serialized as uint64_t. */ 139 writeUnsignedInteger(uSize | UINT64_C(0x0100000000000000)); 140 RTFileSeek(m_hFile, uPos, RTFILE_SEEK_BEGIN, NULL); 141 m_Elements.pop(); 142 return *this; 143 } 144 145 /** Serializes a null-terminated string. */ 146 EBMLWriter& EBMLWriter::serializeString(EbmlClassId classId, const char *str) 147 { 148 writeClassId(classId); 149 uint64_t size = strlen(str); 150 writeSize(size); 151 write(str, size); 152 return *this; 153 } 154 155 /** Serializes an UNSIGNED integer. 156 * If size is zero then it will be detected automatically. */ 157 EBMLWriter& EBMLWriter::serializeUnsignedInteger(EbmlClassId classId, uint64_t parm, size_t size /* = 0 */) 158 { 159 writeClassId(classId); 160 if (!size) size = getSizeOfUInt(parm); 161 writeSize(size); 162 writeUnsignedInteger(parm, size); 163 return *this; 164 } 165 166 /** Serializes a floating point value. 167 * 168 * Only 8-bytes double precision values are supported 169 * by this function. 170 */ 171 EBMLWriter& EBMLWriter::serializeFloat(EbmlClassId classId, float value) 172 { 173 writeClassId(classId); 174 Assert(sizeof(uint32_t) == sizeof(float)); 175 writeSize(sizeof(float)); 176 177 union 89 178 { 90 AssertPtrReturn(phFile, VERR_INVALID_POINTER); 91 92 m_hFile = *phFile; 93 m_fFlags |= VBOX_EBMLWRITER_FLAG_HANDLE_INHERITED; 94 m_strFile = a_pszFile; 95 96 return VINF_SUCCESS; 97 } 98 99 /** Creates an EBML output file using a file name. */ 100 inline int create(const char *a_pszFile, uint64_t fOpen) 101 { 102 int rc = RTFileOpen(&m_hFile, a_pszFile, fOpen); 103 if (RT_SUCCESS(rc)) 104 m_strFile = a_pszFile; 105 106 return rc; 107 } 108 109 /** Returns the file name. */ 110 inline const Utf8Str& getFileName(void) 111 { 112 return m_strFile; 113 } 114 115 /** Returns file size. */ 116 inline uint64_t getFileSize(void) 117 { 118 return RTFileTell(m_hFile); 119 } 120 121 /** Get reference to file descriptor */ 122 inline const RTFILE &getFile(void) 123 { 124 return m_hFile; 125 } 126 127 /** Returns available space on storage. */ 128 inline uint64_t getAvailableSpace(void) 129 { 130 RTFOFF pcbFree; 131 int rc = RTFileQueryFsSizes(m_hFile, NULL, &pcbFree, 0, 0); 132 return (RT_SUCCESS(rc)? (uint64_t)pcbFree : UINT64_MAX); 133 } 134 135 /** Closes the file. */ 136 inline void close(void) 137 { 138 if (!isOpen()) 139 return; 140 141 AssertMsg(m_Elements.size() == 0, 142 ("%zu elements are not closed yet (next element to close is 0x%x)\n", 143 m_Elements.size(), m_Elements.top().classId)); 144 145 if (!(m_fFlags & VBOX_EBMLWRITER_FLAG_HANDLE_INHERITED)) 146 { 147 RTFileClose(m_hFile); 148 m_hFile = NIL_RTFILE; 149 } 150 151 m_fFlags = VBOX_EBMLWRITER_FLAG_NONE; 152 m_strFile = ""; 153 } 154 155 /** 156 * Returns whether the file is open or not. 157 * 158 * @returns True if open, false if not. 179 float f; 180 uint8_t u8[4]; 181 } u; 182 183 u.f = value; 184 185 for (int i = 3; i >= 0; i--) /* Converts values to big endian. */ 186 write(&u.u8[i], 1); 187 188 return *this; 189 } 190 191 /** Serializes binary data. */ 192 EBMLWriter& EBMLWriter::serializeData(EbmlClassId classId, const void *pvData, size_t cbData) 193 { 194 writeClassId(classId); 195 writeSize(cbData); 196 write(pvData, cbData); 197 return *this; 198 } 199 200 /** Writes raw data to file. */ 201 int EBMLWriter::write(const void *data, size_t size) 202 { 203 return RTFileWrite(m_hFile, data, size, NULL); 204 } 205 206 /** Writes an unsigned integer of variable of fixed size. */ 207 void EBMLWriter::writeUnsignedInteger(uint64_t value, size_t size /* = sizeof(uint64_t) */) 208 { 209 /* convert to big-endian */ 210 value = RT_H2BE_U64(value); 211 write(reinterpret_cast<uint8_t*>(&value) + sizeof(value) - size, size); 212 } 213 214 /** Writes EBML class ID to file. 215 * 216 * EBML ID already has a UTF8-like represenation 217 * so getSizeOfUInt is used to determine 218 * the number of its bytes. 219 */ 220 void EBMLWriter::writeClassId(EbmlClassId parm) 221 { 222 writeUnsignedInteger(parm, getSizeOfUInt(parm)); 223 } 224 225 /** Writes data size value. */ 226 void EBMLWriter::writeSize(uint64_t parm) 227 { 228 /* The following expression defines the size of the value that will be serialized 229 * as an EBML UTF-8 like integer (with trailing bits represeting its size): 230 1xxx xxxx - value 0 to 2^7-2 231 01xx xxxx xxxx xxxx - value 0 to 2^14-2 232 001x xxxx xxxx xxxx xxxx xxxx - value 0 to 2^21-2 233 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^28-2 234 0000 1xxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^35-2 235 0000 01xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^42-2 236 0000 001x xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^49-2 237 0000 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^56-2 159 238 */ 160 inline bool isOpen(void) 161 { 162 return RTFileIsValid(m_hFile); 163 } 164 165 /** Starts an EBML sub-element. */ 166 inline EBMLWriter &subStart(EbmlClassId classId) 167 { 168 writeClassId(classId); 169 /* store the current file offset. */ 170 m_Elements.push(EbmlSubElement(RTFileTell(m_hFile), classId)); 171 /* Indicates that size of the element 172 * is unkown (as according to EBML specs). 173 */ 174 writeUnsignedInteger(UINT64_C(0x01FFFFFFFFFFFFFF)); 175 return *this; 176 } 177 178 /** Ends an EBML sub-element. */ 179 inline EBMLWriter &subEnd(EbmlClassId classId) 180 { 181 #ifdef VBOX_STRICT 182 /* Class ID on the top of the stack should match the class ID passed 183 * to the function. Otherwise it may mean that we have a bug in the code. 184 */ 185 AssertMsg(!m_Elements.empty(), ("No elements to close anymore\n")); 186 AssertMsg(m_Elements.top().classId == classId, 187 ("Ending sub element 0x%x is in wrong order (next to close is 0x%x)\n", classId, m_Elements.top().classId)); 188 #else 189 RT_NOREF(classId); 190 #endif 191 192 uint64_t uPos = RTFileTell(m_hFile); 193 uint64_t uSize = uPos - m_Elements.top().offset - 8; 194 RTFileSeek(m_hFile, m_Elements.top().offset, RTFILE_SEEK_BEGIN, NULL); 195 196 /* Make sure that size will be serialized as uint64_t. */ 197 writeUnsignedInteger(uSize | UINT64_C(0x0100000000000000)); 198 RTFileSeek(m_hFile, uPos, RTFILE_SEEK_BEGIN, NULL); 199 m_Elements.pop(); 200 return *this; 201 } 202 203 /** Serializes a null-terminated string. */ 204 inline EBMLWriter &serializeString(EbmlClassId classId, const char *str) 205 { 206 writeClassId(classId); 207 uint64_t size = strlen(str); 208 writeSize(size); 209 write(str, size); 210 return *this; 211 } 212 213 /* Serializes an UNSIGNED integer 214 * If size is zero then it will be detected automatically. */ 215 inline EBMLWriter &serializeUnsignedInteger(EbmlClassId classId, uint64_t parm, size_t size = 0) 216 { 217 writeClassId(classId); 218 if (!size) size = getSizeOfUInt(parm); 219 writeSize(size); 220 writeUnsignedInteger(parm, size); 221 return *this; 222 } 223 224 /** Serializes a floating point value. 225 * 226 * Only 8-bytes double precision values are supported 227 * by this function. 228 */ 229 inline EBMLWriter &serializeFloat(EbmlClassId classId, float value) 230 { 231 writeClassId(classId); 232 Assert(sizeof(uint32_t) == sizeof(float)); 233 writeSize(sizeof(float)); 234 235 union 236 { 237 float f; 238 uint8_t u8[4]; 239 } u; 240 241 u.f = value; 242 243 for (int i = 3; i >= 0; i--) /* Converts values to big endian. */ 244 write(&u.u8[i], 1); 245 246 return *this; 247 } 248 249 /** Serializes binary data. */ 250 inline EBMLWriter &serializeData(EbmlClassId classId, const void *pvData, size_t cbData) 251 { 252 writeClassId(classId); 253 writeSize(cbData); 254 write(pvData, cbData); 255 return *this; 256 } 257 258 /** Writes raw data to file. */ 259 inline int write(const void *data, size_t size) 260 { 261 return RTFileWrite(m_hFile, data, size, NULL); 262 } 263 264 /** Writes an unsigned integer of variable of fixed size. */ 265 inline void writeUnsignedInteger(uint64_t value, size_t size = sizeof(uint64_t)) 266 { 267 /* convert to big-endian */ 268 value = RT_H2BE_U64(value); 269 write(reinterpret_cast<uint8_t*>(&value) + sizeof(value) - size, size); 270 } 271 272 /** Writes EBML class ID to file. 273 * 274 * EBML ID already has a UTF8-like represenation 275 * so getSizeOfUInt is used to determine 276 * the number of its bytes. 277 */ 278 inline void writeClassId(EbmlClassId parm) 279 { 280 writeUnsignedInteger(parm, getSizeOfUInt(parm)); 281 } 282 283 /** Writes data size value. */ 284 inline void writeSize(uint64_t parm) 285 { 286 /* The following expression defines the size of the value that will be serialized 287 * as an EBML UTF-8 like integer (with trailing bits represeting its size): 288 1xxx xxxx - value 0 to 2^7-2 289 01xx xxxx xxxx xxxx - value 0 to 2^14-2 290 001x xxxx xxxx xxxx xxxx xxxx - value 0 to 2^21-2 291 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^28-2 292 0000 1xxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^35-2 293 0000 01xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^42-2 294 0000 001x xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^49-2 295 0000 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^56-2 296 */ 297 size_t size = 8 - ! (parm & (UINT64_MAX << 49)) - ! (parm & (UINT64_MAX << 42)) - 298 ! (parm & (UINT64_MAX << 35)) - ! (parm & (UINT64_MAX << 28)) - 299 ! (parm & (UINT64_MAX << 21)) - ! (parm & (UINT64_MAX << 14)) - 300 ! (parm & (UINT64_MAX << 7)); 301 /* One is subtracted in order to avoid loosing significant bit when size = 8. */ 302 uint64_t mask = RT_BIT_64(size * 8 - 1); 303 writeUnsignedInteger((parm & (((mask << 1) - 1) >> size)) | (mask >> (size - 1)), size); 304 } 305 306 /** Size calculation for variable size UNSIGNED integer. 307 * 308 * The function defines the size of the number by trimming 309 * consequent trailing zero bytes starting from the most significant. 310 * The following statement is always true: 311 * 1 <= getSizeOfUInt(arg) <= 8. 312 * 313 * Every !(arg & (UINT64_MAX << X)) expression gives one 314 * if an only if all the bits from X to 63 are set to zero. 315 */ 316 static inline size_t getSizeOfUInt(uint64_t arg) 317 { 318 return 8 - ! (arg & (UINT64_MAX << 56)) - ! (arg & (UINT64_MAX << 48)) - 319 ! (arg & (UINT64_MAX << 40)) - ! (arg & (UINT64_MAX << 32)) - 320 ! (arg & (UINT64_MAX << 24)) - ! (arg & (UINT64_MAX << 16)) - 321 ! (arg & (UINT64_MAX << 8)); 322 } 323 324 private: 325 void operator=(const EBMLWriter &); 326 327 }; 328 329 /** No flags specified. */ 330 #define VBOX_WEBM_BLOCK_FLAG_NONE 0 331 /** Invisible block which can be skipped. */ 332 #define VBOX_WEBM_BLOCK_FLAG_INVISIBLE 0x08 333 /** The block marks a key frame. */ 334 #define VBOX_WEBM_BLOCK_FLAG_KEY_FRAME 0x80 335 336 /** The default timecode scale factor for WebM -- all timecodes in the segments are expressed in ms. 337 * This allows every cluster to have blocks with positive values up to 32.767 seconds. */ 338 #define VBOX_WEBM_TIMECODESCALE_FACTOR_MS 1000000 339 340 /** Maximum time (in ms) a cluster can store. */ 341 #define VBOX_WEBM_CLUSTER_MAX_LEN_MS INT16_MAX 342 343 /** Maximum time a block can store. 344 * With signed 16-bit timecodes and a default timecode scale of 1ms per unit this makes 65536ms. */ 345 #define VBOX_WEBM_BLOCK_MAX_LEN_MS UINT16_MAX 346 347 #ifdef VBOX_WITH_LIBOPUS 348 # pragma pack(push) 349 # pragma pack(1) 350 /** Opus codec private data within the MKV (WEBM) container. 351 * Taken from: https://wiki.xiph.org/MatroskaOpus */ 352 typedef struct WEBMOPUSPRIVDATA 353 { 354 WEBMOPUSPRIVDATA(uint32_t a_u32SampleRate, uint8_t a_u8Channels) 355 { 356 au64Head = RT_MAKE_U64_FROM_U8('O', 'p', 'u', 's', 'H', 'e', 'a', 'd'); 357 u8Version = 1; 358 u8Channels = a_u8Channels; 359 u16PreSkip = 0; 360 u32SampleRate = a_u32SampleRate; 361 u16Gain = 0; 362 u8MappingFamily = 0; 363 } 364 365 uint64_t au64Head; /**< Defaults to "OpusHead". */ 366 uint8_t u8Version; /**< Must be set to 1. */ 367 uint8_t u8Channels; 368 uint16_t u16PreSkip; 369 /** Sample rate *before* encoding to Opus. 370 * Note: This rate has nothing to do with the playback rate later! */ 371 uint32_t u32SampleRate; 372 uint16_t u16Gain; 373 /** Must stay 0 -- otherwise a mapping table must be appended 374 * right after this header. */ 375 uint8_t u8MappingFamily; 376 } WEBMOPUSPRIVDATA, *PWEBMOPUSPRIVDATA; 377 AssertCompileSize(WEBMOPUSPRIVDATA, 19); 378 # pragma pack(pop) 379 #endif /* VBOX_WITH_LIBOPUS */ 380 381 class WebMWriter_Impl 382 { 383 /** Defines a WebM timecode. */ 384 typedef uint16_t WebMTimecode; 385 386 /** Defines the WebM block flags data type. */ 387 typedef uint8_t WebMBlockFlags; 388 389 /** 390 * Track type enumeration. 391 */ 392 enum WebMTrackType 393 { 394 /** Unknown / invalid type. */ 395 WebMTrackType_Invalid = 0, 396 /** Only writes audio. */ 397 WebMTrackType_Audio = 1, 398 /** Only writes video. */ 399 WebMTrackType_Video = 2 400 }; 401 402 struct WebMTrack; 403 404 /** 405 * Structure for defining a WebM simple block. 406 */ 407 struct WebMSimpleBlock 408 { 409 WebMSimpleBlock(WebMTrack *a_pTrack, 410 WebMTimecode a_tcPTSMs, const void *a_pvData, size_t a_cbData, WebMBlockFlags a_fFlags) 411 : pTrack(a_pTrack) 412 { 413 Data.tcPTSMs = a_tcPTSMs; 414 Data.cb = a_cbData; 415 Data.fFlags = a_fFlags; 416 417 if (Data.cb) 418 { 419 Data.pv = RTMemDup(a_pvData, a_cbData); 420 if (!Data.pv) 421 throw; 422 } 423 } 424 425 virtual ~WebMSimpleBlock() 426 { 427 if (Data.pv) 428 { 429 Assert(Data.cb); 430 RTMemFree(Data.pv); 431 } 432 } 433 434 WebMTrack *pTrack; 435 436 /** Actual simple block data. */ 437 struct 438 { 439 WebMTimecode tcPTSMs; 440 WebMTimecode tcRelToClusterMs; 441 void *pv; 442 size_t cb; 443 WebMBlockFlags fFlags; 444 } Data; 445 }; 446 447 /** A simple block queue.*/ 448 typedef std::queue<WebMSimpleBlock *> WebMSimpleBlockQueue; 449 450 /** Structure for queuing all simple blocks bound to a single timecode. 451 * This can happen if multiple tracks are being involved. */ 452 struct WebMTimecodeBlocks 453 { 454 WebMTimecodeBlocks(void) 455 : fClusterNeeded(false) 456 , fClusterStarted(false) { } 457 458 /** The actual block queue for this timecode. */ 459 WebMSimpleBlockQueue Queue; 460 /** Whether a new cluster is needed for this timecode or not. */ 461 bool fClusterNeeded; 462 /** Whether a new cluster already has been started for this timecode or not. */ 463 bool fClusterStarted; 464 465 /** 466 * Enqueues a simple block into the internal queue. 467 * 468 * @param a_pBlock Block to enqueue and take ownership of. 469 */ 470 void Enqueue(WebMSimpleBlock *a_pBlock) 471 { 472 Queue.push(a_pBlock); 473 474 if (a_pBlock->Data.fFlags & VBOX_WEBM_BLOCK_FLAG_KEY_FRAME) 475 fClusterNeeded = true; 476 } 477 }; 478 479 /** A block map containing all currently queued blocks. 480 * The key specifies a unique timecode, whereas the value 481 * is a queue of blocks which all correlate to the key (timecode). */ 482 typedef std::map<WebMTimecode, WebMTimecodeBlocks> WebMBlockMap; 483 484 /** 485 * Structure for defining a WebM (encoding) queue. 486 */ 487 struct WebMQueue 488 { 489 WebMQueue(void) 490 : tcLastBlockWrittenMs(0) 491 , tslastProcessedMs(0) { } 492 493 /** Blocks as FIFO (queue). */ 494 WebMBlockMap Map; 495 /** Timecode (in ms) of last written block to queue. */ 496 WebMTimecode tcLastBlockWrittenMs; 497 /** Time stamp (in ms) of when the queue was processed last. */ 498 uint64_t tslastProcessedMs; 499 }; 500 501 /** 502 * Structure for keeping a WebM track entry. 503 */ 504 struct WebMTrack 505 { 506 WebMTrack(WebMTrackType a_enmType, uint8_t a_uTrack, uint64_t a_offID) 507 : enmType(a_enmType) 508 , uTrack(a_uTrack) 509 , offUUID(a_offID) 510 , cTotalBlocks(0) 511 , tcLastWrittenMs(0) 512 { 513 uUUID = RTRandU32(); 514 } 515 516 /** The type of this track. */ 517 WebMTrackType enmType; 518 /** Track parameters. */ 519 union 520 { 521 struct 522 { 523 /** Sample rate of input data. */ 524 uint32_t uHz; 525 /** Duration of the frame in samples (per channel). 526 * Valid frame size are: 527 * 528 * ms Frame size 529 * 2.5 120 530 * 5 240 531 * 10 480 532 * 20 (Default) 960 533 * 40 1920 534 * 60 2880 535 */ 536 uint16_t framesPerBlock; 537 /** How many milliseconds (ms) one written (simple) block represents. */ 538 uint16_t msPerBlock; 539 } Audio; 540 }; 541 /** This track's track number. Also used as key in track map. */ 542 uint8_t uTrack; 543 /** The track's "UUID". 544 * Needed in case this track gets mux'ed with tracks from other files. Not really unique though. */ 545 uint32_t uUUID; 546 /** Absolute offset in file of track UUID. 547 * Needed to write the hash sum within the footer. */ 548 uint64_t offUUID; 549 /** Total number of blocks. */ 550 uint64_t cTotalBlocks; 551 /** Timecode (in ms) of last write. */ 552 WebMTimecode tcLastWrittenMs; 553 }; 554 555 /** 556 * Structure for keeping a cue point. 557 */ 558 struct WebMCuePoint 559 { 560 WebMCuePoint(WebMTrack *a_pTrack, uint64_t a_offCluster, WebMTimecode a_tcAbs) 561 : pTrack(a_pTrack) 562 , offCluster(a_offCluster), tcAbs(a_tcAbs) { } 563 564 /** Associated track. */ 565 WebMTrack *pTrack; 566 /** Offset (in bytes) of the related cluster containing the given position. */ 567 uint64_t offCluster; 568 /** Time code (absolute) of this cue point. */ 569 WebMTimecode tcAbs; 570 }; 571 572 /** 573 * Structure for keeping a WebM cluster entry. 574 */ 575 struct WebMCluster 576 { 577 WebMCluster(void) 578 : uID(0) 579 , offStart(0) 580 , fOpen(false) 581 , tcStartMs(0) 582 , cBlocks(0) { } 583 584 /** This cluster's ID. */ 585 uint64_t uID; 586 /** Absolute offset (in bytes) of this cluster. 587 * Needed for seeking info table. */ 588 uint64_t offStart; 589 /** Whether this cluster element is opened currently. */ 590 bool fOpen; 591 /** Timecode (in ms) when this cluster starts. */ 592 WebMTimecode tcStartMs; 593 /** Number of (simple) blocks in this cluster. */ 594 uint64_t cBlocks; 595 }; 596 597 /** 598 * Structure for keeping a WebM segment entry. 599 * 600 * Current we're only using one segment. 601 */ 602 struct WebMSegment 603 { 604 WebMSegment(void) 605 : tcStartMs(0) 606 , tcLastWrittenMs(0) 607 , offStart(0) 608 , offInfo(0) 609 , offSeekInfo(0) 610 , offTracks(0) 611 , offCues(0) 612 { 613 uTimecodeScaleFactor = VBOX_WEBM_TIMECODESCALE_FACTOR_MS; 614 615 LogFunc(("Default timecode scale is: %RU64ns\n", uTimecodeScaleFactor)); 616 } 617 618 int init(void) 619 { 620 return RTCritSectInit(&CritSect); 621 } 622 623 void destroy(void) 624 { 625 RTCritSectDelete(&CritSect); 626 } 627 628 /** Critical section for serializing access to this segment. */ 629 RTCRITSECT CritSect; 630 631 /** The timecode scale factor of this segment. */ 632 uint64_t uTimecodeScaleFactor; 633 634 /** Timecode (in ms) when starting this segment. */ 635 WebMTimecode tcStartMs; 636 /** Timecode (in ms) of last write. */ 637 WebMTimecode tcLastWrittenMs; 638 639 /** Absolute offset (in bytes) of CurSeg. */ 640 uint64_t offStart; 641 /** Absolute offset (in bytes) of general info. */ 642 uint64_t offInfo; 643 /** Absolute offset (in bytes) of seeking info. */ 644 uint64_t offSeekInfo; 645 /** Absolute offset (in bytes) of tracks. */ 646 uint64_t offTracks; 647 /** Absolute offset (in bytes) of cues table. */ 648 uint64_t offCues; 649 /** List of cue points. Needed for seeking table. */ 650 std::list<WebMCuePoint> lstCues; 651 652 /** Total number of clusters. */ 653 uint64_t cClusters; 654 655 /** Map of tracks. 656 * The key marks the track number (*not* the UUID!). */ 657 std::map <uint8_t, WebMTrack *> mapTracks; 658 659 /** Current cluster which is being handled. 660 * 661 * Note that we don't need (and shouldn't need, as this can be a *lot* of data!) a 662 * list of all clusters. */ 663 WebMCluster CurCluster; 664 665 WebMQueue queueBlocks; 666 667 } CurSeg; 668 669 /** Audio codec to use. */ 670 WebMWriter::AudioCodec m_enmAudioCodec; 671 /** Video codec to use. */ 672 WebMWriter::VideoCodec m_enmVideoCodec; 673 674 /** Whether we're currently in the tracks section. */ 675 bool m_fInTracksSection; 676 677 /** Size of timecodes in bytes. */ 678 size_t m_cbTimecode; 679 /** Maximum value a timecode can have. */ 680 uint32_t m_uTimecodeMax; 681 682 EBMLWriter m_Ebml; 683 684 public: 685 686 typedef std::map <uint8_t, WebMTrack *> WebMTracks; 687 688 public: 689 690 WebMWriter_Impl() : 691 m_fInTracksSection(false) 692 { 693 /* Size (in bytes) of time code to write. We use 2 bytes (16 bit) by default. */ 694 m_cbTimecode = 2; 695 m_uTimecodeMax = UINT16_MAX; 696 } 697 698 virtual ~WebMWriter_Impl() 699 { 700 close(); 701 } 702 703 int init(void) 704 { 705 return CurSeg.init(); 706 } 707 708 void destroy(void) 709 { 710 CurSeg.destroy(); 711 } 712 713 /** 714 * Adds an audio track. 715 * 716 * @returns IPRT status code. 717 * @param uHz Input sampling rate. 718 * Must be supported by the selected audio codec. 719 * @param cChannels Number of input audio channels. 720 * @param cBits Number of input bits per channel. 721 * @param puTrack Track number on successful creation. Optional. 722 */ 723 int AddAudioTrack(uint16_t uHz, uint8_t cChannels, uint8_t cBits, uint8_t *puTrack) 724 { 725 #ifdef VBOX_WITH_LIBOPUS 726 int rc; 727 728 /* 729 * Check if the requested codec rate is supported. 730 * 731 * Only the following values are supported by an Opus standard build 732 * -- every other rate only is supported by a custom build. 733 */ 734 switch (uHz) 735 { 736 case 48000: 737 case 24000: 738 case 16000: 739 case 12000: 740 case 8000: 741 rc = VINF_SUCCESS; 742 break; 743 744 default: 745 rc = VERR_NOT_SUPPORTED; 746 break; 747 } 748 749 if (RT_FAILURE(rc)) 750 return rc; 751 752 const uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size(); 753 754 m_Ebml.subStart(MkvElem_TrackEntry); 755 756 m_Ebml.serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack); 757 m_Ebml.serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */); 758 m_Ebml.serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0); 759 760 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Audio, uTrack, RTFileTell(m_Ebml.getFile())); 761 762 pTrack->Audio.uHz = uHz; 763 pTrack->Audio.msPerBlock = 20; /** Opus uses 20ms by default. Make this configurable? */ 764 pTrack->Audio.framesPerBlock = uHz / (1000 /* s in ms */ / pTrack->Audio.msPerBlock); 765 766 WEBMOPUSPRIVDATA opusPrivData(uHz, cChannels); 767 768 LogFunc(("Opus @ %RU16Hz (%RU16ms + %RU16 frames per block)\n", 769 pTrack->Audio.uHz, pTrack->Audio.msPerBlock, pTrack->Audio.framesPerBlock)); 770 771 m_Ebml.serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID, 4) 772 .serializeUnsignedInteger(MkvElem_TrackType, 2 /* Audio */) 773 .serializeString(MkvElem_CodecID, "A_OPUS") 774 .serializeData(MkvElem_CodecPrivate, &opusPrivData, sizeof(opusPrivData)) 775 .serializeUnsignedInteger(MkvElem_CodecDelay, 0) 776 .serializeUnsignedInteger(MkvElem_SeekPreRoll, 80 * 1000000) /* 80ms in ns. */ 777 .subStart(MkvElem_Audio) 778 .serializeFloat(MkvElem_SamplingFrequency, (float)uHz) 779 .serializeUnsignedInteger(MkvElem_Channels, cChannels) 780 .serializeUnsignedInteger(MkvElem_BitDepth, cBits) 781 .subEnd(MkvElem_Audio) 782 .subEnd(MkvElem_TrackEntry); 783 784 CurSeg.mapTracks[uTrack] = pTrack; 785 786 if (puTrack) 787 *puTrack = uTrack; 788 789 return VINF_SUCCESS; 790 #else 791 RT_NOREF(uHz, cChannels, cBits, puTrack); 792 return VERR_NOT_SUPPORTED; 793 #endif 794 } 795 796 int AddVideoTrack(uint16_t uWidth, uint16_t uHeight, double dbFPS, uint8_t *puTrack) 797 { 798 #ifdef VBOX_WITH_LIBVPX 799 RT_NOREF(dbFPS); 800 801 const uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size(); 802 803 m_Ebml.subStart(MkvElem_TrackEntry); 804 805 m_Ebml.serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack); 806 m_Ebml.serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */); 807 m_Ebml.serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0); 808 809 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Video, uTrack, RTFileTell(m_Ebml.getFile())); 810 811 /** @todo Resolve codec type. */ 812 m_Ebml.serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID /* UID */, 4) 813 .serializeUnsignedInteger(MkvElem_TrackType, 1 /* Video */) 814 .serializeString(MkvElem_CodecID, "V_VP8") 815 .subStart(MkvElem_Video) 816 .serializeUnsignedInteger(MkvElem_PixelWidth, uWidth) 817 .serializeUnsignedInteger(MkvElem_PixelHeight, uHeight) 818 .subEnd(MkvElem_Video); 819 820 m_Ebml.subEnd(MkvElem_TrackEntry); 821 822 CurSeg.mapTracks[uTrack] = pTrack; 823 824 if (puTrack) 825 *puTrack = uTrack; 826 827 return VINF_SUCCESS; 828 #else 829 RT_NOREF(uWidth, uHeight, dbFPS, puTrack); 830 return VERR_NOT_SUPPORTED; 831 #endif 832 } 833 834 /** 835 * Writes the WebM file header. 836 * 837 * @returns IPRT status code. 838 */ 839 int writeHeader(void) 840 { 841 LogFunc(("Header @ %RU64\n", RTFileTell(m_Ebml.getFile()))); 842 843 m_Ebml.subStart(MkvElem_EBML) 844 .serializeUnsignedInteger(MkvElem_EBMLVersion, 1) 845 .serializeUnsignedInteger(MkvElem_EBMLReadVersion, 1) 846 .serializeUnsignedInteger(MkvElem_EBMLMaxIDLength, 4) 847 .serializeUnsignedInteger(MkvElem_EBMLMaxSizeLength, 8) 848 .serializeString(MkvElem_DocType, "webm") 849 .serializeUnsignedInteger(MkvElem_DocTypeVersion, 2) 850 .serializeUnsignedInteger(MkvElem_DocTypeReadVersion, 2) 851 .subEnd(MkvElem_EBML); 852 853 m_Ebml.subStart(MkvElem_Segment); 854 855 /* Save offset of current segment. */ 856 CurSeg.offStart = RTFileTell(m_Ebml.getFile()); 857 858 writeSegSeekInfo(); 859 860 /* Save offset of upcoming tracks segment. */ 861 CurSeg.offTracks = RTFileTell(m_Ebml.getFile()); 862 863 /* The tracks segment starts right after this header. */ 864 m_Ebml.subStart(MkvElem_Tracks); 865 m_fInTracksSection = true; 866 867 return VINF_SUCCESS; 868 } 869 870 /** 871 * Writes a simple block into the EBML structure. 872 * 873 * @returns IPRT status code. 874 * @param a_pTrack Track the simple block is assigned to. 875 * @param a_pBlock Simple block to write. 876 */ 877 int writeSimpleBlockEBML(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock) 878 { 879 #ifdef LOG_ENABLED 880 WebMCluster &Cluster = CurSeg.CurCluster; 881 882 Log3Func(("[T%RU8C%RU64] Off=%RU64, PTS=%RU16, RelToClusterMs=%RU16, %zu bytes\n", 883 a_pTrack->uTrack, Cluster.uID, RTFileTell(m_Ebml.getFile()), 884 a_pBlock->Data.tcPTSMs, a_pBlock->Data.tcRelToClusterMs, a_pBlock->Data.cb)); 885 #endif 886 /* 887 * Write a "Simple Block". 888 */ 889 m_Ebml.writeClassId(MkvElem_SimpleBlock); 890 /* Block size. */ 891 m_Ebml.writeUnsignedInteger(0x10000000u | ( 1 /* Track number size. */ 892 + m_cbTimecode /* Timecode size .*/ 893 + 1 /* Flags size. */ 894 + a_pBlock->Data.cb /* Actual frame data size. */), 4); 895 /* Track number. */ 896 m_Ebml.writeSize(a_pTrack->uTrack); 897 /* Timecode (relative to cluster opening timecode). */ 898 m_Ebml.writeUnsignedInteger(a_pBlock->Data.tcRelToClusterMs, m_cbTimecode); 899 /* Flags. */ 900 m_Ebml.writeUnsignedInteger(a_pBlock->Data.fFlags, 1); 901 /* Frame data. */ 902 m_Ebml.write(a_pBlock->Data.pv, a_pBlock->Data.cb); 903 904 return VINF_SUCCESS; 905 } 906 907 /** 908 * Writes a simple block and enqueues it into the segment's render queue. 909 * 910 * @returns IPRT status code. 911 * @param a_pTrack Track the simple block is assigned to. 912 * @param a_pBlock Simple block to write and enqueue. 913 */ 914 int writeSimpleBlockQueued(WebMTrack *a_pTrack, WebMSimpleBlock *a_pBlock) 915 { 916 RT_NOREF(a_pTrack); 917 918 int rc = VINF_SUCCESS; 919 920 try 921 { 922 const WebMTimecode tcMap = a_pBlock->Data.tcPTSMs; 923 924 /* See if we already have an entry for the specified timecode in our queue. */ 925 WebMBlockMap::iterator itQueue = CurSeg.queueBlocks.Map.find(tcMap); 926 if (itQueue != CurSeg.queueBlocks.Map.end()) /* Use existing queue. */ 927 { 928 WebMTimecodeBlocks &Blocks = itQueue->second; 929 Blocks.Enqueue(a_pBlock); 930 } 931 else /* Create a new timecode entry. */ 932 { 933 WebMTimecodeBlocks Blocks; 934 Blocks.Enqueue(a_pBlock); 935 936 CurSeg.queueBlocks.Map[tcMap] = Blocks; 937 } 938 939 processQueues(&CurSeg.queueBlocks, false /* fForce */); 940 } 941 catch(...) 942 { 943 delete a_pBlock; 944 a_pBlock = NULL; 945 946 rc = VERR_NO_MEMORY; 947 } 948 949 return rc; 950 } 951 952 #ifdef VBOX_WITH_LIBVPX 953 /** 954 * Writes VPX (VP8 video) simple data block. 955 * 956 * @returns IPRT status code. 957 * @param a_pTrack Track ID to write data to. 958 * @param a_pCfg VPX encoder configuration to use. 959 * @param a_pPkt VPX packet video data packet to write. 960 */ 961 int writeSimpleBlockVP8(WebMTrack *a_pTrack, const vpx_codec_enc_cfg_t *a_pCfg, const vpx_codec_cx_pkt_t *a_pPkt) 962 { 963 RT_NOREF(a_pTrack); 964 965 /* Calculate the PTS of this frame (in ms). */ 966 WebMTimecode tcPTSMs = a_pPkt->data.frame.pts * 1000 967 * (uint64_t) a_pCfg->g_timebase.num / a_pCfg->g_timebase.den; 968 969 if ( tcPTSMs 970 && tcPTSMs <= a_pTrack->tcLastWrittenMs) 971 { 972 tcPTSMs = a_pTrack->tcLastWrittenMs + 1; 973 } 974 975 const bool fKeyframe = RT_BOOL(a_pPkt->data.frame.flags & VPX_FRAME_IS_KEY); 976 977 uint8_t fFlags = VBOX_WEBM_BLOCK_FLAG_NONE; 978 if (fKeyframe) 979 fFlags |= VBOX_WEBM_BLOCK_FLAG_KEY_FRAME; 980 if (a_pPkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE) 981 fFlags |= VBOX_WEBM_BLOCK_FLAG_INVISIBLE; 982 983 return writeSimpleBlockQueued(a_pTrack, 984 new WebMSimpleBlock(a_pTrack, 985 tcPTSMs, a_pPkt->data.frame.buf, a_pPkt->data.frame.sz, fFlags)); 986 } 987 #endif /* VBOX_WITH_LIBVPX */ 988 989 #ifdef VBOX_WITH_LIBOPUS 990 /** 991 * Writes an Opus (audio) simple data block. 992 * 993 * @returns IPRT status code. 994 * @param a_pTrack Track ID to write data to. 995 * @param pvData Pointer to simple data block to write. 996 * @param cbData Size (in bytes) of simple data block to write. 997 * @param uPTSMs PTS of simple data block. 998 * 999 * @remarks Audio blocks that have same absolute timecode as video blocks SHOULD be written before the video blocks. 1000 */ 1001 int writeSimpleBlockOpus(WebMTrack *a_pTrack, const void *pvData, size_t cbData, WebMTimecode uPTSMs) 1002 { 1003 AssertPtrReturn(a_pTrack, VERR_INVALID_POINTER); 1004 AssertPtrReturn(pvData, VERR_INVALID_POINTER); 1005 AssertReturn(cbData, VERR_INVALID_PARAMETER); 1006 1007 /* Every Opus frame is a key frame. */ 1008 const uint8_t fFlags = VBOX_WEBM_BLOCK_FLAG_KEY_FRAME; 1009 1010 return writeSimpleBlockQueued(a_pTrack, 1011 new WebMSimpleBlock(a_pTrack, 1012 uPTSMs, pvData, cbData, fFlags)); 1013 } 1014 #endif /* VBOX_WITH_LIBOPUS */ 1015 1016 /** 1017 * Writes a data block to the specified track. 1018 * 1019 * @returns IPRT status code. 1020 * @param uTrack Track ID to write data to. 1021 * @param pvData Pointer to data block to write. 1022 * @param cbData Size (in bytes) of data block to write. 1023 */ 1024 int WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData) 1025 { 1026 RT_NOREF(cbData); /* Only needed for assertions for now. */ 1027 1028 int rc = RTCritSectEnter(&CurSeg.CritSect); 1029 AssertRC(rc); 1030 1031 WebMTracks::iterator itTrack = CurSeg.mapTracks.find(uTrack); 1032 if (itTrack == CurSeg.mapTracks.end()) 1033 { 1034 RTCritSectLeave(&CurSeg.CritSect); 1035 return VERR_NOT_FOUND; 1036 } 1037 1038 WebMTrack *pTrack = itTrack->second; 1039 AssertPtr(pTrack); 1040 1041 if (m_fInTracksSection) 1042 { 1043 m_Ebml.subEnd(MkvElem_Tracks); 1044 m_fInTracksSection = false; 1045 } 1046 1047 switch (pTrack->enmType) 1048 { 1049 1050 case WebMTrackType_Audio: 1051 { 1052 #ifdef VBOX_WITH_LIBOPUS 1053 if (m_enmAudioCodec == WebMWriter::AudioCodec_Opus) 1054 { 1055 Assert(cbData == sizeof(WebMWriter::BlockData_Opus)); 1056 WebMWriter::BlockData_Opus *pData = (WebMWriter::BlockData_Opus *)pvData; 1057 rc = writeSimpleBlockOpus(pTrack, pData->pvData, pData->cbData, pData->uPTSMs); 1058 } 1059 else 1060 #endif /* VBOX_WITH_LIBOPUS */ 1061 rc = VERR_NOT_SUPPORTED; 1062 break; 1063 } 1064 1065 case WebMTrackType_Video: 1066 { 1067 #ifdef VBOX_WITH_LIBVPX 1068 if (m_enmVideoCodec == WebMWriter::VideoCodec_VP8) 1069 { 1070 Assert(cbData == sizeof(WebMWriter::BlockData_VP8)); 1071 WebMWriter::BlockData_VP8 *pData = (WebMWriter::BlockData_VP8 *)pvData; 1072 rc = writeSimpleBlockVP8(pTrack, pData->pCfg, pData->pPkt); 1073 } 1074 else 1075 #endif /* VBOX_WITH_LIBVPX */ 1076 rc = VERR_NOT_SUPPORTED; 1077 break; 1078 } 1079 1080 default: 1081 rc = VERR_NOT_SUPPORTED; 1082 break; 1083 } 1084 1085 int rc2 = RTCritSectLeave(&CurSeg.CritSect); 1086 AssertRC(rc2); 1087 1088 return rc; 1089 } 1090 1091 /** 1092 * Processes a render queue. 1093 * 1094 * @returns IPRT status code. 1095 * @param pQueue Queue to process. 1096 * @param fForce Whether forcing to process the render queue or not. 1097 * Needed to drain the queues when terminating. 1098 */ 1099 int processQueues(WebMQueue *pQueue, bool fForce) 1100 { 1101 if (pQueue->tslastProcessedMs == 0) 1102 pQueue->tslastProcessedMs = RTTimeMilliTS(); 1103 1104 if (!fForce) 1105 { 1106 /* Only process when we reached a certain threshold. */ 1107 if (RTTimeMilliTS() - pQueue->tslastProcessedMs < 5000 /* ms */ /** @todo Make this configurable */) 1108 return VINF_SUCCESS; 1109 } 1110 1111 WebMCluster &Cluster = CurSeg.CurCluster; 1112 1113 /* Iterate through the block map. */ 1114 WebMBlockMap::iterator it = pQueue->Map.begin(); 1115 while (it != CurSeg.queueBlocks.Map.end()) 1116 { 1117 WebMTimecode mapTC = it->first; 1118 RT_NOREF(mapTC); 1119 WebMTimecodeBlocks mapBlocks = it->second; 1120 1121 /* Whether to start a new cluster or not. */ 1122 bool fClusterStart = false; 1123 1124 /* No blocks written yet? Start a new cluster. */ 1125 if (Cluster.cBlocks == 0) 1126 fClusterStart = true; 1127 1128 /* Did we reach the maximum a cluster can hold? Use a new cluster then. */ 1129 if (mapTC - Cluster.tcStartMs > VBOX_WEBM_CLUSTER_MAX_LEN_MS) 1130 fClusterStart = true; 1131 1132 /* If the block map indicates that a cluster is needed for this timecode, create one. */ 1133 if (mapBlocks.fClusterNeeded) 1134 fClusterStart = true; 1135 1136 if ( fClusterStart 1137 && !mapBlocks.fClusterStarted) 1138 { 1139 if (Cluster.fOpen) /* Close current cluster first. */ 1140 { 1141 /* Make sure that the current cluster contained some data. */ 1142 Assert(Cluster.offStart); 1143 Assert(Cluster.cBlocks); 1144 1145 m_Ebml.subEnd(MkvElem_Cluster); 1146 Cluster.fOpen = false; 1147 } 1148 1149 Cluster.fOpen = true; 1150 Cluster.uID = CurSeg.cClusters; 1151 Cluster.tcStartMs = mapTC; 1152 Cluster.offStart = RTFileTell(m_Ebml.getFile()); 1153 Cluster.cBlocks = 0; 1154 1155 if (CurSeg.cClusters) 1156 AssertMsg(Cluster.tcStartMs, ("[C%RU64] @ %RU64 starting timecode is 0 which is invalid\n", 1157 Cluster.uID, Cluster.offStart)); 1158 1159 Log2Func(("[C%RU64] Start @ %RU64ms (map TC is %RU64) / %RU64 bytes\n", 1160 Cluster.uID, Cluster.tcStartMs, mapTC, Cluster.offStart)); 1161 1162 m_Ebml.subStart(MkvElem_Cluster) 1163 .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tcStartMs); 1164 1165 CurSeg.cClusters++; 1166 1167 mapBlocks.fClusterStarted = true; 1168 } 1169 1170 /* Iterate through all blocks related to the current timecode. */ 1171 while (!mapBlocks.Queue.empty()) 1172 { 1173 WebMSimpleBlock *pBlock = mapBlocks.Queue.front(); 1174 AssertPtr(pBlock); 1175 1176 WebMTrack *pTrack = pBlock->pTrack; 1177 AssertPtr(pTrack); 1178 1179 /* Calculate the block's relative time code to the current cluster's starting time code. */ 1180 Assert(pBlock->Data.tcPTSMs >= Cluster.tcStartMs); 1181 pBlock->Data.tcRelToClusterMs = pBlock->Data.tcPTSMs - Cluster.tcStartMs; 1182 1183 int rc2 = writeSimpleBlockEBML(pTrack, pBlock); 1184 AssertRC(rc2); 1185 1186 Cluster.cBlocks++; 1187 1188 pTrack->cTotalBlocks++; 1189 pTrack->tcLastWrittenMs = pBlock->Data.tcPTSMs; 1190 1191 if (CurSeg.tcLastWrittenMs < pTrack->tcLastWrittenMs) 1192 CurSeg.tcLastWrittenMs = pTrack->tcLastWrittenMs; 1193 1194 /* Save a cue point if this is a keyframe. */ 1195 if (pBlock->Data.fFlags & VBOX_WEBM_BLOCK_FLAG_KEY_FRAME) 1196 { 1197 WebMCuePoint cue(pBlock->pTrack, Cluster.offStart, pBlock->Data.tcPTSMs); 1198 CurSeg.lstCues.push_back(cue); 1199 } 1200 1201 delete pBlock; 1202 pBlock = NULL; 1203 1204 mapBlocks.Queue.pop(); 1205 } 1206 1207 Assert(mapBlocks.Queue.empty()); 1208 1209 CurSeg.queueBlocks.Map.erase(it); 1210 1211 it = CurSeg.queueBlocks.Map.begin(); 1212 } 1213 1214 Assert(CurSeg.queueBlocks.Map.empty()); 1215 1216 pQueue->tslastProcessedMs = RTTimeMilliTS(); 1217 1218 return VINF_SUCCESS; 1219 } 1220 1221 /** 1222 * Writes the WebM footer. 1223 * 1224 * @returns IPRT status code. 1225 */ 1226 int writeFooter(void) 1227 { 1228 AssertReturn(m_Ebml.isOpen(), VERR_WRONG_ORDER); 1229 1230 if (m_fInTracksSection) 1231 { 1232 m_Ebml.subEnd(MkvElem_Tracks); 1233 m_fInTracksSection = false; 1234 } 1235 1236 if (CurSeg.CurCluster.fOpen) 1237 { 1238 m_Ebml.subEnd(MkvElem_Cluster); 1239 CurSeg.CurCluster.fOpen = false; 1240 } 1241 1242 /* 1243 * Write Cues element. 1244 */ 1245 LogFunc(("Cues @ %RU64\n", RTFileTell(m_Ebml.getFile()))); 1246 1247 CurSeg.offCues = RTFileTell(m_Ebml.getFile()); 1248 1249 m_Ebml.subStart(MkvElem_Cues); 1250 1251 std::list<WebMCuePoint>::iterator itCuePoint = CurSeg.lstCues.begin(); 1252 while (itCuePoint != CurSeg.lstCues.end()) 1253 { 1254 /* Sanity. */ 1255 AssertPtr(itCuePoint->pTrack); 1256 1257 LogFunc(("CuePoint @ %RU64: Track #%RU8 (Cluster @ %RU64, TC %RU64)\n", 1258 RTFileTell(m_Ebml.getFile()), itCuePoint->pTrack->uTrack, 1259 itCuePoint->offCluster, itCuePoint->tcAbs)); 1260 1261 m_Ebml.subStart(MkvElem_CuePoint) 1262 .serializeUnsignedInteger(MkvElem_CueTime, itCuePoint->tcAbs) 1263 .subStart(MkvElem_CueTrackPositions) 1264 .serializeUnsignedInteger(MkvElem_CueTrack, itCuePoint->pTrack->uTrack) 1265 .serializeUnsignedInteger(MkvElem_CueClusterPosition, itCuePoint->offCluster, 8) 1266 .subEnd(MkvElem_CueTrackPositions) 1267 .subEnd(MkvElem_CuePoint); 1268 1269 itCuePoint++; 1270 } 1271 1272 m_Ebml.subEnd(MkvElem_Cues); 1273 m_Ebml.subEnd(MkvElem_Segment); 1274 1275 /* 1276 * Re-Update SeekHead / Info elements. 1277 */ 1278 1279 writeSegSeekInfo(); 1280 1281 return RTFileSeek(m_Ebml.getFile(), 0, RTFILE_SEEK_END, NULL); 1282 } 1283 1284 /** 1285 * Closes the WebM file and drains all queues. 1286 * 1287 * @returns IPRT status code. 1288 */ 1289 int close(void) 1290 { 1291 if (!m_Ebml.isOpen()) 1292 return VINF_SUCCESS; 1293 1294 LogFunc(("\n")); 1295 1296 /* Make sure to drain all queues. */ 1297 processQueues(&CurSeg.queueBlocks, true /* fForce */); 1298 1299 writeFooter(); 1300 1301 WebMTracks::iterator itTrack = CurSeg.mapTracks.begin(); 1302 for (; itTrack != CurSeg.mapTracks.end(); ++itTrack) 1303 { 1304 WebMTrack *pTrack = itTrack->second; 1305 1306 delete pTrack; 1307 CurSeg.mapTracks.erase(itTrack); 1308 } 1309 1310 Assert(CurSeg.queueBlocks.Map.size() == 0); 1311 Assert(CurSeg.mapTracks.size() == 0); 1312 1313 m_Ebml.close(); 1314 1315 return VINF_SUCCESS; 1316 } 1317 1318 friend class Ebml; 1319 friend class WebMWriter; 1320 1321 private: 1322 1323 /** 1324 * Writes the segment's seek information and cue points. 1325 */ 1326 void writeSegSeekInfo(void) 1327 { 1328 if (CurSeg.offSeekInfo) 1329 RTFileSeek(m_Ebml.getFile(), CurSeg.offSeekInfo, RTFILE_SEEK_BEGIN, NULL); 1330 else 1331 CurSeg.offSeekInfo = RTFileTell(m_Ebml.getFile()); 1332 1333 LogFunc(("SeekHead @ %RU64\n", CurSeg.offSeekInfo)); 1334 1335 m_Ebml.subStart(MkvElem_SeekHead); 1336 1337 m_Ebml.subStart(MkvElem_Seek) 1338 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Tracks) 1339 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offTracks - CurSeg.offStart, 8) 1340 .subEnd(MkvElem_Seek); 1341 1342 Assert(CurSeg.offCues - CurSeg.offStart > 0); /* Sanity. */ 1343 1344 m_Ebml.subStart(MkvElem_Seek) 1345 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Cues) 1346 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offCues - CurSeg.offStart, 8) 1347 .subEnd(MkvElem_Seek); 1348 1349 m_Ebml.subStart(MkvElem_Seek) 1350 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Info) 1351 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offInfo - CurSeg.offStart, 8) 1352 .subEnd(MkvElem_Seek); 1353 1354 m_Ebml.subEnd(MkvElem_SeekHead); 1355 1356 //int64_t iFrameTime = (int64_t)1000 * 1 / 25; //m_Framerate.den / m_Framerate.num; /** @todo Fix this! */ 1357 CurSeg.offInfo = RTFileTell(m_Ebml.getFile()); 1358 1359 LogFunc(("Info @ %RU64\n", CurSeg.offInfo)); 1360 1361 char szMux[64]; 1362 RTStrPrintf(szMux, sizeof(szMux), 1363 #ifdef VBOX_WITH_LIBVPX 1364 "vpxenc%s", vpx_codec_version_str()); 1365 #else 1366 "unknown"); 1367 #endif 1368 char szApp[64]; 1369 RTStrPrintf(szApp, sizeof(szApp), VBOX_PRODUCT " %sr%u", VBOX_VERSION_STRING, RTBldCfgRevision()); 1370 1371 const WebMTimecode tcDuration = CurSeg.tcLastWrittenMs - CurSeg.tcStartMs; 1372 1373 if (!CurSeg.lstCues.empty()) 1374 { 1375 LogFunc(("tcDuration=%RU64\n", tcDuration)); 1376 AssertMsg(tcDuration, ("Segment seems to be empty\n")); 1377 } 1378 1379 m_Ebml.subStart(MkvElem_Info) 1380 .serializeUnsignedInteger(MkvElem_TimecodeScale, CurSeg.uTimecodeScaleFactor) 1381 .serializeFloat(MkvElem_Segment_Duration, tcDuration) 1382 .serializeString(MkvElem_MuxingApp, szMux) 1383 .serializeString(MkvElem_WritingApp, szApp) 1384 .subEnd(MkvElem_Info); 1385 } 1386 }; 1387 1388 WebMWriter::WebMWriter(void) 1389 : m_pImpl(new WebMWriter_Impl()) {} 1390 1391 WebMWriter::~WebMWriter(void) 1392 { 1393 if (m_pImpl) 1394 { 1395 m_pImpl->destroy(); 1396 1397 delete m_pImpl; 1398 } 1399 } 1400 1401 int WebMWriter::OpenEx(const char *a_pszFilename, PRTFILE a_phFile, 1402 WebMWriter::AudioCodec a_enmAudioCodec, WebMWriter::VideoCodec a_enmVideoCodec) 1403 { 1404 try 1405 { 1406 m_pImpl->m_enmAudioCodec = a_enmAudioCodec; 1407 m_pImpl->m_enmVideoCodec = a_enmVideoCodec; 1408 1409 LogFunc(("Creating '%s'\n", a_pszFilename)); 1410 1411 int rc = m_pImpl->m_Ebml.createEx(a_pszFilename, a_phFile); 1412 if (RT_SUCCESS(rc)) 1413 { 1414 rc = m_pImpl->init(); 1415 if (RT_SUCCESS(rc)) 1416 rc = m_pImpl->writeHeader(); 1417 } 1418 } 1419 catch(int rc) 1420 { 1421 return rc; 1422 } 1423 return VINF_SUCCESS; 1424 } 1425 1426 int WebMWriter::Open(const char *a_pszFilename, uint64_t a_fOpen, 1427 WebMWriter::AudioCodec a_enmAudioCodec, WebMWriter::VideoCodec a_enmVideoCodec) 1428 { 1429 try 1430 { 1431 m_pImpl->m_enmAudioCodec = a_enmAudioCodec; 1432 m_pImpl->m_enmVideoCodec = a_enmVideoCodec; 1433 1434 LogFunc(("Creating '%s'\n", a_pszFilename)); 1435 1436 int rc = m_pImpl->m_Ebml.create(a_pszFilename, a_fOpen); 1437 if (RT_SUCCESS(rc)) 1438 { 1439 rc = m_pImpl->init(); 1440 if (RT_SUCCESS(rc)) 1441 rc = m_pImpl->writeHeader(); 1442 } 1443 } 1444 catch(int rc) 1445 { 1446 return rc; 1447 } 1448 return VINF_SUCCESS; 1449 } 1450 1451 int WebMWriter::Close(void) 1452 { 1453 return m_pImpl->close(); 1454 } 1455 1456 int WebMWriter::AddAudioTrack(uint16_t uHz, uint8_t cChannels, uint8_t cBitDepth, uint8_t *puTrack) 1457 { 1458 return m_pImpl->AddAudioTrack(uHz, cChannels, cBitDepth, puTrack); 1459 } 1460 1461 int WebMWriter::AddVideoTrack(uint16_t uWidth, uint16_t uHeight, double dbFPS, uint8_t *puTrack) 1462 { 1463 return m_pImpl->AddVideoTrack(uWidth, uHeight, dbFPS, puTrack); 1464 } 1465 1466 int WebMWriter::WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData) 1467 { 1468 int rc; 1469 1470 try 1471 { 1472 rc = m_pImpl->WriteBlock(uTrack, pvData, cbData); 1473 } 1474 catch(int rc2) 1475 { 1476 rc = rc2; 1477 } 1478 return rc; 1479 } 1480 1481 const Utf8Str& WebMWriter::GetFileName(void) 1482 { 1483 return m_pImpl->m_Ebml.getFileName(); 1484 } 1485 1486 uint64_t WebMWriter::GetFileSize(void) 1487 { 1488 return m_pImpl->m_Ebml.getFileSize(); 1489 } 1490 1491 uint64_t WebMWriter::GetAvailableSpace(void) 1492 { 1493 return m_pImpl->m_Ebml.getAvailableSpace(); 1494 } 1495 239 size_t size = 8 - ! (parm & (UINT64_MAX << 49)) - ! (parm & (UINT64_MAX << 42)) - 240 ! (parm & (UINT64_MAX << 35)) - ! (parm & (UINT64_MAX << 28)) - 241 ! (parm & (UINT64_MAX << 21)) - ! (parm & (UINT64_MAX << 14)) - 242 ! (parm & (UINT64_MAX << 7)); 243 /* One is subtracted in order to avoid loosing significant bit when size = 8. */ 244 uint64_t mask = RT_BIT_64(size * 8 - 1); 245 writeUnsignedInteger((parm & (((mask << 1) - 1) >> size)) | (mask >> (size - 1)), size); 246 } 247 248 /** Size calculation for variable size UNSIGNED integer. 249 * 250 * The function defines the size of the number by trimming 251 * consequent trailing zero bytes starting from the most significant. 252 * The following statement is always true: 253 * 1 <= getSizeOfUInt(arg) <= 8. 254 * 255 * Every !(arg & (UINT64_MAX << X)) expression gives one 256 * if an only if all the bits from X to 63 are set to zero. 257 */ 258 size_t EBMLWriter::getSizeOfUInt(uint64_t arg) 259 { 260 return 8 - ! (arg & (UINT64_MAX << 56)) - ! (arg & (UINT64_MAX << 48)) - 261 ! (arg & (UINT64_MAX << 40)) - ! (arg & (UINT64_MAX << 32)) - 262 ! (arg & (UINT64_MAX << 24)) - ! (arg & (UINT64_MAX << 16)) - 263 ! (arg & (UINT64_MAX << 8)); 264 } 265 -
trunk/src/VBox/Main/src-client/EBMLWriter.h
r69192 r69683 1 1 /* $Id$ */ 2 2 /** @file 3 * E bmlWriter.h - EBML writer + WebM container.3 * EBMLWriter.h - EBML writer. 4 4 */ 5 5 … … 19 19 #define ____EBMLWRITER 20 20 21 #ifdef VBOX_WITH_LIBVPX22 # ifdef _MSC_VER23 # pragma warning(push)24 # pragma warning(disable: 4668) /* vpx_codec.h(64) : warning C4668: '__GNUC__' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' */25 # include <vpx/vpx_encoder.h>26 # pragma warning(pop)27 # else28 # include <vpx/vpx_encoder.h>29 # endif30 #endif /* VBOX_WITH_LIBVPX */31 32 21 #include <iprt/file.h> 33 34 22 #include <VBox/com/string.h> /* For Utf8Str. */ 35 23 36 24 using namespace com; 37 25 38 class WebMWriter_Impl; 26 #ifdef LOG_GROUP 27 # undef LOG_GROUP 28 #endif 29 #define LOG_GROUP LOG_GROUP_MAIN_DISPLAY 30 #include "LoggingNew.h" 39 31 40 class WebMWriter 32 #include <list> 33 #include <map> 34 #include <queue> 35 #include <stack> 36 37 #include <math.h> /* For lround.h. */ 38 39 #include <iprt/asm.h> 40 #include <iprt/buildconfig.h> 41 #include <iprt/cdefs.h> 42 #include <iprt/critsect.h> 43 #include <iprt/err.h> 44 #include <iprt/file.h> 45 #include <iprt/rand.h> 46 #include <iprt/string.h> 47 48 #include <VBox/log.h> 49 #include <VBox/version.h> 50 51 /** No flags set. */ 52 #define VBOX_EBMLWRITER_FLAG_NONE 0 53 /** The file handle was inherited. */ 54 #define VBOX_EBMLWRITER_FLAG_HANDLE_INHERITED RT_BIT(0) 55 56 class EBMLWriter 41 57 { 58 public: 59 typedef uint32_t EbmlClassId; 60 61 private: 62 63 struct EbmlSubElement 64 { 65 uint64_t offset; 66 EbmlClassId classId; 67 EbmlSubElement(uint64_t offs, EbmlClassId cid) : offset(offs), classId(cid) {} 68 }; 69 70 /** Stack of EBML sub elements. */ 71 std::stack<EbmlSubElement> m_Elements; 72 /** The file's handle. */ 73 RTFILE m_hFile; 74 /** The file's name (path). */ 75 Utf8Str m_strFile; 76 /** Flags. */ 77 uint32_t m_fFlags; 42 78 43 79 public: 44 80 45 /** 46 * Supported audio codecs. 47 */ 48 enum AudioCodec 49 { 50 /** No audio codec specified. */ 51 AudioCodec_None = 0, 52 /** Opus. */ 53 AudioCodec_Opus = 1 54 }; 81 EBMLWriter(void) 82 : m_hFile(NIL_RTFILE) 83 , m_fFlags(VBOX_EBMLWRITER_FLAG_NONE) { } 55 84 56 /** 57 * Supported video codecs. 58 */ 59 enum VideoCodec 60 { 61 /** No video codec specified. */ 62 VideoCodec_None = 0, 63 /** VP8. */ 64 VideoCodec_VP8 = 1 65 }; 66 67 #ifdef VBOX_WITH_LIBVPX 68 /** 69 * Block data for VP8-encoded video data. 70 */ 71 struct BlockData_VP8 72 { 73 const vpx_codec_enc_cfg_t *pCfg; 74 const vpx_codec_cx_pkt_t *pPkt; 75 }; 76 #endif /* VBOX_WITH_LIBVPX */ 77 78 #ifdef VBOX_WITH_LIBOPUS 79 /** 80 * Block data for Opus-encoded audio data. 81 */ 82 struct BlockData_Opus 83 { 84 /** Pointer to encoded Opus audio data. */ 85 const void *pvData; 86 /** Size (in bytes) of encoded Opus audio data. */ 87 size_t cbData; 88 /** PTS (in ms) of encoded Opus audio data. */ 89 uint64_t uPTSMs; 90 }; 91 #endif /* VBOX_WITH_LIBOPUS */ 85 virtual ~EBMLWriter(void) { close(); } 92 86 93 87 public: 94 88 95 WebMWriter(); 96 virtual ~WebMWriter(); 89 int createEx(const char *a_pszFile, PRTFILE phFile); 90 91 int create(const char *a_pszFile, uint64_t fOpen); 92 93 void close(void); 94 95 /** Returns the file name. */ 96 const Utf8Str& getFileName(void) { return m_strFile; } 97 98 /** Returns file size. */ 99 uint64_t getFileSize(void) { return RTFileTell(m_hFile); } 100 101 /** Get reference to file descriptor */ 102 inline const RTFILE &getFile(void) { return m_hFile; } 103 104 /** Returns available space on storage. */ 105 uint64_t getAvailableSpace(void); 97 106 98 107 /** 99 * Opens (creates) an output file using an already open file handle.108 * Returns whether the file is open or not. 100 109 * 101 * @param a_pszFilename Name of the file the file handle points at. 102 * @param a_phFile Pointer to open file handle to use. 103 * @param a_enmAudioCodec Audio codec to use. 104 * @param a_enmVideoCodec Video codec to use. 105 * 106 * @returns VBox status code. */ 107 int OpenEx(const char *a_pszFilename, PRTFILE a_phFile, 108 WebMWriter::AudioCodec a_enmAudioCodec, WebMWriter::VideoCodec a_enmVideoCodec); 110 * @returns True if open, false if not. 111 */ 112 bool isOpen(void) { return RTFileIsValid(m_hFile); } 109 113 110 /** 111 * Opens an output file. 112 * 113 * @param a_pszFilename Name of the file to create. 114 * @param a_fOpen File open mode of type RTFILE_O_. 115 * @param a_enmAudioCodec Audio codec to use. 116 * @param a_enmVideoCodec Video codec to use. 117 * 118 * @returns VBox status code. */ 119 int Open(const char *a_pszFilename, uint64_t a_fOpen, 120 WebMWriter::AudioCodec a_enmAudioCodec, WebMWriter::VideoCodec a_enmVideoCodec); 114 public: 121 115 122 /** Closes output file. */ 123 int Close(void); 116 EBMLWriter &subStart(EbmlClassId classId); 124 117 125 int AddAudioTrack(uint16_t uHz, uint8_t cChannels, uint8_t cBits, uint8_t *puTrack);118 EBMLWriter &subEnd(EbmlClassId classId); 126 119 127 int AddVideoTrack(uint16_t uWidth, uint16_t uHeight, double dbFPS, uint8_t *puTrack);120 EBMLWriter &serializeString(EbmlClassId classId, const char *str); 128 121 129 /** 130 * Writes a block of compressed data. 131 * 132 * @param uTrack Track number to write data to. 133 * @param pvData Pointer to block data to write. 134 * @param cbData Size (in bytes) of block data to write. 135 * 136 * @returns VBox status code. 137 */ 138 int WriteBlock(uint8_t uTrack, const void *pvData, size_t cbData); 122 EBMLWriter &serializeUnsignedInteger(EbmlClassId classId, uint64_t parm, size_t size = 0); 139 123 140 /** 141 * Gets file name. 142 * 143 * @returns File name as UTF-8 string. 144 */ 145 const Utf8Str& GetFileName(void); 124 EBMLWriter &serializeFloat(EbmlClassId classId, float value); 146 125 147 /** 148 * Gets current output file size. 149 * 150 * @returns File size in bytes. 151 */ 152 uint64_t GetFileSize(void); 126 EBMLWriter &serializeData(EbmlClassId classId, const void *pvData, size_t cbData); 153 127 154 /** 155 * Gets current free storage space available for the file. 156 * 157 * @returns Available storage free space. 158 */ 159 uint64_t GetAvailableSpace(void); 128 int write(const void *data, size_t size); 129 130 void writeUnsignedInteger(uint64_t value, size_t size = sizeof(uint64_t)); 131 132 void writeClassId(EbmlClassId parm); 133 134 void writeSize(uint64_t parm); 135 136 static inline size_t getSizeOfUInt(uint64_t arg); 160 137 161 138 private: 162 139 163 /** WebMWriter implementation. 164 * To isolate some include files. */ 165 WebMWriter_Impl *m_pImpl; 166 167 DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(WebMWriter); 140 void operator=(const EBMLWriter &); 168 141 }; 169 142 -
trunk/src/VBox/Main/src-client/VideoRec.cpp
r69240 r69683 16 16 */ 17 17 18 #ifdef LOG_GROUP 19 # undef LOG_GROUP 20 #endif 18 21 #define LOG_GROUP LOG_GROUP_MAIN_DISPLAY 19 22 #include "LoggingNew.h" … … 32 35 #include <VBox/com/VirtualBox.h> 33 36 34 #include " EBMLWriter.h"37 #include "WebMWriter.h" 35 38 #include "VideoRec.h" 36 39
Note:
See TracChangeset
for help on using the changeset viewer.