/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
nsTArray<uint8_t> ReadTestFile(constchar* aFilename) { if (!aFilename) { return {};
}
FILE* f = fopen(aFilename, "rb"); if (!f) { return {};
}
if (fseek(f, 0, SEEK_END) != 0) {
fclose(f); return {};
} long position = ftell(f); // I know EOF==-1, so this test is made obsolete by '<0', but I don't want // the code to rely on that. if (position == 0 || position == EOF || position < 0) {
fclose(f); return {};
} if (fseek(f, 0, SEEK_SET) != 0) {
fclose(f); return {};
}
struct TestFileData { constchar* mFilename; bool mParseResult;
uint32_t mNumberVideoTracks; bool mHasVideoIndice; double mVideoDuration; // For first video track, -1 if N/A, in seconds.
int32_t mWidth;
int32_t mHeight;
uint32_t mNumberAudioTracks; double mAudioDuration; // For first audio track, -1 if N/A, in seconds. bool mHasCrypto; // Note, MP4Metadata only considers pssh box for crypto.
uint64_t mMoofReachedOffset; // or 0 for the end. bool mValidMoofForTrack1; bool mValidMoofForAllTracks;
int8_t mAudioProfile;
};
MP4Metadata::ResultAndIndice indices =
metadata.GetTrackIndice(audioInfo->mTrackId);
EXPECT_TRUE(!!indices.Ref()) << tests[test].mFilename; for (size_t i = 0; i < indices.Ref()->Length(); i++) {
MP4SampleIndex::Indice data;
EXPECT_TRUE(indices.Ref()->GetIndice(i, data)) << tests[test].mFilename;
EXPECT_TRUE(data.start_offset <= data.end_offset)
<< tests[test].mFilename;
EXPECT_TRUE(int64_t(data.start_composition) <=
int64_t(data.end_composition))
<< tests[test].mFilename;
}
}
EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kTextTrack, 0).Ref())
<< tests[test].mFilename; // We can see anywhere in any MPEG4.
EXPECT_TRUE(metadata.CanSeek()) << tests[test].mFilename;
EXPECT_EQ(tests[test].mHasCrypto, metadata.Crypto().Ref()->valid)
<< tests[test].mFilename;
}
}
// This test was disabled by Bug 1224019 for producing way too much output. // This test no longer produces such output, as we've moved away from // stagefright, but it does take a long time to run. I can be useful to enable // as a sanity check on changes to the parser, but is too taxing to run as part // of normal test execution. #if0
TEST(MP4Metadata, test_case_mp4_subsets) { staticconst size_t step = 1u; for (size_t test = 0; test < std::size(testFiles); ++test) {
nsTArray<uint8_t> buffer = ReadTestFile(testFiles[test].mFilename);
ASSERT_FALSE(buffer.IsEmpty());
ASSERT_LE(step, buffer.Length()); // Just exercizing the parser starting at different points through the file, // making sure it doesn't crash. // No checks because results would differ for each position. for (size_t offset = 0; offset < buffer.Length() - step; offset += step) {
size_t size = buffer.Length() - offset; while (size > 0) {
RefPtr<TestStream> stream = new TestStream(buffer.Elements() + offset, size);
if (stream->mHighestSuccessfulEndOffset <= 0) { // No successful reads -> Cutting down the size won't change anything. break;
} if (stream->mHighestSuccessfulEndOffset < size) { // Read up to a point before the end -> Resize down to that point.
size = stream->mHighestSuccessfulEndOffset;
} else { // Read up to the end (or after?!) -> Just cut 1 byte.
size -= 1;
}
}
}
}
} #endif
#if !defined(XP_WIN) || !defined(MOZ_ASAN) // OOMs on Windows ASan
TEST(MoofParser, test_case_mp4)
{ const TestFileData* tests = nullptr;
size_t length = 0;
tests = testFiles;
length = std::size(testFiles);
for (size_t test = 0; test < length; ++test) {
nsTArray<uint8_t> buffer = ReadTestFile(tests[test].mFilename);
ASSERT_FALSE(buffer.IsEmpty());
RefPtr<ByteStream> stream = new TestStream(buffer.Elements(), buffer.Length());
EXPECT_FALSE(parser.mInitRange.IsEmpty()) << tests[test].mFilename;
EXPECT_TRUE(parser.GetCompositionRange(byteRanges).IsNull())
<< tests[test].mFilename;
EXPECT_TRUE(parser.FirstCompleteMediaSegment().IsEmpty())
<< tests[test].mFilename; // If we expect a valid moof we should have that moof's range stored.
EXPECT_EQ(tests[test].mValidMoofForAllTracks,
!parser.FirstCompleteMediaHeader().IsEmpty())
<< tests[test].mFilename;
}
}
for (size_t test = 0; test < length; ++test) {
nsTArray<uint8_t> buffer = ReadTestFile(tests[test].mFilename);
ASSERT_FALSE(buffer.IsEmpty());
RefPtr<ByteStream> stream = new TestStream(buffer.Elements(), buffer.Length());
// Parse the first track. Treating it as audio is hacky, but this doesn't // affect how we read the sample description entries.
uint32_t trackNumber = 1;
MoofParser parser(stream, AsVariant(trackNumber), false);
EXPECT_EQ(0u, parser.mOffset) << tests[test].mFilename;
EXPECT_FALSE(parser.ReachedEnd()) << tests[test].mFilename;
EXPECT_TRUE(parser.mInitRange.IsEmpty()) << tests[test].mFilename;
// Explicitly don't call parser.Metadata() so that the parser itself will // read the metadata as if we're in a fragmented case. Otherwise the parser // won't read the sample description table.
// We only care about crypto data from the samples descriptions right now. // This test should be expanded should we read further information. if (tests[test].mHasCrypto) {
uint32_t numEncryptedEntries = 0; // It's possible to have multiple sample description entries. Bug // 1714626 tracks more robust handling of multiple entries, for now just // check that we have at least one. for (SampleDescriptionEntry entry : parser.mSampleDescriptions) { if (entry.mIsEncryptedEntry) {
numEncryptedEntries++;
}
}
EXPECT_GE(numEncryptedEntries, 1u) << tests[test].mFilename;
}
}
} #endif// !defined(XP_WIN) || !defined(MOZ_ASAN)
// We should gracefully handle track_id 0 since Bug 1519617. We'd previously // used id 0 to trigger special handling in the MoofParser to read multiple // track metadata, but since muxers use track id 0 in the wild, we want to // make sure they can't accidentally trigger such handling.
TEST(MoofParser, test_case_track_id_0_does_not_read_multitracks)
{ constchar* zeroTrackIdFileName = "test_case_1519617-video-has-track_id-0.mp4";
nsTArray<uint8_t> buffer = ReadTestFile(zeroTrackIdFileName);
ASSERT_FALSE(buffer.IsEmpty());
RefPtr<ByteStream> stream = new TestStream(buffer.Elements(), buffer.Length());
// Parse track id 0. We expect to only get metadata from that track, not the // other track with id 2. const uint32_t videoTrackId = 0;
MoofParser parser(stream, AsVariant(videoTrackId), false);
// Explicitly don't call parser.Metadata() so that the parser itself will // read the metadata as if we're in a fragmented case. Otherwise we won't // read the trak data.
const MediaByteRangeSet byteRanges(
MediaByteRange(0, int64_t(buffer.Length())));
EXPECT_TRUE(parser.RebuildFragmentedIndex(byteRanges))
<< "MoofParser should find a valid moof as the file contains one!";
// Verify we only have data from track 0, if we parsed multiple tracks we'd // find some of the audio track metadata here. Only check for values that // differ between tracks. const uint32_t videoTimescale = 90000; const uint32_t videoSampleDuration = 3000; const uint32_t videoSampleFlags = 0x10000; const uint32_t videoNumSampleDescriptionEntries = 1;
EXPECT_EQ(videoTimescale, parser.mMdhd.mTimescale)
<< "Wrong timescale for video track! If value is 22050, we've read from " "the audio track!";
EXPECT_EQ(videoTrackId, parser.mTrex.mTrackId)
<< "Wrong track id for video track! If value is 2, we've read from the " "audio track!";
EXPECT_EQ(videoSampleDuration, parser.mTrex.mDefaultSampleDuration)
<< "Wrong sample duration for video track! If value is 1024, we've read " "from the audio track!";
EXPECT_EQ(videoSampleFlags, parser.mTrex.mDefaultSampleFlags)
<< "Wrong sample flags for video track! If value is 0x2000000 (note " "that's hex), we've read from the audio track!";
EXPECT_EQ(videoNumSampleDescriptionEntries,
parser.mSampleDescriptions.Length())
<< "Wrong number of sample descriptions for video track! If value is 2, " "then we've read sample description information from video and audio " "tracks!";
}
// We should gracefully handle track_id 0 since Bug 1519617. This includes // handling crypto data from the sinf box in the MoofParser. Note, as of the // time of writing, MP4Metadata uses the presence of a pssh box to determine // if its crypto member is valid. However, even on files where the pssh isn't // in the init segment, the MoofParser should still read the sinf, as in this // testcase.
TEST(MoofParser, test_case_track_id_0_reads_crypto_metadata)
{ constchar* zeroTrackIdFileName = "test_case_1519617-cenc-init-with-track_id-0.mp4";
nsTArray<uint8_t> buffer = ReadTestFile(zeroTrackIdFileName);
ASSERT_FALSE(buffer.IsEmpty());
RefPtr<ByteStream> stream = new TestStream(buffer.Elements(), buffer.Length());
// Parse track id 0. We expect to only get metadata from that track, not the // other track with id 2. const uint32_t videoTrackId = 0;
MoofParser parser(stream, AsVariant(videoTrackId), false);
// Explicitly don't call parser.Metadata() so that the parser itself will // read the metadata as if we're in a fragmented case. Otherwise we won't // read the trak data.
const MediaByteRangeSet byteRanges(
MediaByteRange(0, int64_t(buffer.Length())));
EXPECT_FALSE(parser.RebuildFragmentedIndex(byteRanges))
<< "MoofParser should not find a valid moof, this is just an init " "segment!";
// Verify we only have data from track 0, if we parsed multiple tracks we'd // find some of the audio track metadata here. Only check for values that // differ between tracks. const size_t numSampleDescriptionEntries = 1; const uint32_t defaultPerSampleIVSize = 8; const size_t keyIdLength = 16; const uint32_t defaultKeyId[keyIdLength] = { 0x43, 0xbe, 0x13, 0xd0, 0x26, 0xc9, 0x41, 0x54, 0x8f, 0xed, 0xf9, 0x54, 0x1a, 0xef, 0x6b, 0x0e};
EXPECT_TRUE(parser.mSinf.IsValid())
<< "Should have a sinf that has crypto data!";
EXPECT_EQ(defaultPerSampleIVSize, parser.mSinf.mDefaultIVSize)
<< "Wrong default per sample IV size for track! If 0 indicates we failed " "to parse some crypto info!"; for (size_t i = 0; i < keyIdLength; i++) {
EXPECT_EQ(defaultKeyId[i], parser.mSinf.mDefaultKeyID[i])
<< "Mismatched default key ID byte at index " << i
<< " indicates we failed to parse some crypto info!";
}
ASSERT_EQ(numSampleDescriptionEntries, parser.mSampleDescriptions.Length())
<< "Wrong number of sample descriptions for track! If 0, indicates we " "failed to parse some expected crypto!";
EXPECT_TRUE(parser.mSampleDescriptions[0].mIsEncryptedEntry)
<< "Sample description should be marked as encrypted!";
}
// The MoofParser may be asked to parse metadata for multiple tracks, but then // be presented with fragments/moofs that contain data for only a subset of // those tracks. I.e. metadata contains information for tracks with ids 1 and 2, // but then the moof parser only receives moofs with data for track id 1. We // should parse such fragmented media. In this test the metadata contains info // for track ids 1 and 2, but track 2's track fragment headers (traf) have been // over written with free space boxes (free).
TEST(MoofParser, test_case_moofs_missing_trafs)
{ constchar* noTrafsForTrack2MoofsFileName = "test_case_1519617-track2-trafs-removed.mp4";
nsTArray<uint8_t> buffer = ReadTestFile(noTrafsForTrack2MoofsFileName);
ASSERT_FALSE(buffer.IsEmpty());
RefPtr<ByteStream> stream = new TestStream(buffer.Elements(), buffer.Length());
// Create parser that will read metadata from all tracks.
MoofParser parser(stream, AsVariant(ParseAllTracks{}), false);
// Explicitly don't call parser.Metadata() so that the parser itself will // read the metadata as if we're in a fragmented case. Otherwise we won't // read the trak data.
const MediaByteRangeSet byteRanges(
MediaByteRange(0, int64_t(buffer.Length())));
EXPECT_TRUE(parser.RebuildFragmentedIndex(byteRanges))
<< "MoofParser should find a valid moof, there's 2 in the file!";
// Verify we've found 2 moofs and that the parser was able to parse them. const size_t numMoofs = 2;
EXPECT_EQ(numMoofs, parser.Moofs().Length())
<< "File has 2 moofs, we should have read both"; for (size_t i = 0; i < parser.Moofs().Length(); i++) {
EXPECT_TRUE(parser.Moofs()[i].IsValid()) << "All moofs should be valid";
}
}
// This test was disabled by Bug 1224019 for producing way too much output. // This test no longer produces such output, as we've moved away from // stagefright, but it does take a long time to run. I can be useful to enable // as a sanity check on changes to the parser, but is too taxing to run as part // of normal test execution. #if0
TEST(MoofParser, test_case_mp4_subsets) { const size_t step = 1u; for (size_t test = 0; test < std::size(testFiles); ++test) {
nsTArray<uint8_t> buffer = ReadTestFile(testFiles[test].mFilename);
ASSERT_FALSE(buffer.IsEmpty());
ASSERT_LE(step, buffer.Length()); // Just exercizing the parser starting at different points through the file, // making sure it doesn't crash. // No checks because results would differ for each position. for (size_t offset = 0; offset < buffer.Length() - step; offset += step) {
size_t size = buffer.Length() - offset; while (size > 0) {
RefPtr<TestStream> stream = new TestStream(buffer.Elements() + offset, size);
if (stream->mHighestSuccessfulEndOffset <= 0) { // No successful reads -> Cutting down the size won't change anything. break;
} if (stream->mHighestSuccessfulEndOffset < size) { // Read up to a point before the end -> Resize down to that point.
size = stream->mHighestSuccessfulEndOffset;
} else { // Read up to the end (or after?!) -> Just cut 1 byte.
size -= 1;
}
}
}
}
} #endif
MP4Metadata metadata(stream);
EXPECT_EQ(metadata.Parse(), NS_OK);
EXPECT_EQ(1u, metadata.GetNumberTracks(TrackInfo::kVideoTrack).Ref());
MP4Metadata::ResultAndTrackInfo track =
metadata.GetTrackInfo(TrackInfo::kVideoTrack, 0);
EXPECT_TRUE(track.Ref() != nullptr); // We can seek anywhere in any MPEG4.
EXPECT_TRUE(metadata.CanSeek());
EXPECT_FALSE(metadata.Crypto().Ref()->valid);
}
// Fixture so we test telemetry probes. class MP4MetadataTelemetryFixture : public TelemetryTestFixture {};
TEST_F(MP4MetadataTelemetryFixture, Telemetry) { // Helper to fetch the metadata from a file and send telemetry in the process. auto UpdateMetadataAndHistograms = [](constchar* testFileName) {
nsTArray<uint8_t> buffer = ReadTestFile(testFileName);
ASSERT_FALSE(buffer.IsEmpty());
RefPtr<ByteStream> stream = new TestStream(buffer.Elements(), buffer.Length());
MP4Metadata metadata(stream);
nsresult res = metadata.Parse();
EXPECT_NS_SUCCEEDED(res); auto audioTrackCount = metadata.GetNumberTracks(TrackInfo::kAudioTrack);
ASSERT_NE(audioTrackCount.Ref(), MP4Metadata::NumberTracksError()); auto videoTrackCount = metadata.GetNumberTracks(TrackInfo::kVideoTrack);
ASSERT_NE(videoTrackCount.Ref(), MP4Metadata::NumberTracksError());
// Need to read the track data to get telemetry to fire. for (uint32_t i = 0; i < audioTrackCount.Ref(); i++) {
metadata.GetTrackInfo(TrackInfo::kAudioTrack, i);
} for (uint32_t i = 0; i < videoTrackCount.Ref(); i++) {
metadata.GetTrackInfo(TrackInfo::kVideoTrack, i);
}
};
AutoJSContextWithGlobal cx(mCleanGlobal);
// Checks the current state of the histograms relating to sample description // entries and verifies they're in an expected state. // aExpectedMultipleCodecCounts is a tuple where the first value represents // the number of expected 'false' count, and the second the expected 'true' // count for the sample description entries have multiple codecs histogram. // aExpectedMultipleCryptoCounts is the same, but for the sample description // entires have multiple crypto histogram. // aExpectedSampleDescriptionEntryCounts is a tuple with 6 values, each is // the expected number of sample description seen. I.e, the first value in the // tuple is the number of tracks we've seen with 0 sample descriptions, the // second value with 1 sample description, and so on up to 5 sample // descriptions. aFileName is the name of the most recent file we've parsed, // and is used to log if our telem counts are not in an expected state. auto CheckHistograms =
[this, &cx]( const std::tuple<uint32_t, uint32_t>& aExpectedMultipleCodecCounts, const std::tuple<uint32_t, uint32_t>& aExpectedMultipleCryptoCounts, const std::tuple<uint32_t, uint32_t, uint32_t, uint32_t, uint32_t,
uint32_t>& aExpectedSampleDescriptionEntryCounts, constchar* aFileName) { // Get a snapshot of the current histograms
JS::Rooted<JS::Value> snapshot(cx.GetJSContext());
TelemetryTestHelpers::GetSnapshots(cx.GetJSContext(), mTelemetry, ""/* this string is unused */,
&snapshot, false/* is_keyed */);
// We'll use these to pull values out of the histograms.
JS::Rooted<JS::Value> values(cx.GetJSContext());
JS::Rooted<JS::Value> value(cx.GetJSContext());
// The snapshot won't have any data in it until we populate our histograms, so // we don't check for a baseline here. Just read out first MP4 metadata.
// Grab one of the test cases we know should parse and parse it, this should // trigger telemetry gathering.
// This file contains 2 moovs, each with a video and audio track with one // sample description entry. So we should see 4 tracks, each with a single // codec, no crypto, and a single sample description entry.
UpdateMetadataAndHistograms("test_case_1185230.mp4");
// Parse another test case. This one has a single moov with a single video // track. However, the track has two sample description entries, and our // updated telemetry should reflect that.
UpdateMetadataAndHistograms( "test_case_1513651-2-sample-description-entries.mp4");
// Parse another test case. This one has 2 sample decription entries, both // with crypto information, which should be reflected in our telemetry.
UpdateMetadataAndHistograms( "test_case_1714125-2-sample-description-entires-with-identical-crypto." "mp4");
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.