Bug 1500713: P1 - MP3 Test for the files with VBRI headers. r=kinetik
☠☠ backed out by 89988d424c06 ☠ ☠
authorChun-Min Chang <chun.m.chang@gmail.com>
Sat, 09 Mar 2019 01:09:16 +0000
changeset 463589 7e5fa7b1f7bc9872a319c2eae52aaa612992560c
parent 463588 8f0821b17fd680a419dd6ce7f8b839c0318f3d69
child 463590 670b24af24d4c8d1adcdf67eff46e73346e83ab1
push id35686
push userbtara@mozilla.com
push dateTue, 12 Mar 2019 09:50:48 +0000
treeherdermozilla-central@7196b821847c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs1500713
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1500713: P1 - MP3 Test for the files with VBRI headers. r=kinetik Differential Revision: https://phabricator.services.mozilla.com/D19096
dom/media/gtest/TestMP3Demuxer.cpp
dom/media/gtest/moz.build
dom/media/gtest/test_vbri.mp3
--- a/dom/media/gtest/TestMP3Demuxer.cpp
+++ b/dom/media/gtest/TestMP3Demuxer.cpp
@@ -42,18 +42,21 @@ class MockMP3StreamMediaResource
 
   int64_t GetLength() override { return -1; }
 
  protected:
   virtual ~MockMP3StreamMediaResource() {}
 };
 
 struct MP3Resource {
+  enum HeaderType { NONE, XING, VBRI };
+
   const char* mFilePath;
   bool mIsVBR;
+  HeaderType mHeaderType;
   int64_t mFileSize;
   int32_t mMPEGLayer;
   int32_t mMPEGVersion;
   uint8_t mID3MajorVersion;
   uint8_t mID3MinorVersion;
   uint8_t mID3Flags;
   uint32_t mID3Size;
 
@@ -78,16 +81,17 @@ struct MP3Resource {
 
 class MP3DemuxerTest : public ::testing::Test {
  protected:
   void SetUp() override {
     {
       MP3Resource res;
       res.mFilePath = "noise.mp3";
       res.mIsVBR = false;
+      res.mHeaderType = MP3Resource::NONE;
       res.mFileSize = 965257;
       res.mMPEGLayer = 3;
       res.mMPEGVersion = 1;
       res.mID3MajorVersion = 3;
       res.mID3MinorVersion = 0;
       res.mID3Flags = 0;
       res.mID3Size = 2141;
       res.mDuration = 30067000;
@@ -122,16 +126,17 @@ class MP3DemuxerTest : public ::testing:
       MP3Resource res;
       // This file trips up the MP3 demuxer if ID3v2 tags aren't properly
       // skipped. If skipping is not properly implemented, depending on the
       // strictness of the MPEG frame parser a false sync will be detected
       // somewhere within the metadata at or after 112087, or failing that, at
       // the artificially added extraneous header at 114532.
       res.mFilePath = "id3v2header.mp3";
       res.mIsVBR = false;
+      res.mHeaderType = MP3Resource::NONE;
       res.mFileSize = 191302;
       res.mMPEGLayer = 3;
       res.mMPEGVersion = 1;
       res.mID3MajorVersion = 3;
       res.mID3MinorVersion = 0;
       res.mID3Flags = 0;
       res.mID3Size = 115304;
       res.mDuration = 3166167;
@@ -161,16 +166,17 @@ class MP3DemuxerTest : public ::testing:
       streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
       mTargets.push_back(streamRes);
     }
 
     {
       MP3Resource res;
       res.mFilePath = "noise_vbr.mp3";
       res.mIsVBR = true;
+      res.mHeaderType = MP3Resource::XING;
       res.mFileSize = 583679;
       res.mMPEGLayer = 3;
       res.mMPEGVersion = 1;
       res.mID3MajorVersion = 3;
       res.mID3MinorVersion = 0;
       res.mID3Flags = 0;
       res.mID3Size = 2221;
       res.mDuration = 30081000;
@@ -199,16 +205,17 @@ class MP3DemuxerTest : public ::testing:
       streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
       mTargets.push_back(streamRes);
     }
 
     {
       MP3Resource res;
       res.mFilePath = "small-shot.mp3";
       res.mIsVBR = true;
+      res.mHeaderType = MP3Resource::XING;
       res.mFileSize = 6825;
       res.mMPEGLayer = 3;
       res.mMPEGVersion = 1;
       res.mID3MajorVersion = 4;
       res.mID3MinorVersion = 0;
       res.mID3Flags = 0;
       res.mID3Size = 24;
       res.mDuration = 336686;
@@ -239,16 +246,17 @@ class MP3DemuxerTest : public ::testing:
     }
 
     {
       MP3Resource res;
       // This file contains a false frame sync at 34, just after the ID3 tag,
       // which should be identified as a false positive and skipped.
       res.mFilePath = "small-shot-false-positive.mp3";
       res.mIsVBR = true;
+      res.mHeaderType = MP3Resource::XING;
       res.mFileSize = 6845;
       res.mMPEGLayer = 3;
       res.mMPEGVersion = 1;
       res.mID3MajorVersion = 4;
       res.mID3MinorVersion = 0;
       res.mID3Flags = 0;
       res.mID3Size = 24;
       res.mDuration = 336686;
@@ -277,16 +285,17 @@ class MP3DemuxerTest : public ::testing:
       streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
       mTargets.push_back(streamRes);
     }
 
     {
       MP3Resource res;
       res.mFilePath = "small-shot-partial-xing.mp3";
       res.mIsVBR = true;
+      res.mHeaderType = MP3Resource::XING;
       res.mFileSize = 6825;
       res.mMPEGLayer = 3;
       res.mMPEGVersion = 1;
       res.mID3MajorVersion = 4;
       res.mID3MinorVersion = 0;
       res.mID3Flags = 0;
       res.mID3Size = 24;
       res.mDuration = 336686;
@@ -311,16 +320,55 @@ class MP3DemuxerTest : public ::testing:
       res.mDemuxer = new MP3TrackDemuxer(res.mResource);
       mTargets.push_back(res);
 
       streamRes.mResource = new MockMP3StreamMediaResource(streamRes.mFilePath);
       streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
       mTargets.push_back(streamRes);
     }
 
+    {
+      MP3Resource res;
+      res.mFilePath = "test_vbri.mp3";
+      res.mIsVBR = true;
+      res.mHeaderType = MP3Resource::VBRI;
+      res.mFileSize = 16519;
+      res.mMPEGLayer = 3;
+      res.mMPEGVersion = 1;
+      res.mID3MajorVersion = 3;
+      res.mID3MinorVersion = 0;
+      res.mID3Flags = 0;
+      res.mID3Size = 4202;
+      res.mDuration = 783660;
+      res.mDurationError = 0.01f;
+      res.mSeekError = 0.02f;
+      res.mSampleRate = 44100;
+      res.mSamplesPerFrame = 1152;
+      res.mNumSamples = 29;
+      res.mNumTrailingFrames = 0;
+      res.mBitrate = 0;
+      res.mSlotSize = 1;
+      res.mPrivate = 0;
+      const int syncs[] = {4212, 4734, 5047, 5464, 5986, 6403};
+      res.mSyncOffsets.insert(res.mSyncOffsets.begin(), syncs, syncs + 6);
+
+      // VBR stream resources contain header info on total frames numbers, which
+      // is used to estimate the total duration.
+      MP3Resource streamRes = res;
+      streamRes.mFileSize = -1;
+
+      res.mResource = new MockMP3MediaResource(res.mFilePath);
+      res.mDemuxer = new MP3TrackDemuxer(res.mResource);
+      mTargets.push_back(res);
+
+      streamRes.mResource = new MockMP3StreamMediaResource(streamRes.mFilePath);
+      streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
+      mTargets.push_back(streamRes);
+    }
+
     for (auto& target : mTargets) {
       ASSERT_EQ(NS_OK, target.mResource->Open());
       ASSERT_TRUE(target.mDemuxer->Init());
     }
   }
 
   std::vector<MP3Resource> mTargets;
 };
@@ -342,22 +390,25 @@ TEST_F(MP3DemuxerTest, ID3Tags) {
 
 TEST_F(MP3DemuxerTest, VBRHeader) {
   for (const auto& target : mTargets) {
     RefPtr<MediaRawData> frame(target.mDemuxer->DemuxSample());
     ASSERT_TRUE(frame);
 
     const auto& vbr = target.mDemuxer->VBRInfo();
 
-    if (target.mIsVBR) {
+    if (target.mHeaderType == MP3Resource::XING) {
       EXPECT_EQ(FrameParser::VBRHeader::XING, vbr.Type());
       // TODO: find reference number which accounts for trailing headers.
       // EXPECT_EQ(target.mNumSamples / target.mSamplesPerFrame,
       // vbr.NumAudioFrames().value());
-    } else {
+    } else if (target.mHeaderType == MP3Resource::VBRI) {
+      EXPECT_TRUE(target.mIsVBR);
+      EXPECT_EQ(FrameParser::VBRHeader::VBRI, vbr.Type());
+    } else {  // MP3Resource::NONE
       EXPECT_EQ(FrameParser::VBRHeader::NONE, vbr.Type());
       EXPECT_FALSE(vbr.NumAudioFrames());
     }
   }
 }
 
 TEST_F(MP3DemuxerTest, FrameParsing) {
   for (const auto& target : mTargets) {
--- a/dom/media/gtest/moz.build
+++ b/dom/media/gtest/moz.build
@@ -68,16 +68,17 @@ TEST_HARNESS_FILES.gtest += [
     'short-zero-inband.mov',
     'small-shot-false-positive.mp3',
     'small-shot-partial-xing.mp3',
     'small-shot.mp3',
     'test.webm',
     'test_case_1224361.vp8.ivf',
     'test_case_1224363.vp8.ivf',
     'test_case_1224369.vp8.ivf',
+    'test_vbri.mp3',
 ]
 
 TEST_DIRS += [
     'mp4_demuxer',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..efd74503385c9d8c6afaf59a8923cf67ebd71805
GIT binary patch
literal 16519
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
RO7CGzPyM{||8^tz{{j-3?G*q3