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