// Copyright (c) 2012 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 "remoting/client/audio_player.h" #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" #include "testing/gtest/include/gtest/gtest.h" namespace { const int kAudioSamplesPerFrame = 25; const int kAudioSampleBytes = 4; const int kAudioFrameBytes = kAudioSamplesPerFrame * kAudioSampleBytes; const int kPaddingBytes = 16; // TODO(garykac): Generate random audio data in the tests rather than having // a single constant value. const uint8 kDefaultBufferData = 0x5A; const uint8 kDummyAudioData = 0x8B; } // namespace namespace remoting { class FakeAudioPlayer : public AudioPlayer { public: FakeAudioPlayer() { } virtual bool ResetAudioPlayer(AudioPacket::SamplingRate) OVERRIDE { return true; } virtual uint32 GetSamplesPerFrame() OVERRIDE { return kAudioSamplesPerFrame; } }; class AudioPlayerTest : public ::testing::Test { protected: virtual void SetUp() { audio_.reset(new FakeAudioPlayer()); buffer_.reset(new char[kAudioFrameBytes + kPaddingBytes]); } virtual void TearDown() { } void ConsumeAudioFrame() { uint8* buffer = reinterpret_cast<uint8*>(buffer_.get()); memset(buffer, kDefaultBufferData, kAudioFrameBytes + kPaddingBytes); AudioPlayer::AudioPlayerCallback(reinterpret_cast<void*>(buffer_.get()), kAudioFrameBytes, reinterpret_cast<void*>(audio_.get())); // Verify we haven't written beyond the end of the buffer. for (int i = 0; i < kPaddingBytes; i++) ASSERT_EQ(kDefaultBufferData, *(buffer + kAudioFrameBytes + i)); } // Check that the first |num_bytes| bytes are filled with audio data and // the rest of the buffer is zero-filled. void CheckAudioFrameBytes(int num_bytes) { uint8* buffer = reinterpret_cast<uint8*>(buffer_.get()); int i = 0; for (; i < num_bytes; i++) { ASSERT_EQ(kDummyAudioData, *(buffer + i)); } // Rest of audio frame must be filled with '0's. for (; i < kAudioFrameBytes; i++) { ASSERT_EQ(0, *(buffer + i)); } } int GetNumQueuedSamples() { return audio_->queued_bytes_ / kAudioSampleBytes; } int GetNumQueuedPackets() { return static_cast<int>(audio_->queued_packets_.size()); } int GetBytesConsumed() { return static_cast<int>(audio_->bytes_consumed_); } scoped_ptr<AudioPlayer> audio_; scoped_ptr<char[]> buffer_; }; scoped_ptr<AudioPacket> CreatePacketWithSamplingRate( AudioPacket::SamplingRate rate, int samples) { scoped_ptr<AudioPacket> packet(new AudioPacket()); packet->set_encoding(AudioPacket::ENCODING_RAW); packet->set_sampling_rate(rate); packet->set_bytes_per_sample(AudioPacket::BYTES_PER_SAMPLE_2); packet->set_channels(AudioPacket::CHANNELS_STEREO); // The data must be a multiple of 4 bytes (channels x bytes_per_sample). std::string data; data.resize(samples * kAudioSampleBytes, kDummyAudioData); packet->add_data(data); return packet.Pass(); } scoped_ptr<AudioPacket> CreatePacket44100Hz(int samples) { return CreatePacketWithSamplingRate(AudioPacket::SAMPLING_RATE_44100, samples); } scoped_ptr<AudioPacket> CreatePacket48000Hz(int samples) { return CreatePacketWithSamplingRate(AudioPacket::SAMPLING_RATE_48000, samples); } TEST_F(AudioPlayerTest, Init) { ASSERT_EQ(0, GetNumQueuedPackets()); scoped_ptr<AudioPacket> packet(CreatePacket44100Hz(10)); audio_->ProcessAudioPacket(packet.Pass()); ASSERT_EQ(1, GetNumQueuedPackets()); } TEST_F(AudioPlayerTest, MultipleSamples) { scoped_ptr<AudioPacket> packet1(CreatePacket44100Hz(10)); audio_->ProcessAudioPacket(packet1.Pass()); ASSERT_EQ(10, GetNumQueuedSamples()); ASSERT_EQ(1, GetNumQueuedPackets()); scoped_ptr<AudioPacket> packet2(CreatePacket44100Hz(20)); audio_->ProcessAudioPacket(packet2.Pass()); ASSERT_EQ(30, GetNumQueuedSamples()); ASSERT_EQ(2, GetNumQueuedPackets()); } TEST_F(AudioPlayerTest, ChangeSampleRate) { scoped_ptr<AudioPacket> packet1(CreatePacket44100Hz(10)); audio_->ProcessAudioPacket(packet1.Pass()); ASSERT_EQ(10, GetNumQueuedSamples()); ASSERT_EQ(1, GetNumQueuedPackets()); // New packet with different sampling rate causes previous samples to // be removed. scoped_ptr<AudioPacket> packet2(CreatePacket48000Hz(20)); audio_->ProcessAudioPacket(packet2.Pass()); ASSERT_EQ(20, GetNumQueuedSamples()); ASSERT_EQ(1, GetNumQueuedPackets()); } TEST_F(AudioPlayerTest, ExceedLatency) { // Push about 4 seconds worth of samples. for (int i = 0; i < 100; ++i) { scoped_ptr<AudioPacket> packet1(CreatePacket48000Hz(2000)); audio_->ProcessAudioPacket(packet1.Pass()); } // Verify that we don't have more than 0.5s. EXPECT_LT(GetNumQueuedSamples(), 24000); } // Incoming packets: 100 // Consume: 25 (w/ 75 remaining, offset 25 into packet) TEST_F(AudioPlayerTest, ConsumePartialPacket) { int total_samples = 0; int bytes_consumed = 0; // Process 100 samples. int packet1_samples = 100; scoped_ptr<AudioPacket> packet(CreatePacket44100Hz(packet1_samples)); total_samples += packet1_samples; audio_->ProcessAudioPacket(packet.Pass()); ASSERT_EQ(total_samples, GetNumQueuedSamples()); ASSERT_EQ(1, GetNumQueuedPackets()); ASSERT_EQ(bytes_consumed, GetBytesConsumed()); // Consume one frame (=25) of samples. ConsumeAudioFrame(); total_samples -= kAudioSamplesPerFrame; bytes_consumed += kAudioFrameBytes; ASSERT_EQ(total_samples, GetNumQueuedSamples()); ASSERT_EQ(1, GetNumQueuedPackets()); ASSERT_EQ(bytes_consumed, GetBytesConsumed()); CheckAudioFrameBytes(kAudioFrameBytes); // Remaining samples. ASSERT_EQ(75, total_samples); ASSERT_EQ(25 * kAudioSampleBytes, bytes_consumed); } // Incoming packets: 20, 70 // Consume: 25, 25 (w/ 40 remaining, offset 30 into packet) TEST_F(AudioPlayerTest, ConsumeAcrossPackets) { int total_samples = 0; int bytes_consumed = 0; // Packet 1. int packet1_samples = 20; scoped_ptr<AudioPacket> packet1(CreatePacket44100Hz(packet1_samples)); total_samples += packet1_samples; audio_->ProcessAudioPacket(packet1.Pass()); ASSERT_EQ(total_samples, GetNumQueuedSamples()); // Packet 2. int packet2_samples = 70; scoped_ptr<AudioPacket> packet2(CreatePacket44100Hz(packet2_samples)); total_samples += packet2_samples; audio_->ProcessAudioPacket(packet2.Pass()); ASSERT_EQ(total_samples, GetNumQueuedSamples()); ASSERT_EQ(bytes_consumed, GetBytesConsumed()); // Consume 1st frame of 25 samples. // This will consume the entire 1st packet. ConsumeAudioFrame(); total_samples -= kAudioSamplesPerFrame; bytes_consumed += kAudioFrameBytes - (packet1_samples * kAudioSampleBytes); ASSERT_EQ(total_samples, GetNumQueuedSamples()); ASSERT_EQ(1, GetNumQueuedPackets()); ASSERT_EQ(bytes_consumed, GetBytesConsumed()); CheckAudioFrameBytes(kAudioFrameBytes); // Consume 2nd frame of 25 samples. ConsumeAudioFrame(); total_samples -= kAudioSamplesPerFrame; bytes_consumed += kAudioFrameBytes; ASSERT_EQ(total_samples, GetNumQueuedSamples()); ASSERT_EQ(1, GetNumQueuedPackets()); ASSERT_EQ(bytes_consumed, GetBytesConsumed()); CheckAudioFrameBytes(kAudioFrameBytes); // Remaining samples. ASSERT_EQ(40, total_samples); ASSERT_EQ(30 * kAudioSampleBytes, bytes_consumed); } // Incoming packets: 50, 30 // Consume: 25, 25, 25 (w/ 5 remaining, offset 25 into packet) TEST_F(AudioPlayerTest, ConsumeEntirePacket) { int total_samples = 0; int bytes_consumed = 0; // Packet 1. int packet1_samples = 50; scoped_ptr<AudioPacket> packet1(CreatePacket44100Hz(packet1_samples)); total_samples += packet1_samples; audio_->ProcessAudioPacket(packet1.Pass()); ASSERT_EQ(total_samples, GetNumQueuedSamples()); ASSERT_EQ(bytes_consumed, GetBytesConsumed()); // Packet 2. int packet2_samples = 30; scoped_ptr<AudioPacket> packet2(CreatePacket44100Hz(packet2_samples)); total_samples += packet2_samples; audio_->ProcessAudioPacket(packet2.Pass()); ASSERT_EQ(total_samples, GetNumQueuedSamples()); ASSERT_EQ(bytes_consumed, GetBytesConsumed()); // Consume 1st frame of 25 samples. ConsumeAudioFrame(); total_samples -= kAudioSamplesPerFrame; bytes_consumed += kAudioFrameBytes; ASSERT_EQ(total_samples, GetNumQueuedSamples()); ASSERT_EQ(2, GetNumQueuedPackets()); ASSERT_EQ(bytes_consumed, GetBytesConsumed()); CheckAudioFrameBytes(kAudioFrameBytes); // Consume 2nd frame of 25 samples. // This will consume the entire first packet (exactly), but the entry for // this packet will stick around (empty) until the next audio chunk is // consumed. ConsumeAudioFrame(); total_samples -= kAudioSamplesPerFrame; bytes_consumed += kAudioFrameBytes; ASSERT_EQ(total_samples, GetNumQueuedSamples()); ASSERT_EQ(2, GetNumQueuedPackets()); ASSERT_EQ(bytes_consumed, GetBytesConsumed()); CheckAudioFrameBytes(kAudioFrameBytes); // Consume 3rd frame of 25 samples. ConsumeAudioFrame(); total_samples -= kAudioSamplesPerFrame; bytes_consumed += kAudioFrameBytes - (packet1_samples * kAudioSampleBytes); ASSERT_EQ(total_samples, GetNumQueuedSamples()); ASSERT_EQ(1, GetNumQueuedPackets()); ASSERT_EQ(bytes_consumed, GetBytesConsumed()); CheckAudioFrameBytes(kAudioFrameBytes); // Remaining samples. ASSERT_EQ(5, total_samples); ASSERT_EQ(25 * kAudioSampleBytes, bytes_consumed); } // Incoming packets: <none> // Consume: 25 TEST_F(AudioPlayerTest, NoDataToConsume) { // Attempt to consume a frame of 25 samples. ConsumeAudioFrame(); ASSERT_EQ(0, GetNumQueuedSamples()); ASSERT_EQ(0, GetNumQueuedPackets()); ASSERT_EQ(0, GetBytesConsumed()); CheckAudioFrameBytes(0); } // Incoming packets: 10 // Consume: 25 TEST_F(AudioPlayerTest, NotEnoughDataToConsume) { int total_samples = 0; int bytes_consumed = 0; // Packet 1. int packet1_samples = 10; scoped_ptr<AudioPacket> packet1(CreatePacket44100Hz(packet1_samples)); total_samples += packet1_samples; audio_->ProcessAudioPacket(packet1.Pass()); ASSERT_EQ(total_samples, GetNumQueuedSamples()); ASSERT_EQ(bytes_consumed, GetBytesConsumed()); // Attempt to consume a frame of 25 samples. ConsumeAudioFrame(); ASSERT_EQ(0, GetNumQueuedSamples()); ASSERT_EQ(0, GetNumQueuedPackets()); ASSERT_EQ(0, GetBytesConsumed()); CheckAudioFrameBytes(packet1_samples * kAudioSampleBytes); } } // namespace remoting