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, &params);
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, &params);
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