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

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: Megapatch! 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
« no previous file with comments | « no previous file | media/filters/audio_file_reader.h » ('j') | media/filters/audio_file_reader.cc » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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/md5.h"
9 #include "base/message_loop/message_loop.h"
10 #include "base/run_loop.h"
11 #include "build/build_config.h"
12 #include "media/base/audio_buffer.h"
13 #include "media/base/audio_bus.h"
14 #include "media/base/audio_hash.h"
15 #include "media/base/decoder_buffer.h"
16 #include "media/base/test_data_util.h"
17 #include "media/base/test_helpers.h"
18 #include "media/ffmpeg/ffmpeg_common.h"
19 #include "media/filters/audio_file_reader.h"
20 #include "media/filters/ffmpeg_audio_decoder.h"
21 #include "media/filters/in_memory_url_protocol.h"
22 #include "media/filters/opus_audio_decoder.h"
23 #include "testing/gtest/include/gtest/gtest.h"
24
wolenetz 2014/06/06 21:00:03 nit: add using testing::ValuesIn; ?
DaleCurtis 2014/06/06 21:16:59 No, I dislike those.
25 namespace media {
26
27 // The number of packets to read and then decode from each file.
28 static const int kDecodeRuns = 3;
29
30 enum AudioDecoderType {
31 FFMPEG_DECODER,
32 OPUS_DECODER,
33 };
34
35 struct AudioSample {
wolenetz 2014/06/06 21:00:03 nit: "Sample" seems strange term to me for what ap
DaleCurtis 2014/06/12 22:00:47 Done.
36 const int64 timestamp;
37 const int64 duration;
38 const char* hash;
39 };
40
41 struct AudioDecoderTestData {
42 const AudioDecoderType decoder_type;
43 const char* filename;
44 const AudioSample* samples;
45 };
46
47 // Tells gtest how to print our AudioDecoderTestData structure.
48 std::ostream& operator<<(std::ostream& os, const AudioDecoderTestData& data) {
49 return os << data.filename;
wolenetz 2014/06/06 21:00:04 nit: include decoder type or any summary of the sa
DaleCurtis 2014/06/06 21:17:00 No, not relevant for figuring out what's wrong. Th
50 }
51
52 class AudioDecoderTest : public testing::TestWithParam<AudioDecoderTestData> {
53 public:
54 AudioDecoderTest() : pending_decode_(false), pending_reset_(false) {
55 switch (GetParam().decoder_type) {
56 case FFMPEG_DECODER:
57 decoder_.reset(new FFmpegAudioDecoder(
58 message_loop_.message_loop_proxy(), LogCB()));
59 break;
60 case OPUS_DECODER:
61 decoder_.reset(
62 new OpusAudioDecoder(message_loop_.message_loop_proxy()));
wolenetz 2014/06/06 21:00:04 nit: wrap previous line like Decoder(\n ?
DaleCurtis 2014/06/06 21:17:00 Are you arguing with our deity, clang-format?! :)
wolenetz 2014/06/06 21:52:31 I figured as much. No worries.
63 break;
64 }
65 }
66
67 virtual ~AudioDecoderTest() {
68 EXPECT_FALSE(pending_decode_);
69 EXPECT_FALSE(pending_reset_);
70 }
71
72 protected:
73 void SatisfyPendingDecode() { base::RunLoop().RunUntilIdle(); }
74
75 void SendEndOfStream() {
76 pending_decode_ = true;
wolenetz 2014/06/06 21:00:04 First, ASSERT/EXPECT_FALSE(pending_decode_); and A
DaleCurtis 2014/06/12 22:00:47 Done.
77 decoder_->Decode(
78 DecoderBuffer::CreateEOSBuffer(),
79 base::Bind(&AudioDecoderTest::DecodeFinished, base::Unretained(this)));
80 base::RunLoop().RunUntilIdle();
81 }
82
83 void Initialize() {
84 // Load the test data file.
85 data_ = ReadTestDataFile(GetParam().filename);
86 protocol_.reset(
87 new InMemoryUrlProtocol(data_->data(), data_->data_size(), false));
88 reader_.reset(new AudioFileReader(protocol_.get()));
89 reader_->Open();
wolenetz 2014/06/06 21:00:04 nit: We only decode/verify the first enumerated au
DaleCurtis 2014/06/06 21:17:00 No, we don't support multiple streams of the same
90
91 // Load the first packet and save its timestamp.
92 AVPacket packet;
93 ASSERT_TRUE(reader_->ReadPacketForTesting(&packet));
94 start_timestamp_ = ConvertFromTimeBase(
wolenetz 2014/06/06 21:00:04 nit: Verify expected start timestamp?
DaleCurtis 2014/06/06 21:16:59 Maybe. I'll see about adding it.
DaleCurtis 2014/06/12 22:00:48 Done.
95 reader_->codec_context_for_testing()->time_base, packet.pts);
96 av_free_packet(&packet);
97
98 // Seek back to the unconverted timestamp.
99 ASSERT_TRUE(
100 reader_->SeekForTesting(base::TimeDelta::FromMicroseconds(packet.pts)));
101
102 AudioDecoderConfig config;
103 AVCodecContextToAudioDecoderConfig(
wolenetz 2014/06/06 21:00:04 nit: Verify expected config?
DaleCurtis 2014/06/06 21:17:00 Maybe. I could verify codec, channels, and sample
DaleCurtis 2014/06/12 22:00:47 Done.
104 reader_->codec_context_for_testing(), false, &config, false);
wolenetz 2014/06/06 21:00:05 Can the config change during any test? If not, ver
DaleCurtis 2014/06/06 21:17:00 This is already checked within the decoder. Only
105 InitializeDecoder(config);
106 }
107
108 void InitializeDecoder(const AudioDecoderConfig& config) {
109 decoder_->Initialize(config, NewExpectedStatusCB(PIPELINE_OK));
110 base::RunLoop().RunUntilIdle();
111 }
112
113 void Decode() {
114 pending_decode_ = true;
wolenetz 2014/06/06 21:00:04 ditto: (expect nothing pending, or add tests if th
DaleCurtis 2014/06/12 22:00:47 Done.
115
116 AVPacket packet;
117 ASSERT_TRUE(reader_->ReadPacketForTesting(&packet));
118
119 scoped_refptr<DecoderBuffer> buffer =
120 DecoderBuffer::CopyFrom(packet.data, packet.size);
121 buffer->set_timestamp(ConvertFromTimeBase(
122 reader_->codec_context_for_testing()->time_base, packet.pts));
wolenetz 2014/06/06 21:00:03 Do decoders care at all about decode timestamp? Do
DaleCurtis 2014/06/06 21:17:00 No. DecodeTimestamp is a StreamParserBuffer only c
123 buffer->set_duration(ConvertFromTimeBase(
124 reader_->codec_context_for_testing()->time_base, packet.duration));
125 decoder_->Decode(
126 buffer,
127 base::Bind(&AudioDecoderTest::DecodeFinished, base::Unretained(this)));
128 base::RunLoop().RunUntilIdle();
129 av_free_packet(&packet);
wolenetz 2014/06/06 21:00:03 nit: Why free after RunUntilIdle() and not before?
DaleCurtis 2014/06/06 21:17:00 To ensure all operations which might be using the
130 }
131
132 void Reset() {
133 pending_reset_ = true;
wolenetz 2014/06/06 21:00:04 ditto: (expect nothing pending, or add tests if th
DaleCurtis 2014/06/12 22:00:48 Done.
134 decoder_->Reset(
135 base::Bind(&AudioDecoderTest::ResetFinished, base::Unretained(this)));
136 base::RunLoop().RunUntilIdle();
137 }
138
139 void Stop() {
140 decoder_->Stop();
wolenetz 2014/06/06 21:00:05 nit: does setting some flag like |stopped_| true m
DaleCurtis 2014/06/12 22:00:48 Seems unnecessary, that'll blow up in all sorts of
141 base::RunLoop().RunUntilIdle();
142 }
143
144 void Seek(base::TimeDelta seek_time) {
145 Reset();
146 decoded_audio_.clear();
147 const base::TimeDelta converted_time =
wolenetz 2014/06/06 21:00:04 ditto of audio_file_reader.cc comment: this looks
DaleCurtis 2014/06/06 21:16:59 Reading the docs, it looks like this is wrong. Th
wolenetz 2014/06/06 21:52:31 I'm not sure about the automatic conversion, unles
DaleCurtis 2014/06/12 22:00:47 Ah yeah, you're correct. I need to convert to the
148 base::TimeDelta::FromMicroseconds(ConvertToTimeBase(
149 reader_->codec_context_for_testing()->time_base, seek_time));
150 ASSERT_TRUE(reader_->SeekForTesting(converted_time));
151 }
152
153 void DecodeFinished(AudioDecoder::Status status,
154 const scoped_refptr<AudioBuffer>& buffer) {
155 EXPECT_TRUE(pending_decode_);
156 pending_decode_ = false;
157
158 if (status == AudioDecoder::kNotEnoughData) {
wolenetz 2014/06/06 21:00:04 Verify versus expectation of not enough data?
DaleCurtis 2014/06/06 21:16:59 I don't understand what you're saying.
wolenetz 2014/06/06 21:52:31 Sorry. Can we deterministically expect and verify
DaleCurtis 2014/06/12 22:00:47 Not easily and I don't think it adds any value tha
159 EXPECT_TRUE(buffer.get() == NULL);
160 Decode();
161 return;
162 }
163
164 decoded_audio_.push_back(buffer);
165
166 // If we hit a NULL buffer or have a pending reset, we expect an abort.
167 if (buffer.get() == NULL || pending_reset_) {
168 EXPECT_TRUE(buffer.get() == NULL);
169 EXPECT_EQ(status, AudioDecoder::kAborted);
wolenetz 2014/06/06 21:00:04 nit: here and multiple other places in this file:
DaleCurtis 2014/06/12 22:00:47 Done.
170 return;
171 }
172
173 EXPECT_EQ(status, AudioDecoder::kOk);
174 }
175
176 void ResetFinished() {
177 EXPECT_TRUE(pending_reset_);
178 // Reset should always finish after Decode.
179 EXPECT_FALSE(pending_decode_);
180
181 pending_reset_ = false;
182 }
183
184 // Generates an exact hash of the audio signal. Should not be used for checks
185 // across platforms as audio varies slightly across platforms.
wolenetz 2014/06/06 21:00:04 Is cross-platform audio variance a bug?
DaleCurtis 2014/06/06 21:17:00 No, it's expected due to differing implementations
wolenetz 2014/06/06 21:52:31 Much learning for me today. Many wow :)
186 std::string GetDecodedAudioMD5(size_t i) {
187 CHECK_LT(i, decoded_audio_.size());
188 const scoped_refptr<AudioBuffer>& buffer = decoded_audio_[i];
189
190 scoped_ptr<AudioBus> output =
191 AudioBus::Create(buffer->channel_count(), buffer->frame_count());
192 buffer->ReadFrames(buffer->frame_count(), 0, 0, output.get());
193
194 // Generate an exact MD5 hash for comparison of multiple runs on the same
wolenetz 2014/06/06 21:00:05 nit: truncated comment
DaleCurtis 2014/06/12 22:00:47 Removed. Function comment says more.
195 base::MD5Context context;
196 base::MD5Init(&context);
197 for (int ch = 0; ch < output->channels(); ++ch) {
198 base::MD5Update(
199 &context,
200 base::StringPiece(reinterpret_cast<char*>(output->channel(ch)),
201 output->frames() * sizeof(*output->channel(ch))));
202 }
203 base::MD5Digest digest;
204 base::MD5Final(&digest, &context);
205 return base::MD5DigestToBase16(digest);
206 }
207
208 void ExpectDecodedAudio(size_t i, const std::string& exact_hash) {
209 EXPECT_LT(i, decoded_audio_.size());
wolenetz 2014/06/06 21:00:03 nit: s/EXPECT/ASSERT or CHECK/
DaleCurtis 2014/06/12 22:00:47 Done.
210 const scoped_refptr<AudioBuffer>& buffer = decoded_audio_[i];
211
212 const AudioSample& sample_info = GetParam().samples[i];
213 EXPECT_EQ(sample_info.timestamp, buffer->timestamp().InMicroseconds());
214 EXPECT_EQ(sample_info.duration, buffer->duration().InMicroseconds());
215 EXPECT_FALSE(buffer->end_of_stream());
216
217 scoped_ptr<AudioBus> output =
218 AudioBus::Create(buffer->channel_count(), buffer->frame_count());
219 buffer->ReadFrames(buffer->frame_count(), 0, 0, output.get());
220
221 // Generate a lossy hash of the audio used for comparison across platforms.
222 AudioHash audio_hash;
223 audio_hash.Update(output.get(), output->frames());
wolenetz 2014/06/06 21:00:05 nit: I'm not sure the claim of "Value chosen by di
DaleCurtis 2014/06/06 21:17:00 Again, I don't understand what you're asking. The
wolenetz 2014/06/06 21:52:31 I was commenting that the apparent 'randomness' of
DaleCurtis 2014/06/12 22:00:48 Still don't understand :) That value is used for t
wolenetz 2014/06/16 23:36:08 We're missing each other :) I think AudioHash's cl
224 EXPECT_EQ(sample_info.hash, audio_hash.ToString());
225
226 if (!exact_hash.empty())
227 EXPECT_EQ(exact_hash, GetDecodedAudioMD5(i));
wolenetz 2014/06/06 21:00:04 nit: have a test show that this portion of the met
DaleCurtis 2014/06/12 22:00:47 Done.
228 }
229
230 void ExpectEndOfStream(size_t i) {
231 EXPECT_LT(i, decoded_audio_.size());
wolenetz 2014/06/06 21:00:03 nit: s/EXPECT/ASSERT or CHECK/
DaleCurtis 2014/06/12 22:00:47 Done.
232 EXPECT_TRUE(decoded_audio_[i]->end_of_stream());
233 }
234
235 size_t decoded_audio_size() const { return decoded_audio_.size(); }
236 base::TimeDelta start_timestamp() const { return start_timestamp_; }
237
238 private:
239 base::MessageLoop message_loop_;
240 scoped_refptr<DecoderBuffer> data_;
241 scoped_ptr<InMemoryUrlProtocol> protocol_;
242 scoped_ptr<AudioFileReader> reader_;
243
244 scoped_ptr<AudioDecoder> decoder_;
245 bool pending_decode_;
246 bool pending_reset_;
247
248 std::deque<scoped_refptr<AudioBuffer> > decoded_audio_;
249 base::TimeDelta start_timestamp_;
250
251 DISALLOW_COPY_AND_ASSIGN(AudioDecoderTest);
252 };
253
254 TEST_P(AudioDecoderTest, Initialize) {
255 Initialize();
256 Stop();
257 }
258
259 TEST_P(AudioDecoderTest, InitializeWithNoCodecDelay) {
260 if (GetParam().decoder_type != OPUS_DECODER)
wolenetz 2014/06/06 21:00:03 nit: Can this test can be done explicitly (and sim
DaleCurtis 2014/06/06 21:17:00 Possibly, but it's dirty since I'll need to make A
wolenetz 2014/06/06 21:52:31 That's icky. Please forget I asked :).
261 return;
262
263 const uint8_t kOpusExtraData[] = {
264 0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64, 0x01, 0x02,
265 // The next two bytes represent the codec delay.
266 0x00, 0x00, 0x80, 0xbb, 0x00, 0x00, 0x00, 0x00, 0x00};
267 AudioDecoderConfig decoder_config;
268 decoder_config.Initialize(kCodecOpus,
269 kSampleFormatF32,
270 CHANNEL_LAYOUT_STEREO,
271 48000,
272 kOpusExtraData,
273 ARRAYSIZE_UNSAFE(kOpusExtraData),
274 false,
275 false,
276 base::TimeDelta::FromMilliseconds(80),
277 0);
278 InitializeDecoder(decoder_config);
279 Stop();
280 }
281
282 TEST_P(AudioDecoderTest, ProduceAudioSamples) {
283 Initialize();
284
285 // Run the test multiple times with a seek back to the beginning in between.
286 std::vector<std::string> decoded_audio_md5_hashes;
287 for (int i = 0; i < 2; ++i) {
288 for (int j = 0; j < kDecodeRuns; ++j) {
289 Decode();
290
291 // On the first pass record the exact MD5 hash for each decoded buffer.
292 if (i == 0)
293 decoded_audio_md5_hashes.push_back(GetDecodedAudioMD5(j));
294 }
295
296 ASSERT_EQ(static_cast<size_t>(kDecodeRuns), decoded_audio_size());
297
298 // On the first pass verify the basic audio hash and sample info. On the
299 // second, verify the exact MD5 sum for each packet. It shouldn't change.
300 for (int j = 0; j < kDecodeRuns; ++j)
301 ExpectDecodedAudio(j, i == 0 ? "" : decoded_audio_md5_hashes[j]);
302
303 // Call one more time to trigger EOS.
304 SendEndOfStream();
305
306 ASSERT_EQ(static_cast<size_t>(kDecodeRuns + 1), decoded_audio_size());
307 ExpectEndOfStream(kDecodeRuns);
308
309 // Seek back to the beginning.
310 Seek(start_timestamp());
311 }
312
313 Stop();
314 }
315
316 TEST_P(AudioDecoderTest, DecodeAbort) {
317 Initialize();
318 Decode();
319 Stop();
wolenetz 2014/06/06 21:00:04 We had some further verification in previous FFmpe
DaleCurtis 2014/06/12 22:00:48 Fixed. Exposed a bug in the opus decoder!
320 }
321
322 TEST_P(AudioDecoderTest, PendingDecode_Stop) {
323 Initialize();
324 Decode();
325 Stop();
326 SatisfyPendingDecode();
wolenetz 2014/06/06 21:00:04 Is there some deterministic [partial/empty] decode
DaleCurtis 2014/06/06 21:17:00 I don't understand what you're asking. ProduceAudi
wolenetz 2014/06/06 21:52:31 Sorry. I'm confused about the difference between t
DaleCurtis 2014/06/12 22:00:47 Reworked since this covers the Decode() -> Stop()
327 }
328
329 TEST_P(AudioDecoderTest, PendingDecode_Reset) {
330 Initialize();
331 Decode();
332 Reset();
333 SatisfyPendingDecode();
334 Stop();
wolenetz 2014/06/06 21:00:03 ditto
DaleCurtis 2014/06/12 22:00:47 Nuked. ProduceAudioSamples covers this now. The
335 }
336
337 TEST_P(AudioDecoderTest, PendingDecode_ResetStop) {
338 Initialize();
339 Decode();
340 Reset();
341 Stop();
342 SatisfyPendingDecode();
wolenetz 2014/06/06 21:00:05 ditto
DaleCurtis 2014/06/12 22:00:47 Nuked, ProduceAudioSamples covers the Reset() -> S
343 }
344
345 const AudioSample kBearOpusSamples[] = {
346 {0, 3500, "-0.26,0.87,1.36,0.84,-0.30,-1.22,"},
347 {3500, 10000, "0.09,0.23,0.21,0.03,-0.17,-0.24,"},
348 {13500, 10000, "0.10,0.24,0.23,0.04,-0.14,-0.23,"},
349 };
350
351 const AudioDecoderTestData kOpusTests[] = {
352 {OPUS_DECODER, "bear-opus.ogg", kBearOpusSamples},
353 };
354
355 INSTANTIATE_TEST_CASE_P(OpusAudioDecoderTest,
356 AudioDecoderTest,
357 testing::ValuesIn(kOpusTests));
358
359 #if defined(USE_PROPRIETARY_CODECS)
360 const AudioSample kSfxMp3Samples[] = {
361 {0, 1065, "2.81,3.99,4.53,4.10,3.08,2.46,"},
362 {1065, 26122, "-3.81,-4.14,-3.90,-3.36,-3.03,-3.23,"},
363 {27188, 26122, "4.24,3.95,4.22,4.78,5.13,4.93,"},
364 };
365
366 const AudioSample kSfxAdtsSamples[] = {
367 {0, 23219, "-1.90,-1.53,-0.15,1.28,1.23,-0.33,"},
368 {23219, 23219, "0.54,0.88,2.19,3.54,3.24,1.63,"},
369 {46439, 23219, "1.42,1.69,2.95,4.23,4.02,2.36,"},
370 };
371 #endif
372
373 #if defined(OS_CHROMEOS)
374 const AudioSample kBearFlacSamples[] = {
375 {0, 104489, "-2.24,-0.86,0.82,2.06,1.41,-0.16,"},
376 {104489, 104489, "-1.58,-0.52,1.70,2.34,2.06,-0.05,"},
377 {208979, 104489, "-2.17,-0.75,1.03,2.14,1.41,-0.25,"},
378 };
379 #endif
380
381 const AudioSample kBearOgvSamples[] = {
382 {0, 13061, "-2.09,-0.21,1.34,2.09,0.76,-0.95,"},
383 {13061, 23219, "-1.44,-1.27,0.18,1.37,1.95,0.13,"},
384 {36281, 23219, "-1.80,-1.41,-0.13,1.30,1.65,0.01,"},
385 };
386
387 const AudioSample kSfxWaveSamples[] = {
388 {0, 23219, "-1.23,-0.87,0.47,1.85,1.88,0.29,"},
389 {23219, 23219, "0.75,1.10,2.43,3.78,3.53,1.93,"},
390 {46439, 23219, "1.27,1.56,2.83,4.13,3.87,2.23,"},
391 };
392
393 const AudioDecoderTestData kFFmpegTests[] = {
394 #if defined(USE_PROPRIETARY_CODECS)
395 {FFMPEG_DECODER, "sfx.mp3", kSfxMp3Samples},
396 {FFMPEG_DECODER, "sfx.adts", kSfxAdtsSamples},
397 #endif
398 #if defined(OS_CHROMEOS)
399 {FFMPEG_DECODER, "bear.flac", kBearFlacSamples},
400 #endif
401 {FFMPEG_DECODER, "sfx_f32le.wav", kSfxWaveSamples},
402 {FFMPEG_DECODER, "bear.ogv", kBearOgvSamples},
403 };
404
405 INSTANTIATE_TEST_CASE_P(FFmpegAudioDecoderTest,
406 AudioDecoderTest,
407 testing::ValuesIn(kFFmpegTests));
408
409 } // namespace media
OLDNEW
« no previous file with comments | « no previous file | media/filters/audio_file_reader.h » ('j') | media/filters/audio_file_reader.cc » ('J')

Powered by Google App Engine
This is Rietveld 408576698