| // 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 |