EbmlComposer.cpp (7110B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 4 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "EbmlComposer.h" 7 8 #include "libmkv/EbmlIDs.h" 9 #include "libmkv/EbmlWriter.h" 10 #include "libmkv/WebMElement.h" 11 #include "limits.h" 12 #include "mozilla/EndianUtils.h" 13 #include "mozilla/UniquePtr.h" 14 #include "prtime.h" 15 16 namespace mozilla { 17 18 // Timecode scale in nanoseconds 19 constexpr unsigned long TIME_CODE_SCALE = 1000000; 20 // The WebM header size without audio CodecPrivateData 21 constexpr int32_t DEFAULT_HEADER_SIZE = 1024; 22 // Number of milliseconds after which we flush audio-only clusters 23 constexpr int32_t FLUSH_AUDIO_ONLY_AFTER_MS = 1000; 24 25 void EbmlComposer::GenerateHeader() { 26 MOZ_RELEASE_ASSERT(!mMetadataFinished); 27 MOZ_RELEASE_ASSERT(mHasAudio || mHasVideo); 28 29 // Write the EBML header. 30 EbmlGlobal ebml; 31 // The WEbM header default size usually smaller than 1k. 32 auto buffer = 33 MakeUnique<uint8_t[]>(DEFAULT_HEADER_SIZE + mCodecPrivateData.Length()); 34 ebml.buf = buffer.get(); 35 ebml.offset = 0; 36 writeHeader(&ebml); 37 { 38 EbmlLoc segEbmlLoc, ebmlLocseg, ebmlLoc; 39 Ebml_StartSubElement(&ebml, &segEbmlLoc, Segment); 40 { 41 Ebml_StartSubElement(&ebml, &ebmlLocseg, SeekHead); 42 // Todo: We don't know the exact sizes of encoded data and 43 // ignore this section. 44 Ebml_EndSubElement(&ebml, &ebmlLocseg); 45 writeSegmentInformation(&ebml, &ebmlLoc, TIME_CODE_SCALE, 0); 46 { 47 EbmlLoc trackLoc; 48 Ebml_StartSubElement(&ebml, &trackLoc, Tracks); 49 { 50 // Video 51 if (mWidth > 0 && mHeight > 0) { 52 writeVideoTrack(&ebml, 0x1, 0, "V_VP8", mWidth, mHeight, 53 mDisplayWidth, mDisplayHeight); 54 } 55 // Audio 56 if (mCodecPrivateData.Length() > 0) { 57 // Extract the pre-skip from mCodecPrivateData 58 // then convert it to nanoseconds. 59 // For more details see 60 // https://tools.ietf.org/html/rfc7845#section-4.2 61 uint64_t codecDelay = (uint64_t)LittleEndian::readUint16( 62 mCodecPrivateData.Elements() + 10) * 63 PR_NSEC_PER_SEC / 48000; 64 // Fixed 80ms, convert into nanoseconds. 65 uint64_t seekPreRoll = 80 * PR_NSEC_PER_MSEC; 66 writeAudioTrack(&ebml, 0x2, 0x0, "A_OPUS", mSampleFreq, mChannels, 67 codecDelay, seekPreRoll, 68 mCodecPrivateData.Elements(), 69 mCodecPrivateData.Length()); 70 } 71 } 72 Ebml_EndSubElement(&ebml, &trackLoc); 73 } 74 } 75 // The Recording length is unknown and 76 // ignore write the whole Segment element size 77 } 78 MOZ_ASSERT(ebml.offset <= DEFAULT_HEADER_SIZE + mCodecPrivateData.Length(), 79 "write more data > EBML_BUFFER_SIZE"); 80 auto block = mBuffer.AppendElement(); 81 block->SetLength(ebml.offset); 82 memcpy(block->Elements(), ebml.buf, ebml.offset); 83 mMetadataFinished = true; 84 } 85 86 nsresult EbmlComposer::WriteSimpleBlock(EncodedFrame* aFrame) { 87 MOZ_RELEASE_ASSERT(mMetadataFinished); 88 auto frameType = aFrame->mFrameType; 89 const bool isVP8IFrame = (frameType == EncodedFrame::FrameType::VP8_I_FRAME); 90 const bool isVP8PFrame = (frameType == EncodedFrame::FrameType::VP8_P_FRAME); 91 const bool isOpus = (frameType == EncodedFrame::FrameType::OPUS_AUDIO_FRAME); 92 93 MOZ_ASSERT_IF(isVP8IFrame, mHasVideo); 94 MOZ_ASSERT_IF(isVP8PFrame, mHasVideo); 95 MOZ_ASSERT_IF(isOpus, mHasAudio); 96 97 if (isVP8PFrame && !mHasWrittenCluster) { 98 // We ensure there is a cluster header and an I-frame prior to any P-frame. 99 return NS_ERROR_INVALID_ARG; 100 } 101 102 int64_t timeCode = aFrame->mTime.ToMicroseconds() / PR_USEC_PER_MSEC - 103 mCurrentClusterTimecode; 104 105 const bool needClusterHeader = 106 !mHasWrittenCluster || 107 (!mHasVideo && timeCode >= FLUSH_AUDIO_ONLY_AFTER_MS) || isVP8IFrame; 108 109 auto block = mBuffer.AppendElement(); 110 block->SetLength(aFrame->mFrameData->Length() + DEFAULT_HEADER_SIZE); 111 112 EbmlGlobal ebml; 113 ebml.offset = 0; 114 ebml.buf = block->Elements(); 115 116 if (needClusterHeader) { 117 mHasWrittenCluster = true; 118 EbmlLoc ebmlLoc; 119 // This starts the Cluster element. Note that we never end this element 120 // through Ebml_EndSubElement. What the ending would allow us to do is write 121 // the full length of the cluster in the element header. That would also 122 // force us to keep the entire cluster in memory until we know where it 123 // ends. Now it instead ends through the start of the next cluster. This 124 // allows us to stream the muxed data with much lower latency than if we 125 // would have to wait for clusters to end. 126 Ebml_StartSubElement(&ebml, &ebmlLoc, Cluster); 127 // if timeCode didn't under/overflow before, it shouldn't after this 128 mCurrentClusterTimecode = aFrame->mTime.ToMicroseconds() / PR_USEC_PER_MSEC; 129 Ebml_SerializeUnsigned(&ebml, Timecode, mCurrentClusterTimecode); 130 131 // Can't under-/overflow now 132 timeCode = 0; 133 } 134 135 if (MOZ_UNLIKELY(timeCode < SHRT_MIN || timeCode > SHRT_MAX)) { 136 MOZ_CRASH_UNSAFE_PRINTF( 137 "Invalid cluster timecode! audio=%d, video=%d, timeCode=%" PRId64 138 "ms, currentClusterTimecode=%" PRIu64 "ms", 139 mHasAudio, mHasVideo, timeCode, mCurrentClusterTimecode); 140 } 141 142 writeSimpleBlock(&ebml, isOpus ? 0x2 : 0x1, static_cast<short>(timeCode), 143 isVP8IFrame, 0, 0, 144 (unsigned char*)aFrame->mFrameData->Elements(), 145 aFrame->mFrameData->Length()); 146 MOZ_ASSERT(ebml.offset <= DEFAULT_HEADER_SIZE + aFrame->mFrameData->Length(), 147 "write more data > EBML_BUFFER_SIZE"); 148 block->SetLength(ebml.offset); 149 150 return NS_OK; 151 } 152 153 void EbmlComposer::SetVideoConfig(uint32_t aWidth, uint32_t aHeight, 154 uint32_t aDisplayWidth, 155 uint32_t aDisplayHeight) { 156 MOZ_RELEASE_ASSERT(!mMetadataFinished); 157 MOZ_ASSERT(aWidth > 0, "Width should > 0"); 158 MOZ_ASSERT(aHeight > 0, "Height should > 0"); 159 MOZ_ASSERT(aDisplayWidth > 0, "DisplayWidth should > 0"); 160 MOZ_ASSERT(aDisplayHeight > 0, "DisplayHeight should > 0"); 161 mWidth = aWidth; 162 mHeight = aHeight; 163 mDisplayWidth = aDisplayWidth; 164 mDisplayHeight = aDisplayHeight; 165 mHasVideo = true; 166 } 167 168 void EbmlComposer::SetAudioConfig(uint32_t aSampleFreq, uint32_t aChannels) { 169 MOZ_RELEASE_ASSERT(!mMetadataFinished); 170 MOZ_ASSERT(aSampleFreq > 0, "SampleFreq should > 0"); 171 MOZ_ASSERT(aChannels > 0, "Channels should > 0"); 172 mSampleFreq = aSampleFreq; 173 mChannels = aChannels; 174 mHasAudio = true; 175 } 176 177 void EbmlComposer::ExtractBuffer(nsTArray<nsTArray<uint8_t> >* aDestBufs, 178 uint32_t aFlag) { 179 if (!mMetadataFinished) { 180 return; 181 } 182 aDestBufs->AppendElements(std::move(mBuffer)); 183 MOZ_ASSERT(mBuffer.IsEmpty()); 184 } 185 186 } // namespace mozilla