Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(95)

Side by Side Diff: media/filters/audio_decoder_unittest.cc

Issue 311373004: Consolidate and improve audio decoding test for all decoders. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Comments. DEPS roll. Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698