#include <limits>
#include <memory>
#include <vector>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_push_fifo.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
namespace {
class AudioPushFifoTest : public testing::TestWithParam<int> {
public:
AudioPushFifoTest() = default;
AudioPushFifoTest(const AudioPushFifoTest&) = delete;
AudioPushFifoTest& operator=(const AudioPushFifoTest&) = delete;
~AudioPushFifoTest() override = default;
int output_chunk_size() const { return GetParam(); }
void SetUp() final {
fifo_ = std::make_unique<AudioPushFifo>(base::BindRepeating(
&AudioPushFifoTest::ReceiveAndCheckNextChunk, base::Unretained(this)));
fifo_->Reset(output_chunk_size());
ASSERT_EQ(output_chunk_size(), fifo_->frames_per_buffer());
}
protected:
struct OutputChunkResult {
int num_frames;
float first_sample_value;
float last_sample_value;
int frame_delay;
};
size_t GetExpectedOutputChunks(int frames_pushed) const {
return static_cast<size_t>(frames_pushed / output_chunk_size());
}
int GetNumPushTestIterations(int input_chunk_size) const {
return 3 * std::max(1, output_chunk_size() / input_chunk_size);
}
void RunSimpleRechunkTest(int input_chunk_size) {
const int num_iterations = GetNumPushTestIterations(input_chunk_size);
int sample_value = 0;
const std::unique_ptr<AudioBus> audio_bus =
AudioBus::Create(1, input_chunk_size);
for (int i = 0; i < num_iterations; ++i) {
EXPECT_EQ(GetExpectedOutputChunks(i * input_chunk_size), results_.size());
std::ranges::generate(audio_bus->channel_span(0),
[&sample_value]() { return sample_value++; });
fifo_->Push(*audio_bus);
}
EXPECT_EQ(GetExpectedOutputChunks(num_iterations * input_chunk_size),
results_.size());
ASSERT_FALSE(results_.empty());
EXPECT_EQ(0.0f, results_.front().first_sample_value);
const float last_value_in_last_chunk = static_cast<float>(
GetExpectedOutputChunks(num_iterations * input_chunk_size) *
output_chunk_size() -
1);
EXPECT_EQ(last_value_in_last_chunk, results_.back().last_sample_value);
if (input_chunk_size < output_chunk_size()) {
const int num_queued_before_first_output =
((output_chunk_size() - 1) / input_chunk_size) * input_chunk_size;
EXPECT_EQ(-num_queued_before_first_output, results_.front().frame_delay);
} else if (input_chunk_size >= output_chunk_size()) {
EXPECT_EQ(0, results_[0].frame_delay);
if (input_chunk_size >= 2 * output_chunk_size()) {
EXPECT_EQ(output_chunk_size(), results_[1].frame_delay);
} else {
const int num_remaining_after_first_output =
input_chunk_size - output_chunk_size();
EXPECT_EQ(-num_remaining_after_first_output, results_[1].frame_delay);
}
}
const size_t num_results_before_flush = results_.size();
fifo_->Flush();
const size_t num_results_after_flush = results_.size();
if (num_results_after_flush > num_results_before_flush) {
EXPECT_NE(0, results_.back().frame_delay);
EXPECT_LT(-output_chunk_size(), results_.back().frame_delay);
}
}
int GetRandomInRange(int begin, int end) {
const int len = end - begin;
const int rand_offset = (len == 0) ? 0 : (NextRandomInt() % (end - begin));
return begin + rand_offset;
}
std::unique_ptr<AudioPushFifo> fifo_;
std::vector<OutputChunkResult> results_;
private:
void ReceiveAndCheckNextChunk(const AudioBus& audio_bus, int frame_delay) {
OutputChunkResult result;
auto channel_data = audio_bus.channel_span(0);
result.num_frames = audio_bus.frames();
result.first_sample_value = channel_data[0];
result.last_sample_value = channel_data[audio_bus.frames() - 1];
result.frame_delay = frame_delay;
for (int i = 1; i < audio_bus.frames(); ++i) {
const float expected_value = result.first_sample_value + i;
const float actual_value = channel_data[i];
if (actual_value != expected_value) {
if (actual_value == 0.0f) {
ASSERT_GT(0, frame_delay);
ASSERT_TRUE(std::ranges::all_of(
channel_data.subspan(static_cast<size_t>(i + 1)),
[](float value) { return value == 0.0f; }));
break;
} else {
ASSERT_EQ(expected_value, actual_value) << "Sample at offset " << i
<< " is incorrect.";
}
}
}
results_.push_back(result);
}
int NextRandomInt() {
rand_seed_ = (1103515245 * rand_seed_ + 12345) % (1 << 31);
return static_cast<int>(rand_seed_);
}
uint32_t rand_seed_ = 0x7e110;
};
TEST_P(AudioPushFifoTest, PushOneFrameAtATime) {
RunSimpleRechunkTest(1);
}
TEST_P(AudioPushFifoTest, Push128FramesAtATime) {
RunSimpleRechunkTest(128);
}
TEST_P(AudioPushFifoTest, Push512FramesAtATime) {
RunSimpleRechunkTest(512);
}
TEST_P(AudioPushFifoTest, Push441FramesAtATime) {
RunSimpleRechunkTest(441);
}
TEST_P(AudioPushFifoTest, Push480FramesAtATime) {
RunSimpleRechunkTest(480);
}
TEST_P(AudioPushFifoTest, PushArbitraryNumbersOfFramesAtATime) {
const int kMinNumIterations = 30;
int sample_value = 0;
int frames_pushed_so_far = 0;
for (int i = 0; i < kMinNumIterations || results_.size() < 3; ++i) {
EXPECT_EQ(GetExpectedOutputChunks(frames_pushed_so_far), results_.size());
const int input_chunk_size = GetRandomInRange(1, 1920);
const std::unique_ptr<AudioBus> audio_bus =
AudioBus::Create(1, input_chunk_size);
std::ranges::generate(audio_bus->channel_span(0),
[&sample_value]() { return sample_value++; });
fifo_->Push(*audio_bus);
frames_pushed_so_far += input_chunk_size;
}
EXPECT_EQ(GetExpectedOutputChunks(frames_pushed_so_far), results_.size());
ASSERT_FALSE(results_.empty());
EXPECT_EQ(0.0f, results_.front().first_sample_value);
const float last_value_in_last_chunk = static_cast<float>(
GetExpectedOutputChunks(frames_pushed_so_far) * output_chunk_size() - 1);
EXPECT_EQ(last_value_in_last_chunk, results_.back().last_sample_value);
const size_t num_results_before_flush = results_.size();
fifo_->Flush();
const size_t num_results_after_flush = results_.size();
if (num_results_after_flush > num_results_before_flush) {
EXPECT_NE(0, results_.back().frame_delay);
EXPECT_LT(-output_chunk_size(), results_.back().frame_delay);
}
}
INSTANTIATE_TEST_SUITE_P(All,
AudioPushFifoTest,
::testing::Values(
16,
22,
44,
48,
160,
220,
441,
480,
960,
1323,
2646,
2880
));
}
}