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