Index: media/filters/audio_decoder_unittest.cc |
diff --git a/media/filters/audio_decoder_unittest.cc b/media/filters/audio_decoder_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..62eac185df93f2af71c6f3a7528ae14e304849da |
--- /dev/null |
+++ b/media/filters/audio_decoder_unittest.cc |
@@ -0,0 +1,433 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include <deque> |
+ |
+#include "base/bind.h" |
+#include "base/format_macros.h" |
+#include "base/md5.h" |
+#include "base/message_loop/message_loop.h" |
+#include "base/run_loop.h" |
+#include "base/strings/stringprintf.h" |
+#include "build/build_config.h" |
+#include "media/base/audio_buffer.h" |
+#include "media/base/audio_bus.h" |
+#include "media/base/audio_hash.h" |
+#include "media/base/decoder_buffer.h" |
+#include "media/base/test_data_util.h" |
+#include "media/base/test_helpers.h" |
+#include "media/ffmpeg/ffmpeg_common.h" |
+#include "media/filters/audio_file_reader.h" |
+#include "media/filters/ffmpeg_audio_decoder.h" |
+#include "media/filters/in_memory_url_protocol.h" |
+#include "media/filters/opus_audio_decoder.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace media { |
+ |
+// The number of packets to read and then decode from each file. |
+static const size_t kDecodeRuns = 3; |
+ |
+enum AudioDecoderType { |
+ FFMPEG, |
+ OPUS, |
+}; |
+ |
+struct DecodedBufferExpectations { |
+ const int64 timestamp; |
+ const int64 duration; |
+ const char* hash; |
+}; |
+ |
+struct DecoderTestData { |
+ const AudioDecoderType decoder_type; |
+ const AudioCodec codec; |
+ const char* filename; |
+ const DecodedBufferExpectations* expectations; |
+ const int first_packet_pts_us; |
+ const int samples_per_second; |
+ const ChannelLayout channel_layout; |
+}; |
+ |
+// Tells gtest how to print our DecoderTestData structure. |
+std::ostream& operator<<(std::ostream& os, const DecoderTestData& data) { |
+ return os << data.filename; |
+} |
+ |
+class AudioDecoderTest : public testing::TestWithParam<DecoderTestData> { |
+ public: |
+ AudioDecoderTest() : pending_decode_(false), pending_reset_(false) { |
+ switch (GetParam().decoder_type) { |
+ case FFMPEG: |
+ decoder_.reset(new FFmpegAudioDecoder( |
+ message_loop_.message_loop_proxy(), LogCB())); |
+ break; |
+ case OPUS: |
+ decoder_.reset( |
+ new OpusAudioDecoder(message_loop_.message_loop_proxy())); |
+ break; |
+ } |
+ } |
+ |
+ virtual ~AudioDecoderTest() { |
+ EXPECT_FALSE(pending_decode_); |
+ EXPECT_FALSE(pending_reset_); |
+ } |
+ |
+ protected: |
+ void SendAbort() { |
+ ASSERT_FALSE(pending_decode_); |
+ pending_decode_ = true; |
+ decoder_->Decode( |
+ NULL, |
+ base::Bind(&AudioDecoderTest::DecodeFinished, base::Unretained(this))); |
+ base::RunLoop().RunUntilIdle(); |
+ } |
+ |
+ void SendEndOfStream() { |
+ ASSERT_FALSE(pending_decode_); |
+ pending_decode_ = true; |
+ decoder_->Decode( |
+ DecoderBuffer::CreateEOSBuffer(), |
+ base::Bind(&AudioDecoderTest::DecodeFinished, base::Unretained(this))); |
+ base::RunLoop().RunUntilIdle(); |
+ } |
+ |
+ void Initialize() { |
+ // Load the test data file. |
+ data_ = ReadTestDataFile(GetParam().filename); |
+ protocol_.reset( |
+ new InMemoryUrlProtocol(data_->data(), data_->data_size(), false)); |
+ reader_.reset(new AudioFileReader(protocol_.get())); |
+ reader_->Open(); |
+ |
+ // Load the first packet and check its timestamp. |
+ AVPacket packet; |
+ ASSERT_TRUE(reader_->ReadPacketForTesting(&packet)); |
+ 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.
|
+ start_timestamp_ = ConvertFromTimeBase( |
+ reader_->codec_context_for_testing()->time_base, packet.pts); |
+ av_free_packet(&packet); |
+ |
+ // Seek back to the beginning. |
+ ASSERT_TRUE(reader_->SeekForTesting(start_timestamp_)); |
+ |
+ AudioDecoderConfig config; |
+ AVCodecContextToAudioDecoderConfig( |
+ reader_->codec_context_for_testing(), false, &config, false); |
+ |
+ EXPECT_EQ(GetParam().codec, config.codec()); |
+ EXPECT_EQ(GetParam().samples_per_second, config.samples_per_second()); |
+ EXPECT_EQ(GetParam().channel_layout, config.channel_layout()); |
+ |
+ InitializeDecoder(config); |
+ } |
+ |
+ void InitializeDecoder(const AudioDecoderConfig& config) { |
+ 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.
|
+ base::RunLoop().RunUntilIdle(); |
+ } |
+ |
+ void Decode() { |
+ ASSERT_FALSE(pending_decode_); |
+ pending_decode_ = true; |
+ |
+ AVPacket packet; |
+ ASSERT_TRUE(reader_->ReadPacketForTesting(&packet)); |
+ |
+ scoped_refptr<DecoderBuffer> buffer = |
+ DecoderBuffer::CopyFrom(packet.data, packet.size); |
+ buffer->set_timestamp(ConvertFromTimeBase( |
+ reader_->codec_context_for_testing()->time_base, packet.pts)); |
+ buffer->set_duration(ConvertFromTimeBase( |
+ reader_->codec_context_for_testing()->time_base, packet.duration)); |
+ |
+ decoder_->Decode( |
+ buffer, |
+ base::Bind(&AudioDecoderTest::DecodeFinished, base::Unretained(this))); |
+ base::RunLoop().RunUntilIdle(); |
+ av_free_packet(&packet); |
+ } |
+ |
+ void Reset() { |
+ ASSERT_FALSE(pending_reset_); |
+ pending_reset_ = true; |
+ decoder_->Reset( |
+ base::Bind(&AudioDecoderTest::ResetFinished, base::Unretained(this))); |
+ base::RunLoop().RunUntilIdle(); |
+ ASSERT_FALSE(pending_reset_); |
+ } |
+ |
+ void Stop() { |
+ decoder_->Stop(); |
+ } |
+ |
+ void Seek(base::TimeDelta seek_time) { |
+ Reset(); |
+ decoded_audio_.clear(); |
+ ASSERT_TRUE(reader_->SeekForTesting(seek_time)); |
+ } |
+ |
+ void DecodeFinished(AudioDecoder::Status status, |
+ const scoped_refptr<AudioBuffer>& buffer) { |
+ EXPECT_TRUE(pending_decode_); |
+ pending_decode_ = false; |
+ |
+ if (status == AudioDecoder::kNotEnoughData) { |
+ EXPECT_FALSE(buffer); |
+ Decode(); |
+ return; |
+ } |
+ |
+ decoded_audio_.push_back(buffer); |
+ |
+ // If we hit a NULL buffer or have a pending reset, we expect an abort. |
+ if (!buffer || pending_reset_) { |
+ EXPECT_FALSE(buffer); |
+ EXPECT_EQ(AudioDecoder::kAborted, status); |
+ return; |
+ } |
+ |
+ EXPECT_EQ(AudioDecoder::kOk, status); |
+ } |
+ |
+ void ResetFinished() { |
+ EXPECT_TRUE(pending_reset_); |
+ // Reset should always finish after Decode. |
+ EXPECT_FALSE(pending_decode_); |
+ |
+ pending_reset_ = false; |
+ } |
+ |
+ // Generates an MD5 hash of the audio signal. Should not be used for checks |
+ // across platforms as audio varies slightly across platforms. |
+ std::string GetDecodedAudioMD5(size_t i) { |
+ CHECK_LT(i, decoded_audio_.size()); |
+ const scoped_refptr<AudioBuffer>& buffer = decoded_audio_[i]; |
+ |
+ scoped_ptr<AudioBus> output = |
+ AudioBus::Create(buffer->channel_count(), buffer->frame_count()); |
+ buffer->ReadFrames(buffer->frame_count(), 0, 0, output.get()); |
+ |
+ base::MD5Context context; |
+ base::MD5Init(&context); |
+ for (int ch = 0; ch < output->channels(); ++ch) { |
+ base::MD5Update( |
+ &context, |
+ base::StringPiece(reinterpret_cast<char*>(output->channel(ch)), |
+ output->frames() * sizeof(*output->channel(ch)))); |
+ } |
+ base::MD5Digest digest; |
+ base::MD5Final(&digest, &context); |
+ return base::MD5DigestToBase16(digest); |
+ } |
+ |
+ void ExpectDecodedAudio(size_t i, const std::string& exact_hash) { |
+ CHECK_LT(i, decoded_audio_.size()); |
+ const scoped_refptr<AudioBuffer>& buffer = decoded_audio_[i]; |
+ |
+ const DecodedBufferExpectations& sample_info = GetParam().expectations[i]; |
+ EXPECT_EQ(sample_info.timestamp, buffer->timestamp().InMicroseconds()); |
+ EXPECT_EQ(sample_info.duration, buffer->duration().InMicroseconds()); |
+ EXPECT_FALSE(buffer->end_of_stream()); |
+ |
+ scoped_ptr<AudioBus> output = |
+ AudioBus::Create(buffer->channel_count(), buffer->frame_count()); |
+ buffer->ReadFrames(buffer->frame_count(), 0, 0, output.get()); |
+ |
+ // Generate a lossy hash of the audio used for comparison across platforms. |
+ AudioHash audio_hash; |
+ audio_hash.Update(output.get(), output->frames()); |
+ EXPECT_EQ(sample_info.hash, audio_hash.ToString()); |
+ |
+ if (!exact_hash.empty()) { |
+ EXPECT_EQ(exact_hash, GetDecodedAudioMD5(i)); |
+ |
+ // Verify different hashes are being generated. None of our test data |
+ // files have audio that hashes out exactly the same. |
+ if (i > 0) |
+ EXPECT_NE(exact_hash, GetDecodedAudioMD5(i - 1)); |
+ } |
+ } |
+ |
+ void ExpectEndOfStream(size_t i) { |
+ CHECK_LT(i, decoded_audio_.size()); |
+ EXPECT_TRUE(decoded_audio_[i]->end_of_stream()); |
+ } |
+ |
+ size_t decoded_audio_size() const { return decoded_audio_.size(); } |
+ base::TimeDelta start_timestamp() const { return start_timestamp_; } |
+ const scoped_refptr<AudioBuffer>& decoded_audio(size_t i) { |
+ 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
|
+ } |
+ |
+ private: |
+ base::MessageLoop message_loop_; |
+ scoped_refptr<DecoderBuffer> data_; |
+ scoped_ptr<InMemoryUrlProtocol> protocol_; |
+ scoped_ptr<AudioFileReader> reader_; |
+ |
+ scoped_ptr<AudioDecoder> decoder_; |
+ bool pending_decode_; |
+ bool pending_reset_; |
+ |
+ std::deque<scoped_refptr<AudioBuffer> > decoded_audio_; |
+ base::TimeDelta start_timestamp_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(AudioDecoderTest); |
+}; |
+ |
+TEST_P(AudioDecoderTest, Initialize) { |
+ ASSERT_NO_FATAL_FAILURE(Initialize()); |
+ Stop(); |
+} |
+ |
+TEST_P(AudioDecoderTest, InitializeWithNoCodecDelay) { |
+ if (GetParam().decoder_type != OPUS) |
+ return; |
+ |
+ const uint8_t kOpusExtraData[] = { |
+ 0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64, 0x01, 0x02, |
+ // The next two bytes represent the codec delay. |
+ 0x00, 0x00, 0x80, 0xbb, 0x00, 0x00, 0x00, 0x00, 0x00}; |
+ AudioDecoderConfig decoder_config; |
+ decoder_config.Initialize(kCodecOpus, |
+ kSampleFormatF32, |
+ CHANNEL_LAYOUT_STEREO, |
+ 48000, |
+ kOpusExtraData, |
+ ARRAYSIZE_UNSAFE(kOpusExtraData), |
+ false, |
+ false, |
+ base::TimeDelta::FromMilliseconds(80), |
+ 0); |
+ InitializeDecoder(decoder_config); |
+ Stop(); |
+} |
+ |
+// Verifies decode audio as well as the Decode() -> Reset() -> Stop() sequence. |
+TEST_P(AudioDecoderTest, ProduceAudioSamples) { |
+ ASSERT_NO_FATAL_FAILURE(Initialize()); |
+ |
+ // Run the test multiple times with a seek back to the beginning in between. |
+ std::vector<std::string> decoded_audio_md5_hashes; |
+ for (int i = 0; i < 2; ++i) { |
+ for (size_t j = 0; j < kDecodeRuns; ++j) { |
+ Decode(); |
+ |
+ // On the first pass record the exact MD5 hash for each decoded buffer. |
+ if (i == 0) |
+ decoded_audio_md5_hashes.push_back(GetDecodedAudioMD5(j)); |
+ } |
+ |
+ ASSERT_EQ(kDecodeRuns, decoded_audio_size()); |
+ |
+ // On the first pass verify the basic audio hash and sample info. On the |
+ // second, verify the exact MD5 sum for each packet. It shouldn't change. |
+ for (size_t j = 0; j < kDecodeRuns; ++j) { |
+ SCOPED_TRACE(base::StringPrintf("i = %d, j = %" PRIuS, i, j)); |
+ ExpectDecodedAudio(j, i == 0 ? "" : decoded_audio_md5_hashes[j]); |
+ } |
+ |
+ // Call one more time to trigger EOS. |
+ SendEndOfStream(); |
+ |
+ ASSERT_EQ(kDecodeRuns + 1, decoded_audio_size()); |
+ ExpectEndOfStream(kDecodeRuns); |
+ |
+ // Seek back to the beginning. Calls Reset() on the decoder. |
+ Seek(start_timestamp()); |
+ } |
+ |
+ Stop(); |
+} |
+ |
+TEST_P(AudioDecoderTest, DecodeAbort) { |
+ ASSERT_NO_FATAL_FAILURE(Initialize()); |
+ SendAbort(); |
+ EXPECT_EQ(1U, decoded_audio_size()); |
+ EXPECT_FALSE(decoded_audio(0)); |
+ Stop(); |
+} |
+ |
+TEST_P(AudioDecoderTest, DecodeStop) { |
+ ASSERT_NO_FATAL_FAILURE(Initialize()); |
+ Decode(); |
+ EXPECT_EQ(1U, decoded_audio_size()); |
+ ExpectDecodedAudio(0, ""); |
+ Stop(); |
+ EXPECT_EQ(1U, decoded_audio_size()); |
+} |
+ |
+const DecodedBufferExpectations kBearOpusExpectations[] = { |
+ {0, 3500, "-0.26,0.87,1.36,0.84,-0.30,-1.22,"}, |
+ {3500, 10000, "0.09,0.23,0.21,0.03,-0.17,-0.24,"}, |
+ {13500, 10000, "0.10,0.24,0.23,0.04,-0.14,-0.23,"}, |
+}; |
+ |
+const DecoderTestData kOpusTests[] = { |
+ {OPUS, kCodecOpus, "bear-opus.ogg", kBearOpusExpectations, 24, 48000, |
+ CHANNEL_LAYOUT_STEREO}, |
+}; |
+ |
+INSTANTIATE_TEST_CASE_P(OpusAudioDecoderTest, |
+ AudioDecoderTest, |
+ testing::ValuesIn(kOpusTests)); |
+ |
+#if defined(USE_PROPRIETARY_CODECS) |
+const DecodedBufferExpectations kSfxMp3Expectations[] = { |
+ {0, 1065, "2.81,3.99,4.53,4.10,3.08,2.46,"}, |
+ {1065, 26122, "-3.81,-4.14,-3.90,-3.36,-3.03,-3.23,"}, |
+ {27188, 26122, "4.24,3.95,4.22,4.78,5.13,4.93,"}, |
+}; |
+ |
+const DecodedBufferExpectations kSfxAdtsExpectations[] = { |
+ {0, 23219, "-1.90,-1.53,-0.15,1.28,1.23,-0.33,"}, |
+ {23219, 23219, "0.54,0.88,2.19,3.54,3.24,1.63,"}, |
+ {46439, 23219, "1.42,1.69,2.95,4.23,4.02,2.36,"}, |
+}; |
+#endif |
+ |
+#if defined(OS_CHROMEOS) |
+const DecodedBufferExpectations kBearFlacExpectations[] = { |
+ {0, 104489, "-2.24,-0.86,0.82,2.06,1.41,-0.16,"}, |
+ {104489, 104489, "-1.58,-0.52,1.70,2.34,2.06,-0.05,"}, |
+ {208979, 104489, "-2.17,-0.75,1.03,2.14,1.41,-0.25,"}, |
+}; |
+#endif |
+ |
+const DecodedBufferExpectations kBearOgvExpectations[] = { |
+ {0, 23219, "-1.44,-1.27,0.18,1.37,1.95,0.13,"}, |
+ {23219, 23219, "-1.80,-1.41,-0.13,1.30,1.65,0.01,"}, |
+ {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.
|
+}; |
+ |
+const DecodedBufferExpectations kSfxWaveExpectations[] = { |
+ {0, 23219, "-1.23,-0.87,0.47,1.85,1.88,0.29,"}, |
+ {23219, 23219, "0.75,1.10,2.43,3.78,3.53,1.93,"}, |
+ {46439, 23219, "1.27,1.56,2.83,4.13,3.87,2.23,"}, |
+}; |
+ |
+const DecoderTestData kFFmpegTests[] = { |
+#if defined(USE_PROPRIETARY_CODECS) |
+ {FFMPEG, kCodecMP3, "sfx.mp3", kSfxMp3Expectations, 0, 44100, |
+ CHANNEL_LAYOUT_MONO}, |
+ {FFMPEG, kCodecAAC, "sfx.adts", kSfxAdtsExpectations, 0, 44100, |
+ CHANNEL_LAYOUT_MONO}, |
+#endif |
+#if defined(OS_CHROMEOS) |
+ {FFMPEG, kCodecFLAC, "bear.flac", kBearFlacExpectations, 0, 44100, |
+ CHANNEL_LAYOUT_STEREO}, |
+#endif |
+ {FFMPEG, kCodecPCM, "sfx_f32le.wav", kSfxWaveExpectations, 0, 44100, |
+ CHANNEL_LAYOUT_MONO}, |
+ {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.
|
+ CHANNEL_LAYOUT_STEREO}, |
+}; |
+ |
+INSTANTIATE_TEST_CASE_P(FFmpegAudioDecoderTest, |
+ AudioDecoderTest, |
+ testing::ValuesIn(kFFmpegTests)); |
+ |
+} // namespace media |