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