blob: d5d3e63da093a4b606f3a902907058f4f9d30298 [file] [log] [blame]
// Copyright 2018 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 <cstdint>
#include <list>
#include <memory>
#include <string>
#include "base/bind.h"
#include "base/callback.h"
#include "remoting/client/audio/audio_jitter_buffer.h"
#include "remoting/client/audio/audio_stream_format.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace remoting {
namespace {
constexpr AudioPacket::BytesPerSample kBytesPerSample =
AudioPacket::BYTES_PER_SAMPLE_2;
constexpr AudioPacket::Channels kChannels = AudioPacket::CHANNELS_STEREO;
constexpr uint32_t kAudioSampleBytes = kChannels * kBytesPerSample;
constexpr uint32_t kNumConsumerBuffers = 3;
constexpr uint32_t kConsumerBufferMaxByteSize = 5000 * kAudioSampleBytes;
constexpr uint8_t kDefaultBufferData = 0x5A;
constexpr uint8_t kDummyAudioData = 0x8B;
std::unique_ptr<AudioPacket> CreateAudioPacketWithSamplingRate(
AudioPacket::SamplingRate rate,
size_t bytes) {
std::unique_ptr<AudioPacket> packet = std::make_unique<AudioPacket>();
packet->set_encoding(AudioPacket::ENCODING_RAW);
packet->set_sampling_rate(rate);
packet->set_bytes_per_sample(kBytesPerSample);
packet->set_channels(kChannels);
std::string data;
data.resize(bytes, kDummyAudioData);
packet->add_data(data);
return packet;
}
// Check that the first |bytes_written| bytes are filled with audio data and
// the rest of the buffer is unchanged.
void CheckDataBytes(const uint8_t* buffer, size_t bytes_written) {
uint32_t i = 0;
for (; i < bytes_written; i++) {
ASSERT_EQ(kDummyAudioData, *(buffer + i));
}
// Rest of audio frame must be unchanged.
for (; i < kConsumerBufferMaxByteSize; i++) {
ASSERT_EQ(kDefaultBufferData, *(buffer + i));
}
}
} // namespace
class AudioJitterBufferTest : public ::testing::Test {
protected:
void SetUp() override;
void TearDown() override;
void SetSampleRate(AudioPacket::SamplingRate sample_rate);
std::unique_ptr<AudioPacket> CreatePacket(int time_ms);
void AsyncConsumeData(size_t duration);
void VerifyStreamFormat();
void VerifyBuffersNotLost();
size_t ByteFromTime(int time_ms) const;
size_t GetNumQueuedPackets() const;
int GetNumQueuedTime() const;
size_t GetNumQueuedRequests() const;
std::unique_ptr<AudioJitterBuffer> audio_;
std::list<std::unique_ptr<uint8_t[]>> consumer_buffers_;
private:
class SimpleGetDataRequest;
void OnFormatChanged(const AudioStreamFormat& format);
AudioPacket::SamplingRate sample_rate_;
std::unique_ptr<AudioStreamFormat> stream_format_;
};
class AudioJitterBufferTest::SimpleGetDataRequest
: public AsyncAudioDataSupplier::GetDataRequest {
public:
SimpleGetDataRequest(AudioJitterBufferTest* test, size_t bytes_to_write);
~SimpleGetDataRequest() override;
void OnDataFilled() override;
private:
AudioJitterBufferTest* test_;
std::unique_ptr<uint8_t[]> buffer_;
size_t bytes_to_write_;
};
// Test fixture definitions
void AudioJitterBufferTest::SetUp() {
audio_ = std::make_unique<AudioJitterBuffer>(base::BindRepeating(
&AudioJitterBufferTest::OnFormatChanged, base::Unretained(this)));
consumer_buffers_.clear();
for (uint32_t i = 0u; i < kNumConsumerBuffers; i++) {
consumer_buffers_.push_back(
std::make_unique<uint8_t[]>(kConsumerBufferMaxByteSize));
}
SetSampleRate(AudioPacket::SAMPLING_RATE_48000);
}
void AudioJitterBufferTest::TearDown() {
VerifyBuffersNotLost();
audio_.reset();
consumer_buffers_.clear();
}
void AudioJitterBufferTest::SetSampleRate(
AudioPacket::SamplingRate sample_rate) {
sample_rate_ = sample_rate;
}
std::unique_ptr<AudioPacket> AudioJitterBufferTest::CreatePacket(int time_ms) {
return CreateAudioPacketWithSamplingRate(sample_rate_, ByteFromTime(time_ms));
}
void AudioJitterBufferTest::AsyncConsumeData(size_t duration) {
size_t bytes_to_write = ByteFromTime(duration);
ASSERT_LE(bytes_to_write, kConsumerBufferMaxByteSize);
ASSERT_FALSE(consumer_buffers_.empty());
audio_->AsyncGetData(
std::make_unique<SimpleGetDataRequest>(this, bytes_to_write));
}
void AudioJitterBufferTest::VerifyStreamFormat() {
ASSERT_TRUE(stream_format_);
ASSERT_EQ(kBytesPerSample, stream_format_->bytes_per_sample);
ASSERT_EQ(kChannels, stream_format_->channels);
ASSERT_EQ(sample_rate_, stream_format_->sample_rate);
}
void AudioJitterBufferTest::VerifyBuffersNotLost() {
size_t queued_requests = GetNumQueuedRequests();
ASSERT_EQ(kNumConsumerBuffers, queued_requests + consumer_buffers_.size());
}
size_t AudioJitterBufferTest::ByteFromTime(int time_ms) const {
return time_ms * sample_rate_ * kAudioSampleBytes /
base::Time::kMillisecondsPerSecond;
}
size_t AudioJitterBufferTest::GetNumQueuedPackets() const {
return audio_->queued_packets_.size();
}
int AudioJitterBufferTest::GetNumQueuedTime() const {
return audio_->queued_bytes_ * base::Time::kMillisecondsPerSecond /
kAudioSampleBytes / sample_rate_;
}
size_t AudioJitterBufferTest::GetNumQueuedRequests() const {
return audio_->queued_requests_.size();
}
void AudioJitterBufferTest::OnFormatChanged(const AudioStreamFormat& format) {
stream_format_ = std::make_unique<AudioStreamFormat>(format);
}
// SimpleGetDataRequest definitions
AudioJitterBufferTest::SimpleGetDataRequest::SimpleGetDataRequest(
AudioJitterBufferTest* test,
size_t bytes_to_write)
: GetDataRequest(test->consumer_buffers_.front().get(), bytes_to_write),
test_(test),
buffer_(std::move(test->consumer_buffers_.front())),
bytes_to_write_(bytes_to_write) {
test_->consumer_buffers_.pop_front();
memset(buffer_.get(), kDefaultBufferData, kConsumerBufferMaxByteSize);
}
AudioJitterBufferTest::SimpleGetDataRequest::~SimpleGetDataRequest() {
if (buffer_) {
test_->consumer_buffers_.push_back(std::move(buffer_));
}
}
void AudioJitterBufferTest::SimpleGetDataRequest::OnDataFilled() {
CheckDataBytes(buffer_.get(), bytes_to_write_);
test_->consumer_buffers_.push_back(std::move(buffer_));
}
// Test cases
TEST_F(AudioJitterBufferTest, Init) {
ASSERT_EQ(0u, GetNumQueuedPackets());
audio_->AddAudioPacket(CreatePacket(20));
ASSERT_EQ(1u, GetNumQueuedPackets());
VerifyStreamFormat();
}
TEST_F(AudioJitterBufferTest, MultipleSamples) {
audio_->AddAudioPacket(CreatePacket(10));
ASSERT_EQ(10, GetNumQueuedTime());
ASSERT_EQ(1u, GetNumQueuedPackets());
audio_->AddAudioPacket(CreatePacket(20));
ASSERT_EQ(30, GetNumQueuedTime());
ASSERT_EQ(2u, GetNumQueuedPackets());
}
TEST_F(AudioJitterBufferTest, ExceedLatency) {
// Push about 4 seconds worth of samples.
for (uint32_t i = 0; i < 100; ++i) {
audio_->AddAudioPacket(CreatePacket(40));
}
// Verify that we don't have more than 0.5s.
ASSERT_LT(GetNumQueuedTime(), 500);
}
TEST_F(AudioJitterBufferTest, SingleAsyncRequest_UnderrunProtection) {
// Add samples that are enough to fulfill one request but still doesn't get
// passed the underrun protection.
audio_->AddAudioPacket(CreatePacket(10));
// Create an Audio Request.
AsyncConsumeData(10);
// The request is not fulfilled.
ASSERT_EQ(1u, GetNumQueuedPackets());
ASSERT_EQ(1u, GetNumQueuedRequests());
}
TEST_F(AudioJitterBufferTest, SingleAsyncRequest_Fulfilled) {
// Add samples that are enough to bypass underrun protection.
audio_->AddAudioPacket(CreatePacket(80));
// Create an Audio Request.
AsyncConsumeData(10);
// Request is fulfilled and buffer is returned.
ASSERT_EQ(1u, GetNumQueuedPackets());
ASSERT_EQ(0u, GetNumQueuedRequests());
}
TEST_F(AudioJitterBufferTest, TwoAsyncRequest_FulfillOneByOne) {
// Add just enough samples to fulfill one request.
audio_->AddAudioPacket(CreatePacket(80));
ASSERT_EQ(1u, GetNumQueuedPackets());
AsyncConsumeData(80);
// Request is immediately fulfilled.
ASSERT_EQ(0u, GetNumQueuedPackets());
ASSERT_EQ(0u, GetNumQueuedRequests());
VerifyBuffersNotLost();
// Add another request.
AsyncConsumeData(80);
ASSERT_EQ(0u, GetNumQueuedPackets());
ASSERT_EQ(1u, GetNumQueuedRequests());
VerifyBuffersNotLost();
// Add packet fulfill the request.
audio_->AddAudioPacket(CreatePacket(80));
ASSERT_EQ(0u, GetNumQueuedPackets());
ASSERT_EQ(0u, GetNumQueuedRequests());
}
TEST_F(AudioJitterBufferTest, TwoAsyncRequest_OnePacketFulfillsTwoRequests) {
// Add packet big enough to fulfill two requests.
audio_->AddAudioPacket(CreatePacket(100));
ASSERT_EQ(1u, GetNumQueuedPackets());
AsyncConsumeData(50);
// Request is immediately fulfilled.
ASSERT_EQ(1u, GetNumQueuedPackets());
ASSERT_EQ(0u, GetNumQueuedRequests());
VerifyBuffersNotLost();
// Add another request.
AsyncConsumeData(50);
ASSERT_EQ(0u, GetNumQueuedPackets());
ASSERT_EQ(0u, GetNumQueuedRequests());
}
TEST_F(AudioJitterBufferTest, TwoAsyncRequest_UnderrunProtectionKicksIn) {
audio_->AddAudioPacket(CreatePacket(80));
ASSERT_EQ(1u, GetNumQueuedPackets());
// Consumes all packets while still waiting for 20ms of more data.
AsyncConsumeData(100);
ASSERT_EQ(0u, GetNumQueuedPackets());
ASSERT_EQ(1u, GetNumQueuedRequests());
VerifyBuffersNotLost();
// The package does not get pass underrun protection.
audio_->AddAudioPacket(CreatePacket(20));
ASSERT_EQ(1u, GetNumQueuedPackets());
ASSERT_EQ(1u, GetNumQueuedRequests());
// Add a bigger packet, which bypasses underrun protection.
audio_->AddAudioPacket(CreatePacket(100));
ASSERT_EQ(1u, GetNumQueuedPackets());
ASSERT_EQ(0u, GetNumQueuedRequests());
}
TEST_F(AudioJitterBufferTest, TwoAsyncRequest_TwoPacketsFulfillTwoRequests) {
// Add sample that doesn't fulfill the first request.
audio_->AddAudioPacket(CreatePacket(70));
// Create two requests.
AsyncConsumeData(80);
AsyncConsumeData(80);
// The first packet has been used to fill the first request.
ASSERT_EQ(0u, GetNumQueuedPackets());
ASSERT_EQ(2u, GetNumQueuedRequests());
VerifyBuffersNotLost();
// Add the rest to fulfill both requests.
audio_->AddAudioPacket(CreatePacket(90));
ASSERT_EQ(0u, GetNumQueuedPackets());
ASSERT_EQ(0u, GetNumQueuedRequests());
}
TEST_F(AudioJitterBufferTest, ChangeSampleRate) {
ASSERT_EQ(0u, GetNumQueuedPackets());
audio_->AddAudioPacket(CreatePacket(20));
AsyncConsumeData(80);
ASSERT_EQ(1u, GetNumQueuedPackets());
ASSERT_EQ(1u, GetNumQueuedRequests());
VerifyBuffersNotLost();
VerifyStreamFormat();
SetSampleRate(AudioPacket::SAMPLING_RATE_44100);
audio_->AddAudioPacket(CreatePacket(20));
// Previous packet has been removed.
ASSERT_EQ(1u, GetNumQueuedPackets());
// Previous pending requests are cleared and callbacks has been run.
ASSERT_EQ(0u, GetNumQueuedRequests());
VerifyStreamFormat();
}
} // namespace remoting