123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453 |
- // 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 <cstring>
- #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<media::AudioBus> 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<media::AudioBus> 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<ProcessingAudioFifo>(
- params_, kTestFifoSize, std::move(callback), base::DoNothing());
- fifo_->Start();
- }
- void SetupFifoWithFakeEvent(
- ProcessingAudioFifo::ProcessAudioCallback callback) {
- fifo_ = std::make_unique<ProcessingAudioFifo>(
- 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<media::AudioBus> 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<TestCaptureData>* 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<ProcessingAudioFifo> 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<ProcessingAudioFifo>(
- params_, kTestFifoSize, base::DoNothing(), base::DoNothing());
- fifo.reset();
- }
- TEST_F(ProcessingAudioFifoTest, ConstructStartDestroy) {
- auto fifo = std::make_unique<ProcessingAudioFifo>(
- 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<TestCaptureData> 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<TestCaptureData> 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<TestCaptureData> 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<TestCaptureData> 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<TestCaptureData> 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
|