diff options
Diffstat (limited to 'chromium/media/cast/receiver/audio_decoder_unittest.cc')
-rw-r--r-- | chromium/media/cast/receiver/audio_decoder_unittest.cc | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/chromium/media/cast/receiver/audio_decoder_unittest.cc b/chromium/media/cast/receiver/audio_decoder_unittest.cc new file mode 100644 index 00000000000..6985a694232 --- /dev/null +++ b/chromium/media/cast/receiver/audio_decoder_unittest.cc @@ -0,0 +1,241 @@ +// 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 "base/bind.h" +#include "base/bind_helpers.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "base/sys_byteorder.h" +#include "base/time/time.h" +#include "media/cast/cast_config.h" +#include "media/cast/receiver/audio_decoder.h" +#include "media/cast/test/utility/audio_utility.h" +#include "media/cast/test/utility/standalone_cast_environment.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/opus/src/include/opus.h" + +namespace media { +namespace cast { + +namespace { +struct TestScenario { + transport::AudioCodec codec; + int num_channels; + int sampling_rate; + + TestScenario(transport::AudioCodec c, int n, int s) + : codec(c), num_channels(n), sampling_rate(s) {} +}; +} // namespace + +class AudioDecoderTest : public ::testing::TestWithParam<TestScenario> { + public: + AudioDecoderTest() + : cast_environment_(new StandaloneCastEnvironment()), + cond_(&lock_) {} + + protected: + virtual void SetUp() OVERRIDE { + audio_decoder_.reset(new AudioDecoder(cast_environment_, + GetParam().num_channels, + GetParam().sampling_rate, + GetParam().codec)); + CHECK_EQ(STATUS_AUDIO_INITIALIZED, audio_decoder_->InitializationResult()); + + audio_bus_factory_.reset( + new TestAudioBusFactory(GetParam().num_channels, + GetParam().sampling_rate, + TestAudioBusFactory::kMiddleANoteFreq, + 0.5f)); + last_frame_id_ = 0; + seen_a_decoded_frame_ = false; + + if (GetParam().codec == transport::kOpus) { + opus_encoder_memory_.reset( + new uint8[opus_encoder_get_size(GetParam().num_channels)]); + OpusEncoder* const opus_encoder = + reinterpret_cast<OpusEncoder*>(opus_encoder_memory_.get()); + CHECK_EQ(OPUS_OK, opus_encoder_init(opus_encoder, + GetParam().sampling_rate, + GetParam().num_channels, + OPUS_APPLICATION_AUDIO)); + CHECK_EQ(OPUS_OK, + opus_encoder_ctl(opus_encoder, OPUS_SET_BITRATE(OPUS_AUTO))); + } + + total_audio_feed_in_ = base::TimeDelta(); + total_audio_decoded_ = base::TimeDelta(); + } + + // Called from the unit test thread to create another EncodedFrame and push it + // into the decoding pipeline. + void FeedMoreAudio(const base::TimeDelta& duration, + int num_dropped_frames) { + // Prepare a simulated EncodedFrame to feed into the AudioDecoder. + scoped_ptr<transport::EncodedFrame> encoded_frame( + new transport::EncodedFrame()); + encoded_frame->dependency = transport::EncodedFrame::KEY; + encoded_frame->frame_id = last_frame_id_ + 1 + num_dropped_frames; + encoded_frame->referenced_frame_id = encoded_frame->frame_id; + last_frame_id_ = encoded_frame->frame_id; + + const scoped_ptr<AudioBus> audio_bus( + audio_bus_factory_->NextAudioBus(duration).Pass()); + + // Encode |audio_bus| into |encoded_frame->data|. + const int num_elements = audio_bus->channels() * audio_bus->frames(); + std::vector<int16> interleaved(num_elements); + audio_bus->ToInterleaved( + audio_bus->frames(), sizeof(int16), &interleaved.front()); + if (GetParam().codec == transport::kPcm16) { + encoded_frame->data.resize(num_elements * sizeof(int16)); + int16* const pcm_data = + reinterpret_cast<int16*>(encoded_frame->mutable_bytes()); + for (size_t i = 0; i < interleaved.size(); ++i) + pcm_data[i] = static_cast<int16>(base::HostToNet16(interleaved[i])); + } else if (GetParam().codec == transport::kOpus) { + OpusEncoder* const opus_encoder = + reinterpret_cast<OpusEncoder*>(opus_encoder_memory_.get()); + const int kOpusEncodeBufferSize = 4000; + encoded_frame->data.resize(kOpusEncodeBufferSize); + const int payload_size = + opus_encode(opus_encoder, + &interleaved.front(), + audio_bus->frames(), + encoded_frame->mutable_bytes(), + encoded_frame->data.size()); + CHECK_GT(payload_size, 1); + encoded_frame->data.resize(payload_size); + } else { + ASSERT_TRUE(false); // Not reached. + } + + { + base::AutoLock auto_lock(lock_); + total_audio_feed_in_ += duration; + } + + cast_environment_->PostTask( + CastEnvironment::MAIN, + FROM_HERE, + base::Bind(&AudioDecoder::DecodeFrame, + base::Unretained(audio_decoder_.get()), + base::Passed(&encoded_frame), + base::Bind(&AudioDecoderTest::OnDecodedFrame, + base::Unretained(this), + num_dropped_frames == 0))); + } + + // Blocks the caller until all audio that has been feed in has been decoded. + void WaitForAllAudioToBeDecoded() { + DCHECK(!cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + base::AutoLock auto_lock(lock_); + while (total_audio_decoded_ < total_audio_feed_in_) + cond_.Wait(); + EXPECT_EQ(total_audio_feed_in_.InMicroseconds(), + total_audio_decoded_.InMicroseconds()); + } + + private: + // Called by |audio_decoder_| to deliver each frame of decoded audio. + void OnDecodedFrame(bool should_be_continuous, + scoped_ptr<AudioBus> audio_bus, + bool is_continuous) { + DCHECK(cast_environment_->CurrentlyOn(CastEnvironment::MAIN)); + + // A NULL |audio_bus| indicates a decode error, which we don't expect. + ASSERT_FALSE(!audio_bus); + + // Did the decoder detect whether frames were dropped? + EXPECT_EQ(should_be_continuous, is_continuous); + + // Does the audio data seem to be intact? For Opus, we have to ignore the + // first frame seen at the start (and immediately after dropped packet + // recovery) because it introduces a tiny, significant delay. + bool examine_signal = true; + if (GetParam().codec == transport::kOpus) { + examine_signal = seen_a_decoded_frame_ && should_be_continuous; + seen_a_decoded_frame_ = true; + } + if (examine_signal) { + for (int ch = 0; ch < audio_bus->channels(); ++ch) { + EXPECT_NEAR( + TestAudioBusFactory::kMiddleANoteFreq * 2 * audio_bus->frames() / + GetParam().sampling_rate, + CountZeroCrossings(audio_bus->channel(ch), audio_bus->frames()), + 1); + } + } + + // Signal the main test thread that more audio was decoded. + base::AutoLock auto_lock(lock_); + total_audio_decoded_ += base::TimeDelta::FromSeconds(1) * + audio_bus->frames() / GetParam().sampling_rate; + cond_.Signal(); + } + + const scoped_refptr<StandaloneCastEnvironment> cast_environment_; + scoped_ptr<AudioDecoder> audio_decoder_; + scoped_ptr<TestAudioBusFactory> audio_bus_factory_; + uint32 last_frame_id_; + bool seen_a_decoded_frame_; + scoped_ptr<uint8[]> opus_encoder_memory_; + + base::Lock lock_; + base::ConditionVariable cond_; + base::TimeDelta total_audio_feed_in_; + base::TimeDelta total_audio_decoded_; + + DISALLOW_COPY_AND_ASSIGN(AudioDecoderTest); +}; + +TEST_P(AudioDecoderTest, DecodesFramesWithSameDuration) { + const base::TimeDelta kTenMilliseconds = + base::TimeDelta::FromMilliseconds(10); + const int kNumFrames = 10; + for (int i = 0; i < kNumFrames; ++i) + FeedMoreAudio(kTenMilliseconds, 0); + WaitForAllAudioToBeDecoded(); +} + +TEST_P(AudioDecoderTest, DecodesFramesWithVaryingDuration) { + // These are the set of frame durations supported by the Opus encoder. + const int kFrameDurationMs[] = { 5, 10, 20, 40, 60 }; + + const int kNumFrames = 10; + for (size_t i = 0; i < arraysize(kFrameDurationMs); ++i) + for (int j = 0; j < kNumFrames; ++j) + FeedMoreAudio(base::TimeDelta::FromMilliseconds(kFrameDurationMs[i]), 0); + WaitForAllAudioToBeDecoded(); +} + +TEST_P(AudioDecoderTest, RecoversFromDroppedFrames) { + const base::TimeDelta kTenMilliseconds = + base::TimeDelta::FromMilliseconds(10); + const int kNumFrames = 100; + int next_drop_at = 3; + int next_num_dropped = 1; + for (int i = 0; i < kNumFrames; ++i) { + if (i == next_drop_at) { + const int num_dropped = next_num_dropped++; + next_drop_at *= 2; + i += num_dropped; + FeedMoreAudio(kTenMilliseconds, num_dropped); + } else { + FeedMoreAudio(kTenMilliseconds, 0); + } + } + WaitForAllAudioToBeDecoded(); +} + +INSTANTIATE_TEST_CASE_P(AudioDecoderTestScenarios, + AudioDecoderTest, + ::testing::Values( + TestScenario(transport::kPcm16, 1, 8000), + TestScenario(transport::kPcm16, 2, 48000), + TestScenario(transport::kOpus, 1, 8000), + TestScenario(transport::kOpus, 2, 48000))); + +} // namespace cast +} // namespace media |