OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include <deque> | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/format_macros.h" | |
9 #include "base/md5.h" | |
10 #include "base/message_loop/message_loop.h" | |
11 #include "base/run_loop.h" | |
12 #include "base/strings/stringprintf.h" | |
13 #include "build/build_config.h" | |
14 #include "media/base/audio_buffer.h" | |
15 #include "media/base/audio_bus.h" | |
16 #include "media/base/audio_hash.h" | |
17 #include "media/base/decoder_buffer.h" | |
18 #include "media/base/test_data_util.h" | |
19 #include "media/base/test_helpers.h" | |
20 #include "media/ffmpeg/ffmpeg_common.h" | |
21 #include "media/filters/audio_file_reader.h" | |
22 #include "media/filters/ffmpeg_audio_decoder.h" | |
23 #include "media/filters/in_memory_url_protocol.h" | |
24 #include "media/filters/opus_audio_decoder.h" | |
25 #include "testing/gtest/include/gtest/gtest.h" | |
26 | |
27 namespace media { | |
28 | |
29 // The number of packets to read and then decode from each file. | |
30 static const size_t kDecodeRuns = 3; | |
31 | |
32 enum AudioDecoderType { | |
33 FFMPEG, | |
34 OPUS, | |
35 }; | |
36 | |
37 struct DecodedBufferExpectations { | |
38 const int64 timestamp; | |
39 const int64 duration; | |
40 const char* hash; | |
41 }; | |
42 | |
43 struct DecoderTestData { | |
44 const AudioDecoderType decoder_type; | |
45 const AudioCodec codec; | |
46 const char* filename; | |
47 const DecodedBufferExpectations* expectations; | |
48 const int first_packet_pts_us; | |
49 const int samples_per_second; | |
50 const ChannelLayout channel_layout; | |
51 }; | |
52 | |
53 // Tells gtest how to print our DecoderTestData structure. | |
54 std::ostream& operator<<(std::ostream& os, const DecoderTestData& data) { | |
55 return os << data.filename; | |
56 } | |
57 | |
58 class AudioDecoderTest : public testing::TestWithParam<DecoderTestData> { | |
59 public: | |
60 AudioDecoderTest() : pending_decode_(false), pending_reset_(false) { | |
61 switch (GetParam().decoder_type) { | |
62 case FFMPEG: | |
63 decoder_.reset(new FFmpegAudioDecoder( | |
64 message_loop_.message_loop_proxy(), LogCB())); | |
65 break; | |
66 case OPUS: | |
67 decoder_.reset( | |
68 new OpusAudioDecoder(message_loop_.message_loop_proxy())); | |
69 break; | |
70 } | |
71 } | |
72 | |
73 virtual ~AudioDecoderTest() { | |
74 EXPECT_FALSE(pending_decode_); | |
75 EXPECT_FALSE(pending_reset_); | |
76 } | |
77 | |
78 protected: | |
79 void SendAbort() { | |
80 ASSERT_FALSE(pending_decode_); | |
81 pending_decode_ = true; | |
82 decoder_->Decode( | |
83 NULL, | |
84 base::Bind(&AudioDecoderTest::DecodeFinished, base::Unretained(this))); | |
85 base::RunLoop().RunUntilIdle(); | |
86 } | |
87 | |
88 void SendEndOfStream() { | |
89 ASSERT_FALSE(pending_decode_); | |
90 pending_decode_ = true; | |
91 decoder_->Decode( | |
92 DecoderBuffer::CreateEOSBuffer(), | |
93 base::Bind(&AudioDecoderTest::DecodeFinished, base::Unretained(this))); | |
94 base::RunLoop().RunUntilIdle(); | |
95 } | |
96 | |
97 void Initialize() { | |
98 // Load the test data file. | |
99 data_ = ReadTestDataFile(GetParam().filename); | |
100 protocol_.reset( | |
101 new InMemoryUrlProtocol(data_->data(), data_->data_size(), false)); | |
102 reader_.reset(new AudioFileReader(protocol_.get())); | |
103 reader_->Open(); | |
104 | |
105 // Load the first packet and check its timestamp. | |
106 AVPacket packet; | |
107 ASSERT_TRUE(reader_->ReadPacketForTesting(&packet)); | |
108 EXPECT_EQ(GetParam().first_packet_pts_us, packet.pts); | |
wolenetz
2014/06/16 23:36:08
Explicitly compare a converted-to-microseconds pac
DaleCurtis
2014/06/19 01:05:52
Prefer the latter.
| |
109 start_timestamp_ = ConvertFromTimeBase( | |
110 reader_->codec_context_for_testing()->time_base, packet.pts); | |
111 av_free_packet(&packet); | |
112 | |
113 // Seek back to the beginning. | |
114 ASSERT_TRUE(reader_->SeekForTesting(start_timestamp_)); | |
115 | |
116 AudioDecoderConfig config; | |
117 AVCodecContextToAudioDecoderConfig( | |
118 reader_->codec_context_for_testing(), false, &config, false); | |
119 | |
120 EXPECT_EQ(GetParam().codec, config.codec()); | |
121 EXPECT_EQ(GetParam().samples_per_second, config.samples_per_second()); | |
122 EXPECT_EQ(GetParam().channel_layout, config.channel_layout()); | |
123 | |
124 InitializeDecoder(config); | |
125 } | |
126 | |
127 void InitializeDecoder(const AudioDecoderConfig& config) { | |
128 decoder_->Initialize(config, NewExpectedStatusCB(PIPELINE_OK)); | |
wolenetz
2014/06/16 23:36:08
ToT has recent change around decoder init + decode
DaleCurtis
2014/06/19 01:05:52
Merged.
| |
129 base::RunLoop().RunUntilIdle(); | |
130 } | |
131 | |
132 void Decode() { | |
133 ASSERT_FALSE(pending_decode_); | |
134 pending_decode_ = true; | |
135 | |
136 AVPacket packet; | |
137 ASSERT_TRUE(reader_->ReadPacketForTesting(&packet)); | |
138 | |
139 scoped_refptr<DecoderBuffer> buffer = | |
140 DecoderBuffer::CopyFrom(packet.data, packet.size); | |
141 buffer->set_timestamp(ConvertFromTimeBase( | |
142 reader_->codec_context_for_testing()->time_base, packet.pts)); | |
143 buffer->set_duration(ConvertFromTimeBase( | |
144 reader_->codec_context_for_testing()->time_base, packet.duration)); | |
145 | |
146 decoder_->Decode( | |
147 buffer, | |
148 base::Bind(&AudioDecoderTest::DecodeFinished, base::Unretained(this))); | |
149 base::RunLoop().RunUntilIdle(); | |
150 av_free_packet(&packet); | |
151 } | |
152 | |
153 void Reset() { | |
154 ASSERT_FALSE(pending_reset_); | |
155 pending_reset_ = true; | |
156 decoder_->Reset( | |
157 base::Bind(&AudioDecoderTest::ResetFinished, base::Unretained(this))); | |
158 base::RunLoop().RunUntilIdle(); | |
159 ASSERT_FALSE(pending_reset_); | |
160 } | |
161 | |
162 void Stop() { | |
163 decoder_->Stop(); | |
164 } | |
165 | |
166 void Seek(base::TimeDelta seek_time) { | |
167 Reset(); | |
168 decoded_audio_.clear(); | |
169 ASSERT_TRUE(reader_->SeekForTesting(seek_time)); | |
170 } | |
171 | |
172 void DecodeFinished(AudioDecoder::Status status, | |
173 const scoped_refptr<AudioBuffer>& buffer) { | |
174 EXPECT_TRUE(pending_decode_); | |
175 pending_decode_ = false; | |
176 | |
177 if (status == AudioDecoder::kNotEnoughData) { | |
178 EXPECT_FALSE(buffer); | |
179 Decode(); | |
180 return; | |
181 } | |
182 | |
183 decoded_audio_.push_back(buffer); | |
184 | |
185 // If we hit a NULL buffer or have a pending reset, we expect an abort. | |
186 if (!buffer || pending_reset_) { | |
187 EXPECT_FALSE(buffer); | |
188 EXPECT_EQ(AudioDecoder::kAborted, status); | |
189 return; | |
190 } | |
191 | |
192 EXPECT_EQ(AudioDecoder::kOk, status); | |
193 } | |
194 | |
195 void ResetFinished() { | |
196 EXPECT_TRUE(pending_reset_); | |
197 // Reset should always finish after Decode. | |
198 EXPECT_FALSE(pending_decode_); | |
199 | |
200 pending_reset_ = false; | |
201 } | |
202 | |
203 // Generates an MD5 hash of the audio signal. Should not be used for checks | |
204 // across platforms as audio varies slightly across platforms. | |
205 std::string GetDecodedAudioMD5(size_t i) { | |
206 CHECK_LT(i, decoded_audio_.size()); | |
207 const scoped_refptr<AudioBuffer>& buffer = decoded_audio_[i]; | |
208 | |
209 scoped_ptr<AudioBus> output = | |
210 AudioBus::Create(buffer->channel_count(), buffer->frame_count()); | |
211 buffer->ReadFrames(buffer->frame_count(), 0, 0, output.get()); | |
212 | |
213 base::MD5Context context; | |
214 base::MD5Init(&context); | |
215 for (int ch = 0; ch < output->channels(); ++ch) { | |
216 base::MD5Update( | |
217 &context, | |
218 base::StringPiece(reinterpret_cast<char*>(output->channel(ch)), | |
219 output->frames() * sizeof(*output->channel(ch)))); | |
220 } | |
221 base::MD5Digest digest; | |
222 base::MD5Final(&digest, &context); | |
223 return base::MD5DigestToBase16(digest); | |
224 } | |
225 | |
226 void ExpectDecodedAudio(size_t i, const std::string& exact_hash) { | |
227 CHECK_LT(i, decoded_audio_.size()); | |
228 const scoped_refptr<AudioBuffer>& buffer = decoded_audio_[i]; | |
229 | |
230 const DecodedBufferExpectations& sample_info = GetParam().expectations[i]; | |
231 EXPECT_EQ(sample_info.timestamp, buffer->timestamp().InMicroseconds()); | |
232 EXPECT_EQ(sample_info.duration, buffer->duration().InMicroseconds()); | |
233 EXPECT_FALSE(buffer->end_of_stream()); | |
234 | |
235 scoped_ptr<AudioBus> output = | |
236 AudioBus::Create(buffer->channel_count(), buffer->frame_count()); | |
237 buffer->ReadFrames(buffer->frame_count(), 0, 0, output.get()); | |
238 | |
239 // Generate a lossy hash of the audio used for comparison across platforms. | |
240 AudioHash audio_hash; | |
241 audio_hash.Update(output.get(), output->frames()); | |
242 EXPECT_EQ(sample_info.hash, audio_hash.ToString()); | |
243 | |
244 if (!exact_hash.empty()) { | |
245 EXPECT_EQ(exact_hash, GetDecodedAudioMD5(i)); | |
246 | |
247 // Verify different hashes are being generated. None of our test data | |
248 // files have audio that hashes out exactly the same. | |
249 if (i > 0) | |
250 EXPECT_NE(exact_hash, GetDecodedAudioMD5(i - 1)); | |
251 } | |
252 } | |
253 | |
254 void ExpectEndOfStream(size_t i) { | |
255 CHECK_LT(i, decoded_audio_.size()); | |
256 EXPECT_TRUE(decoded_audio_[i]->end_of_stream()); | |
257 } | |
258 | |
259 size_t decoded_audio_size() const { return decoded_audio_.size(); } | |
260 base::TimeDelta start_timestamp() const { return start_timestamp_; } | |
261 const scoped_refptr<AudioBuffer>& decoded_audio(size_t i) { | |
262 return decoded_audio_[i]; | |
wolenetz
2014/06/16 23:36:08
nit: bounds-check |i|
DaleCurtis
2014/06/19 01:05:52
Decline. This is a hacker style method meant to mi
| |
263 } | |
264 | |
265 private: | |
266 base::MessageLoop message_loop_; | |
267 scoped_refptr<DecoderBuffer> data_; | |
268 scoped_ptr<InMemoryUrlProtocol> protocol_; | |
269 scoped_ptr<AudioFileReader> reader_; | |
270 | |
271 scoped_ptr<AudioDecoder> decoder_; | |
272 bool pending_decode_; | |
273 bool pending_reset_; | |
274 | |
275 std::deque<scoped_refptr<AudioBuffer> > decoded_audio_; | |
276 base::TimeDelta start_timestamp_; | |
277 | |
278 DISALLOW_COPY_AND_ASSIGN(AudioDecoderTest); | |
279 }; | |
280 | |
281 TEST_P(AudioDecoderTest, Initialize) { | |
282 ASSERT_NO_FATAL_FAILURE(Initialize()); | |
283 Stop(); | |
284 } | |
285 | |
286 TEST_P(AudioDecoderTest, InitializeWithNoCodecDelay) { | |
287 if (GetParam().decoder_type != OPUS) | |
288 return; | |
289 | |
290 const uint8_t kOpusExtraData[] = { | |
291 0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64, 0x01, 0x02, | |
292 // The next two bytes represent the codec delay. | |
293 0x00, 0x00, 0x80, 0xbb, 0x00, 0x00, 0x00, 0x00, 0x00}; | |
294 AudioDecoderConfig decoder_config; | |
295 decoder_config.Initialize(kCodecOpus, | |
296 kSampleFormatF32, | |
297 CHANNEL_LAYOUT_STEREO, | |
298 48000, | |
299 kOpusExtraData, | |
300 ARRAYSIZE_UNSAFE(kOpusExtraData), | |
301 false, | |
302 false, | |
303 base::TimeDelta::FromMilliseconds(80), | |
304 0); | |
305 InitializeDecoder(decoder_config); | |
306 Stop(); | |
307 } | |
308 | |
309 // Verifies decode audio as well as the Decode() -> Reset() -> Stop() sequence. | |
310 TEST_P(AudioDecoderTest, ProduceAudioSamples) { | |
311 ASSERT_NO_FATAL_FAILURE(Initialize()); | |
312 | |
313 // Run the test multiple times with a seek back to the beginning in between. | |
314 std::vector<std::string> decoded_audio_md5_hashes; | |
315 for (int i = 0; i < 2; ++i) { | |
316 for (size_t j = 0; j < kDecodeRuns; ++j) { | |
317 Decode(); | |
318 | |
319 // On the first pass record the exact MD5 hash for each decoded buffer. | |
320 if (i == 0) | |
321 decoded_audio_md5_hashes.push_back(GetDecodedAudioMD5(j)); | |
322 } | |
323 | |
324 ASSERT_EQ(kDecodeRuns, decoded_audio_size()); | |
325 | |
326 // On the first pass verify the basic audio hash and sample info. On the | |
327 // second, verify the exact MD5 sum for each packet. It shouldn't change. | |
328 for (size_t j = 0; j < kDecodeRuns; ++j) { | |
329 SCOPED_TRACE(base::StringPrintf("i = %d, j = %" PRIuS, i, j)); | |
330 ExpectDecodedAudio(j, i == 0 ? "" : decoded_audio_md5_hashes[j]); | |
331 } | |
332 | |
333 // Call one more time to trigger EOS. | |
334 SendEndOfStream(); | |
335 | |
336 ASSERT_EQ(kDecodeRuns + 1, decoded_audio_size()); | |
337 ExpectEndOfStream(kDecodeRuns); | |
338 | |
339 // Seek back to the beginning. Calls Reset() on the decoder. | |
340 Seek(start_timestamp()); | |
341 } | |
342 | |
343 Stop(); | |
344 } | |
345 | |
346 TEST_P(AudioDecoderTest, DecodeAbort) { | |
347 ASSERT_NO_FATAL_FAILURE(Initialize()); | |
348 SendAbort(); | |
349 EXPECT_EQ(1U, decoded_audio_size()); | |
350 EXPECT_FALSE(decoded_audio(0)); | |
351 Stop(); | |
352 } | |
353 | |
354 TEST_P(AudioDecoderTest, DecodeStop) { | |
355 ASSERT_NO_FATAL_FAILURE(Initialize()); | |
356 Decode(); | |
357 EXPECT_EQ(1U, decoded_audio_size()); | |
358 ExpectDecodedAudio(0, ""); | |
359 Stop(); | |
360 EXPECT_EQ(1U, decoded_audio_size()); | |
361 } | |
362 | |
363 const DecodedBufferExpectations kBearOpusExpectations[] = { | |
364 {0, 3500, "-0.26,0.87,1.36,0.84,-0.30,-1.22,"}, | |
365 {3500, 10000, "0.09,0.23,0.21,0.03,-0.17,-0.24,"}, | |
366 {13500, 10000, "0.10,0.24,0.23,0.04,-0.14,-0.23,"}, | |
367 }; | |
368 | |
369 const DecoderTestData kOpusTests[] = { | |
370 {OPUS, kCodecOpus, "bear-opus.ogg", kBearOpusExpectations, 24, 48000, | |
371 CHANNEL_LAYOUT_STEREO}, | |
372 }; | |
373 | |
374 INSTANTIATE_TEST_CASE_P(OpusAudioDecoderTest, | |
375 AudioDecoderTest, | |
376 testing::ValuesIn(kOpusTests)); | |
377 | |
378 #if defined(USE_PROPRIETARY_CODECS) | |
379 const DecodedBufferExpectations kSfxMp3Expectations[] = { | |
380 {0, 1065, "2.81,3.99,4.53,4.10,3.08,2.46,"}, | |
381 {1065, 26122, "-3.81,-4.14,-3.90,-3.36,-3.03,-3.23,"}, | |
382 {27188, 26122, "4.24,3.95,4.22,4.78,5.13,4.93,"}, | |
383 }; | |
384 | |
385 const DecodedBufferExpectations kSfxAdtsExpectations[] = { | |
386 {0, 23219, "-1.90,-1.53,-0.15,1.28,1.23,-0.33,"}, | |
387 {23219, 23219, "0.54,0.88,2.19,3.54,3.24,1.63,"}, | |
388 {46439, 23219, "1.42,1.69,2.95,4.23,4.02,2.36,"}, | |
389 }; | |
390 #endif | |
391 | |
392 #if defined(OS_CHROMEOS) | |
393 const DecodedBufferExpectations kBearFlacExpectations[] = { | |
394 {0, 104489, "-2.24,-0.86,0.82,2.06,1.41,-0.16,"}, | |
395 {104489, 104489, "-1.58,-0.52,1.70,2.34,2.06,-0.05,"}, | |
396 {208979, 104489, "-2.17,-0.75,1.03,2.14,1.41,-0.25,"}, | |
397 }; | |
398 #endif | |
399 | |
400 const DecodedBufferExpectations kBearOgvExpectations[] = { | |
401 {0, 23219, "-1.44,-1.27,0.18,1.37,1.95,0.13,"}, | |
402 {23219, 23219, "-1.80,-1.41,-0.13,1.30,1.65,0.01,"}, | |
403 {46439, 23219, "-1.43,-1.25,0.11,1.29,1.86,0.14,"}, | |
wolenetz
2014/06/16 23:36:08
Did bear.ogv change, are these changes vs PS2 due
DaleCurtis
2014/06/19 01:05:52
FFmpeg roll.
| |
404 }; | |
405 | |
406 const DecodedBufferExpectations kSfxWaveExpectations[] = { | |
407 {0, 23219, "-1.23,-0.87,0.47,1.85,1.88,0.29,"}, | |
408 {23219, 23219, "0.75,1.10,2.43,3.78,3.53,1.93,"}, | |
409 {46439, 23219, "1.27,1.56,2.83,4.13,3.87,2.23,"}, | |
410 }; | |
411 | |
412 const DecoderTestData kFFmpegTests[] = { | |
413 #if defined(USE_PROPRIETARY_CODECS) | |
414 {FFMPEG, kCodecMP3, "sfx.mp3", kSfxMp3Expectations, 0, 44100, | |
415 CHANNEL_LAYOUT_MONO}, | |
416 {FFMPEG, kCodecAAC, "sfx.adts", kSfxAdtsExpectations, 0, 44100, | |
417 CHANNEL_LAYOUT_MONO}, | |
418 #endif | |
419 #if defined(OS_CHROMEOS) | |
420 {FFMPEG, kCodecFLAC, "bear.flac", kBearFlacExpectations, 0, 44100, | |
421 CHANNEL_LAYOUT_STEREO}, | |
422 #endif | |
423 {FFMPEG, kCodecPCM, "sfx_f32le.wav", kSfxWaveExpectations, 0, 44100, | |
424 CHANNEL_LAYOUT_MONO}, | |
425 {FFMPEG, kCodecVorbis, "bear.ogv", kBearOgvExpectations, -704, 44100, | |
wolenetz
2014/06/16 23:36:08
Negative start timestamp?! Is this normal?
DaleCurtis
2014/06/19 01:05:52
Yup, I've added a comment with some more info.
| |
426 CHANNEL_LAYOUT_STEREO}, | |
427 }; | |
428 | |
429 INSTANTIATE_TEST_CASE_P(FFmpegAudioDecoderTest, | |
430 AudioDecoderTest, | |
431 testing::ValuesIn(kFFmpegTests)); | |
432 | |
433 } // namespace media | |
OLD | NEW |