// Copyright 2022 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 "services/audio/processing_audio_fifo.h" #include #include "base/bind.h" #include "base/callback_helpers.h" #include "base/run_loop.h" #include "base/test/bind.h" #include "media/audio/simple_sources.h" #include "testing/gtest/include/gtest/gtest.h" namespace audio { const int kSampleRate = 48000; const int kFramesPerBuffer = kSampleRate / 200; // 5ms, for testing. const int kTestToneFrequency = 440; const int kTestFifoSize = 4; const double kTestVolume = 0.567; struct TestCaptureData { TestCaptureData(std::unique_ptr audio_bus, base::TimeTicks capture_time, double volume, bool key_pressed) : audio_bus(std::move(audio_bus)), capture_time(capture_time), volume(volume), key_pressed(key_pressed) {} TestCaptureData(const TestCaptureData&) = delete; TestCaptureData& operator=(const TestCaptureData&) = delete; TestCaptureData(TestCaptureData&&) = default; TestCaptureData& operator=(TestCaptureData&& other) = default; ~TestCaptureData() = default; std::unique_ptr audio_bus; base::TimeTicks capture_time; double volume; bool key_pressed; }; void VerifyAudioDataEqual(const media::AudioBus& first, const media::AudioBus& second) { DCHECK_EQ(first.channels(), second.channels()); DCHECK_EQ(first.frames(), second.frames()); for (int ch = 0; ch < first.channels(); ++ch) { EXPECT_EQ(0, memcmp(first.channel(ch), second.channel(ch), sizeof(float) * first.frames())); } } void VerifyProcessingData(const TestCaptureData& expected_data, const media::AudioBus& audio_bus, base::TimeTicks capture_time, double volume, bool key_pressed) { VerifyAudioDataEqual(*expected_data.audio_bus, audio_bus); EXPECT_EQ(expected_data.capture_time, capture_time); EXPECT_DOUBLE_EQ(expected_data.volume, volume); EXPECT_EQ(expected_data.key_pressed, key_pressed); } class ProcessingAudioFifoTest : public testing::Test { public: ProcessingAudioFifoTest() : params_(media::AudioParameters::Format::AUDIO_PCM_LINEAR, media::CHANNEL_LAYOUT_STEREO, kSampleRate, kFramesPerBuffer), audio_source_(params_.channels(), kTestToneFrequency, params_.sample_rate()) {} ProcessingAudioFifoTest(const ProcessingAudioFifoTest&) = delete; ProcessingAudioFifoTest& operator=(const ProcessingAudioFifoTest&) = delete; ~ProcessingAudioFifoTest() override { if (fifo_) TearDownFifo(); } protected: const media::AudioParameters params_; ProcessingAudioFifo* fifo() { return fifo_.get(); } void SetupFifo(ProcessingAudioFifo::ProcessAudioCallback callback) { fifo_ = std::make_unique( params_, kTestFifoSize, std::move(callback), base::DoNothing()); fifo_->Start(); } void SetupFifoWithFakeEvent( ProcessingAudioFifo::ProcessAudioCallback callback) { fifo_ = std::make_unique( params_, kTestFifoSize, std::move(callback), base::DoNothing()); using_fake_event_ = true; fifo_->StartForTesting(&fake_new_capture_event_); } void SetFifoStoppingFlag() { fifo_->fifo_stopping_.Set(); } void TearDownFifo() { DCHECK(fifo_); if (using_fake_event_) { // Manually set the FIFO stopping flag, and allow the processing thread to // exit, otherwise we will wait forever in StopProcessingLoop(). SetFifoStoppingFlag(); SignalFakeNewCaptureEvent(); } fifo_.reset(); } std::unique_ptr CreateAudioData( base::TimeTicks timestamp = base::TimeTicks::Now()) { auto data = media::AudioBus::Create(params_); audio_source_.OnMoreData(base::TimeDelta(), timestamp, 0, data.get()); return data; } void GenerateTestCaptureData(std::vector* dest, int count) { double volume_step = 1.0 / count; base::TimeTicks capture_time = base::TimeTicks::Now(); base::TimeTicks timestamp = base::TimeTicks(); for (int i = 0; i < count; ++i) { dest->emplace_back(CreateAudioData(timestamp), capture_time, i * volume_step, i % 2); timestamp += params_.GetBufferDuration(); capture_time += base::Milliseconds(5); } } void SignalFakeNewCaptureEvent() { DCHECK(using_fake_event_); fake_new_capture_event_.Signal(); } private: std::unique_ptr fifo_; bool using_fake_event_ = false; base::WaitableEvent fake_new_capture_event_{ base::WaitableEvent::ResetPolicy::AUTOMATIC}; // The audio source used to generate data. media::SineWaveAudioSource audio_source_; }; TEST_F(ProcessingAudioFifoTest, ConstructDestroy) { auto fifo = std::make_unique( params_, kTestFifoSize, base::DoNothing(), base::DoNothing()); fifo.reset(); } TEST_F(ProcessingAudioFifoTest, ConstructStartDestroy) { auto fifo = std::make_unique( params_, kTestFifoSize, base::DoNothing(), base::DoNothing()); fifo->Start(); fifo.reset(); } TEST_F(ProcessingAudioFifoTest, PushData_OneBuffer) { TestCaptureData test_data(CreateAudioData(), base::TimeTicks(), kTestVolume, true); base::WaitableEvent data_processed; auto verify_data = [&](const media::AudioBus& audio_bus, base::TimeTicks capture_time, double volume, bool key_pressed) { // The processing callback should receive the same data that was pushed into // the fifo. VerifyProcessingData(test_data, audio_bus, capture_time, volume, key_pressed); data_processed.Signal(); }; SetupFifo(base::BindLambdaForTesting(verify_data)); fifo()->PushData(test_data.audio_bus.get(), test_data.capture_time, test_data.volume, test_data.key_pressed); data_processed.Wait(); } TEST_F(ProcessingAudioFifoTest, PushData_MultipleBuffers_SingleBatch) { // Stay below the FIFO's size to avoid dropping buffers. const int kNumberOfBuffers = kTestFifoSize; std::vector capture_buffers; GenerateTestCaptureData(&capture_buffers, kNumberOfBuffers); base::WaitableEvent all_data_processed; int buffer_number = 0; auto verify_sequential_data = [&](const media::AudioBus& process_data, base::TimeTicks capture_time, double volume, bool key_pressed) { // Callbacks should receive buffers from |capture_buffers| in the order in // which they are pushed. VerifyProcessingData(capture_buffers[buffer_number], process_data, capture_time, volume, key_pressed); if (++buffer_number == kNumberOfBuffers) all_data_processed.Signal(); }; SetupFifo(base::BindLambdaForTesting(verify_sequential_data)); // Push all data at once. for (int i = 0; i < kNumberOfBuffers; ++i) { TestCaptureData& data = capture_buffers[i]; fifo()->PushData(data.audio_bus.get(), data.capture_time, data.volume, data.key_pressed); } all_data_processed.Wait(); EXPECT_EQ(buffer_number, kNumberOfBuffers); } TEST_F(ProcessingAudioFifoTest, PushData_MultipleBuffers_WaitBetweenBuffers) { // Make sure push enough buffers to overwrite old ones. const int kNumberOfBuffers = kTestFifoSize * 3; std::vector capture_buffers; GenerateTestCaptureData(&capture_buffers, kNumberOfBuffers); base::WaitableEvent single_buffer_processed( base::WaitableEvent::ResetPolicy::AUTOMATIC); int buffer_number = 0; auto verify_sequential_data = [&](const media::AudioBus& process_data, base::TimeTicks capture_time, double volume, bool key_pressed) { // Callbacks should receive buffers from |capture_buffers| in the order in // which they are pushed. VerifyProcessingData(capture_buffers[buffer_number++], process_data, capture_time, volume, key_pressed); single_buffer_processed.Signal(); }; SetupFifo(base::BindLambdaForTesting(verify_sequential_data)); // Wait for the previous buffer to be processed before pushing a new one. // This should guarantee we never drop buffers. for (int i = 0; i < kNumberOfBuffers; ++i) { TestCaptureData& data = capture_buffers[i]; fifo()->PushData(data.audio_bus.get(), data.capture_time, data.volume, data.key_pressed); single_buffer_processed.Wait(); } } TEST_F(ProcessingAudioFifoTest, ProcessesAllAvailableData) { // Stay below the FIFO's size to avoid dropping buffers. const int kNumberOfBuffers = kTestFifoSize; std::vector capture_buffers; GenerateTestCaptureData(&capture_buffers, kNumberOfBuffers); base::WaitableEvent any_buffer_processed; base::WaitableEvent all_buffers_processed; int buffer_number = 0; auto verify_sequential_data = [&](const media::AudioBus& process_data, base::TimeTicks capture_time, double volume, bool key_pressed) { VerifyProcessingData(capture_buffers[buffer_number++], process_data, capture_time, volume, key_pressed); any_buffer_processed.Signal(); if (buffer_number == kNumberOfBuffers) all_buffers_processed.Signal(); }; // The processing thread won't be woken up when new data is pushed. SetupFifoWithFakeEvent(base::BindLambdaForTesting(verify_sequential_data)); // Push all data. for (int i = 0; i < kNumberOfBuffers; ++i) { TestCaptureData& data = capture_buffers[i]; fifo()->PushData(data.audio_bus.get(), data.capture_time, data.volume, data.key_pressed); } // No data should have been processed by now. EXPECT_FALSE(any_buffer_processed.TimedWait(base::Milliseconds(10))); EXPECT_EQ(buffer_number, 0); // The processing thread should process all pending data at once. SignalFakeNewCaptureEvent(); all_buffers_processed.Wait(); } TEST_F(ProcessingAudioFifoTest, NoDataToProcess) { base::WaitableEvent processing_callback_called; SetupFifoWithFakeEvent(base::BindLambdaForTesting( [&](const media::AudioBus& process_data, base::TimeTicks capture_time, double volume, bool key_pressed) { ADD_FAILURE() << "Processing callback unexpectedly called"; processing_callback_called.Signal(); })); // Wake up the processing thread without any queued data. This might not // happen in practice, but the processing thread should be resilient to race // conditions nonetheless. SignalFakeNewCaptureEvent(); EXPECT_FALSE(processing_callback_called.TimedWait(base::Milliseconds(10))); } TEST_F(ProcessingAudioFifoTest, DontProcessPendingDataDuringStop) { base::WaitableEvent processing_callback_called; SetupFifoWithFakeEvent(base::BindLambdaForTesting( [&](const media::AudioBus& process_data, base::TimeTicks capture_time, double volume, bool key_pressed) { ADD_FAILURE() << "Processing callback unexpectedly called"; processing_callback_called.Signal(); })); // Push data into the FIFO, without calling SignalFakeNewCaptureEvent(). fifo()->PushData(CreateAudioData().get(), base::TimeTicks(), kTestVolume, true); // The pushed data should not be processed when we stop and destroy the FIFO. TearDownFifo(); EXPECT_FALSE(processing_callback_called.TimedWait(base::Milliseconds(10))); } TEST_F(ProcessingAudioFifoTest, FifoFull_DroppedFrames) { // Make sure push enough buffers to overwrite old ones. const int kNumberOfBatches = 3; const int kBatchSize = kTestFifoSize; const int kNumberOfBuffers = kBatchSize * kNumberOfBatches; std::vector capture_buffers; GenerateTestCaptureData(&capture_buffers, kNumberOfBuffers); // Get the buffer idx in |capture_buffers| from the batch and buffer number. auto get_idx = [](int buffer_number, int batch_number) { return buffer_number + batch_number * kBatchSize; }; base::WaitableEvent buffer_batch_processed( base::WaitableEvent::ResetPolicy::AUTOMATIC); // This test pushes 3 batches of buffers of size kBatchSize into the FIFO. // The first batch will be queued, the second will be dropped; the third // batch will be queued, after the first batch is done processing. int buffer_number = 0; int batch_number = 0; auto verify_dropped_data = [&](const media::AudioBus& process_data, base::TimeTicks capture_time, double volume, bool key_pressed) { // Verify we don't get any buffers from the 2nd batch. VerifyProcessingData(capture_buffers[get_idx(buffer_number, batch_number)], process_data, capture_time, volume, key_pressed); if (++buffer_number == kBatchSize) { buffer_number = 0; batch_number = 2; // Skip from 1st to 3rd batch of buffers. buffer_batch_processed.Signal(); } }; SetupFifoWithFakeEvent(base::BindLambdaForTesting(verify_dropped_data)); // Send 3 batches. for (int batch = 0; batch < kNumberOfBatches; ++batch) { // Queue a batch of buffers. for (int buffer = 0; buffer < kBatchSize; ++buffer) { TestCaptureData& data = capture_buffers[get_idx(buffer, batch)]; fifo()->PushData(data.audio_bus.get(), data.capture_time, data.volume, data.key_pressed); } // Skip processing the first batch, ensuring the second batch will be // dropped. if (batch == 0) continue; // Process queued buffers. SignalFakeNewCaptureEvent(); buffer_batch_processed.Wait(); } } TEST_F(ProcessingAudioFifoTest, StopDuringBatchProcess) { const int kNumberOfBuffers = kTestFifoSize; std::vector capture_buffers; GenerateTestCaptureData(&capture_buffers, kNumberOfBuffers); base::WaitableEvent any_buffer_processed( base::WaitableEvent::ResetPolicy::AUTOMATIC); base::WaitableEvent stop_flag_set; int number_of_calls = 0; auto verify_stopping = [&](const media::AudioBus& process_data, base::TimeTicks capture_time, double volume, bool key_pressed) { // We should only get one processing callback call, since calling // SetFifoStoppingFlag() should prevent further data from being processed. EXPECT_LE(++number_of_calls, 1); any_buffer_processed.Signal(); stop_flag_set.Wait(); }; SetupFifoWithFakeEvent(base::BindLambdaForTesting(verify_stopping)); // Queue data but don't process it. for (int i = 0; i < kNumberOfBuffers; ++i) { TestCaptureData& data = capture_buffers[i]; fifo()->PushData(data.audio_bus.get(), data.capture_time, data.volume, data.key_pressed); } // Process a single buffer. SignalFakeNewCaptureEvent(); any_buffer_processed.Wait(); // Simulate StopProcessingLoop() being called (without stopping the processing // thread). SetFifoStoppingFlag(); stop_flag_set.Signal(); // The remaining buffers in the FIFO should not be processed. EXPECT_FALSE(any_buffer_processed.TimedWait(base::Milliseconds(10))); EXPECT_EQ(1, number_of_calls); // Explicitly destroy the FIFO, to avoid TSAN failures. TearDownFifo(); } } // namespace audio