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