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