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 "js/Conversions.h"
8 #include "MediaData.h"
9 #include "mozilla/ArrayUtils.h"
10 #include "mozilla/Preferences.h"
11 #include "mozilla/Tuple.h"
12 #include "BufferStream.h"
13 #include "MP4Metadata.h"
14 #include "MoofParser.h"
15 #include "TelemetryFixture.h"
16 #include "TelemetryTestHelpers.h"
17 
18 class TestStream;
19 namespace mozilla {
20 DDLoggedTypeNameAndBase(::TestStream, ByteStream);
21 }  // namespace mozilla
22 
23 using namespace mozilla;
24 
25 static const uint32_t E = MP4Metadata::NumberTracksError();
26 
27 class TestStream : public ByteStream,
28                    public DecoderDoctorLifeLogger<TestStream> {
29  public:
TestStream(const uint8_t * aBuffer,size_t aSize)30   TestStream(const uint8_t* aBuffer, size_t aSize)
31       : mHighestSuccessfulEndOffset(0), mBuffer(aBuffer), mSize(aSize) {}
ReadAt(int64_t aOffset,void * aData,size_t aLength,size_t * aBytesRead)32   bool ReadAt(int64_t aOffset, void* aData, size_t aLength,
33               size_t* aBytesRead) override {
34     if (aOffset < 0 || aOffset > static_cast<int64_t>(mSize)) {
35       return false;
36     }
37     // After the test, 0 <= aOffset <= mSize <= SIZE_MAX, so it's safe to cast
38     // to size_t.
39     size_t offset = static_cast<size_t>(aOffset);
40     // Don't read past the end (but it's not an error to try).
41     if (aLength > mSize - offset) {
42       aLength = mSize - offset;
43     }
44     // Now, 0 <= offset <= offset + aLength <= mSize <= SIZE_MAX.
45     *aBytesRead = aLength;
46     memcpy(aData, mBuffer + offset, aLength);
47     if (mHighestSuccessfulEndOffset < offset + aLength) {
48       mHighestSuccessfulEndOffset = offset + aLength;
49     }
50     return true;
51   }
CachedReadAt(int64_t aOffset,void * aData,size_t aLength,size_t * aBytesRead)52   bool CachedReadAt(int64_t aOffset, void* aData, size_t aLength,
53                     size_t* aBytesRead) override {
54     return ReadAt(aOffset, aData, aLength, aBytesRead);
55   }
Length(int64_t * aLength)56   bool Length(int64_t* aLength) override {
57     *aLength = mSize;
58     return true;
59   }
DiscardBefore(int64_t aOffset)60   void DiscardBefore(int64_t aOffset) override {}
61 
62   // Offset past the last character ever read. 0 when nothing read yet.
63   size_t mHighestSuccessfulEndOffset;
64 
65  protected:
66   virtual ~TestStream() = default;
67 
68   const uint8_t* mBuffer;
69   size_t mSize;
70 };
71 
TEST(MP4Metadata,EmptyStream)72 TEST(MP4Metadata, EmptyStream)
73 {
74   RefPtr<ByteStream> stream = new TestStream(nullptr, 0);
75 
76   MP4Metadata::ResultAndByteBuffer metadataBuffer =
77       MP4Metadata::Metadata(stream);
78   EXPECT_TRUE(NS_OK != metadataBuffer.Result());
79   EXPECT_FALSE(static_cast<bool>(metadataBuffer.Ref()));
80 
81   MP4Metadata metadata(stream);
82   EXPECT_TRUE(0u ==
83                   metadata.GetNumberTracks(TrackInfo::kUndefinedTrack).Ref() ||
84               E == metadata.GetNumberTracks(TrackInfo::kUndefinedTrack).Ref());
85   EXPECT_TRUE(0u == metadata.GetNumberTracks(TrackInfo::kAudioTrack).Ref() ||
86               E == metadata.GetNumberTracks(TrackInfo::kAudioTrack).Ref());
87   EXPECT_TRUE(0u == metadata.GetNumberTracks(TrackInfo::kVideoTrack).Ref() ||
88               E == metadata.GetNumberTracks(TrackInfo::kVideoTrack).Ref());
89   EXPECT_TRUE(0u == metadata.GetNumberTracks(TrackInfo::kTextTrack).Ref() ||
90               E == metadata.GetNumberTracks(TrackInfo::kTextTrack).Ref());
91   EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kAudioTrack, 0).Ref());
92   EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kVideoTrack, 0).Ref());
93   EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kTextTrack, 0).Ref());
94   // We can seek anywhere in any MPEG4.
95   EXPECT_TRUE(metadata.CanSeek());
96   EXPECT_FALSE(metadata.Crypto().Ref()->valid);
97 }
98 
TEST(MoofParser,EmptyStream)99 TEST(MoofParser, EmptyStream)
100 {
101   RefPtr<ByteStream> stream = new TestStream(nullptr, 0);
102 
103   MoofParser parser(stream, AsVariant(ParseAllTracks{}), false);
104   EXPECT_EQ(0u, parser.mOffset);
105   EXPECT_TRUE(parser.ReachedEnd());
106 
107   MediaByteRangeSet byteRanges;
108   EXPECT_FALSE(parser.RebuildFragmentedIndex(byteRanges));
109 
110   EXPECT_TRUE(parser.GetCompositionRange(byteRanges).IsNull());
111   EXPECT_TRUE(parser.mInitRange.IsEmpty());
112   EXPECT_EQ(0u, parser.mOffset);
113   EXPECT_TRUE(parser.ReachedEnd());
114   RefPtr<MediaByteBuffer> metadataBuffer = parser.Metadata();
115   EXPECT_FALSE(metadataBuffer);
116   EXPECT_TRUE(parser.FirstCompleteMediaSegment().IsEmpty());
117   EXPECT_TRUE(parser.FirstCompleteMediaHeader().IsEmpty());
118 }
119 
ReadTestFile(const char * aFilename)120 nsTArray<uint8_t> ReadTestFile(const char* aFilename) {
121   if (!aFilename) {
122     return {};
123   }
124   FILE* f = fopen(aFilename, "rb");
125   if (!f) {
126     return {};
127   }
128 
129   if (fseek(f, 0, SEEK_END) != 0) {
130     fclose(f);
131     return {};
132   }
133   long position = ftell(f);
134   // I know EOF==-1, so this test is made obsolete by '<0', but I don't want
135   // the code to rely on that.
136   if (position == 0 || position == EOF || position < 0) {
137     fclose(f);
138     return {};
139   }
140   if (fseek(f, 0, SEEK_SET) != 0) {
141     fclose(f);
142     return {};
143   }
144 
145   size_t len = static_cast<size_t>(position);
146   nsTArray<uint8_t> buffer(len);
147   buffer.SetLength(len);
148   size_t read = fread(buffer.Elements(), 1, len, f);
149   fclose(f);
150   if (read != len) {
151     return {};
152   }
153 
154   return buffer;
155 }
156 
157 struct TestFileData {
158   const char* mFilename;
159   bool mParseResult;
160   uint32_t mNumberVideoTracks;
161   bool mHasVideoIndice;
162   int64_t mVideoDuration;  // For first video track, -1 if N/A.
163   int32_t mWidth;
164   int32_t mHeight;
165   uint32_t mNumberAudioTracks;
166   int64_t mAudioDuration;  // For first audio track, -1 if N/A.
167   bool mHasCrypto;  // Note, MP4Metadata only considers pssh box for crypto.
168   uint64_t mMoofReachedOffset;  // or 0 for the end.
169   bool mValidMoof;
170   int8_t mAudioProfile;
171 };
172 
173 static const TestFileData testFiles[] = {
174     // filename parses? #V hasVideoIndex vDur w h #A aDur hasCrypto? moofOffset
175     // validMoof? audio_profile
176     {"test_case_1156505.mp4", false, 0, false, -1, 0, 0, 0, -1, false, 152,
177      false, 0},  // invalid ''trak box
178     {"test_case_1181213.mp4", true, 1, true, 416666, 320, 240, 1, 477460, true,
179      0, false, 2},
180     {"test_case_1181215.mp4", true, 0, false, -1, 0, 0, 0, -1, false, 0, false,
181      0},
182     {"test_case_1181223.mp4", false, 0, false, 416666, 320, 240, 0, -1, false,
183      0, false, 0},
184     {"test_case_1181719.mp4", false, 0, false, -1, 0, 0, 0, -1, false, 0, false,
185      0},
186     {"test_case_1185230.mp4", true, 2, true, 416666, 320, 240, 2, 5, false, 0,
187      false, 2},
188     {"test_case_1187067.mp4", true, 1, true, 80000, 160, 90, 0, -1, false, 0,
189      false, 0},
190     {"test_case_1200326.mp4", false, 0, false, -1, 0, 0, 0, -1, false, 0, false,
191      0},
192     {"test_case_1204580.mp4", true, 1, true, 502500, 320, 180, 0, -1, false, 0,
193      false, 0},
194     {"test_case_1216748.mp4", false, 0, false, -1, 0, 0, 0, -1, false, 152,
195      false, 0},  // invalid 'trak' box
196     {"test_case_1296473.mp4", false, 0, false, -1, 0, 0, 0, -1, false, 0, false,
197      0},
198     {"test_case_1296532.mp4", true, 1, true, 5589333, 560, 320, 1, 5589333,
199      true, 0, true, 2},
200     {"test_case_1301065.mp4", true, 0, false, -1, 0, 0, 1, 100079991719000000,
201      false, 0, false, 2},
202     {"test_case_1301065-u32max.mp4", true, 0, false, -1, 0, 0, 1, 97391548639,
203      false, 0, false, 2},
204     {"test_case_1301065-max-ez.mp4", true, 0, false, -1, 0, 0, 1,
205      209146758205306, false, 0, false, 2},
206     {"test_case_1301065-harder.mp4", true, 0, false, -1, 0, 0, 1,
207      209146758205328, false, 0, false, 2},
208     {"test_case_1301065-max-ok.mp4", true, 0, false, -1, 0, 0, 1,
209      9223372036854775804, false, 0, false, 2},
210     // The duration is overflow for int64_t in TestFileData, parser uses
211     // uint64_t so
212     // this file is ignore.
213     //{ "test_case_1301065-overfl.mp4", 0, -1,   0,   0, 1, 9223372036854775827,
214     //                                                          false,   0,
215     //                                                          false, 2
216     //                                                          },
217     {"test_case_1301065-i64max.mp4", true, 0, false, -1, 0, 0, 0, -1, false, 0,
218      false, 0},
219     {"test_case_1301065-i64min.mp4", true, 0, false, -1, 0, 0, 0, -1, false, 0,
220      false, 0},
221     {"test_case_1301065-u64max.mp4", true, 0, false, -1, 0, 0, 1, 0, false, 0,
222      false, 2},
223     {"test_case_1329061.mov", false, 0, false, -1, 0, 0, 1, 234567981, false, 0,
224      false, 2},
225     {"test_case_1351094.mp4", true, 0, false, -1, 0, 0, 0, -1, false, 0, true,
226      0},
227     {"test_case_1389299.mp4", true, 1, true, 5589333, 560, 320, 1, 5589333,
228      true, 0, true, 2},
229 
230     {"test_case_1389527.mp4", true, 1, false, 5005000, 80, 128, 1, 4992000,
231      false, 0, false, 2},
232     {"test_case_1395244.mp4", true, 1, true, 416666, 320, 240, 1, 477460, false,
233      0, false, 2},
234     {"test_case_1388991.mp4", true, 0, false, -1, 0, 0, 1, 30000181, false, 0,
235      false, 2},
236     {"test_case_1380468.mp4", false, 0, false, 0, 0, 0, 0, 0, false, 0, false,
237      0},
238     {"test_case_1410565.mp4", false, 0, false, 0, 0, 0, 0, 0, false, 955100,
239      true, 2},  // negative 'timescale'
240     {"test_case_1513651-2-sample-description-entries.mp4", true, 1, true,
241      9843344, 400, 300, 0, -1, true, 0, false, 0},
242     {"test_case_1519617-cenc-init-with-track_id-0.mp4", true, 1, true, 0, 1272,
243      530, 0, -1, false, 0, false,
244      0},  // Uses bad track id 0 and has a sinf but no pssh
245     {"test_case_1519617-track2-trafs-removed.mp4", true, 1, true, 10032000, 400,
246      300, 1, 10032000, false, 0, true, 2},
247     {"test_case_1519617-video-has-track_id-0.mp4", true, 1, true, 10032000, 400,
248      300, 1, 10032000, false, 0, true, 2},  // Uses bad track id 0
249     // The following file has multiple sample description entries with the same
250     // crypto information. This does not cover multiple entries with different
251     // crypto information which is tracked by
252     // https://bugzilla.mozilla.org/show_bug.cgi?id=1714626
253     {"test_case_1714125-2-sample-description-entires-with-identical-crypto.mp4",
254      true, 1, true, 0, 1920, 1080, 0, 0, true, 0, false, 0},
255 };
256 
TEST(MP4Metadata,test_case_mp4)257 TEST(MP4Metadata, test_case_mp4)
258 {
259   const TestFileData* tests = nullptr;
260   size_t length = 0;
261 
262   tests = testFiles;
263   length = ArrayLength(testFiles);
264 
265   for (size_t test = 0; test < length; ++test) {
266     nsTArray<uint8_t> buffer = ReadTestFile(tests[test].mFilename);
267     ASSERT_FALSE(buffer.IsEmpty());
268     RefPtr<ByteStream> stream =
269         new TestStream(buffer.Elements(), buffer.Length());
270 
271     MP4Metadata::ResultAndByteBuffer metadataBuffer =
272         MP4Metadata::Metadata(stream);
273     EXPECT_EQ(NS_OK, metadataBuffer.Result());
274     EXPECT_TRUE(metadataBuffer.Ref());
275 
276     MP4Metadata metadata(stream);
277     nsresult res = metadata.Parse();
278     EXPECT_EQ(tests[test].mParseResult, NS_SUCCEEDED(res))
279         << tests[test].mFilename;
280     if (!tests[test].mParseResult) {
281       continue;
282     }
283 
284     EXPECT_EQ(tests[test].mNumberAudioTracks,
285               metadata.GetNumberTracks(TrackInfo::kAudioTrack).Ref())
286         << tests[test].mFilename;
287     EXPECT_EQ(tests[test].mNumberVideoTracks,
288               metadata.GetNumberTracks(TrackInfo::kVideoTrack).Ref())
289         << tests[test].mFilename;
290     // If there is an error, we should expect an error code instead of zero
291     // for non-Audio/Video tracks.
292     const uint32_t None = (tests[test].mNumberVideoTracks == E) ? E : 0;
293     EXPECT_EQ(None, metadata.GetNumberTracks(TrackInfo::kUndefinedTrack).Ref())
294         << tests[test].mFilename;
295     EXPECT_EQ(None, metadata.GetNumberTracks(TrackInfo::kTextTrack).Ref())
296         << tests[test].mFilename;
297     EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kUndefinedTrack, 0).Ref());
298     MP4Metadata::ResultAndTrackInfo trackInfo =
299         metadata.GetTrackInfo(TrackInfo::kVideoTrack, 0);
300     if (!!tests[test].mNumberVideoTracks) {
301       ASSERT_TRUE(!!trackInfo.Ref());
302       const VideoInfo* videoInfo = trackInfo.Ref()->GetAsVideoInfo();
303       ASSERT_TRUE(!!videoInfo);
304       EXPECT_TRUE(videoInfo->IsValid()) << tests[test].mFilename;
305       EXPECT_TRUE(videoInfo->IsVideo()) << tests[test].mFilename;
306       EXPECT_EQ(tests[test].mVideoDuration,
307                 videoInfo->mDuration.ToMicroseconds())
308           << tests[test].mFilename;
309       EXPECT_EQ(tests[test].mWidth, videoInfo->mDisplay.width)
310           << tests[test].mFilename;
311       EXPECT_EQ(tests[test].mHeight, videoInfo->mDisplay.height)
312           << tests[test].mFilename;
313 
314       MP4Metadata::ResultAndIndice indices =
315           metadata.GetTrackIndice(videoInfo->mTrackId);
316       EXPECT_EQ(!!indices.Ref(), tests[test].mHasVideoIndice)
317           << tests[test].mFilename;
318       if (tests[test].mHasVideoIndice) {
319         for (size_t i = 0; i < indices.Ref()->Length(); i++) {
320           Index::Indice data;
321           EXPECT_TRUE(indices.Ref()->GetIndice(i, data))
322               << tests[test].mFilename;
323           EXPECT_TRUE(data.start_offset <= data.end_offset)
324               << tests[test].mFilename;
325           EXPECT_TRUE(data.start_composition <= data.end_composition)
326               << tests[test].mFilename;
327         }
328       }
329     }
330     trackInfo = metadata.GetTrackInfo(TrackInfo::kAudioTrack, 0);
331     if (tests[test].mNumberAudioTracks == 0 ||
332         tests[test].mNumberAudioTracks == E) {
333       EXPECT_TRUE(!trackInfo.Ref()) << tests[test].mFilename;
334     } else {
335       ASSERT_TRUE(!!trackInfo.Ref());
336       const AudioInfo* audioInfo = trackInfo.Ref()->GetAsAudioInfo();
337       ASSERT_TRUE(!!audioInfo);
338       EXPECT_TRUE(audioInfo->IsValid()) << tests[test].mFilename;
339       EXPECT_TRUE(audioInfo->IsAudio()) << tests[test].mFilename;
340       EXPECT_EQ(tests[test].mAudioDuration,
341                 audioInfo->mDuration.ToMicroseconds())
342           << tests[test].mFilename;
343       EXPECT_EQ(tests[test].mAudioProfile, audioInfo->mProfile)
344           << tests[test].mFilename;
345       if (tests[test].mAudioDuration != audioInfo->mDuration.ToMicroseconds()) {
346         MOZ_RELEASE_ASSERT(false);
347       }
348 
349       MP4Metadata::ResultAndIndice indices =
350           metadata.GetTrackIndice(audioInfo->mTrackId);
351       EXPECT_TRUE(!!indices.Ref()) << tests[test].mFilename;
352       for (size_t i = 0; i < indices.Ref()->Length(); i++) {
353         Index::Indice data;
354         EXPECT_TRUE(indices.Ref()->GetIndice(i, data)) << tests[test].mFilename;
355         EXPECT_TRUE(data.start_offset <= data.end_offset)
356             << tests[test].mFilename;
357         EXPECT_TRUE(int64_t(data.start_composition) <=
358                     int64_t(data.end_composition))
359             << tests[test].mFilename;
360       }
361     }
362     EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kTextTrack, 0).Ref())
363         << tests[test].mFilename;
364     // We can see anywhere in any MPEG4.
365     EXPECT_TRUE(metadata.CanSeek()) << tests[test].mFilename;
366     EXPECT_EQ(tests[test].mHasCrypto, metadata.Crypto().Ref()->valid)
367         << tests[test].mFilename;
368   }
369 }
370 
371 // This test was disabled by Bug 1224019 for producing way too much output.
372 // This test no longer produces such output, as we've moved away from
373 // stagefright, but it does take a long time to run. I can be useful to enable
374 // as a sanity check on changes to the parser, but is too taxing to run as part
375 // of normal test execution.
376 #if 0
377 TEST(MP4Metadata, test_case_mp4_subsets) {
378   static const size_t step = 1u;
379   for (size_t test = 0; test < ArrayLength(testFiles); ++test) {
380     nsTArray<uint8_t> buffer = ReadTestFile(testFiles[test].mFilename);
381     ASSERT_FALSE(buffer.IsEmpty());
382     ASSERT_LE(step, buffer.Length());
383     // Just exercizing the parser starting at different points through the file,
384     // making sure it doesn't crash.
385     // No checks because results would differ for each position.
386     for (size_t offset = 0; offset < buffer.Length() - step; offset += step) {
387       size_t size = buffer.Length() - offset;
388       while (size > 0) {
389         RefPtr<TestStream> stream =
390           new TestStream(buffer.Elements() + offset, size);
391 
392         MP4Metadata::ResultAndByteBuffer metadataBuffer =
393           MP4Metadata::Metadata(stream);
394         MP4Metadata metadata(stream);
395 
396         if (stream->mHighestSuccessfulEndOffset <= 0) {
397           // No successful reads -> Cutting down the size won't change anything.
398           break;
399         }
400         if (stream->mHighestSuccessfulEndOffset < size) {
401           // Read up to a point before the end -> Resize down to that point.
402           size = stream->mHighestSuccessfulEndOffset;
403         } else {
404           // Read up to the end (or after?!) -> Just cut 1 byte.
405           size -= 1;
406         }
407       }
408     }
409   }
410 }
411 #endif
412 
413 #if !defined(XP_WIN) || !defined(MOZ_ASAN)  // OOMs on Windows ASan
TEST(MoofParser,test_case_mp4)414 TEST(MoofParser, test_case_mp4)
415 {
416   const TestFileData* tests = nullptr;
417   size_t length = 0;
418 
419   tests = testFiles;
420   length = ArrayLength(testFiles);
421 
422   for (size_t test = 0; test < length; ++test) {
423     nsTArray<uint8_t> buffer = ReadTestFile(tests[test].mFilename);
424     ASSERT_FALSE(buffer.IsEmpty());
425     RefPtr<ByteStream> stream =
426         new TestStream(buffer.Elements(), buffer.Length());
427 
428     MoofParser parser(stream, AsVariant(ParseAllTracks{}), false);
429     EXPECT_EQ(0u, parser.mOffset) << tests[test].mFilename;
430     EXPECT_FALSE(parser.ReachedEnd()) << tests[test].mFilename;
431     EXPECT_TRUE(parser.mInitRange.IsEmpty()) << tests[test].mFilename;
432 
433     RefPtr<MediaByteBuffer> metadataBuffer = parser.Metadata();
434     EXPECT_TRUE(metadataBuffer) << tests[test].mFilename;
435 
436     EXPECT_FALSE(parser.mInitRange.IsEmpty()) << tests[test].mFilename;
437     const MediaByteRangeSet byteRanges(
438         MediaByteRange(0, int64_t(buffer.Length())));
439     EXPECT_EQ(tests[test].mValidMoof, parser.RebuildFragmentedIndex(byteRanges))
440         << tests[test].mFilename;
441     if (tests[test].mMoofReachedOffset == 0) {
442       EXPECT_EQ(buffer.Length(), parser.mOffset) << tests[test].mFilename;
443       EXPECT_TRUE(parser.ReachedEnd()) << tests[test].mFilename;
444     } else {
445       EXPECT_EQ(tests[test].mMoofReachedOffset, parser.mOffset)
446           << tests[test].mFilename;
447       EXPECT_FALSE(parser.ReachedEnd()) << tests[test].mFilename;
448     }
449 
450     EXPECT_FALSE(parser.mInitRange.IsEmpty()) << tests[test].mFilename;
451     EXPECT_TRUE(parser.GetCompositionRange(byteRanges).IsNull())
452         << tests[test].mFilename;
453     EXPECT_TRUE(parser.FirstCompleteMediaSegment().IsEmpty())
454         << tests[test].mFilename;
455     // If we expect a valid moof we should have that moof's range stored.
456     EXPECT_EQ(tests[test].mValidMoof,
457               !parser.FirstCompleteMediaHeader().IsEmpty())
458         << tests[test].mFilename;
459   }
460 }
461 
TEST(MoofParser,test_case_sample_description_entries)462 TEST(MoofParser, test_case_sample_description_entries)
463 {
464   const TestFileData* tests = testFiles;
465   size_t length = ArrayLength(testFiles);
466 
467   for (size_t test = 0; test < length; ++test) {
468     nsTArray<uint8_t> buffer = ReadTestFile(tests[test].mFilename);
469     ASSERT_FALSE(buffer.IsEmpty());
470     RefPtr<ByteStream> stream =
471         new TestStream(buffer.Elements(), buffer.Length());
472 
473     // Parse the first track. Treating it as audio is hacky, but this doesn't
474     // affect how we read the sample description entries.
475     uint32_t trackNumber = 1;
476     MoofParser parser(stream, AsVariant(trackNumber), false);
477     EXPECT_EQ(0u, parser.mOffset) << tests[test].mFilename;
478     EXPECT_FALSE(parser.ReachedEnd()) << tests[test].mFilename;
479     EXPECT_TRUE(parser.mInitRange.IsEmpty()) << tests[test].mFilename;
480 
481     // Explicitly don't call parser.Metadata() so that the parser itself will
482     // read the metadata as if we're in a fragmented case. Otherwise the parser
483     // won't read the sample description table.
484 
485     const MediaByteRangeSet byteRanges(
486         MediaByteRange(0, int64_t(buffer.Length())));
487     EXPECT_EQ(tests[test].mValidMoof, parser.RebuildFragmentedIndex(byteRanges))
488         << tests[test].mFilename;
489 
490     // We only care about crypto data from the samples descriptions right now.
491     // This test should be expanded should we read further information.
492     if (tests[test].mHasCrypto) {
493       uint32_t numEncryptedEntries = 0;
494       // It's possible to have multiple sample description entries. Bug
495       // 1714626 tracks more robust handling of multiple entries, for now just
496       // check that we have at least one.
497       for (SampleDescriptionEntry entry : parser.mSampleDescriptions) {
498         if (entry.mIsEncryptedEntry) {
499           numEncryptedEntries++;
500         }
501       }
502       EXPECT_GE(numEncryptedEntries, 1u) << tests[test].mFilename;
503     }
504   }
505 }
506 #endif  // !defined(XP_WIN) || !defined(MOZ_ASAN)
507 
508 // We should gracefully handle track_id 0 since Bug 1519617. We'd previously
509 // used id 0 to trigger special handling in the MoofParser to read multiple
510 // track metadata, but since muxers use track id 0 in the wild, we want to
511 // make sure they can't accidentally trigger such handling.
TEST(MoofParser,test_case_track_id_0_does_not_read_multitracks)512 TEST(MoofParser, test_case_track_id_0_does_not_read_multitracks)
513 {
514   const char* zeroTrackIdFileName =
515       "test_case_1519617-video-has-track_id-0.mp4";
516   nsTArray<uint8_t> buffer = ReadTestFile(zeroTrackIdFileName);
517 
518   ASSERT_FALSE(buffer.IsEmpty());
519   RefPtr<ByteStream> stream =
520       new TestStream(buffer.Elements(), buffer.Length());
521 
522   // Parse track id 0. We expect to only get metadata from that track, not the
523   // other track with id 2.
524   const uint32_t videoTrackId = 0;
525   MoofParser parser(stream, AsVariant(videoTrackId), false);
526 
527   // Explicitly don't call parser.Metadata() so that the parser itself will
528   // read the metadata as if we're in a fragmented case. Otherwise we won't
529   // read the trak data.
530 
531   const MediaByteRangeSet byteRanges(
532       MediaByteRange(0, int64_t(buffer.Length())));
533   EXPECT_TRUE(parser.RebuildFragmentedIndex(byteRanges))
534       << "MoofParser should find a valid moof as the file contains one!";
535 
536   // Verify we only have data from track 0, if we parsed multiple tracks we'd
537   // find some of the audio track metadata here. Only check for values that
538   // differ between tracks.
539   const uint32_t videoTimescale = 90000;
540   const uint32_t videoSampleDuration = 3000;
541   const uint32_t videoSampleFlags = 0x10000;
542   const uint32_t videoNumSampleDescriptionEntries = 1;
543   EXPECT_EQ(videoTimescale, parser.mMdhd.mTimescale)
544       << "Wrong timescale for video track! If value is 22050, we've read from "
545          "the audio track!";
546   EXPECT_EQ(videoTrackId, parser.mTrex.mTrackId)
547       << "Wrong track id for video track! If value is 2, we've read from the "
548          "audio track!";
549   EXPECT_EQ(videoSampleDuration, parser.mTrex.mDefaultSampleDuration)
550       << "Wrong sample duration for video track! If value is 1024, we've read "
551          "from the audio track!";
552   EXPECT_EQ(videoSampleFlags, parser.mTrex.mDefaultSampleFlags)
553       << "Wrong sample flags for video track! If value is 0x2000000 (note "
554          "that's hex), we've read from the audio track!";
555   EXPECT_EQ(videoNumSampleDescriptionEntries,
556             parser.mSampleDescriptions.Length())
557       << "Wrong number of sample descriptions for video track! If value is 2, "
558          "then we've read sample description information from video and audio "
559          "tracks!";
560 }
561 
562 // We should gracefully handle track_id 0 since Bug 1519617. This includes
563 // handling crypto data from the sinf box in the MoofParser. Note, as of the
564 // time of writing, MP4Metadata uses the presence of a pssh box to determine
565 // if its crypto member is valid. However, even on files where the pssh isn't
566 // in the init segment, the MoofParser should still read the sinf, as in this
567 // testcase.
TEST(MoofParser,test_case_track_id_0_reads_crypto_metadata)568 TEST(MoofParser, test_case_track_id_0_reads_crypto_metadata)
569 {
570   const char* zeroTrackIdFileName =
571       "test_case_1519617-cenc-init-with-track_id-0.mp4";
572   nsTArray<uint8_t> buffer = ReadTestFile(zeroTrackIdFileName);
573 
574   ASSERT_FALSE(buffer.IsEmpty());
575   RefPtr<ByteStream> stream =
576       new TestStream(buffer.Elements(), buffer.Length());
577 
578   // Parse track id 0. We expect to only get metadata from that track, not the
579   // other track with id 2.
580   const uint32_t videoTrackId = 0;
581   MoofParser parser(stream, AsVariant(videoTrackId), false);
582 
583   // Explicitly don't call parser.Metadata() so that the parser itself will
584   // read the metadata as if we're in a fragmented case. Otherwise we won't
585   // read the trak data.
586 
587   const MediaByteRangeSet byteRanges(
588       MediaByteRange(0, int64_t(buffer.Length())));
589   EXPECT_FALSE(parser.RebuildFragmentedIndex(byteRanges))
590       << "MoofParser should not find a valid moof, this is just an init "
591          "segment!";
592 
593   // Verify we only have data from track 0, if we parsed multiple tracks we'd
594   // find some of the audio track metadata here. Only check for values that
595   // differ between tracks.
596   const size_t numSampleDescriptionEntries = 1;
597   const uint32_t defaultPerSampleIVSize = 8;
598   const size_t keyIdLength = 16;
599   const uint32_t defaultKeyId[keyIdLength] = {
600       0x43, 0xbe, 0x13, 0xd0, 0x26, 0xc9, 0x41, 0x54,
601       0x8f, 0xed, 0xf9, 0x54, 0x1a, 0xef, 0x6b, 0x0e};
602   EXPECT_TRUE(parser.mSinf.IsValid())
603       << "Should have a sinf that has crypto data!";
604   EXPECT_EQ(defaultPerSampleIVSize, parser.mSinf.mDefaultIVSize)
605       << "Wrong default per sample IV size for track! If 0 indicates we failed "
606          "to parse some crypto info!";
607   for (size_t i = 0; i < keyIdLength; i++) {
608     EXPECT_EQ(defaultKeyId[i], parser.mSinf.mDefaultKeyID[i])
609         << "Mismatched default key ID byte at index " << i
610         << " indicates we failed to parse some crypto info!";
611   }
612   ASSERT_EQ(numSampleDescriptionEntries, parser.mSampleDescriptions.Length())
613       << "Wrong number of sample descriptions for track! If 0, indicates we "
614          "failed to parse some expected crypto!";
615   EXPECT_TRUE(parser.mSampleDescriptions[0].mIsEncryptedEntry)
616       << "Sample description should be marked as encrypted!";
617 }
618 
619 // The MoofParser may be asked to parse metadata for multiple tracks, but then
620 // be presented with fragments/moofs that contain data for only a subset of
621 // those tracks. I.e. metadata contains information for tracks with ids 1 and 2,
622 // but then the moof parser only receives moofs with data for track id 1. We
623 // should parse such fragmented media. In this test the metadata contains info
624 // for track ids 1 and 2, but track 2's track fragment headers (traf) have been
625 // over written with free space boxes (free).
TEST(MoofParser,test_case_moofs_missing_trafs)626 TEST(MoofParser, test_case_moofs_missing_trafs)
627 {
628   const char* noTrafsForTrack2MoofsFileName =
629       "test_case_1519617-track2-trafs-removed.mp4";
630   nsTArray<uint8_t> buffer = ReadTestFile(noTrafsForTrack2MoofsFileName);
631 
632   ASSERT_FALSE(buffer.IsEmpty());
633   RefPtr<ByteStream> stream =
634       new TestStream(buffer.Elements(), buffer.Length());
635 
636   // Create parser that will read metadata from all tracks.
637   MoofParser parser(stream, AsVariant(ParseAllTracks{}), false);
638 
639   // Explicitly don't call parser.Metadata() so that the parser itself will
640   // read the metadata as if we're in a fragmented case. Otherwise we won't
641   // read the trak data.
642 
643   const MediaByteRangeSet byteRanges(
644       MediaByteRange(0, int64_t(buffer.Length())));
645   EXPECT_TRUE(parser.RebuildFragmentedIndex(byteRanges))
646       << "MoofParser should find a valid moof, there's 2 in the file!";
647 
648   // Verify we've found 2 moofs and that the parser was able to parse them.
649   const size_t numMoofs = 2;
650   EXPECT_EQ(numMoofs, parser.Moofs().Length())
651       << "File has 2 moofs, we should have read both";
652   for (size_t i = 0; i < parser.Moofs().Length(); i++) {
653     EXPECT_TRUE(parser.Moofs()[i].IsValid()) << "All moofs should be valid";
654   }
655 }
656 
657 // This test was disabled by Bug 1224019 for producing way too much output.
658 // This test no longer produces such output, as we've moved away from
659 // stagefright, but it does take a long time to run. I can be useful to enable
660 // as a sanity check on changes to the parser, but is too taxing to run as part
661 // of normal test execution.
662 #if 0
663 TEST(MoofParser, test_case_mp4_subsets) {
664   const size_t step = 1u;
665   for (size_t test = 0; test < ArrayLength(testFiles); ++test) {
666     nsTArray<uint8_t> buffer = ReadTestFile(testFiles[test].mFilename);
667     ASSERT_FALSE(buffer.IsEmpty());
668     ASSERT_LE(step, buffer.Length());
669     // Just exercizing the parser starting at different points through the file,
670     // making sure it doesn't crash.
671     // No checks because results would differ for each position.
672     for (size_t offset = 0; offset < buffer.Length() - step; offset += step) {
673       size_t size = buffer.Length() - offset;
674       while (size > 0) {
675         RefPtr<TestStream> stream =
676           new TestStream(buffer.Elements() + offset, size);
677 
678         MoofParser parser(stream, AsVariant(ParseAllTracks{}), false);
679         MediaByteRangeSet byteRanges;
680         EXPECT_FALSE(parser.RebuildFragmentedIndex(byteRanges));
681         parser.GetCompositionRange(byteRanges);
682         RefPtr<MediaByteBuffer> metadataBuffer = parser.Metadata();
683         parser.FirstCompleteMediaSegment();
684         parser.FirstCompleteMediaHeader();
685 
686         if (stream->mHighestSuccessfulEndOffset <= 0) {
687           // No successful reads -> Cutting down the size won't change anything.
688           break;
689         }
690         if (stream->mHighestSuccessfulEndOffset < size) {
691           // Read up to a point before the end -> Resize down to that point.
692           size = stream->mHighestSuccessfulEndOffset;
693         } else {
694           // Read up to the end (or after?!) -> Just cut 1 byte.
695           size -= 1;
696         }
697       }
698     }
699   }
700 }
701 #endif
702 
703 uint8_t media_gtest_video_init_mp4[] = {
704     0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x6d,
705     0x00, 0x00, 0x00, 0x01, 0x69, 0x73, 0x6f, 0x6d, 0x61, 0x76, 0x63, 0x31,
706     0x00, 0x00, 0x02, 0xd1, 0x6d, 0x6f, 0x6f, 0x76, 0x00, 0x00, 0x00, 0x6c,
707     0x6d, 0x76, 0x68, 0x64, 0x00, 0x00, 0x00, 0x00, 0xc8, 0x49, 0x73, 0xf8,
708     0xc8, 0x4a, 0xc5, 0x7a, 0x00, 0x00, 0x02, 0x58, 0x00, 0x00, 0x00, 0x00,
709     0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
710     0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
711     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
712     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
713     0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
714     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
715     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x18,
716     0x69, 0x6f, 0x64, 0x73, 0x00, 0x00, 0x00, 0x00, 0x10, 0x80, 0x80, 0x80,
717     0x07, 0x00, 0x4f, 0xff, 0xff, 0x29, 0x15, 0xff, 0x00, 0x00, 0x02, 0x0d,
718     0x74, 0x72, 0x61, 0x6b, 0x00, 0x00, 0x00, 0x5c, 0x74, 0x6b, 0x68, 0x64,
719     0x00, 0x00, 0x00, 0x01, 0xc8, 0x49, 0x73, 0xf8, 0xc8, 0x49, 0x73, 0xf9,
720     0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
721     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
722     0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
723     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
724     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
725     0x40, 0x00, 0x00, 0x00, 0x02, 0x80, 0x00, 0x00, 0x01, 0x68, 0x00, 0x00,
726     0x00, 0x00, 0x01, 0xa9, 0x6d, 0x64, 0x69, 0x61, 0x00, 0x00, 0x00, 0x20,
727     0x6d, 0x64, 0x68, 0x64, 0x00, 0x00, 0x00, 0x00, 0xc8, 0x49, 0x73, 0xf8,
728     0xc8, 0x49, 0x73, 0xf9, 0x00, 0x00, 0x75, 0x30, 0x00, 0x00, 0x00, 0x00,
729     0x55, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x68, 0x64, 0x6c, 0x72,
730     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x69, 0x64, 0x65,
731     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
732     0x47, 0x50, 0x41, 0x43, 0x20, 0x49, 0x53, 0x4f, 0x20, 0x56, 0x69, 0x64,
733     0x65, 0x6f, 0x20, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00, 0x00,
734     0x00, 0x00, 0x01, 0x49, 0x6d, 0x69, 0x6e, 0x66, 0x00, 0x00, 0x00, 0x14,
735     0x76, 0x6d, 0x68, 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
736     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x64, 0x69, 0x6e, 0x66,
737     0x00, 0x00, 0x00, 0x1c, 0x64, 0x72, 0x65, 0x66, 0x00, 0x00, 0x00, 0x00,
738     0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x75, 0x72, 0x6c, 0x20,
739     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x09, 0x73, 0x74, 0x62, 0x6c,
740     0x00, 0x00, 0x00, 0xad, 0x73, 0x74, 0x73, 0x64, 0x00, 0x00, 0x00, 0x00,
741     0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x9d, 0x61, 0x76, 0x63, 0x31,
742     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
743     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
744     0x02, 0x80, 0x01, 0x68, 0x00, 0x48, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00,
745     0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
746     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
747     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
748     0x00, 0x00, 0x00, 0x18, 0xff, 0xff, 0x00, 0x00, 0x00, 0x33, 0x61, 0x76,
749     0x63, 0x43, 0x01, 0x64, 0x00, 0x1f, 0xff, 0xe1, 0x00, 0x1b, 0x67, 0x64,
750     0x00, 0x1f, 0xac, 0x2c, 0xc5, 0x02, 0x80, 0xbf, 0xe5, 0xc0, 0x44, 0x00,
751     0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0xf2, 0x3c, 0x60, 0xc6,
752     0x58, 0x01, 0x00, 0x05, 0x68, 0xe9, 0x2b, 0x2c, 0x8b, 0x00, 0x00, 0x00,
753     0x14, 0x62, 0x74, 0x72, 0x74, 0x00, 0x01, 0x5a, 0xc2, 0x00, 0x24, 0x74,
754     0x38, 0x00, 0x09, 0x22, 0x00, 0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x74,
755     0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
756     0x10, 0x63, 0x74, 0x74, 0x73, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
757     0x00, 0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x73, 0x63, 0x00, 0x00, 0x00,
758     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x73, 0x74, 0x73,
759     0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
760     0x00, 0x00, 0x00, 0x00, 0x10, 0x73, 0x74, 0x63, 0x6f, 0x00, 0x00, 0x00,
761     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x6d, 0x76, 0x65,
762     0x78, 0x00, 0x00, 0x00, 0x10, 0x6d, 0x65, 0x68, 0x64, 0x00, 0x00, 0x00,
763     0x00, 0x00, 0x05, 0x76, 0x18, 0x00, 0x00, 0x00, 0x20, 0x74, 0x72, 0x65,
764     0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
765     0x01, 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
766     0x00};
767 
768 const uint32_t media_gtest_video_init_mp4_len = 745;
769 
TEST(MP4Metadata,EmptyCTTS)770 TEST(MP4Metadata, EmptyCTTS)
771 {
772   RefPtr<MediaByteBuffer> buffer =
773       new MediaByteBuffer(media_gtest_video_init_mp4_len);
774   buffer->AppendElements(media_gtest_video_init_mp4,
775                          media_gtest_video_init_mp4_len);
776   RefPtr<BufferStream> stream = new BufferStream(buffer);
777 
778   MP4Metadata::ResultAndByteBuffer metadataBuffer =
779       MP4Metadata::Metadata(stream);
780   EXPECT_EQ(NS_OK, metadataBuffer.Result());
781   EXPECT_TRUE(metadataBuffer.Ref());
782 
783   MP4Metadata metadata(stream);
784   EXPECT_EQ(metadata.Parse(), NS_OK);
785   EXPECT_EQ(1u, metadata.GetNumberTracks(TrackInfo::kVideoTrack).Ref());
786   MP4Metadata::ResultAndTrackInfo track =
787       metadata.GetTrackInfo(TrackInfo::kVideoTrack, 0);
788   EXPECT_TRUE(track.Ref() != nullptr);
789   // We can seek anywhere in any MPEG4.
790   EXPECT_TRUE(metadata.CanSeek());
791   EXPECT_FALSE(metadata.Crypto().Ref()->valid);
792 }
793 
794 // Fixture so we test telemetry probes.
795 class MP4MetadataTelemetryFixture : public TelemetryTestFixture {};
796 
TEST_F(MP4MetadataTelemetryFixture,Telemetry)797 TEST_F(MP4MetadataTelemetryFixture, Telemetry) {
798   // Helper to fetch the metadata from a file and send telemetry in the process.
799   auto UpdateMetadataAndHistograms = [](const char* testFileName) {
800     nsTArray<uint8_t> buffer = ReadTestFile(testFileName);
801     ASSERT_FALSE(buffer.IsEmpty());
802     RefPtr<ByteStream> stream =
803         new TestStream(buffer.Elements(), buffer.Length());
804 
805     MP4Metadata::ResultAndByteBuffer metadataBuffer =
806         MP4Metadata::Metadata(stream);
807     EXPECT_EQ(NS_OK, metadataBuffer.Result());
808     EXPECT_TRUE(metadataBuffer.Ref());
809 
810     MP4Metadata metadata(stream);
811     nsresult res = metadata.Parse();
812     EXPECT_TRUE(NS_SUCCEEDED(res));
813     auto audioTrackCount = metadata.GetNumberTracks(TrackInfo::kAudioTrack);
814     ASSERT_NE(audioTrackCount.Ref(), MP4Metadata::NumberTracksError());
815     auto videoTrackCount = metadata.GetNumberTracks(TrackInfo::kVideoTrack);
816     ASSERT_NE(videoTrackCount.Ref(), MP4Metadata::NumberTracksError());
817 
818     // Need to read the track data to get telemetry to fire.
819     for (uint32_t i = 0; i < audioTrackCount.Ref(); i++) {
820       metadata.GetTrackInfo(TrackInfo::kAudioTrack, i);
821     }
822     for (uint32_t i = 0; i < videoTrackCount.Ref(); i++) {
823       metadata.GetTrackInfo(TrackInfo::kVideoTrack, i);
824     }
825   };
826 
827   AutoJSContextWithGlobal cx(mCleanGlobal);
828 
829   // Checks the current state of the histograms relating to sample description
830   // entries and verifies they're in an expected state.
831   // aExpectedMultipleCodecCounts is a tuple where the first value represents
832   // the number of expected 'false' count, and the second the expected 'true'
833   // count for the sample description entries have multiple codecs histogram.
834   // aExpectedMultipleCryptoCounts is the same, but for the sample description
835   // entires have multiple crypto histogram.
836   // aExpectedSampleDescriptionEntryCounts is a tuple with 6 values, each is
837   // the expected number of sample description seen. I.e, the first value in the
838   // tuple is the number of tracks we've seen with 0 sample descriptions, the
839   // second value with 1 sample description, and so on up to 5 sample
840   // descriptions. aFileName is the name of the most recent file we've parsed,
841   // and is used to log if our telem counts are not in an expected state.
842   auto CheckHistograms =
843       [this, &cx](
844           const Tuple<uint32_t, uint32_t>& aExpectedMultipleCodecCounts,
845           const Tuple<uint32_t, uint32_t>& aExpectedMultipleCryptoCounts,
846           const Tuple<uint32_t, uint32_t, uint32_t, uint32_t, uint32_t,
847                       uint32_t>& aExpectedSampleDescriptionEntryCounts,
848           const char* aFileName) {
849         // Get a snapshot of the current histograms
850         JS::RootedValue snapshot(cx.GetJSContext());
851         TelemetryTestHelpers::GetSnapshots(cx.GetJSContext(), mTelemetry,
852                                            "" /* this string is unused */,
853                                            &snapshot, false /* is_keyed */);
854 
855         // We'll use these to pull values out of the histograms.
856         JS::RootedValue values(cx.GetJSContext());
857         JS::RootedValue value(cx.GetJSContext());
858 
859         // Verify our multiple codecs count histogram.
860         JS::RootedValue multipleCodecsHistogram(cx.GetJSContext());
861         TelemetryTestHelpers::GetProperty(
862             cx.GetJSContext(),
863             "MEDIA_MP4_PARSE_SAMPLE_DESCRIPTION_ENTRIES_HAVE_MULTIPLE_CODECS",
864             snapshot, &multipleCodecsHistogram);
865         ASSERT_TRUE(multipleCodecsHistogram.isObject())
866         << "Multiple codecs histogram should exist!";
867 
868         TelemetryTestHelpers::GetProperty(cx.GetJSContext(), "values",
869                                           multipleCodecsHistogram, &values);
870         // False count.
871         TelemetryTestHelpers::GetElement(cx.GetJSContext(), 0, values, &value);
872         uint32_t uValue = 0;
873         JS::ToUint32(cx.GetJSContext(), value, &uValue);
874         EXPECT_EQ(Get<0>(aExpectedMultipleCodecCounts), uValue)
875             << "Unexpected number of false multiple codecs after parsing "
876             << aFileName;
877         // True count.
878         TelemetryTestHelpers::GetElement(cx.GetJSContext(), 1, values, &value);
879         JS::ToUint32(cx.GetJSContext(), value, &uValue);
880         EXPECT_EQ(Get<1>(aExpectedMultipleCodecCounts), uValue)
881             << "Unexpected number of true multiple codecs after parsing "
882             << aFileName;
883 
884         // Verify our multiple crypto count histogram.
885         JS::RootedValue multipleCryptoHistogram(cx.GetJSContext());
886         TelemetryTestHelpers::GetProperty(
887             cx.GetJSContext(),
888             "MEDIA_MP4_PARSE_SAMPLE_DESCRIPTION_ENTRIES_HAVE_MULTIPLE_CRYPTO",
889             snapshot, &multipleCryptoHistogram);
890         ASSERT_TRUE(multipleCryptoHistogram.isObject())
891         << "Multiple crypto histogram should exist!";
892 
893         TelemetryTestHelpers::GetProperty(cx.GetJSContext(), "values",
894                                           multipleCryptoHistogram, &values);
895         // False count.
896         TelemetryTestHelpers::GetElement(cx.GetJSContext(), 0, values, &value);
897         JS::ToUint32(cx.GetJSContext(), value, &uValue);
898         EXPECT_EQ(Get<0>(aExpectedMultipleCryptoCounts), uValue)
899             << "Unexpected number of false multiple cryptos after parsing "
900             << aFileName;
901         // True count.
902         TelemetryTestHelpers::GetElement(cx.GetJSContext(), 1, values, &value);
903         JS::ToUint32(cx.GetJSContext(), value, &uValue);
904         EXPECT_EQ(Get<1>(aExpectedMultipleCryptoCounts), uValue)
905             << "Unexpected number of true multiple cryptos after parsing "
906             << aFileName;
907 
908         // Verify our sample description entry count histogram.
909         JS::RootedValue numSamplesHistogram(cx.GetJSContext());
910         TelemetryTestHelpers::GetProperty(
911             cx.GetJSContext(), "MEDIA_MP4_PARSE_NUM_SAMPLE_DESCRIPTION_ENTRIES",
912             snapshot, &numSamplesHistogram);
913         ASSERT_TRUE(numSamplesHistogram.isObject())
914         << "Num sample description entries histogram should exist!";
915 
916         TelemetryTestHelpers::GetProperty(cx.GetJSContext(), "values",
917                                           numSamplesHistogram, &values);
918 
919         TelemetryTestHelpers::GetElement(cx.GetJSContext(), 0, values, &value);
920         JS::ToUint32(cx.GetJSContext(), value, &uValue);
921         EXPECT_EQ(Get<0>(aExpectedSampleDescriptionEntryCounts), uValue)
922             << "Unexpected number of 0 sample entry descriptions after parsing "
923             << aFileName;
924         TelemetryTestHelpers::GetElement(cx.GetJSContext(), 1, values, &value);
925         JS::ToUint32(cx.GetJSContext(), value, &uValue);
926         EXPECT_EQ(Get<1>(aExpectedSampleDescriptionEntryCounts), uValue)
927             << "Unexpected number of 1 sample entry descriptions after parsing "
928             << aFileName;
929         TelemetryTestHelpers::GetElement(cx.GetJSContext(), 2, values, &value);
930         JS::ToUint32(cx.GetJSContext(), value, &uValue);
931         EXPECT_EQ(Get<2>(aExpectedSampleDescriptionEntryCounts), uValue)
932             << "Unexpected number of 2 sample entry descriptions after parsing "
933             << aFileName;
934         TelemetryTestHelpers::GetElement(cx.GetJSContext(), 3, values, &value);
935         JS::ToUint32(cx.GetJSContext(), value, &uValue);
936         EXPECT_EQ(Get<3>(aExpectedSampleDescriptionEntryCounts), uValue)
937             << "Unexpected number of 3 sample entry descriptions after parsing "
938             << aFileName;
939         TelemetryTestHelpers::GetElement(cx.GetJSContext(), 4, values, &value);
940         JS::ToUint32(cx.GetJSContext(), value, &uValue);
941         EXPECT_EQ(Get<4>(aExpectedSampleDescriptionEntryCounts), uValue)
942             << "Unexpected number of 4 sample entry descriptions after parsing "
943             << aFileName;
944         TelemetryTestHelpers::GetElement(cx.GetJSContext(), 5, values, &value);
945         JS::ToUint32(cx.GetJSContext(), value, &uValue);
946         EXPECT_EQ(Get<5>(aExpectedSampleDescriptionEntryCounts), uValue)
947             << "Unexpected number of 5 sample entry descriptions after parsing "
948             << aFileName;
949       };
950 
951   // Clear histograms
952   TelemetryTestHelpers::GetAndClearHistogram(
953       cx.GetJSContext(), mTelemetry,
954       nsLiteralCString(
955           "MEDIA_MP4_PARSE_SAMPLE_DESCRIPTION_ENTRIES_HAVE_MULTIPLE_CODECS"),
956       false /* is_keyed */);
957 
958   TelemetryTestHelpers::GetAndClearHistogram(
959       cx.GetJSContext(), mTelemetry,
960       nsLiteralCString(
961           "MEDIA_MP4_PARSE_SAMPLE_DESCRIPTION_ENTRIES_HAVE_MULTIPLE_CRYPTO"),
962       false /* is_keyed */);
963 
964   TelemetryTestHelpers::GetAndClearHistogram(
965       cx.GetJSContext(), mTelemetry,
966       "MEDIA_MP4_PARSE_NUM_SAMPLE_DESCRIPTION_ENTRIES"_ns,
967       false /* is_keyed */);
968 
969   // The snapshot won't have any data in it until we populate our histograms, so
970   // we don't check for a baseline here. Just read out first MP4 metadata.
971 
972   // Grab one of the test cases we know should parse and parse it, this should
973   // trigger telemetry gathering.
974 
975   // This file contains 2 moovs, each with a video and audio track with one
976   // sample description entry. So we should see 4 tracks, each with a single
977   // codec, no crypto, and a single sample description entry.
978   UpdateMetadataAndHistograms("test_case_1185230.mp4");
979 
980   // Verify our histograms are updated.
981   CheckHistograms(
982       MakeTuple<uint32_t, uint32_t>(4, 0), MakeTuple<uint32_t, uint32_t>(4, 0),
983       MakeTuple<uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t>(
984           0, 4, 0, 0, 0, 0),
985       "test_case_1185230.mp4");
986 
987   // Parse another test case. This one has a single moov with a single video
988   // track. However, the track has two sample description entries, and our
989   // updated telemetry should reflect that.
990   UpdateMetadataAndHistograms(
991       "test_case_1513651-2-sample-description-entries.mp4");
992 
993   // Verify our histograms are updated.
994   CheckHistograms(
995       MakeTuple<uint32_t, uint32_t>(5, 0), MakeTuple<uint32_t, uint32_t>(5, 0),
996       MakeTuple<uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t>(
997           0, 4, 1, 0, 0, 0),
998       "test_case_1513651-2-sample-description-entries.mp4");
999 
1000   // Parse another test case. This one has 2 sample decription entries, both
1001   // with crypto information, which should be reflected in our telemetry.
1002   UpdateMetadataAndHistograms(
1003       "test_case_1714125-2-sample-description-entires-with-identical-crypto."
1004       "mp4");
1005 
1006   // Verify our histograms are updated.
1007   CheckHistograms(
1008       MakeTuple<uint32_t, uint32_t>(6, 0), MakeTuple<uint32_t, uint32_t>(5, 1),
1009       MakeTuple<uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t>(
1010           0, 4, 2, 0, 0, 0),
1011       "test_case_1714125-2-sample-description-entires-with-identical-crypto."
1012       "mp4");
1013 }
1014