Coverage Report

Created: 2018-09-25 14:53

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