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