#include "services/audio/processing_audio_fifo.h"
#include <cstring>
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/time/time.h"
#include "base/types/zip.h"
#include "media/audio/simple_sources.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_glitch_info.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace audio {
const int kSampleRate = 48000;
const int kFramesPerBuffer = kSampleRate / 200;
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,
const media::AudioGlitchInfo& audio_glitch_info)
: audio_bus(std::move(audio_bus)),
capture_time(capture_time),
volume(volume),
audio_glitch_info(audio_glitch_info) {}
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;
media::AudioGlitchInfo audio_glitch_info;
};
void VerifyAudioDataEqual(const media::AudioBus& first,
const media::AudioBus& second) {
DCHECK_EQ(first.channels(), second.channels());
DCHECK_EQ(first.frames(), second.frames());
for (auto [first_ch, second_ch] :
base::zip(first.AllChannels(), second.AllChannels())) {
EXPECT_EQ(first_ch, second_ch);
}
}
void VerifyProcessingData(const TestCaptureData& expected_data,
const media::AudioBus& audio_bus,
base::TimeTicks capture_time,
double volume,
const media::AudioGlitchInfo& audio_glitch_info) {
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.audio_glitch_info, audio_glitch_info);
}
class ProcessingAudioFifoTest : public testing::Test {
public:
ProcessingAudioFifoTest()
: params_(media::AudioParameters::Format::AUDIO_PCM_LINEAR,
media::ChannelLayoutConfig::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_) {
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, {}, 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, media::AudioGlitchInfo());
timestamp += params_.GetBufferDuration();
capture_time += base::Milliseconds(5);
}
}
void GenerateTestCaptureData(std::vector<TestCaptureData>* dest,
int count,
double volume) {
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, volume,
media::AudioGlitchInfo());
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};
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,
{.duration = base::Milliseconds(123), .count = 5});
base::WaitableEvent data_processed;
auto verify_data = [&](const media::AudioBus& audio_bus,
base::TimeTicks capture_time, double volume,
const media::AudioGlitchInfo& audio_glitch_info) {
VerifyProcessingData(test_data, audio_bus, capture_time, volume,
audio_glitch_info);
data_processed.Signal();
};
SetupFifo(base::BindLambdaForTesting(verify_data));
fifo()->PushData(test_data.audio_bus.get(), test_data.capture_time,
test_data.volume, test_data.audio_glitch_info);
data_processed.Wait();
}
TEST_F(ProcessingAudioFifoTest, PushData_MultipleBuffers_SingleBatch) {
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, const media::AudioGlitchInfo& audio_glitch_info) {
VerifyProcessingData(capture_buffers[buffer_number], process_data,
capture_time, volume, audio_glitch_info);
if (++buffer_number == kNumberOfBuffers) {
all_data_processed.Signal();
}
};
SetupFifo(base::BindLambdaForTesting(verify_sequential_data));
for (int i = 0; i < kNumberOfBuffers; ++i) {
TestCaptureData& data = capture_buffers[i];
fifo()->PushData(data.audio_bus.get(), data.capture_time, data.volume, {});
}
all_data_processed.Wait();
EXPECT_EQ(buffer_number, kNumberOfBuffers);
}
TEST_F(ProcessingAudioFifoTest, PushData_MultipleBuffers_WaitBetweenBuffers) {
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, const media::AudioGlitchInfo& audio_glitch_info) {
VerifyProcessingData(capture_buffers[buffer_number++], process_data,
capture_time, volume, audio_glitch_info);
single_buffer_processed.Signal();
};
SetupFifo(base::BindLambdaForTesting(verify_sequential_data));
for (int i = 0; i < kNumberOfBuffers; ++i) {
TestCaptureData& data = capture_buffers[i];
fifo()->PushData(data.audio_bus.get(), data.capture_time, data.volume, {});
single_buffer_processed.Wait();
}
}
TEST_F(ProcessingAudioFifoTest, ProcessesAllAvailableData) {
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, const media::AudioGlitchInfo& audio_glitch_info) {
VerifyProcessingData(capture_buffers[buffer_number++], process_data,
capture_time, volume, audio_glitch_info);
any_buffer_processed.Signal();
if (buffer_number == kNumberOfBuffers) {
all_buffers_processed.Signal();
}
};
SetupFifoWithFakeEvent(base::BindLambdaForTesting(verify_sequential_data));
for (int i = 0; i < kNumberOfBuffers; ++i) {
TestCaptureData& data = capture_buffers[i];
fifo()->PushData(data.audio_bus.get(), data.capture_time, data.volume, {});
}
EXPECT_FALSE(any_buffer_processed.TimedWait(base::Milliseconds(10)));
EXPECT_EQ(buffer_number, 0);
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, const media::AudioGlitchInfo& audio_glitch_info) {
ADD_FAILURE() << "Processing callback unexpectedly called";
processing_callback_called.Signal();
}));
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, const media::AudioGlitchInfo& audio_glitch_info) {
ADD_FAILURE() << "Processing callback unexpectedly called";
processing_callback_called.Signal();
}));
fifo()->PushData(CreateAudioData().get(), base::TimeTicks(), kTestVolume, {});
TearDownFifo();
EXPECT_FALSE(processing_callback_called.TimedWait(base::Milliseconds(10)));
}
TEST_F(ProcessingAudioFifoTest, FifoFull_DroppedFrames_SavesGlitchInfo) {
const int kNumberOfBuffers = kTestFifoSize;
constexpr double kMaxVolume = 1.0;
constexpr double kMinVolume = 0.0;
std::vector<TestCaptureData> initial_buffers;
std::vector<TestCaptureData> dropped_buffers;
GenerateTestCaptureData(&initial_buffers, kNumberOfBuffers, kMaxVolume);
GenerateTestCaptureData(&dropped_buffers, kNumberOfBuffers, kMaxVolume);
initial_buffers.back().volume = kMinVolume;
dropped_buffers.back().volume = kMinVolume;
base::WaitableEvent buffer_batch_processed(
base::WaitableEvent::ResetPolicy::AUTOMATIC);
int total_buffers_processed = 0;
const int kExpectedBufferWithGlitchInfo = initial_buffers.size() + 1;
media::AudioGlitchInfo expected_glitch_info;
expected_glitch_info.count = dropped_buffers.size();
expected_glitch_info.duration =
dropped_buffers.size() * params_.GetBufferDuration();
bool glitch_info_was_verified = false;
auto verify_dropped_data =
[&](const media::AudioBus& process_data, base::TimeTicks capture_time,
double volume, const media::AudioGlitchInfo& audio_glitch_info) {
const bool should_have_glitch_info =
++total_buffers_processed == kExpectedBufferWithGlitchInfo;
if (should_have_glitch_info) {
EXPECT_EQ(audio_glitch_info.count, expected_glitch_info.count);
EXPECT_EQ(audio_glitch_info.duration, expected_glitch_info.duration);
glitch_info_was_verified = true;
} else {
EXPECT_EQ(audio_glitch_info.count, 0u);
EXPECT_EQ(audio_glitch_info.duration, base::TimeDelta());
}
if (volume == kMinVolume) {
buffer_batch_processed.Signal();
}
};
SetupFifoWithFakeEvent(base::BindLambdaForTesting(verify_dropped_data));
for (const auto& buffer : initial_buffers) {
fifo()->PushData(buffer.audio_bus.get(), buffer.capture_time, buffer.volume,
{});
}
for (const auto& buffer : initial_buffers) {
fifo()->PushData(buffer.audio_bus.get(), buffer.capture_time, buffer.volume,
{});
}
SignalFakeNewCaptureEvent();
buffer_batch_processed.Wait();
const auto& exta_buffer = initial_buffers.front();
fifo()->PushData(exta_buffer.audio_bus.get(), exta_buffer.capture_time,
kMaxVolume, {});
SignalFakeNewCaptureEvent();
fifo()->PushData(exta_buffer.audio_bus.get(), exta_buffer.capture_time,
kMinVolume, {});
SignalFakeNewCaptureEvent();
buffer_batch_processed.Wait();
EXPECT_TRUE(glitch_info_was_verified);
}
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,
const media::AudioGlitchInfo& audio_glitch_info) {
EXPECT_LE(++number_of_calls, 1);
any_buffer_processed.Signal();
stop_flag_set.Wait();
};
SetupFifoWithFakeEvent(base::BindLambdaForTesting(verify_stopping));
for (int i = 0; i < kNumberOfBuffers; ++i) {
TestCaptureData& data = capture_buffers[i];
fifo()->PushData(data.audio_bus.get(), data.capture_time, data.volume, {});
}
SignalFakeNewCaptureEvent();
any_buffer_processed.Wait();
SetFifoStoppingFlag();
stop_flag_set.Signal();
EXPECT_FALSE(any_buffer_processed.TimedWait(base::Milliseconds(10)));
EXPECT_EQ(1, number_of_calls);
TearDownFifo();
}
}