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
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "gtest/gtest.h"
7 #include "mozilla/CheckedInt.h"
8 #include "mozilla/MathAlgorithms.h"
9 #include "nestegg/nestegg.h"
10 #include "DriftCompensation.h"
11 #include "OpusTrackEncoder.h"
12 #include "VP8TrackEncoder.h"
13 #include "WebMWriter.h"
14
15 using namespace mozilla;
16
17 class WebMOpusTrackEncoder : public OpusTrackEncoder {
18 public:
WebMOpusTrackEncoder(TrackRate aTrackRate)19 explicit WebMOpusTrackEncoder(TrackRate aTrackRate)
20 : OpusTrackEncoder(aTrackRate, mEncodedAudioQueue) {}
TestOpusCreation(int aChannels)21 bool TestOpusCreation(int aChannels) {
22 if (NS_SUCCEEDED(Init(aChannels))) {
23 return true;
24 }
25 return false;
26 }
27 MediaQueue<EncodedFrame> mEncodedAudioQueue;
28 };
29
30 class WebMVP8TrackEncoder : public VP8TrackEncoder {
31 public:
WebMVP8TrackEncoder(TrackRate aTrackRate=90000)32 explicit WebMVP8TrackEncoder(TrackRate aTrackRate = 90000)
33 : VP8TrackEncoder(nullptr, aTrackRate, mEncodedVideoQueue,
34 FrameDroppingMode::DISALLOW) {}
35
TestVP8Creation(int32_t aWidth,int32_t aHeight,int32_t aDisplayWidth,int32_t aDisplayHeight)36 bool TestVP8Creation(int32_t aWidth, int32_t aHeight, int32_t aDisplayWidth,
37 int32_t aDisplayHeight) {
38 if (NS_SUCCEEDED(
39 Init(aWidth, aHeight, aDisplayWidth, aDisplayHeight, 30))) {
40 return true;
41 }
42 return false;
43 }
44 MediaQueue<EncodedFrame> mEncodedVideoQueue;
45 };
46
GetOpusMetadata(int aChannels,TrackRate aTrackRate,nsTArray<RefPtr<TrackMetadataBase>> & aMeta)47 static void GetOpusMetadata(int aChannels, TrackRate aTrackRate,
48 nsTArray<RefPtr<TrackMetadataBase>>& aMeta) {
49 WebMOpusTrackEncoder opusEncoder(aTrackRate);
50 EXPECT_TRUE(opusEncoder.TestOpusCreation(aChannels));
51 aMeta.AppendElement(opusEncoder.GetMetadata());
52 }
53
GetVP8Metadata(int32_t aWidth,int32_t aHeight,int32_t aDisplayWidth,int32_t aDisplayHeight,TrackRate aTrackRate,nsTArray<RefPtr<TrackMetadataBase>> & aMeta)54 static void GetVP8Metadata(int32_t aWidth, int32_t aHeight,
55 int32_t aDisplayWidth, int32_t aDisplayHeight,
56 TrackRate aTrackRate,
57 nsTArray<RefPtr<TrackMetadataBase>>& aMeta) {
58 WebMVP8TrackEncoder vp8Encoder;
59 EXPECT_TRUE(vp8Encoder.TestVP8Creation(aWidth, aHeight, aDisplayWidth,
60 aDisplayHeight));
61 aMeta.AppendElement(vp8Encoder.GetMetadata());
62 }
63
64 const uint64_t FIXED_DURATION = 1000000;
65 const uint32_t FIXED_FRAMESIZE = 500;
66
67 class TestWebMWriter : public WebMWriter {
68 public:
TestWebMWriter()69 TestWebMWriter() : WebMWriter() {}
70
71 // When we append an I-Frame into WebM muxer, the muxer will treat previous
72 // data as "a cluster".
73 // In these test cases, we will call the function many times to enclose the
74 // previous cluster so that we can retrieve data by |GetContainerData|.
AppendDummyFrame(EncodedFrame::FrameType aFrameType,uint64_t aDuration)75 void AppendDummyFrame(EncodedFrame::FrameType aFrameType,
76 uint64_t aDuration) {
77 nsTArray<RefPtr<EncodedFrame>> encodedVideoData;
78 auto frameData = MakeRefPtr<EncodedFrame::FrameData>();
79 // Create dummy frame data.
80 frameData->SetLength(FIXED_FRAMESIZE);
81 encodedVideoData.AppendElement(
82 MakeRefPtr<EncodedFrame>(mTimestamp, aDuration, PR_USEC_PER_SEC,
83 aFrameType, std::move(frameData)));
84 WriteEncodedTrack(encodedVideoData, 0);
85 mTimestamp += media::TimeUnit::FromMicroseconds(aDuration);
86 }
87
HaveValidCluster()88 bool HaveValidCluster() {
89 nsTArray<nsTArray<uint8_t>> encodedBuf;
90 GetContainerData(&encodedBuf, 0);
91 return !encodedBuf.IsEmpty();
92 }
93
94 // Timestamp accumulator that increased by AppendDummyFrame.
95 // Keep it public that we can do some testcases about it.
96 media::TimeUnit mTimestamp;
97 };
98
TEST(WebMWriter,Metadata)99 TEST(WebMWriter, Metadata)
100 {
101 TestWebMWriter writer;
102
103 // The output should be empty since we didn't set any metadata in writer.
104 nsTArray<nsTArray<uint8_t>> encodedBuf;
105 writer.GetContainerData(&encodedBuf, ContainerWriter::GET_HEADER);
106 EXPECT_TRUE(encodedBuf.Length() == 0);
107 writer.GetContainerData(&encodedBuf, ContainerWriter::FLUSH_NEEDED);
108 EXPECT_TRUE(encodedBuf.Length() == 0);
109
110 nsTArray<RefPtr<TrackMetadataBase>> meta;
111
112 TrackRate trackRate = 44100;
113
114 // Get opus metadata.
115 int channel = 1;
116 GetOpusMetadata(channel, trackRate, meta);
117
118 // Get vp8 metadata
119 int32_t width = 640;
120 int32_t height = 480;
121 int32_t displayWidth = 640;
122 int32_t displayHeight = 480;
123 GetVP8Metadata(width, height, displayWidth, displayHeight, trackRate, meta);
124
125 // Set metadata
126 writer.SetMetadata(meta);
127
128 writer.GetContainerData(&encodedBuf, ContainerWriter::GET_HEADER);
129 EXPECT_TRUE(encodedBuf.Length() > 0);
130 }
131
TEST(WebMWriter,Cluster)132 TEST(WebMWriter, Cluster)
133 {
134 TestWebMWriter writer;
135 nsTArray<RefPtr<TrackMetadataBase>> meta;
136 TrackRate trackRate = 48000;
137 // Get opus metadata.
138 int channel = 1;
139 GetOpusMetadata(channel, trackRate, meta);
140 // Get vp8 metadata
141 int32_t width = 320;
142 int32_t height = 240;
143 int32_t displayWidth = 320;
144 int32_t displayHeight = 240;
145 GetVP8Metadata(width, height, displayWidth, displayHeight, trackRate, meta);
146 writer.SetMetadata(meta);
147
148 nsTArray<nsTArray<uint8_t>> encodedBuf;
149 writer.GetContainerData(&encodedBuf, ContainerWriter::GET_HEADER);
150 EXPECT_TRUE(encodedBuf.Length() > 0);
151 encodedBuf.Clear();
152
153 // write the first I-Frame.
154 writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION);
155 EXPECT_TRUE(writer.HaveValidCluster());
156
157 // The second I-Frame.
158 writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION);
159 EXPECT_TRUE(writer.HaveValidCluster());
160
161 // P-Frame.
162 writer.AppendDummyFrame(EncodedFrame::VP8_P_FRAME, FIXED_DURATION);
163 EXPECT_TRUE(writer.HaveValidCluster());
164
165 // The third I-Frame.
166 writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION);
167 EXPECT_TRUE(writer.HaveValidCluster());
168 }
169
TEST(WebMWriter,FLUSH_NEEDED)170 TEST(WebMWriter, FLUSH_NEEDED)
171 {
172 TestWebMWriter writer;
173 nsTArray<RefPtr<TrackMetadataBase>> meta;
174 TrackRate trackRate = 44100;
175 // Get opus metadata.
176 int channel = 2;
177 GetOpusMetadata(channel, trackRate, meta);
178 // Get vp8 metadata
179 int32_t width = 176;
180 int32_t height = 352;
181 int32_t displayWidth = 176;
182 int32_t displayHeight = 352;
183 GetVP8Metadata(width, height, displayWidth, displayHeight, trackRate, meta);
184 writer.SetMetadata(meta);
185 // Have data because the metadata is finished.
186 EXPECT_TRUE(writer.HaveValidCluster());
187
188 // write the first I-Frame.
189 writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION);
190
191 // P-Frame
192 writer.AppendDummyFrame(EncodedFrame::VP8_P_FRAME, FIXED_DURATION);
193 // Have data because frames were written.
194 EXPECT_TRUE(writer.HaveValidCluster());
195 // No data because the previous check emptied it.
196 EXPECT_FALSE(writer.HaveValidCluster());
197
198 nsTArray<nsTArray<uint8_t>> encodedBuf;
199 // No data because the flag ContainerWriter::FLUSH_NEEDED does nothing.
200 writer.GetContainerData(&encodedBuf, ContainerWriter::FLUSH_NEEDED);
201 EXPECT_TRUE(encodedBuf.IsEmpty());
202 encodedBuf.Clear();
203
204 // P-Frame
205 writer.AppendDummyFrame(EncodedFrame::VP8_P_FRAME, FIXED_DURATION);
206 // Have data because we continue the previous cluster.
207 EXPECT_TRUE(writer.HaveValidCluster());
208
209 // I-Frame
210 writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION);
211 // Have data with a new cluster.
212 EXPECT_TRUE(writer.HaveValidCluster());
213
214 // I-Frame
215 writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION);
216 // Have data with a new cluster.
217 EXPECT_TRUE(writer.HaveValidCluster());
218 }
219
220 struct WebMioData {
221 nsTArray<uint8_t> data;
222 CheckedInt<size_t> offset;
223 };
224
webm_read(void * aBuffer,size_t aLength,void * aUserData)225 static int webm_read(void* aBuffer, size_t aLength, void* aUserData) {
226 NS_ASSERTION(aUserData, "aUserData must point to a valid WebMioData");
227 WebMioData* ioData = static_cast<WebMioData*>(aUserData);
228
229 // Check the read length.
230 if (aLength > ioData->data.Length()) {
231 return 0;
232 }
233
234 // Check eos.
235 if (ioData->offset.value() >= ioData->data.Length()) {
236 return 0;
237 }
238
239 size_t oldOffset = ioData->offset.value();
240 ioData->offset += aLength;
241 if (!ioData->offset.isValid() ||
242 (ioData->offset.value() > ioData->data.Length())) {
243 return -1;
244 }
245 memcpy(aBuffer, ioData->data.Elements() + oldOffset, aLength);
246 return 1;
247 }
248
webm_seek(int64_t aOffset,int aWhence,void * aUserData)249 static int webm_seek(int64_t aOffset, int aWhence, void* aUserData) {
250 NS_ASSERTION(aUserData, "aUserData must point to a valid WebMioData");
251 WebMioData* ioData = static_cast<WebMioData*>(aUserData);
252
253 if (Abs(aOffset) > ioData->data.Length()) {
254 NS_ERROR("Invalid aOffset");
255 return -1;
256 }
257
258 switch (aWhence) {
259 case NESTEGG_SEEK_END: {
260 CheckedInt<size_t> tempOffset = ioData->data.Length();
261 ioData->offset = tempOffset + aOffset;
262 break;
263 }
264 case NESTEGG_SEEK_CUR:
265 ioData->offset += aOffset;
266 break;
267 case NESTEGG_SEEK_SET:
268 ioData->offset = aOffset;
269 break;
270 default:
271 NS_ERROR("Unknown whence");
272 return -1;
273 }
274
275 if (!ioData->offset.isValid()) {
276 NS_ERROR("Invalid offset");
277 return -1;
278 }
279
280 return 0;
281 }
282
webm_tell(void * aUserData)283 static int64_t webm_tell(void* aUserData) {
284 NS_ASSERTION(aUserData, "aUserData must point to a valid WebMioData");
285 WebMioData* ioData = static_cast<WebMioData*>(aUserData);
286 return ioData->offset.isValid() ? ioData->offset.value() : -1;
287 }
288
TEST(WebMWriter,bug970774_aspect_ratio)289 TEST(WebMWriter, bug970774_aspect_ratio)
290 {
291 TestWebMWriter writer;
292 nsTArray<RefPtr<TrackMetadataBase>> meta;
293 TrackRate trackRate = 44100;
294 // Get opus metadata.
295 int channel = 1;
296 GetOpusMetadata(channel, trackRate, meta);
297 // Set vp8 metadata
298 int32_t width = 640;
299 int32_t height = 480;
300 int32_t displayWidth = 1280;
301 int32_t displayHeight = 960;
302 GetVP8Metadata(width, height, displayWidth, displayHeight, trackRate, meta);
303 writer.SetMetadata(meta);
304
305 // write the first I-Frame.
306 writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION);
307
308 // write the second I-Frame.
309 writer.AppendDummyFrame(EncodedFrame::VP8_I_FRAME, FIXED_DURATION);
310
311 // Get the metadata and the first cluster.
312 nsTArray<nsTArray<uint8_t>> encodedBuf;
313 writer.GetContainerData(&encodedBuf, 0);
314 // Flatten the encodedBuf.
315 WebMioData ioData;
316 ioData.offset = 0;
317 for (uint32_t i = 0; i < encodedBuf.Length(); ++i) {
318 ioData.data.AppendElements(encodedBuf[i]);
319 }
320
321 // Use nestegg to verify the information in metadata.
322 nestegg* context = nullptr;
323 nestegg_io io;
324 io.read = webm_read;
325 io.seek = webm_seek;
326 io.tell = webm_tell;
327 io.userdata = static_cast<void*>(&ioData);
328 int rv = nestegg_init(&context, io, nullptr, -1);
329 EXPECT_EQ(rv, 0);
330 unsigned int ntracks = 0;
331 rv = nestegg_track_count(context, &ntracks);
332 EXPECT_EQ(rv, 0);
333 EXPECT_EQ(ntracks, (unsigned int)2);
334 for (unsigned int track = 0; track < ntracks; ++track) {
335 int id = nestegg_track_codec_id(context, track);
336 EXPECT_NE(id, -1);
337 int type = nestegg_track_type(context, track);
338 if (type == NESTEGG_TRACK_VIDEO) {
339 nestegg_video_params params;
340 rv = nestegg_track_video_params(context, track, ¶ms);
341 EXPECT_EQ(rv, 0);
342 EXPECT_EQ(width, static_cast<int32_t>(params.width));
343 EXPECT_EQ(height, static_cast<int32_t>(params.height));
344 EXPECT_EQ(displayWidth, static_cast<int32_t>(params.display_width));
345 EXPECT_EQ(displayHeight, static_cast<int32_t>(params.display_height));
346 } else if (type == NESTEGG_TRACK_AUDIO) {
347 nestegg_audio_params params;
348 rv = nestegg_track_audio_params(context, track, ¶ms);
349 EXPECT_EQ(rv, 0);
350 EXPECT_EQ(channel, static_cast<int>(params.channels));
351 EXPECT_EQ(static_cast<double>(trackRate), params.rate);
352 }
353 }
354 if (context) {
355 nestegg_destroy(context);
356 }
357 }
358