Changeset 68451 in vbox for trunk/src/VBox/Main
- Timestamp:
- Aug 17, 2017 7:54:52 PM (7 years ago)
- Location:
- trunk/src/VBox/Main/src-client
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/Main/src-client/EbmlMkvIDs.h
r68428 r68451 70 70 MkvElem_PixelWidth = 0xB0, 71 71 MkvElem_PixelHeight = 0xBA, 72 MkvElem_FrameRate = 0x2383E3,73 72 74 73 MkvElem_Audio = 0xE1, -
trunk/src/VBox/Main/src-client/EbmlWriter.cpp
r68372 r68451 14 14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the 15 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 16 22 */ 17 23 … … 309 315 #define VBOX_WEBM_TIMECODESCALE_FACTOR_MS 1000000 310 316 317 /** Maximum time (in ms) a cluster can store. */ 318 #define VBOX_WEBM_CLUSTER_MAX_LEN_MS INT16_MAX 319 320 /** Maximum time a block can store. 321 * With signed 16-bit timecodes and a default timecode scale of 1ms per unit this makes 65536ms. */ 322 #define VBOX_WEBM_BLOCK_MAX_LEN_MS UINT16_MAX 323 311 324 class WebMWriter_Impl 312 325 { 313 /**314 * Structure for keeping a cue entry.315 */316 struct WebMCueEntry317 {318 WebMCueEntry(uint32_t t, uint64_t l)319 : time(t), loc(l) {}320 321 uint32_t time;322 uint64_t loc;323 };324 325 326 /** 326 327 * Track type enumeration. … … 371 372 * 60 2880 372 373 */ 373 uint16_t csFrame; 374 uint16_t framesPerBlock; 375 /** How many milliseconds (ms) one written (simple) block represents. */ 376 uint16_t msPerBlock; 374 377 } Audio; 375 378 }; … … 389 392 390 393 /** 394 * Structure for keeping a cue point. 395 */ 396 struct WebMCuePoint 397 { 398 WebMCuePoint(WebMTrack *a_pTrack, uint32_t a_tcClusterStart, uint64_t a_offClusterStart) 399 : pTrack(a_pTrack) 400 , tcClusterStart(a_tcClusterStart), offClusterStart(a_offClusterStart) {} 401 402 /** Associated track. */ 403 WebMTrack *pTrack; 404 /** Start time code of the related cluster. */ 405 uint32_t tcClusterStart; 406 /** Start offset of the related cluster. */ 407 uint64_t offClusterStart; 408 }; 409 410 /** 391 411 * Structure for keeping a WebM cluster entry. 392 412 */ … … 397 417 , offCluster(0) 398 418 , fOpen(false) 399 , tcStart(0) 400 , tcLast(0) 401 , cBlocks(0) 402 , cbData(0) { } 419 , tcStartMs(0) 420 , tcEndMs(0) 421 , cBlocks(0) { } 403 422 404 423 /** This cluster's ID. */ … … 409 428 /** Whether this cluster element is opened currently. */ 410 429 bool fOpen; 411 /** Timecode when starting this cluster. */412 uint64_t tcStart ;413 /** Timecode when this cluster was last touched. */414 uint64_t tc Last;430 /** Timecode (in ms) when starting this cluster. */ 431 uint64_t tcStartMs; 432 /** Timecode (in ms) when this cluster ends. */ 433 uint64_t tcEndMs; 415 434 /** Number of (simple) blocks in this cluster. */ 416 435 uint64_t cBlocks; 417 /** Size (in bytes) of data already written. */418 uint64_t cbData;419 436 }; 420 437 … … 459 476 uint64_t offCues; 460 477 /** List of cue points. Needed for seeking table. */ 461 std::list<WebMCue Entry> lstCues;478 std::list<WebMCuePoint> lstCues; 462 479 463 480 /** Map of tracks. … … 574 591 return rc; 575 592 593 const uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size(); 594 576 595 m_Ebml.subStart(MkvElem_TrackEntry); 577 m_Ebml.serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)CurSeg.mapTracks.size()); 578 /** @todo Implement track's "Language" property? Currently this defaults to English ("eng"). */579 580 uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size();596 597 m_Ebml.serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack); 598 m_Ebml.serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */); 599 m_Ebml.serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0); 581 600 582 601 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Audio, uTrack, RTFileTell(m_Ebml.getFile())); 583 602 584 pTrack->Audio.uHz = uHz; 585 pTrack->Audio.csFrame = pTrack->Audio.uHz / (1000 /* s in ms */ / 20 /* ms */); /** @todo 20 ms of audio data. Make this configurable? */ 603 pTrack->Audio.uHz = uHz; 604 pTrack->Audio.msPerBlock = 20; /** Opus uses 20ms by default. Make this configurable? */ 605 pTrack->Audio.framesPerBlock = uHz / (1000 /* s in ms */ / pTrack->Audio.msPerBlock); 586 606 587 607 OpusPrivData opusPrivData(uHz, cChannels); 588 608 589 LogFunc(("Opus @ %RU16Hz (Frame size is %RU16 samples / channel))\n", pTrack->Audio.uHz, pTrack->Audio.csFrame)); 609 LogFunc(("Opus @ %RU16Hz (%RU16ms + %RU16 frames per block)\n", 610 pTrack->Audio.uHz, pTrack->Audio.msPerBlock, pTrack->Audio.framesPerBlock)); 590 611 591 612 m_Ebml.serializeUnsignedInteger(MkvElem_TrackUID, pTrack->uUUID, 4) … … 617 638 { 618 639 #ifdef VBOX_WITH_LIBVPX 640 RT_NOREF(dbFPS); 641 642 const uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size(); 643 619 644 m_Ebml.subStart(MkvElem_TrackEntry); 620 m_Ebml.serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)CurSeg.mapTracks.size()); 621 622 uint8_t uTrack = (uint8_t)CurSeg.mapTracks.size(); 645 646 m_Ebml.serializeUnsignedInteger(MkvElem_TrackNumber, (uint8_t)uTrack); 647 m_Ebml.serializeString (MkvElem_Language, "und" /* "Undefined"; see ISO-639-2. */); 648 m_Ebml.serializeUnsignedInteger(MkvElem_FlagLacing, (uint8_t)0); 623 649 624 650 WebMTrack *pTrack = new WebMTrack(WebMTrackType_Video, uTrack, RTFileTell(m_Ebml.getFile())); … … 629 655 .serializeString(MkvElem_CodecID, "V_VP8") 630 656 .subStart(MkvElem_Video) 631 .serializeUnsignedInteger(MkvElem_PixelWidth, uWidth)632 .serializeUnsignedInteger(MkvElem_PixelHeight, uHeight)633 .s erializeFloat(MkvElem_FrameRate, dbFPS)634 .subEnd(MkvElem_Video) 635 657 .serializeUnsignedInteger(MkvElem_PixelWidth, uWidth) 658 .serializeUnsignedInteger(MkvElem_PixelHeight, uHeight) 659 .subEnd(MkvElem_Video); 660 661 m_Ebml.subEnd(MkvElem_TrackEntry); 636 662 637 663 CurSeg.mapTracks[uTrack] = pTrack; … … 713 739 RT_NOREF(a_pTrack); 714 740 715 /* Calculate the PTS of this frame in milliseconds. */ 716 uint64_t tcPTS = a_pPkt->data.frame.pts * 1000 741 WebMCluster &Cluster = CurSeg.CurCluster; 742 743 /* Calculate the PTS of this frame (in ms). */ 744 uint64_t tcPTSMs = a_pPkt->data.frame.pts * 1000 717 745 * (uint64_t) a_pCfg->g_timebase.num / a_pCfg->g_timebase.den; 718 746 719 if (tcPTS <= CurSeg.CurCluster.tcLast) 720 tcPTS = CurSeg.CurCluster.tcLast + 1; 721 722 CurSeg.CurCluster.tcLast = tcPTS; 723 724 if (CurSeg.CurCluster.tcStart == UINT64_MAX) 725 CurSeg.CurCluster.tcStart = CurSeg.CurCluster.tcLast; 726 727 /* Calculate the relative time of this block. */ 728 uint16_t tcBlock = 0; 729 bool fClusterStart = false; 730 731 /* Did we reach the maximum our timecode can hold? Use a new cluster then. */ 732 if (tcPTS - CurSeg.CurCluster.tcStart > m_uTimecodeMax) 747 if ( tcPTSMs 748 && tcPTSMs <= CurSeg.CurCluster.tcEndMs) 749 { 750 tcPTSMs = CurSeg.CurCluster.tcEndMs + 1; 751 } 752 753 /* Whether to start a new cluster or not. */ 754 bool fClusterStart = false; 755 756 /* No blocks written yet? Start a new cluster. */ 757 if (a_pTrack->cTotalBlocks == 0) 733 758 fClusterStart = true; 734 else 735 { 736 /* Calculate the block's timecode, which is relative to the current cluster's starting timecode. */ 737 tcBlock = static_cast<uint16_t>(tcPTS - CurSeg.CurCluster.tcStart); 759 760 /* Did we reach the maximum a cluster can hold? Use a new cluster then. */ 761 if (tcPTSMs > VBOX_WEBM_CLUSTER_MAX_LEN_MS) 762 { 763 tcPTSMs = 0; 764 765 fClusterStart = true; 738 766 } 739 767 … … 743 771 || fKeyframe) 744 772 { 745 WebMCluster &Cluster = CurSeg.CurCluster;746 747 a_pTrack->cTotalClusters++;748 749 773 if (Cluster.fOpen) /* Close current cluster first. */ 750 774 { … … 753 777 } 754 778 755 tcBlock = 0;756 757 /* Open a new cluster. */758 779 Cluster.fOpen = true; 759 Cluster.tcStart = tcPTS; 780 Cluster.uID = a_pTrack->cTotalClusters; 781 Cluster.tcStartMs = tcPTSMs; 760 782 Cluster.offCluster = RTFileTell(m_Ebml.getFile()); 761 783 Cluster.cBlocks = 0; 762 Cluster.cbData = 0; 763 764 LogFunc(("[C%RU64] Start @ tc=%RU64 off=%RU64\n", a_pTrack->cTotalClusters, Cluster.tcStart, Cluster.offCluster));784 785 LogFunc(("[T%RU8C%RU64] Start @ %RU64ms / %RU64 bytes\n", 786 a_pTrack->uTrack, Cluster.uID, Cluster.tcStartMs, Cluster.offCluster)); 765 787 766 788 m_Ebml.subStart(MkvElem_Cluster) 767 .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tcStart );789 .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tcStartMs); 768 790 769 791 /* Save a cue point if this is a keyframe. */ 770 792 if (fKeyframe) 771 793 { 772 WebMCue Entry cue(Cluster.tcStart, Cluster.offCluster);794 WebMCuePoint cue(a_pTrack, Cluster.tcStartMs, Cluster.offCluster); 773 795 CurSeg.lstCues.push_back(cue); 774 796 } 775 } 776 777 LogFunc(("tcPTS=%RU64, s=%RU64, e=%RU64\n", tcPTS, CurSeg.CurCluster.tcStart, CurSeg.CurCluster.tcLast)); 797 798 a_pTrack->cTotalClusters++; 799 } 800 801 Cluster.tcEndMs = tcPTSMs; 802 Cluster.cBlocks++; 803 804 /* Calculate the block's timecode, which is relative to the cluster's starting timecode. */ 805 uint16_t tcBlockMs = static_cast<uint16_t>(tcPTSMs - Cluster.tcStartMs); 806 807 Log2Func(("tcPTSMs=%RU64, tcBlockMs=%RU64\n", tcPTSMs, tcBlockMs)); 808 809 Log2Func(("[C%RU64] cBlocks=%RU64, tcStartMs=%RU64, tcEndMs=%RU64 (%zums)\n", 810 Cluster.uID, Cluster.cBlocks, Cluster.tcStartMs, Cluster.tcEndMs, Cluster.tcEndMs - Cluster.tcStartMs)); 778 811 779 812 uint8_t fFlags = 0; … … 783 816 fFlags |= VBOX_WEBM_BLOCK_FLAG_INVISIBLE; 784 817 785 return writeSimpleBlockInternal(a_pTrack, tcBlock , a_pPkt->data.frame.buf, a_pPkt->data.frame.sz, fFlags);818 return writeSimpleBlockInternal(a_pTrack, tcBlockMs, a_pPkt->data.frame.buf, a_pPkt->data.frame.sz, fFlags); 786 819 } 787 820 #endif /* VBOX_WITH_LIBVPX */ … … 792 825 { 793 826 AssertPtrReturn(a_pTrack, VERR_INVALID_POINTER); 794 AssertPtrReturn(pvData, VERR_INVALID_POINTER);795 AssertReturn(cbData, VERR_INVALID_PARAMETER);827 AssertPtrReturn(pvData, VERR_INVALID_POINTER); 828 AssertReturn(cbData, VERR_INVALID_PARAMETER); 796 829 797 830 RT_NOREF(uTimeStampMs); … … 799 832 WebMCluster &Cluster = CurSeg.CurCluster; 800 833 801 /* Calculate the PTS. */ 802 /* Make sure to round the result. This is very important! */ 803 uint64_t tcPTSRaw = lround((CurSeg.uTimecodeScaleFactor * 1000 * Cluster.cbData) / a_pTrack->Audio.uHz); 804 805 /* Calculate the absolute PTS. */ 806 uint64_t tcPTS = lround(tcPTSRaw / CurSeg.uTimecodeScaleFactor); 807 808 if (Cluster.tcStart == UINT64_MAX) 809 Cluster.tcStart = tcPTS; 810 811 Cluster.tcLast = tcPTS; 812 813 uint16_t tcBlock; 814 bool fClusterStart = false; 834 /* Calculate the PTS of the current block: 835 * 836 * The "raw PTS" is the exact time of an object represented in nanoseconds): 837 * Raw Timecode = (Block timecode + Cluster timecode) * TimecodeScaleFactor 838 */ 839 uint64_t tcPTSMs = Cluster.tcStartMs + (Cluster.cBlocks * 20 /*ms */); 840 841 /* Whether to start a new cluster or not. */ 842 bool fClusterStart = false; 815 843 816 844 if (a_pTrack->cTotalBlocks == 0) 817 845 fClusterStart = true; 818 846 819 /* Did we reach the maximum our timecode can hold? Use a new cluster then. */ 820 if (tcPTS - Cluster.tcStart > m_uTimecodeMax) 821 { 822 tcBlock = 0; 823 847 /* Did we reach the maximum a cluster can hold? Use a new cluster then. */ 848 if (tcPTSMs > VBOX_WEBM_CLUSTER_MAX_LEN_MS) 824 849 fClusterStart = true; 825 }826 else827 {828 /* Calculate the block's timecode, which is relative to the Cluster timecode. */829 tcBlock = static_cast<uint16_t>(tcPTS - Cluster.tcStart);830 }831 850 832 851 if (fClusterStart) 833 852 { 834 if (Cluster.fOpen) /* Close current cluster first. */853 if (Cluster.fOpen) /* Close current clusters first. */ 835 854 { 836 855 m_Ebml.subEnd(MkvElem_Cluster); … … 838 857 } 839 858 840 tcBlock = 0;841 842 859 Cluster.fOpen = true; 843 860 Cluster.uID = a_pTrack->cTotalClusters; 844 Cluster.tcStart = tcPTS; 845 Cluster.tcLast = Cluster.tcStart; 861 Cluster.tcStartMs = Cluster.tcEndMs; 846 862 Cluster.offCluster = RTFileTell(m_Ebml.getFile()); 847 863 Cluster.cBlocks = 0; 848 Cluster.cbData = 0; 849 850 LogFunc(("[C%RU64] Start @ tc=%RU64 off=%RU64\n", Cluster.uID, Cluster.tcStart, Cluster.offCluster)); 864 865 LogFunc(("[T%RU8C%RU64] Start @ %RU64ms / %RU64 bytes\n", 866 a_pTrack->uTrack, Cluster.uID, Cluster.tcStartMs, Cluster.offCluster)); 867 868 /* As all audio frame as key frames, insert a new cue point when a new cluster starts. */ 869 WebMCuePoint cue(a_pTrack, Cluster.tcStartMs, Cluster.offCluster); 870 CurSeg.lstCues.push_back(cue); 851 871 852 872 m_Ebml.subStart(MkvElem_Cluster) 853 .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tcStart );873 .serializeUnsignedInteger(MkvElem_Timecode, Cluster.tcStartMs); 854 874 855 875 a_pTrack->cTotalClusters++; 856 876 } 857 877 858 Cluster.cBlocks += 1; 859 Cluster.cbData += cbData; 860 861 Log2Func(("[C%RU64] tcBlock=%RU64, tcClusterStart=%RU64, tcClusterLast=%RU64, cbData=%zu\n", 862 Cluster.uID, tcBlock, CurSeg.CurCluster.tcStart, CurSeg.CurCluster.tcLast, CurSeg.CurCluster.cbData)); 863 864 return writeSimpleBlockInternal(a_pTrack, tcBlock, pvData, cbData, VBOX_WEBM_BLOCK_FLAG_KEY_FRAME); 878 Cluster.tcEndMs = tcPTSMs; 879 Cluster.cBlocks++; 880 881 /* Calculate the block's timecode, which is relative to the cluster's starting timecode. */ 882 uint16_t tcBlockMs = static_cast<uint16_t>(tcPTSMs - Cluster.tcStartMs); 883 884 Log2Func(("tcPTSMs=%RU64, tcBlockMs=%RU64\n", tcPTSMs, tcBlockMs)); 885 886 Log2Func(("[C%RU64] cBlocks=%RU64, tcStartMs=%RU64, tcEndMs=%RU64 (%zums)\n", 887 Cluster.uID, Cluster.cBlocks, Cluster.tcStartMs, Cluster.tcEndMs, Cluster.tcEndMs - Cluster.tcStartMs)); 888 889 return writeSimpleBlockInternal(a_pTrack, tcBlockMs, 890 pvData, cbData, VBOX_WEBM_BLOCK_FLAG_KEY_FRAME); 865 891 } 866 892 #endif /* VBOX_WITH_LIBOPUS */ … … 943 969 * Write Cues element. 944 970 */ 945 if (CurSeg.lstCues.size()) 946 { 947 LogFunc(("Cues @ %RU64\n", RTFileTell(m_Ebml.getFile()))); 948 949 CurSeg.offCues = RTFileTell(m_Ebml.getFile()); 950 951 m_Ebml.subStart(MkvElem_Cues); 952 953 std::list<WebMCueEntry>::iterator itCuePoint = CurSeg.lstCues.begin(); 954 while (itCuePoint != CurSeg.lstCues.end()) 955 { 956 LogFunc(("CuePoint @ %RU64\n", RTFileTell(m_Ebml.getFile()))); 957 958 m_Ebml.subStart(MkvElem_CuePoint) 959 .serializeUnsignedInteger(MkvElem_CueTime, itCuePoint->time) 971 LogFunc(("Cues @ %RU64\n", RTFileTell(m_Ebml.getFile()))); 972 973 CurSeg.offCues = RTFileTell(m_Ebml.getFile()); 974 975 m_Ebml.subStart(MkvElem_Cues); 976 977 std::list<WebMCuePoint>::iterator itCuePoint = CurSeg.lstCues.begin(); 978 while (itCuePoint != CurSeg.lstCues.end()) 979 { 980 /* Sanity. */ 981 AssertPtr(itCuePoint->pTrack); 982 983 const uint64_t uClusterPos = itCuePoint->offClusterStart - CurSeg.offStart; 984 985 LogFunc(("CuePoint @ %RU64: Track #%RU8 (Time %RU64, Pos %RU64)\n", 986 RTFileTell(m_Ebml.getFile()), itCuePoint->pTrack->uTrack, itCuePoint->tcClusterStart, uClusterPos)); 987 988 m_Ebml.subStart(MkvElem_CuePoint) 989 .serializeUnsignedInteger(MkvElem_CueTime, itCuePoint->tcClusterStart) 960 990 .subStart(MkvElem_CueTrackPositions) 961 .serializeUnsignedInteger(MkvElem_CueTrack, 1) 962 .serializeUnsignedInteger(MkvElem_CueClusterPosition, itCuePoint->loc - CurSeg.offStart, 8) 963 .subEnd(MkvElem_CueTrackPositions) 964 .subEnd(MkvElem_CuePoint); 965 966 itCuePoint++; 967 } 968 969 m_Ebml.subEnd(MkvElem_Cues); 970 } 971 991 .serializeUnsignedInteger(MkvElem_CueTrack, itCuePoint->pTrack->uTrack) 992 .serializeUnsignedInteger(MkvElem_CueClusterPosition, uClusterPos, 8) 993 .subEnd(MkvElem_CueTrackPositions) 994 .subEnd(MkvElem_CuePoint); 995 996 itCuePoint++; 997 } 998 999 m_Ebml.subEnd(MkvElem_Cues); 972 1000 m_Ebml.subEnd(MkvElem_Segment); 973 1001 … … 1023 1051 .subEnd(MkvElem_Seek); 1024 1052 1025 if (CurSeg.lstCues.size()) 1026 { 1027 Assert(CurSeg.offCues - CurSeg.offStart > 0); /* Sanity. */ 1028 1029 m_Ebml.subStart(MkvElem_Seek) 1030 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Cues) 1031 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offCues - CurSeg.offStart, 8) 1032 .subEnd(MkvElem_Seek); 1033 } 1053 Assert(CurSeg.offCues - CurSeg.offStart > 0); /* Sanity. */ 1054 1055 m_Ebml.subStart(MkvElem_Seek) 1056 .serializeUnsignedInteger(MkvElem_SeekID, MkvElem_Cues) 1057 .serializeUnsignedInteger(MkvElem_SeekPosition, CurSeg.offCues - CurSeg.offStart, 8) 1058 .subEnd(MkvElem_Seek); 1034 1059 1035 1060 m_Ebml.subStart(MkvElem_Seek) … … 1059 1084 m_Ebml.subStart(MkvElem_Info) 1060 1085 .serializeUnsignedInteger(MkvElem_TimecodeScale, CurSeg.uTimecodeScaleFactor) 1061 .serializeFloat(MkvElem_Segment_Duration, CurSeg.tcEnd /*+ iFrameTime*/- CurSeg.tcStart)1086 .serializeFloat(MkvElem_Segment_Duration, CurSeg.tcEnd - CurSeg.tcStart) 1062 1087 .serializeString(MkvElem_MuxingApp, szMux) 1063 1088 .serializeString(MkvElem_WritingApp, szApp) -
trunk/src/VBox/Main/src-client/EbmlWriter.h
r68332 r68451 49 49 { 50 50 /** No audio codec specified. */ 51 AudioCodec_ Unknown= 0,51 AudioCodec_None = 0, 52 52 /** Opus. */ 53 AudioCodec_Opus 53 AudioCodec_Opus = 1 54 54 }; 55 55
Note:
See TracChangeset
for help on using the changeset viewer.