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

Unified 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 side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698