/src/mozilla-central/dom/media/webm/EbmlComposer.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | #include "mozilla/UniquePtr.h" |
8 | | #include "mozilla/EndianUtils.h" |
9 | | #include "libmkv/EbmlIDs.h" |
10 | | #include "libmkv/EbmlWriter.h" |
11 | | #include "libmkv/WebMElement.h" |
12 | | #include "prtime.h" |
13 | | #include "limits.h" |
14 | | |
15 | | namespace mozilla { |
16 | | |
17 | | // Timecode scale in nanoseconds |
18 | | static const unsigned long TIME_CODE_SCALE = 1000000; |
19 | | // The WebM header size without audio CodecPrivateData |
20 | | static const int32_t DEFAULT_HEADER_SIZE = 1024; |
21 | | |
22 | | void EbmlComposer::GenerateHeader() |
23 | 0 | { |
24 | 0 | // Write the EBML header. |
25 | 0 | EbmlGlobal ebml; |
26 | 0 | // The WEbM header default size usually smaller than 1k. |
27 | 0 | auto buffer = MakeUnique<uint8_t[]>(DEFAULT_HEADER_SIZE + |
28 | 0 | mCodecPrivateData.Length()); |
29 | 0 | ebml.buf = buffer.get(); |
30 | 0 | ebml.offset = 0; |
31 | 0 | writeHeader(&ebml); |
32 | 0 | { |
33 | 0 | EbmlLoc segEbmlLoc, ebmlLocseg, ebmlLoc; |
34 | 0 | Ebml_StartSubElement(&ebml, &segEbmlLoc, Segment); |
35 | 0 | { |
36 | 0 | Ebml_StartSubElement(&ebml, &ebmlLocseg, SeekHead); |
37 | 0 | // Todo: We don't know the exact sizes of encoded data and |
38 | 0 | // ignore this section. |
39 | 0 | Ebml_EndSubElement(&ebml, &ebmlLocseg); |
40 | 0 | writeSegmentInformation(&ebml, &ebmlLoc, TIME_CODE_SCALE, 0); |
41 | 0 | { |
42 | 0 | EbmlLoc trackLoc; |
43 | 0 | Ebml_StartSubElement(&ebml, &trackLoc, Tracks); |
44 | 0 | { |
45 | 0 | // Video |
46 | 0 | if (mWidth > 0 && mHeight > 0) { |
47 | 0 | writeVideoTrack(&ebml, 0x1, 0, "V_VP8", |
48 | 0 | mWidth, mHeight, |
49 | 0 | mDisplayWidth, mDisplayHeight); |
50 | 0 | } |
51 | 0 | // Audio |
52 | 0 | if (mCodecPrivateData.Length() > 0) { |
53 | 0 | // Extract the pre-skip from mCodecPrivateData |
54 | 0 | // then convert it to nanoseconds. |
55 | 0 | // Details in OpusTrackEncoder.cpp. |
56 | 0 | mCodecDelay = |
57 | 0 | (uint64_t)LittleEndian::readUint16(mCodecPrivateData.Elements() + 10) |
58 | 0 | * PR_NSEC_PER_SEC / 48000; |
59 | 0 | // Fixed 80ms, convert into nanoseconds. |
60 | 0 | uint64_t seekPreRoll = 80 * PR_NSEC_PER_MSEC; |
61 | 0 | writeAudioTrack(&ebml, 0x2, 0x0, "A_OPUS", mSampleFreq, |
62 | 0 | mChannels, mCodecDelay, seekPreRoll, |
63 | 0 | mCodecPrivateData.Elements(), |
64 | 0 | mCodecPrivateData.Length()); |
65 | 0 | } |
66 | 0 | } |
67 | 0 | Ebml_EndSubElement(&ebml, &trackLoc); |
68 | 0 | } |
69 | 0 | } |
70 | 0 | // The Recording length is unknown and |
71 | 0 | // ignore write the whole Segment element size |
72 | 0 | } |
73 | 0 | MOZ_ASSERT(ebml.offset <= DEFAULT_HEADER_SIZE + mCodecPrivateData.Length(), |
74 | 0 | "write more data > EBML_BUFFER_SIZE"); |
75 | 0 | auto block = mClusterBuffs.AppendElement(); |
76 | 0 | block->SetLength(ebml.offset); |
77 | 0 | memcpy(block->Elements(), ebml.buf, ebml.offset); |
78 | 0 | mFlushState |= FLUSH_METADATA; |
79 | 0 | } |
80 | | |
81 | | void EbmlComposer::FinishMetadata() |
82 | 0 | { |
83 | 0 | if (mFlushState & FLUSH_METADATA) { |
84 | 0 | // We don't remove the first element of mClusterBuffs because the |
85 | 0 | // |mClusterHeaderIndex| may have value. |
86 | 0 | mClusterCanFlushBuffs.AppendElement()->SwapElements(mClusterBuffs[0]); |
87 | 0 | mFlushState &= ~FLUSH_METADATA; |
88 | 0 | } |
89 | 0 | } |
90 | | |
91 | | void EbmlComposer::FinishCluster() |
92 | 0 | { |
93 | 0 | FinishMetadata(); |
94 | 0 | if (!(mFlushState & FLUSH_CLUSTER)) { |
95 | 0 | // No completed cluster available. |
96 | 0 | return; |
97 | 0 | } |
98 | 0 | |
99 | 0 | MOZ_ASSERT(mClusterLengthLoc > 0); |
100 | 0 | EbmlGlobal ebml; |
101 | 0 | EbmlLoc ebmlLoc; |
102 | 0 | ebmlLoc.offset = mClusterLengthLoc; |
103 | 0 | ebml.offset = 0; |
104 | 0 | for (uint32_t i = mClusterHeaderIndex; i < mClusterBuffs.Length(); i++) { |
105 | 0 | ebml.offset += mClusterBuffs[i].Length(); |
106 | 0 | } |
107 | 0 | ebml.buf = mClusterBuffs[mClusterHeaderIndex].Elements(); |
108 | 0 | Ebml_EndSubElement(&ebml, &ebmlLoc); |
109 | 0 | // Move the mClusterBuffs data from mClusterHeaderIndex that we can skip |
110 | 0 | // the metadata and the rest P-frames after ContainerWriter::FLUSH_NEEDED. |
111 | 0 | for (uint32_t i = mClusterHeaderIndex; i < mClusterBuffs.Length(); i++) { |
112 | 0 | mClusterCanFlushBuffs.AppendElement()->SwapElements(mClusterBuffs[i]); |
113 | 0 | } |
114 | 0 |
|
115 | 0 | mClusterHeaderIndex = 0; |
116 | 0 | mClusterLengthLoc = 0; |
117 | 0 | mClusterBuffs.Clear(); |
118 | 0 | mFlushState &= ~FLUSH_CLUSTER; |
119 | 0 | } |
120 | | |
121 | | void |
122 | | EbmlComposer::WriteSimpleBlock(EncodedFrame* aFrame) |
123 | 0 | { |
124 | 0 | EbmlGlobal ebml; |
125 | 0 | ebml.offset = 0; |
126 | 0 |
|
127 | 0 | auto frameType = aFrame->GetFrameType(); |
128 | 0 | bool flush = false; |
129 | 0 | bool isVP8IFrame = (frameType == EncodedFrame::FrameType::VP8_I_FRAME); |
130 | 0 | if (isVP8IFrame) { |
131 | 0 | FinishCluster(); |
132 | 0 | flush = true; |
133 | 0 | } else { |
134 | 0 | // Force it to calculate timecode using signed math via cast |
135 | 0 | int64_t timeCode = (aFrame->GetTimeStamp() / ((int) PR_USEC_PER_MSEC) - mClusterTimecode) + |
136 | 0 | (mCodecDelay / PR_NSEC_PER_MSEC); |
137 | 0 | if (timeCode < SHRT_MIN || timeCode > SHRT_MAX ) { |
138 | 0 | // We're probably going to overflow (or underflow) the timeCode value later! |
139 | 0 | FinishCluster(); |
140 | 0 | flush = true; |
141 | 0 | } |
142 | 0 | } |
143 | 0 |
|
144 | 0 | auto block = mClusterBuffs.AppendElement(); |
145 | 0 | block->SetLength(aFrame->GetFrameData().Length() + DEFAULT_HEADER_SIZE); |
146 | 0 | ebml.buf = block->Elements(); |
147 | 0 |
|
148 | 0 | if (flush) { |
149 | 0 | EbmlLoc ebmlLoc; |
150 | 0 | Ebml_StartSubElement(&ebml, &ebmlLoc, Cluster); |
151 | 0 | MOZ_ASSERT(mClusterBuffs.Length() > 0); |
152 | 0 | // current cluster header array index |
153 | 0 | mClusterHeaderIndex = mClusterBuffs.Length() - 1; |
154 | 0 | mClusterLengthLoc = ebmlLoc.offset; |
155 | 0 | // if timeCode didn't under/overflow before, it shouldn't after this |
156 | 0 | mClusterTimecode = aFrame->GetTimeStamp() / PR_USEC_PER_MSEC; |
157 | 0 | Ebml_SerializeUnsigned(&ebml, Timecode, mClusterTimecode); |
158 | 0 | mFlushState |= FLUSH_CLUSTER; |
159 | 0 | } |
160 | 0 |
|
161 | 0 | bool isOpus = (frameType == EncodedFrame::FrameType::OPUS_AUDIO_FRAME); |
162 | 0 | // Can't underflow/overflow now |
163 | 0 | int64_t timeCode = aFrame->GetTimeStamp() / ((int) PR_USEC_PER_MSEC) - mClusterTimecode; |
164 | 0 | if (isOpus) { |
165 | 0 | timeCode += mCodecDelay / PR_NSEC_PER_MSEC; |
166 | 0 | } |
167 | 0 | MOZ_ASSERT(timeCode >= SHRT_MIN && timeCode <= SHRT_MAX); |
168 | 0 | writeSimpleBlock(&ebml, isOpus ? 0x2 : 0x1, static_cast<short>(timeCode), isVP8IFrame, |
169 | 0 | 0, 0, (unsigned char*)aFrame->GetFrameData().Elements(), |
170 | 0 | aFrame->GetFrameData().Length()); |
171 | 0 | MOZ_ASSERT(ebml.offset <= DEFAULT_HEADER_SIZE + |
172 | 0 | aFrame->GetFrameData().Length(), |
173 | 0 | "write more data > EBML_BUFFER_SIZE"); |
174 | 0 | block->SetLength(ebml.offset); |
175 | 0 | } |
176 | | |
177 | | void |
178 | | EbmlComposer::SetVideoConfig(uint32_t aWidth, uint32_t aHeight, |
179 | | uint32_t aDisplayWidth, uint32_t aDisplayHeight) |
180 | 0 | { |
181 | 0 | MOZ_ASSERT(aWidth > 0, "Width should > 0"); |
182 | 0 | MOZ_ASSERT(aHeight > 0, "Height should > 0"); |
183 | 0 | MOZ_ASSERT(aDisplayWidth > 0, "DisplayWidth should > 0"); |
184 | 0 | MOZ_ASSERT(aDisplayHeight > 0, "DisplayHeight should > 0"); |
185 | 0 | mWidth = aWidth; |
186 | 0 | mHeight = aHeight; |
187 | 0 | mDisplayWidth = aDisplayWidth; |
188 | 0 | mDisplayHeight = aDisplayHeight; |
189 | 0 | } |
190 | | |
191 | | void |
192 | | EbmlComposer::SetAudioConfig(uint32_t aSampleFreq, uint32_t aChannels) |
193 | 0 | { |
194 | 0 | MOZ_ASSERT(aSampleFreq > 0, "SampleFreq should > 0"); |
195 | 0 | MOZ_ASSERT(aChannels > 0, "Channels should > 0"); |
196 | 0 | mSampleFreq = aSampleFreq; |
197 | 0 | mChannels = aChannels; |
198 | 0 | } |
199 | | |
200 | | void |
201 | | EbmlComposer::ExtractBuffer(nsTArray<nsTArray<uint8_t> >* aDestBufs, |
202 | | uint32_t aFlag) |
203 | 0 | { |
204 | 0 | if ((aFlag & ContainerWriter::FLUSH_NEEDED) || |
205 | 0 | (aFlag & ContainerWriter::GET_HEADER)) |
206 | 0 | { |
207 | 0 | FinishMetadata(); |
208 | 0 | } |
209 | 0 | if (aFlag & ContainerWriter::FLUSH_NEEDED) |
210 | 0 | { |
211 | 0 | FinishCluster(); |
212 | 0 | } |
213 | 0 | // aDestBufs may have some element |
214 | 0 | for (uint32_t i = 0; i < mClusterCanFlushBuffs.Length(); i++) { |
215 | 0 | aDestBufs->AppendElement()->SwapElements(mClusterCanFlushBuffs[i]); |
216 | 0 | } |
217 | 0 | mClusterCanFlushBuffs.Clear(); |
218 | 0 | } |
219 | | |
220 | | EbmlComposer::EbmlComposer() |
221 | | : mFlushState(FLUSH_NONE) |
222 | | , mClusterHeaderIndex(0) |
223 | | , mClusterLengthLoc(0) |
224 | | , mCodecDelay(0) |
225 | | , mClusterTimecode(0) |
226 | | , mWidth(0) |
227 | | , mHeight(0) |
228 | | , mDisplayWidth(0) |
229 | | , mDisplayHeight(0) |
230 | | , mSampleFreq(0) |
231 | | , mChannels(0) |
232 | 0 | {} |
233 | | |
234 | | } // namespace mozilla |