tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

TestMP3Demuxer.cpp (19635B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include <gtest/gtest.h>
      8 
      9 #include <vector>
     10 
     11 #include "MP3Demuxer.h"
     12 #include "MockMediaResource.h"
     13 
     14 class MockMP3MediaResource;
     15 class MockMP3StreamMediaResource;
     16 namespace mozilla {
     17 DDLoggedTypeNameAndBase(::MockMP3MediaResource, MockMediaResource);
     18 DDLoggedTypeNameAndBase(::MockMP3StreamMediaResource, MockMP3MediaResource);
     19 }  // namespace mozilla
     20 
     21 using namespace mozilla;
     22 using media::TimeUnit;
     23 
     24 // Regular MP3 file mock resource.
     25 class MockMP3MediaResource
     26    : public MockMediaResource,
     27      public DecoderDoctorLifeLogger<MockMP3MediaResource> {
     28 public:
     29  explicit MockMP3MediaResource(const char* aFileName)
     30      : MockMediaResource(aFileName) {}
     31 
     32 protected:
     33  virtual ~MockMP3MediaResource() = default;
     34 };
     35 
     36 // MP3 stream mock resource.
     37 class MockMP3StreamMediaResource
     38    : public MockMP3MediaResource,
     39      public DecoderDoctorLifeLogger<MockMP3StreamMediaResource> {
     40 public:
     41  explicit MockMP3StreamMediaResource(const char* aFileName)
     42      : MockMP3MediaResource(aFileName) {}
     43 
     44  int64_t GetLength() override { return -1; }
     45 
     46 protected:
     47  virtual ~MockMP3StreamMediaResource() = default;
     48 };
     49 
     50 struct MP3Resource {
     51  enum class HeaderType { NONE, XING, VBRI };
     52  struct Duration {
     53    int64_t mMicroseconds;
     54    float mTolerableRate;
     55 
     56    Duration(int64_t aMicroseconds, float aTolerableRate)
     57        : mMicroseconds(aMicroseconds), mTolerableRate(aTolerableRate) {}
     58    int64_t Tolerance() const {
     59      return AssertedCast<int64_t>(mTolerableRate *
     60                                   static_cast<float>(mMicroseconds));
     61    }
     62  };
     63 
     64  const char* mFilePath{};
     65  bool mIsVBR{};
     66  HeaderType mHeaderType{HeaderType::NONE};
     67  int64_t mFileSize{};
     68  uint32_t mMPEGLayer{};
     69  uint32_t mMPEGVersion{};
     70  uint8_t mID3MajorVersion{};
     71  uint8_t mID3MinorVersion{};
     72  uint8_t mID3Flags{};
     73  uint32_t mID3Size{};
     74 
     75  Maybe<Duration> mDuration;
     76  float mSeekError{};
     77  uint32_t mSampleRate{};
     78  uint32_t mSamplesPerFrame{};
     79  uint32_t mNumSamples{};
     80  uint32_t mPadding{};
     81  uint32_t mEncoderDelay{};
     82  uint32_t mBitrate{};
     83  uint32_t mSlotSize{};
     84  int32_t mPrivate{};
     85 
     86  // The first n frame offsets.
     87  std::vector<int32_t> mSyncOffsets;
     88  RefPtr<MockMP3MediaResource> mResource;
     89  RefPtr<MP3TrackDemuxer> mDemuxer;
     90 };
     91 
     92 class MP3DemuxerTest : public ::testing::Test {
     93 protected:
     94  void SetUp() override {
     95    {
     96      MP3Resource res;
     97      res.mFilePath = "noise.mp3";
     98      res.mIsVBR = false;
     99      res.mHeaderType = MP3Resource::HeaderType::NONE;
    100      res.mFileSize = 965257;
    101      res.mMPEGLayer = 3;
    102      res.mMPEGVersion = 1;
    103      res.mID3MajorVersion = 3;
    104      res.mID3MinorVersion = 0;
    105      res.mID3Flags = 0;
    106      res.mID3Size = 2141;
    107      // The tolerance comes from the fact that this file has ID3v1 information
    108      // at the end, this trips our CBR duration calculation. The file has
    109      // however the correct duration when decoded / demuxed completely.
    110      res.mDuration = Some(MP3Resource::Duration{30093063, 0.00015f});
    111      res.mSeekError = 0.02f;
    112      res.mSampleRate = 44100;
    113      res.mSamplesPerFrame = 1152;
    114      res.mNumSamples = 1327104;
    115      res.mPadding = 0;
    116      res.mEncoderDelay = 0;
    117      res.mBitrate = 256000;
    118      res.mSlotSize = 1;
    119      res.mPrivate = 0;
    120      const int syncs[] = {2151, 2987, 3823, 4659, 5495, 6331};
    121      res.mSyncOffsets.insert(res.mSyncOffsets.begin(), syncs, syncs + 6);
    122 
    123      // No content length can be estimated for CBR stream resources.
    124      MP3Resource streamRes = res;
    125      streamRes.mFileSize = -1;
    126      streamRes.mDuration = Nothing();
    127 
    128      res.mResource = new MockMP3MediaResource(res.mFilePath);
    129      res.mDemuxer = new MP3TrackDemuxer(res.mResource);
    130      mTargets.push_back(res);
    131 
    132      streamRes.mResource = new MockMP3StreamMediaResource(streamRes.mFilePath);
    133      streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
    134      mTargets.push_back(streamRes);
    135    }
    136 
    137    {
    138      MP3Resource res;
    139      // This file trips up the MP3 demuxer if ID3v2 tags aren't properly
    140      // skipped. If skipping is not properly implemented, depending on the
    141      // strictness of the MPEG frame parser a false sync will be detected
    142      // somewhere within the metadata at or after 112087, or failing that, at
    143      // the artificially added extraneous header at 114532.
    144      res.mFilePath = "id3v2header.mp3";
    145      res.mIsVBR = false;
    146      res.mHeaderType = MP3Resource::HeaderType::NONE;
    147      res.mFileSize = 191302;
    148      res.mMPEGLayer = 3;
    149      res.mMPEGVersion = 1;
    150      res.mID3MajorVersion = 3;
    151      res.mID3MinorVersion = 0;
    152      res.mID3Flags = 0;
    153      res.mID3Size = 115304;
    154      // The tolerance comes from the fact that this file has ID3v1 information
    155      // at the end, this trips our CBR duration calculation. The file has
    156      // however the correct duration when decoded / demuxed completely.
    157      res.mDuration = Some(MP3Resource::Duration{3160833, 0.0017f});
    158      res.mSeekError = 0.02f;
    159      res.mSampleRate = 44100;
    160      res.mSamplesPerFrame = 1152;
    161      res.mNumSamples = 139392;
    162      res.mPadding = 0;
    163      res.mEncoderDelay = 0;
    164      res.mBitrate = 192000;
    165      res.mSlotSize = 1;
    166      res.mPrivate = 1;
    167      const int syncs[] = {115314, 115941, 116568, 117195, 117822, 118449};
    168      res.mSyncOffsets.insert(res.mSyncOffsets.begin(), syncs, syncs + 6);
    169 
    170      // No content length can be estimated for CBR stream resources.
    171      MP3Resource streamRes = res;
    172      streamRes.mFileSize = -1;
    173      streamRes.mDuration = Nothing();
    174 
    175      res.mResource = new MockMP3MediaResource(res.mFilePath);
    176      res.mDemuxer = new MP3TrackDemuxer(res.mResource);
    177      mTargets.push_back(res);
    178 
    179      streamRes.mResource = new MockMP3StreamMediaResource(streamRes.mFilePath);
    180      streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
    181      mTargets.push_back(streamRes);
    182    }
    183 
    184    {
    185      MP3Resource res;
    186      res.mFilePath = "noise_vbr.mp3";
    187      res.mIsVBR = true;
    188      res.mHeaderType = MP3Resource::HeaderType::XING;
    189      res.mFileSize = 583679;
    190      res.mMPEGLayer = 3;
    191      res.mMPEGVersion = 1;
    192      res.mID3MajorVersion = 3;
    193      res.mID3MinorVersion = 0;
    194      res.mID3Flags = 0;
    195      res.mID3Size = 2221;
    196      res.mDuration = Some(MP3Resource::Duration{30081065, 0.f});
    197      res.mSeekError = 0.02f;
    198      res.mSampleRate = 44100;
    199      res.mSamplesPerFrame = 1152;
    200      res.mNumSamples = 1326575;
    201      res.mPadding = 576;
    202      res.mEncoderDelay = 2257;
    203      res.mBitrate = 154000;
    204      res.mSlotSize = 1;
    205      res.mPrivate = 0;
    206      const int syncs[] = {2231, 2648, 2752, 3796, 4318, 4735};
    207      res.mSyncOffsets.insert(res.mSyncOffsets.begin(), syncs, syncs + 6);
    208 
    209      // VBR stream resources contain header info on total frames numbers, which
    210      // is used to estimate the total duration.
    211      MP3Resource streamRes = res;
    212      streamRes.mFileSize = -1;
    213 
    214      res.mResource = new MockMP3MediaResource(res.mFilePath);
    215      res.mDemuxer = new MP3TrackDemuxer(res.mResource);
    216      mTargets.push_back(res);
    217 
    218      streamRes.mResource = new MockMP3StreamMediaResource(streamRes.mFilePath);
    219      streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
    220      mTargets.push_back(streamRes);
    221    }
    222 
    223    {
    224      MP3Resource res;
    225      res.mFilePath = "small-shot.mp3";
    226      res.mIsVBR = true;
    227      res.mHeaderType = MP3Resource::HeaderType::XING;
    228      res.mFileSize = 6825;
    229      res.mMPEGLayer = 3;
    230      res.mMPEGVersion = 1;
    231      res.mID3MajorVersion = 4;
    232      res.mID3MinorVersion = 0;
    233      res.mID3Flags = 0;
    234      res.mID3Size = 24;
    235      res.mDuration = Some(MP3Resource::Duration{301473, 0.f});
    236      res.mSeekError = 0.2f;
    237      res.mSampleRate = 44100;
    238      res.mSamplesPerFrame = 1152;
    239      res.mNumSamples = 12;
    240      res.mPadding = 0;
    241      res.mEncoderDelay = 1152 + 529;
    242      res.mBitrate = 256000;
    243      res.mSlotSize = 1;
    244      res.mPrivate = 0;
    245      const int syncs[] = {34,   556,  1078, 1601, 2123, 2646, 3168,
    246                           3691, 4213, 4736, 5258, 5781, 6303};
    247      res.mSyncOffsets.insert(res.mSyncOffsets.begin(), syncs, syncs + 13);
    248 
    249      // No content length can be estimated for CBR stream resources.
    250      MP3Resource streamRes = res;
    251      streamRes.mFileSize = -1;
    252 
    253      res.mResource = new MockMP3MediaResource(res.mFilePath);
    254      res.mDemuxer = new MP3TrackDemuxer(res.mResource);
    255      mTargets.push_back(res);
    256 
    257      streamRes.mResource = new MockMP3StreamMediaResource(streamRes.mFilePath);
    258      streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
    259      mTargets.push_back(streamRes);
    260    }
    261 
    262    {
    263      MP3Resource res;
    264      // This file contains a false frame sync at 34, just after the ID3 tag,
    265      // which should be identified as a false positive and skipped.
    266      res.mFilePath = "small-shot-false-positive.mp3";
    267      res.mIsVBR = true;
    268      res.mHeaderType = MP3Resource::HeaderType::XING;
    269      res.mFileSize = 6845;
    270      res.mMPEGLayer = 3;
    271      res.mMPEGVersion = 1;
    272      res.mID3MajorVersion = 4;
    273      res.mID3MinorVersion = 0;
    274      res.mID3Flags = 0;
    275      res.mID3Size = 24;
    276      res.mDuration = Some(MP3Resource::Duration{301473, 0.f});
    277      res.mSeekError = 0.2f;
    278      res.mSampleRate = 44100;
    279      res.mSamplesPerFrame = 1152;
    280      res.mNumSamples = 12;
    281      res.mPadding = 0;
    282      res.mEncoderDelay = 1681;
    283      res.mBitrate = 256000;
    284      res.mSlotSize = 1;
    285      res.mPrivate = 0;
    286      const int syncs[] = {54,   576,  1098, 1621, 2143, 2666, 3188,
    287                           3711, 4233, 4756, 5278, 5801, 6323};
    288      res.mSyncOffsets.insert(res.mSyncOffsets.begin(), syncs, syncs + 13);
    289 
    290      // No content length can be estimated for CBR stream resources.
    291      MP3Resource streamRes = res;
    292      streamRes.mFileSize = -1;
    293 
    294      res.mResource = new MockMP3MediaResource(res.mFilePath);
    295      res.mDemuxer = new MP3TrackDemuxer(res.mResource);
    296      mTargets.push_back(res);
    297 
    298      streamRes.mResource = new MockMP3StreamMediaResource(streamRes.mFilePath);
    299      streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
    300      mTargets.push_back(streamRes);
    301    }
    302 
    303    {
    304      MP3Resource res;
    305      res.mFilePath = "small-shot-partial-xing.mp3";
    306      res.mIsVBR = true;
    307      res.mHeaderType = MP3Resource::HeaderType::XING;
    308      res.mFileSize = 6825;
    309      res.mMPEGLayer = 3;
    310      res.mMPEGVersion = 1;
    311      res.mID3MajorVersion = 4;
    312      res.mID3MinorVersion = 0;
    313      res.mID3Flags = 0;
    314      res.mID3Size = 24;
    315      res.mDuration = Some(MP3Resource::Duration{301473, 0.f});
    316      res.mSeekError = 0.2f;
    317      res.mSampleRate = 44100;
    318      res.mSamplesPerFrame = 1152;
    319      res.mNumSamples = 12;
    320      res.mPadding = 0;
    321      res.mEncoderDelay = 1681;
    322      res.mBitrate = 256000;
    323      res.mSlotSize = 1;
    324      res.mPrivate = 0;
    325      const int syncs[] = {34,   556,  1078, 1601, 2123, 2646, 3168,
    326                           3691, 4213, 4736, 5258, 5781, 6303};
    327      res.mSyncOffsets.insert(res.mSyncOffsets.begin(), syncs, syncs + 13);
    328 
    329      // No content length can be estimated for CBR stream resources.
    330      MP3Resource streamRes = res;
    331      streamRes.mFileSize = -1;
    332 
    333      res.mResource = new MockMP3MediaResource(res.mFilePath);
    334      res.mDemuxer = new MP3TrackDemuxer(res.mResource);
    335      mTargets.push_back(res);
    336 
    337      streamRes.mResource = new MockMP3StreamMediaResource(streamRes.mFilePath);
    338      streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
    339      mTargets.push_back(streamRes);
    340    }
    341 
    342    {
    343      MP3Resource res;
    344      res.mFilePath = "test_vbri.mp3";
    345      res.mIsVBR = true;
    346      res.mHeaderType = MP3Resource::HeaderType::VBRI;
    347      res.mFileSize = 16519;
    348      res.mMPEGLayer = 3;
    349      res.mMPEGVersion = 1;
    350      res.mID3MajorVersion = 3;
    351      res.mID3MinorVersion = 0;
    352      res.mID3Flags = 0;
    353      res.mID3Size = 4202;
    354      res.mDuration = Some(MP3Resource::Duration{731428, 0.f});
    355      res.mSeekError = 0.02f;
    356      res.mSampleRate = 44100;
    357      res.mSamplesPerFrame = 1152;
    358      res.mNumSamples = 29;
    359      res.mPadding = 0;
    360      res.mEncoderDelay = 1152;
    361      res.mBitrate = 0;
    362      res.mSlotSize = 1;
    363      res.mPrivate = 0;
    364      const int syncs[] = {4212, 4734, 5047, 5464, 5986, 6403};
    365      res.mSyncOffsets.insert(res.mSyncOffsets.begin(), syncs, syncs + 6);
    366 
    367      // VBR stream resources contain header info on total frames numbers, which
    368      // is used to estimate the total duration.
    369      MP3Resource streamRes = res;
    370      streamRes.mFileSize = -1;
    371 
    372      res.mResource = new MockMP3MediaResource(res.mFilePath);
    373      res.mDemuxer = new MP3TrackDemuxer(res.mResource);
    374      mTargets.push_back(res);
    375 
    376      streamRes.mResource = new MockMP3StreamMediaResource(streamRes.mFilePath);
    377      streamRes.mDemuxer = new MP3TrackDemuxer(streamRes.mResource);
    378      mTargets.push_back(streamRes);
    379    }
    380 
    381    for (auto& target : mTargets) {
    382      ASSERT_EQ(NS_OK, target.mResource->Open());
    383      ASSERT_TRUE(target.mDemuxer->Init());
    384    }
    385  }
    386 
    387  std::vector<MP3Resource> mTargets;
    388 };
    389 
    390 TEST_F(MP3DemuxerTest, ID3Tags) {
    391  for (const auto& target : mTargets) {
    392    RefPtr<MediaRawData> frame(target.mDemuxer->DemuxSample());
    393    ASSERT_TRUE(frame);
    394 
    395    const auto& id3 = target.mDemuxer->ID3Header();
    396    ASSERT_TRUE(id3.IsValid());
    397 
    398    EXPECT_EQ(target.mID3MajorVersion, id3.MajorVersion());
    399    EXPECT_EQ(target.mID3MinorVersion, id3.MinorVersion());
    400    EXPECT_EQ(target.mID3Flags, id3.Flags());
    401    EXPECT_EQ(target.mID3Size, id3.Size());
    402  }
    403 }
    404 
    405 TEST_F(MP3DemuxerTest, VBRHeader) {
    406  for (const auto& target : mTargets) {
    407    RefPtr<MediaRawData> frame(target.mDemuxer->DemuxSample());
    408    ASSERT_TRUE(frame);
    409 
    410    const auto& vbr = target.mDemuxer->VBRInfo();
    411 
    412    if (target.mHeaderType == MP3Resource::HeaderType::XING) {
    413      EXPECT_EQ(FrameParser::VBRHeader::XING, vbr.Type());
    414    } else if (target.mHeaderType == MP3Resource::HeaderType::VBRI) {
    415      EXPECT_TRUE(target.mIsVBR);
    416      EXPECT_EQ(FrameParser::VBRHeader::VBRI, vbr.Type());
    417    } else {  // MP3Resource::HeaderType::NONE
    418      EXPECT_EQ(FrameParser::VBRHeader::NONE, vbr.Type());
    419      EXPECT_FALSE(vbr.NumAudioFrames());
    420    }
    421  }
    422 }
    423 
    424 TEST_F(MP3DemuxerTest, FrameParsing) {
    425  for (const auto& target : mTargets) {
    426    printf("Testing: %s\n", target.mFilePath);
    427    RefPtr<MediaRawData> frameData(target.mDemuxer->DemuxSample());
    428    ASSERT_TRUE(frameData);
    429    EXPECT_EQ(target.mFileSize, target.mDemuxer->StreamLength());
    430 
    431    const auto& id3 = target.mDemuxer->ID3Header();
    432    ASSERT_TRUE(id3.IsValid());
    433 
    434    int64_t parsedLength = id3.Size();
    435    uint64_t bitrateSum = 0;
    436    uint32_t numFrames = 0;
    437    uint32_t numSamples = 0;
    438 
    439    while (frameData) {
    440      if (static_cast<int64_t>(target.mSyncOffsets.size()) > numFrames) {
    441        // Test sync offsets.
    442        EXPECT_EQ(target.mSyncOffsets[numFrames], frameData->mOffset);
    443      }
    444 
    445      ++numFrames;
    446      parsedLength += AssertedCast<int64_t>(frameData->Size());
    447 
    448      const auto& frame = target.mDemuxer->LastFrame();
    449      const auto& header = frame.Header();
    450      ASSERT_TRUE(header.IsValid());
    451 
    452      numSamples += header.SamplesPerFrame();
    453 
    454      EXPECT_EQ(target.mMPEGLayer, header.Layer());
    455      EXPECT_EQ(target.mSampleRate, header.SampleRate());
    456      EXPECT_EQ(target.mSamplesPerFrame, header.SamplesPerFrame());
    457      EXPECT_EQ(target.mSlotSize, header.SlotSize());
    458      EXPECT_EQ(target.mPrivate, header.Private());
    459 
    460      if (target.mIsVBR) {
    461        // Used to compute the average bitrate for VBR streams.
    462        bitrateSum += target.mBitrate;
    463      } else {
    464        EXPECT_EQ(target.mBitrate, header.Bitrate());
    465      }
    466 
    467      frameData = target.mDemuxer->DemuxSample();
    468    }
    469 
    470    EXPECT_EQ(target.mPadding, target.mDemuxer->PaddingFrames());
    471    EXPECT_EQ(target.mEncoderDelay, target.mDemuxer->EncoderDelayFrames());
    472    EXPECT_GE(numSamples, 0u);
    473 
    474    // There may be trailing headers which we don't parse, so the stream length
    475    // is the upper bound.
    476    if (target.mFileSize > 0) {
    477      EXPECT_GE(target.mFileSize, parsedLength);
    478    }
    479 
    480    if (target.mIsVBR) {
    481      ASSERT_TRUE(numFrames);
    482      EXPECT_EQ(target.mBitrate, bitrateSum / numFrames);
    483    }
    484  }
    485 }
    486 
    487 TEST_F(MP3DemuxerTest, Duration) {
    488  for (const auto& target : mTargets) {
    489    printf("Testing: %s\n", target.mFilePath);
    490    RefPtr<MediaRawData> frameData(target.mDemuxer->DemuxSample());
    491    ASSERT_TRUE(frameData);
    492    EXPECT_EQ(target.mFileSize, target.mDemuxer->StreamLength());
    493 
    494    while (frameData) {
    495      if (target.mDuration) {
    496        ASSERT_TRUE(target.mDemuxer->Duration());
    497        EXPECT_NEAR(target.mDuration->mMicroseconds,
    498                    target.mDemuxer->Duration()->ToMicroseconds(),
    499                    target.mDuration->Tolerance());
    500      } else {
    501        EXPECT_FALSE(target.mDemuxer->Duration());
    502      }
    503      frameData = target.mDemuxer->DemuxSample();
    504    }
    505    if (target.mDuration) {
    506      // At the end, the durations should always be exact.
    507      EXPECT_EQ(target.mDuration->mMicroseconds,
    508                target.mDemuxer->Duration()->ToMicroseconds());
    509    }
    510  }
    511 
    512  // Seek out of range tests.
    513  for (const auto& target : mTargets) {
    514    printf("Testing %s\n", target.mFilePath);
    515    // Skip tests for stream media resources because of lacking duration.
    516    if (target.mFileSize <= 0) {
    517      continue;
    518    }
    519 
    520    target.mDemuxer->Reset();
    521    RefPtr<MediaRawData> frameData(target.mDemuxer->DemuxSample());
    522    ASSERT_TRUE(frameData);
    523 
    524    ASSERT_TRUE(target.mDemuxer->Duration());
    525    const auto duration = target.mDemuxer->Duration().value();
    526    const auto pos = duration + TimeUnit::FromMicroseconds(1e6);
    527 
    528    // Attempt to seek 1 second past the end of stream.
    529    target.mDemuxer->Seek(pos);
    530    // The seek should bring us to the end of the stream.
    531    EXPECT_NEAR(duration.ToMicroseconds(),
    532                target.mDemuxer->SeekPosition().ToMicroseconds(),
    533                target.mSeekError * duration.ToMicroseconds());
    534 
    535    // Since we're at the end of the stream, there should be no frames left.
    536    frameData = target.mDemuxer->DemuxSample();
    537    ASSERT_FALSE(frameData);
    538  }
    539 }
    540 
    541 TEST_F(MP3DemuxerTest, Seek) {
    542  for (const auto& target : mTargets) {
    543    RefPtr<MediaRawData> frameData(target.mDemuxer->DemuxSample());
    544    ASSERT_TRUE(frameData);
    545 
    546    const auto seekTime = TimeUnit::FromSeconds(1);
    547    auto pos = target.mDemuxer->SeekPosition();
    548 
    549    while (frameData) {
    550      EXPECT_NEAR(pos.ToMicroseconds(),
    551                  target.mDemuxer->SeekPosition().ToMicroseconds(),
    552                  target.mSeekError * pos.ToMicroseconds());
    553 
    554      pos += seekTime;
    555      target.mDemuxer->Seek(pos);
    556      frameData = target.mDemuxer->DemuxSample();
    557    }
    558  }
    559 
    560  // Seeking should work with in-between resets, too.
    561  for (const auto& target : mTargets) {
    562    target.mDemuxer->Reset();
    563    RefPtr<MediaRawData> frameData(target.mDemuxer->DemuxSample());
    564    ASSERT_TRUE(frameData);
    565 
    566    const auto seekTime = TimeUnit::FromSeconds(1);
    567    auto pos = target.mDemuxer->SeekPosition();
    568 
    569    while (frameData) {
    570      EXPECT_NEAR(pos.ToMicroseconds(),
    571                  target.mDemuxer->SeekPosition().ToMicroseconds(),
    572                  target.mSeekError * pos.ToMicroseconds());
    573 
    574      pos += seekTime;
    575      target.mDemuxer->Reset();
    576      target.mDemuxer->Seek(pos);
    577      frameData = target.mDemuxer->DemuxSample();
    578    }
    579  }
    580 }