/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 | } |