Avi Drissman | d6cdf9b | 2022-09-15 19:52:53 | [diff] [blame] | 1 | // Copyright 2018 The Chromium Authors |
Yuwei Huang | 5c93d5cc | 2018-08-03 23:58:18 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include <cstdint> |
| 6 | #include <list> |
| 7 | #include <memory> |
| 8 | #include <string> |
| 9 | |
| 10 | #include "base/bind.h" |
| 11 | #include "base/callback.h" |
Keishi Hattori | 0e45c02 | 2021-11-27 09:25:52 | [diff] [blame] | 12 | #include "base/memory/raw_ptr.h" |
Gabriel Charette | d87f10f | 2022-03-31 00:44:22 | [diff] [blame] | 13 | #include "base/time/time.h" |
Yuwei Huang | 5c93d5cc | 2018-08-03 23:58:18 | [diff] [blame] | 14 | #include "remoting/client/audio/audio_jitter_buffer.h" |
Yuwei Huang | 40c53224 | 2018-08-09 05:42:27 | [diff] [blame] | 15 | #include "remoting/client/audio/audio_stream_format.h" |
Yuwei Huang | 5c93d5cc | 2018-08-03 23:58:18 | [diff] [blame] | 16 | #include "testing/gtest/include/gtest/gtest.h" |
| 17 | |
| 18 | namespace remoting { |
| 19 | |
| 20 | namespace { |
| 21 | |
| 22 | constexpr AudioPacket::BytesPerSample kBytesPerSample = |
| 23 | AudioPacket::BYTES_PER_SAMPLE_2; |
| 24 | constexpr AudioPacket::Channels kChannels = AudioPacket::CHANNELS_STEREO; |
Peter Kasting | 0265c3d | 2022-05-06 21:10:05 | [diff] [blame] | 25 | constexpr uint32_t kAudioSampleBytes = uint32_t{kChannels} * kBytesPerSample; |
Yuwei Huang | 5c93d5cc | 2018-08-03 23:58:18 | [diff] [blame] | 26 | |
| 27 | constexpr uint32_t kNumConsumerBuffers = 3; |
| 28 | constexpr uint32_t kConsumerBufferMaxByteSize = 5000 * kAudioSampleBytes; |
| 29 | |
| 30 | constexpr uint8_t kDefaultBufferData = 0x5A; |
| 31 | constexpr uint8_t kDummyAudioData = 0x8B; |
| 32 | |
| 33 | std::unique_ptr<AudioPacket> CreateAudioPacketWithSamplingRate( |
| 34 | AudioPacket::SamplingRate rate, |
| 35 | size_t bytes) { |
| 36 | std::unique_ptr<AudioPacket> packet = std::make_unique<AudioPacket>(); |
| 37 | packet->set_encoding(AudioPacket::ENCODING_RAW); |
| 38 | packet->set_sampling_rate(rate); |
| 39 | packet->set_bytes_per_sample(kBytesPerSample); |
| 40 | packet->set_channels(kChannels); |
| 41 | |
| 42 | std::string data; |
| 43 | data.resize(bytes, kDummyAudioData); |
| 44 | packet->add_data(data); |
| 45 | |
| 46 | return packet; |
| 47 | } |
| 48 | |
| 49 | // Check that the first |bytes_written| bytes are filled with audio data and |
| 50 | // the rest of the buffer is unchanged. |
| 51 | void CheckDataBytes(const uint8_t* buffer, size_t bytes_written) { |
| 52 | uint32_t i = 0; |
| 53 | for (; i < bytes_written; i++) { |
| 54 | ASSERT_EQ(kDummyAudioData, *(buffer + i)); |
| 55 | } |
| 56 | // Rest of audio frame must be unchanged. |
| 57 | for (; i < kConsumerBufferMaxByteSize; i++) { |
| 58 | ASSERT_EQ(kDefaultBufferData, *(buffer + i)); |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | } // namespace |
| 63 | |
| 64 | class AudioJitterBufferTest : public ::testing::Test { |
| 65 | protected: |
| 66 | void SetUp() override; |
| 67 | void TearDown() override; |
| 68 | |
| 69 | void SetSampleRate(AudioPacket::SamplingRate sample_rate); |
| 70 | std::unique_ptr<AudioPacket> CreatePacket(int time_ms); |
| 71 | void AsyncConsumeData(size_t duration); |
| 72 | void VerifyStreamFormat(); |
| 73 | void VerifyBuffersNotLost(); |
| 74 | size_t ByteFromTime(int time_ms) const; |
| 75 | size_t GetNumQueuedPackets() const; |
| 76 | int GetNumQueuedTime() const; |
| 77 | size_t GetNumQueuedRequests() const; |
| 78 | |
| 79 | std::unique_ptr<AudioJitterBuffer> audio_; |
| 80 | std::list<std::unique_ptr<uint8_t[]>> consumer_buffers_; |
| 81 | |
| 82 | private: |
Yuwei Huang | 40c53224 | 2018-08-09 05:42:27 | [diff] [blame] | 83 | class SimpleGetDataRequest; |
Yuwei Huang | 5c93d5cc | 2018-08-03 23:58:18 | [diff] [blame] | 84 | |
Yuwei Huang | 40c53224 | 2018-08-09 05:42:27 | [diff] [blame] | 85 | void OnFormatChanged(const AudioStreamFormat& format); |
Yuwei Huang | 5c93d5cc | 2018-08-03 23:58:18 | [diff] [blame] | 86 | |
| 87 | AudioPacket::SamplingRate sample_rate_; |
Yuwei Huang | 40c53224 | 2018-08-09 05:42:27 | [diff] [blame] | 88 | std::unique_ptr<AudioStreamFormat> stream_format_; |
Yuwei Huang | 5c93d5cc | 2018-08-03 23:58:18 | [diff] [blame] | 89 | }; |
| 90 | |
Yuwei Huang | 40c53224 | 2018-08-09 05:42:27 | [diff] [blame] | 91 | class AudioJitterBufferTest::SimpleGetDataRequest |
Yuwei Huang | 5c93d5cc | 2018-08-03 23:58:18 | [diff] [blame] | 92 | : public AsyncAudioDataSupplier::GetDataRequest { |
| 93 | public: |
| 94 | SimpleGetDataRequest(AudioJitterBufferTest* test, size_t bytes_to_write); |
| 95 | ~SimpleGetDataRequest() override; |
| 96 | |
| 97 | void OnDataFilled() override; |
| 98 | |
| 99 | private: |
Keishi Hattori | 0e45c02 | 2021-11-27 09:25:52 | [diff] [blame] | 100 | raw_ptr<AudioJitterBufferTest> test_; |
Yuwei Huang | 5c93d5cc | 2018-08-03 23:58:18 | [diff] [blame] | 101 | std::unique_ptr<uint8_t[]> buffer_; |
| 102 | size_t bytes_to_write_; |
| 103 | }; |
| 104 | |
| 105 | // Test fixture definitions |
| 106 | |
| 107 | void AudioJitterBufferTest::SetUp() { |
| 108 | audio_ = std::make_unique<AudioJitterBuffer>(base::BindRepeating( |
| 109 | &AudioJitterBufferTest::OnFormatChanged, base::Unretained(this))); |
| 110 | consumer_buffers_.clear(); |
| 111 | for (uint32_t i = 0u; i < kNumConsumerBuffers; i++) { |
| 112 | consumer_buffers_.push_back( |
| 113 | std::make_unique<uint8_t[]>(kConsumerBufferMaxByteSize)); |
| 114 | } |
| 115 | SetSampleRate(AudioPacket::SAMPLING_RATE_48000); |
| 116 | } |
| 117 | |
| 118 | void AudioJitterBufferTest::TearDown() { |
| 119 | VerifyBuffersNotLost(); |
| 120 | audio_.reset(); |
| 121 | consumer_buffers_.clear(); |
| 122 | } |
| 123 | |
| 124 | void AudioJitterBufferTest::SetSampleRate( |
| 125 | AudioPacket::SamplingRate sample_rate) { |
| 126 | sample_rate_ = sample_rate; |
| 127 | } |
| 128 | |
| 129 | std::unique_ptr<AudioPacket> AudioJitterBufferTest::CreatePacket(int time_ms) { |
| 130 | return CreateAudioPacketWithSamplingRate(sample_rate_, ByteFromTime(time_ms)); |
| 131 | } |
| 132 | |
| 133 | void AudioJitterBufferTest::AsyncConsumeData(size_t duration) { |
| 134 | size_t bytes_to_write = ByteFromTime(duration); |
| 135 | ASSERT_LE(bytes_to_write, kConsumerBufferMaxByteSize); |
| 136 | ASSERT_FALSE(consumer_buffers_.empty()); |
| 137 | audio_->AsyncGetData( |
| 138 | std::make_unique<SimpleGetDataRequest>(this, bytes_to_write)); |
| 139 | } |
| 140 | |
| 141 | void AudioJitterBufferTest::VerifyStreamFormat() { |
| 142 | ASSERT_TRUE(stream_format_); |
| 143 | ASSERT_EQ(kBytesPerSample, stream_format_->bytes_per_sample); |
| 144 | ASSERT_EQ(kChannels, stream_format_->channels); |
| 145 | ASSERT_EQ(sample_rate_, stream_format_->sample_rate); |
| 146 | } |
| 147 | |
| 148 | void AudioJitterBufferTest::VerifyBuffersNotLost() { |
| 149 | size_t queued_requests = GetNumQueuedRequests(); |
| 150 | ASSERT_EQ(kNumConsumerBuffers, queued_requests + consumer_buffers_.size()); |
| 151 | } |
| 152 | |
| 153 | size_t AudioJitterBufferTest::ByteFromTime(int time_ms) const { |
| 154 | return time_ms * sample_rate_ * kAudioSampleBytes / |
| 155 | base::Time::kMillisecondsPerSecond; |
| 156 | } |
| 157 | |
| 158 | size_t AudioJitterBufferTest::GetNumQueuedPackets() const { |
| 159 | return audio_->queued_packets_.size(); |
| 160 | } |
| 161 | |
| 162 | int AudioJitterBufferTest::GetNumQueuedTime() const { |
| 163 | return audio_->queued_bytes_ * base::Time::kMillisecondsPerSecond / |
| 164 | kAudioSampleBytes / sample_rate_; |
| 165 | } |
| 166 | |
| 167 | size_t AudioJitterBufferTest::GetNumQueuedRequests() const { |
| 168 | return audio_->queued_requests_.size(); |
| 169 | } |
Yuwei Huang | 40c53224 | 2018-08-09 05:42:27 | [diff] [blame] | 170 | void AudioJitterBufferTest::OnFormatChanged(const AudioStreamFormat& format) { |
| 171 | stream_format_ = std::make_unique<AudioStreamFormat>(format); |
Yuwei Huang | 5c93d5cc | 2018-08-03 23:58:18 | [diff] [blame] | 172 | } |
| 173 | |
| 174 | // SimpleGetDataRequest definitions |
| 175 | |
| 176 | AudioJitterBufferTest::SimpleGetDataRequest::SimpleGetDataRequest( |
| 177 | AudioJitterBufferTest* test, |
| 178 | size_t bytes_to_write) |
| 179 | : GetDataRequest(test->consumer_buffers_.front().get(), bytes_to_write), |
| 180 | test_(test), |
| 181 | buffer_(std::move(test->consumer_buffers_.front())), |
| 182 | bytes_to_write_(bytes_to_write) { |
| 183 | test_->consumer_buffers_.pop_front(); |
| 184 | memset(buffer_.get(), kDefaultBufferData, kConsumerBufferMaxByteSize); |
| 185 | } |
| 186 | |
| 187 | AudioJitterBufferTest::SimpleGetDataRequest::~SimpleGetDataRequest() { |
| 188 | if (buffer_) { |
| 189 | test_->consumer_buffers_.push_back(std::move(buffer_)); |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | void AudioJitterBufferTest::SimpleGetDataRequest::OnDataFilled() { |
| 194 | CheckDataBytes(buffer_.get(), bytes_to_write_); |
| 195 | test_->consumer_buffers_.push_back(std::move(buffer_)); |
| 196 | } |
| 197 | |
| 198 | // Test cases |
| 199 | |
| 200 | TEST_F(AudioJitterBufferTest, Init) { |
| 201 | ASSERT_EQ(0u, GetNumQueuedPackets()); |
| 202 | |
| 203 | audio_->AddAudioPacket(CreatePacket(20)); |
| 204 | ASSERT_EQ(1u, GetNumQueuedPackets()); |
| 205 | VerifyStreamFormat(); |
| 206 | } |
| 207 | |
| 208 | TEST_F(AudioJitterBufferTest, MultipleSamples) { |
| 209 | audio_->AddAudioPacket(CreatePacket(10)); |
| 210 | ASSERT_EQ(10, GetNumQueuedTime()); |
| 211 | ASSERT_EQ(1u, GetNumQueuedPackets()); |
| 212 | |
| 213 | audio_->AddAudioPacket(CreatePacket(20)); |
| 214 | ASSERT_EQ(30, GetNumQueuedTime()); |
| 215 | ASSERT_EQ(2u, GetNumQueuedPackets()); |
| 216 | } |
| 217 | |
| 218 | TEST_F(AudioJitterBufferTest, ExceedLatency) { |
| 219 | // Push about 4 seconds worth of samples. |
| 220 | for (uint32_t i = 0; i < 100; ++i) { |
| 221 | audio_->AddAudioPacket(CreatePacket(40)); |
| 222 | } |
| 223 | |
| 224 | // Verify that we don't have more than 0.5s. |
| 225 | ASSERT_LT(GetNumQueuedTime(), 500); |
| 226 | } |
| 227 | |
| 228 | TEST_F(AudioJitterBufferTest, SingleAsyncRequest_UnderrunProtection) { |
| 229 | // Add samples that are enough to fulfill one request but still doesn't get |
| 230 | // passed the underrun protection. |
| 231 | audio_->AddAudioPacket(CreatePacket(10)); |
| 232 | |
| 233 | // Create an Audio Request. |
| 234 | AsyncConsumeData(10); |
| 235 | |
| 236 | // The request is not fulfilled. |
| 237 | ASSERT_EQ(1u, GetNumQueuedPackets()); |
| 238 | ASSERT_EQ(1u, GetNumQueuedRequests()); |
| 239 | } |
| 240 | |
| 241 | TEST_F(AudioJitterBufferTest, SingleAsyncRequest_Fulfilled) { |
| 242 | // Add samples that are enough to bypass underrun protection. |
| 243 | audio_->AddAudioPacket(CreatePacket(80)); |
| 244 | |
| 245 | // Create an Audio Request. |
| 246 | AsyncConsumeData(10); |
| 247 | |
| 248 | // Request is fulfilled and buffer is returned. |
| 249 | ASSERT_EQ(1u, GetNumQueuedPackets()); |
| 250 | ASSERT_EQ(0u, GetNumQueuedRequests()); |
| 251 | } |
| 252 | |
| 253 | TEST_F(AudioJitterBufferTest, TwoAsyncRequest_FulfillOneByOne) { |
| 254 | // Add just enough samples to fulfill one request. |
| 255 | audio_->AddAudioPacket(CreatePacket(80)); |
| 256 | ASSERT_EQ(1u, GetNumQueuedPackets()); |
| 257 | |
| 258 | AsyncConsumeData(80); |
| 259 | // Request is immediately fulfilled. |
| 260 | ASSERT_EQ(0u, GetNumQueuedPackets()); |
| 261 | ASSERT_EQ(0u, GetNumQueuedRequests()); |
| 262 | VerifyBuffersNotLost(); |
| 263 | |
| 264 | // Add another request. |
| 265 | AsyncConsumeData(80); |
| 266 | ASSERT_EQ(0u, GetNumQueuedPackets()); |
| 267 | ASSERT_EQ(1u, GetNumQueuedRequests()); |
| 268 | VerifyBuffersNotLost(); |
| 269 | |
| 270 | // Add packet fulfill the request. |
| 271 | audio_->AddAudioPacket(CreatePacket(80)); |
| 272 | ASSERT_EQ(0u, GetNumQueuedPackets()); |
| 273 | ASSERT_EQ(0u, GetNumQueuedRequests()); |
| 274 | } |
| 275 | |
| 276 | TEST_F(AudioJitterBufferTest, TwoAsyncRequest_OnePacketFulfillsTwoRequests) { |
| 277 | // Add packet big enough to fulfill two requests. |
| 278 | audio_->AddAudioPacket(CreatePacket(100)); |
| 279 | ASSERT_EQ(1u, GetNumQueuedPackets()); |
| 280 | |
| 281 | AsyncConsumeData(50); |
| 282 | // Request is immediately fulfilled. |
| 283 | ASSERT_EQ(1u, GetNumQueuedPackets()); |
| 284 | ASSERT_EQ(0u, GetNumQueuedRequests()); |
| 285 | VerifyBuffersNotLost(); |
| 286 | |
| 287 | // Add another request. |
| 288 | AsyncConsumeData(50); |
| 289 | ASSERT_EQ(0u, GetNumQueuedPackets()); |
| 290 | ASSERT_EQ(0u, GetNumQueuedRequests()); |
| 291 | } |
| 292 | |
| 293 | TEST_F(AudioJitterBufferTest, TwoAsyncRequest_UnderrunProtectionKicksIn) { |
| 294 | audio_->AddAudioPacket(CreatePacket(80)); |
| 295 | ASSERT_EQ(1u, GetNumQueuedPackets()); |
| 296 | |
| 297 | // Consumes all packets while still waiting for 20ms of more data. |
| 298 | AsyncConsumeData(100); |
| 299 | ASSERT_EQ(0u, GetNumQueuedPackets()); |
| 300 | ASSERT_EQ(1u, GetNumQueuedRequests()); |
| 301 | VerifyBuffersNotLost(); |
| 302 | |
| 303 | // The package does not get pass underrun protection. |
| 304 | audio_->AddAudioPacket(CreatePacket(20)); |
| 305 | ASSERT_EQ(1u, GetNumQueuedPackets()); |
| 306 | ASSERT_EQ(1u, GetNumQueuedRequests()); |
| 307 | |
| 308 | // Add a bigger packet, which bypasses underrun protection. |
| 309 | audio_->AddAudioPacket(CreatePacket(100)); |
| 310 | ASSERT_EQ(1u, GetNumQueuedPackets()); |
| 311 | ASSERT_EQ(0u, GetNumQueuedRequests()); |
| 312 | } |
| 313 | |
| 314 | TEST_F(AudioJitterBufferTest, TwoAsyncRequest_TwoPacketsFulfillTwoRequests) { |
| 315 | // Add sample that doesn't fulfill the first request. |
| 316 | audio_->AddAudioPacket(CreatePacket(70)); |
| 317 | |
| 318 | // Create two requests. |
| 319 | AsyncConsumeData(80); |
| 320 | AsyncConsumeData(80); |
| 321 | |
| 322 | // The first packet has been used to fill the first request. |
| 323 | ASSERT_EQ(0u, GetNumQueuedPackets()); |
| 324 | ASSERT_EQ(2u, GetNumQueuedRequests()); |
| 325 | VerifyBuffersNotLost(); |
| 326 | |
| 327 | // Add the rest to fulfill both requests. |
| 328 | audio_->AddAudioPacket(CreatePacket(90)); |
| 329 | ASSERT_EQ(0u, GetNumQueuedPackets()); |
| 330 | ASSERT_EQ(0u, GetNumQueuedRequests()); |
| 331 | } |
| 332 | |
| 333 | TEST_F(AudioJitterBufferTest, ChangeSampleRate) { |
| 334 | ASSERT_EQ(0u, GetNumQueuedPackets()); |
| 335 | |
| 336 | audio_->AddAudioPacket(CreatePacket(20)); |
| 337 | AsyncConsumeData(80); |
| 338 | ASSERT_EQ(1u, GetNumQueuedPackets()); |
| 339 | ASSERT_EQ(1u, GetNumQueuedRequests()); |
| 340 | VerifyBuffersNotLost(); |
| 341 | VerifyStreamFormat(); |
| 342 | |
| 343 | SetSampleRate(AudioPacket::SAMPLING_RATE_44100); |
| 344 | audio_->AddAudioPacket(CreatePacket(20)); |
| 345 | // Previous packet has been removed. |
| 346 | ASSERT_EQ(1u, GetNumQueuedPackets()); |
| 347 | |
| 348 | // Previous pending requests are cleared and callbacks has been run. |
| 349 | ASSERT_EQ(0u, GetNumQueuedRequests()); |
| 350 | VerifyStreamFormat(); |
| 351 | } |
| 352 | |
| 353 | } // namespace remoting |