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