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/. */
6 #include "EbmlComposer.h"
7 #include "mozilla/UniquePtr.h"
8 #include "mozilla/EndianUtils.h"
9 #include "libmkv/EbmlIDs.h"
10 #include "libmkv/EbmlWriter.h"
11 #include "libmkv/WebMElement.h"
17 // Timecode scale in nanoseconds
18 constexpr unsigned long TIME_CODE_SCALE
= 1000000;
19 // The WebM header size without audio CodecPrivateData
20 constexpr int32_t DEFAULT_HEADER_SIZE
= 1024;
21 // Number of milliseconds after which we flush audio-only clusters
22 constexpr int32_t FLUSH_AUDIO_ONLY_AFTER_MS
= 1000;
24 void EbmlComposer::GenerateHeader() {
25 MOZ_RELEASE_ASSERT(!mMetadataFinished
);
26 MOZ_RELEASE_ASSERT(mHasAudio
|| mHasVideo
);
28 // Write the EBML header.
30 // The WEbM header default size usually smaller than 1k.
32 MakeUnique
<uint8_t[]>(DEFAULT_HEADER_SIZE
+ mCodecPrivateData
.Length());
33 ebml
.buf
= buffer
.get();
37 EbmlLoc segEbmlLoc
, ebmlLocseg
, ebmlLoc
;
38 Ebml_StartSubElement(&ebml
, &segEbmlLoc
, Segment
);
40 Ebml_StartSubElement(&ebml
, &ebmlLocseg
, SeekHead
);
41 // Todo: We don't know the exact sizes of encoded data and
42 // ignore this section.
43 Ebml_EndSubElement(&ebml
, &ebmlLocseg
);
44 writeSegmentInformation(&ebml
, &ebmlLoc
, TIME_CODE_SCALE
, 0);
47 Ebml_StartSubElement(&ebml
, &trackLoc
, Tracks
);
50 if (mWidth
> 0 && mHeight
> 0) {
51 writeVideoTrack(&ebml
, 0x1, 0, "V_VP8", mWidth
, mHeight
,
52 mDisplayWidth
, mDisplayHeight
);
55 if (mCodecPrivateData
.Length() > 0) {
56 // Extract the pre-skip from mCodecPrivateData
57 // then convert it to nanoseconds.
58 // For more details see
59 // https://tools.ietf.org/html/rfc7845#section-4.2
60 uint64_t codecDelay
= (uint64_t)LittleEndian::readUint16(
61 mCodecPrivateData
.Elements() + 10) *
62 PR_NSEC_PER_SEC
/ 48000;
63 // Fixed 80ms, convert into nanoseconds.
64 uint64_t seekPreRoll
= 80 * PR_NSEC_PER_MSEC
;
65 writeAudioTrack(&ebml
, 0x2, 0x0, "A_OPUS", mSampleFreq
, mChannels
,
66 codecDelay
, seekPreRoll
,
67 mCodecPrivateData
.Elements(),
68 mCodecPrivateData
.Length());
71 Ebml_EndSubElement(&ebml
, &trackLoc
);
74 // The Recording length is unknown and
75 // ignore write the whole Segment element size
77 MOZ_ASSERT(ebml
.offset
<= DEFAULT_HEADER_SIZE
+ mCodecPrivateData
.Length(),
78 "write more data > EBML_BUFFER_SIZE");
79 auto block
= mBuffer
.AppendElement();
80 block
->SetLength(ebml
.offset
);
81 memcpy(block
->Elements(), ebml
.buf
, ebml
.offset
);
82 mMetadataFinished
= true;
85 nsresult
EbmlComposer::WriteSimpleBlock(EncodedFrame
* aFrame
) {
86 MOZ_RELEASE_ASSERT(mMetadataFinished
);
87 auto frameType
= aFrame
->mFrameType
;
88 const bool isVP8IFrame
= (frameType
== EncodedFrame::FrameType::VP8_I_FRAME
);
89 const bool isVP8PFrame
= (frameType
== EncodedFrame::FrameType::VP8_P_FRAME
);
90 const bool isOpus
= (frameType
== EncodedFrame::FrameType::OPUS_AUDIO_FRAME
);
92 MOZ_ASSERT_IF(isVP8IFrame
, mHasVideo
);
93 MOZ_ASSERT_IF(isVP8PFrame
, mHasVideo
);
94 MOZ_ASSERT_IF(isOpus
, mHasAudio
);
96 if (isVP8PFrame
&& !mHasWrittenCluster
) {
97 // We ensure there is a cluster header and an I-frame prior to any P-frame.
98 return NS_ERROR_INVALID_ARG
;
101 int64_t timeCode
= aFrame
->mTime
.ToMicroseconds() / PR_USEC_PER_MSEC
-
102 mCurrentClusterTimecode
;
104 const bool needClusterHeader
=
105 !mHasWrittenCluster
||
106 (!mHasVideo
&& timeCode
>= FLUSH_AUDIO_ONLY_AFTER_MS
) || isVP8IFrame
;
108 auto block
= mBuffer
.AppendElement();
109 block
->SetLength(aFrame
->mFrameData
->Length() + DEFAULT_HEADER_SIZE
);
113 ebml
.buf
= block
->Elements();
115 if (needClusterHeader
) {
116 mHasWrittenCluster
= true;
118 // This starts the Cluster element. Note that we never end this element
119 // through Ebml_EndSubElement. What the ending would allow us to do is write
120 // the full length of the cluster in the element header. That would also
121 // force us to keep the entire cluster in memory until we know where it
122 // ends. Now it instead ends through the start of the next cluster. This
123 // allows us to stream the muxed data with much lower latency than if we
124 // would have to wait for clusters to end.
125 Ebml_StartSubElement(&ebml
, &ebmlLoc
, Cluster
);
126 // if timeCode didn't under/overflow before, it shouldn't after this
127 mCurrentClusterTimecode
= aFrame
->mTime
.ToMicroseconds() / PR_USEC_PER_MSEC
;
128 Ebml_SerializeUnsigned(&ebml
, Timecode
, mCurrentClusterTimecode
);
130 // Can't under-/overflow now
134 if (MOZ_UNLIKELY(timeCode
< SHRT_MIN
|| timeCode
> SHRT_MAX
)) {
135 MOZ_CRASH_UNSAFE_PRINTF(
136 "Invalid cluster timecode! audio=%d, video=%d, timeCode=%" PRId64
137 "ms, currentClusterTimecode=%" PRIu64
"ms",
138 mHasAudio
, mHasVideo
, timeCode
, mCurrentClusterTimecode
);
141 writeSimpleBlock(&ebml
, isOpus
? 0x2 : 0x1, static_cast<short>(timeCode
),
143 (unsigned char*)aFrame
->mFrameData
->Elements(),
144 aFrame
->mFrameData
->Length());
145 MOZ_ASSERT(ebml
.offset
<= DEFAULT_HEADER_SIZE
+ aFrame
->mFrameData
->Length(),
146 "write more data > EBML_BUFFER_SIZE");
147 block
->SetLength(ebml
.offset
);
152 void EbmlComposer::SetVideoConfig(uint32_t aWidth
, uint32_t aHeight
,
153 uint32_t aDisplayWidth
,
154 uint32_t aDisplayHeight
) {
155 MOZ_RELEASE_ASSERT(!mMetadataFinished
);
156 MOZ_ASSERT(aWidth
> 0, "Width should > 0");
157 MOZ_ASSERT(aHeight
> 0, "Height should > 0");
158 MOZ_ASSERT(aDisplayWidth
> 0, "DisplayWidth should > 0");
159 MOZ_ASSERT(aDisplayHeight
> 0, "DisplayHeight should > 0");
162 mDisplayWidth
= aDisplayWidth
;
163 mDisplayHeight
= aDisplayHeight
;
167 void EbmlComposer::SetAudioConfig(uint32_t aSampleFreq
, uint32_t aChannels
) {
168 MOZ_RELEASE_ASSERT(!mMetadataFinished
);
169 MOZ_ASSERT(aSampleFreq
> 0, "SampleFreq should > 0");
170 MOZ_ASSERT(aChannels
> 0, "Channels should > 0");
171 mSampleFreq
= aSampleFreq
;
172 mChannels
= aChannels
;
176 void EbmlComposer::ExtractBuffer(nsTArray
<nsTArray
<uint8_t> >* aDestBufs
,
178 if (!mMetadataFinished
) {
181 aDestBufs
->AppendElements(std::move(mBuffer
));
182 MOZ_ASSERT(mBuffer
.IsEmpty());
185 } // namespace mozilla