#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <array>
#include <memory>
#include "base/containers/span.h"
#include "base/environment.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/synchronization/lock.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "base/test/test_timeouts.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/audio_device_info_accessor_for_tests.h"
#include "media/audio/audio_io.h"
#include "media/audio/audio_manager.h"
#include "media/audio/audio_unittest_util.h"
#include "media/audio/test_audio_thread.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_sample_types.h"
#include "media/base/seekable_buffer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
namespace {
static constexpr size_t kMaxDelayMeasurements = 1000;
static const char kDelayValuesFileName[] = "audio_delay_values_ms.txt";
struct AudioDelayState {
AudioDelayState()
: delta_time_ms(0),
buffer_delay_ms(0),
input_delay_ms(0),
output_delay_ms(0) {
}
int delta_time_ms;
int buffer_delay_ms;
int input_delay_ms;
int output_delay_ms;
};
void OnLogMessage(const std::string& message) {}
class AudioLowLatencyInputOutputTest : public testing::Test {
public:
AudioLowLatencyInputOutputTest(const AudioLowLatencyInputOutputTest&) =
delete;
AudioLowLatencyInputOutputTest& operator=(
const AudioLowLatencyInputOutputTest&) = delete;
protected:
AudioLowLatencyInputOutputTest() {
audio_manager_ =
AudioManager::CreateForTesting(std::make_unique<TestAudioThread>());
}
~AudioLowLatencyInputOutputTest() override { audio_manager_->Shutdown(); }
AudioManager* audio_manager() { return audio_manager_.get(); }
scoped_refptr<base::SingleThreadTaskRunner> task_runner() {
return task_environment_.GetMainThreadTaskRunner();
}
private:
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::MainThreadType::UI};
std::unique_ptr<AudioManager> audio_manager_;
};
class FullDuplexAudioSinkSource
: public AudioInputStream::AudioInputCallback,
public AudioOutputStream::AudioSourceCallback {
public:
FullDuplexAudioSinkSource(int sample_rate,
int samples_per_packet,
int channels)
: sample_rate_(sample_rate),
samples_per_packet_(samples_per_packet),
channels_(channels),
input_elements_to_write_(0),
output_elements_to_write_(0),
previous_write_time_(base::TimeTicks::Now()) {
frame_size_ = (16 / 8) * channels_;
buffer_ = std::make_unique<media::SeekableBuffer>(
0, samples_per_packet_ * frame_size_);
frames_to_ms_ = static_cast<double>(1000.0 / sample_rate_);
}
~FullDuplexAudioSinkSource() override {
base::FilePath file_name;
EXPECT_TRUE(base::PathService::Get(base::DIR_EXE, &file_name));
file_name = file_name.AppendASCII(kDelayValuesFileName);
FILE* text_file = base::OpenFile(file_name, "wt");
DLOG_IF(ERROR, !text_file) << "Failed to open log file.";
VLOG(0) << ">> Output file " << file_name.value() << " has been created.";
size_t elements_written = 0;
while (elements_written <
std::min(input_elements_to_write_, output_elements_to_write_)) {
const AudioDelayState state = delay_states_[elements_written];
fprintf(text_file, "%d %d %d %d\n",
state.delta_time_ms,
state.buffer_delay_ms,
state.input_delay_ms,
state.output_delay_ms);
++elements_written;
}
base::CloseFile(text_file);
}
void OnError() override {}
void OnData(const AudioBus* src,
base::TimeTicks capture_time,
double volume,
const AudioGlitchInfo& glitch_info) override {
base::AutoLock lock(lock_);
const base::TimeTicks now_time = base::TimeTicks::Now();
const int diff = (now_time - previous_write_time_).InMilliseconds();
previous_write_time_ = now_time;
if (input_elements_to_write_ < kMaxDelayMeasurements) {
delay_states_[input_elements_to_write_].delta_time_ms = diff;
delay_states_[input_elements_to_write_].buffer_delay_ms =
BytesToMilliseconds(buffer_->forward_bytes());
delay_states_[input_elements_to_write_].input_delay_ms =
(base::TimeTicks::Now() - capture_time).InMilliseconds();
++input_elements_to_write_;
}
}
void OnError(ErrorType type) override {}
int OnMoreData(base::TimeDelta delay,
base::TimeTicks ,
const AudioGlitchInfo& ,
AudioBus* dest) override {
base::AutoLock lock(lock_);
if (output_elements_to_write_ < kMaxDelayMeasurements) {
delay_states_[output_elements_to_write_].output_delay_ms =
delay.InMilliseconds();
++output_elements_to_write_;
}
const base::span<const uint8_t> source = buffer_->GetCurrentChunk();
if (!source.empty()) {
EXPECT_EQ(channels_, dest->channels());
const auto size =
std::min<size_t>(dest->frames() * frame_size_, source.size());
EXPECT_EQ(size % sizeof(float), 0U);
DCHECK_EQ(frame_size_ / channels_, 2);
dest->FromInterleaved<SignedInt16SampleTypeTraits>(
reinterpret_cast<const int16_t*>(source.data()), size / channels_);
buffer_->Seek(size);
return size / frame_size_;
}
return 0;
}
protected:
int BytesToMilliseconds(uint32_t delay_bytes) const {
return static_cast<int>((delay_bytes / frame_size_) * frames_to_ms_ + 0.5);
}
private:
base::Lock lock_;
std::unique_ptr<media::SeekableBuffer> buffer_;
int sample_rate_;
int samples_per_packet_;
int channels_;
int frame_size_;
double frames_to_ms_;
std::array<AudioDelayState, kMaxDelayMeasurements> delay_states_;
size_t input_elements_to_write_;
size_t output_elements_to_write_;
base::TimeTicks previous_write_time_;
};
class AudioInputStreamTraits {
public:
typedef AudioInputStream StreamType;
static AudioParameters GetDefaultAudioStreamParameters(
AudioManager* audio_manager) {
return AudioDeviceInfoAccessorForTests(audio_manager)
.GetInputStreamParameters(AudioDeviceDescription::kDefaultDeviceId);
}
static StreamType* CreateStream(AudioManager* audio_manager,
const AudioParameters& params) {
return audio_manager->MakeAudioInputStream(
params, AudioDeviceDescription::kDefaultDeviceId,
base::BindRepeating(&OnLogMessage));
}
};
class AudioOutputStreamTraits {
public:
typedef AudioOutputStream StreamType;
static AudioParameters GetDefaultAudioStreamParameters(
AudioManager* audio_manager) {
std::string default_device_id =
AudioDeviceInfoAccessorForTests(audio_manager)
.GetDefaultOutputDeviceID();
return AudioDeviceInfoAccessorForTests(audio_manager)
.GetOutputStreamParameters(default_device_id);
}
static StreamType* CreateStream(AudioManager* audio_manager,
const AudioParameters& params) {
return audio_manager->MakeAudioOutputStream(
params, std::string(), base::BindRepeating(&OnLogMessage));
}
};
template <typename StreamTraits>
class StreamWrapper {
public:
typedef typename StreamTraits::StreamType StreamType;
explicit StreamWrapper(AudioManager* audio_manager)
: audio_manager_(audio_manager),
format_(AudioParameters::AUDIO_PCM_LOW_LATENCY),
#if BUILDFLAG(IS_ANDROID)
channel_layout_(CHANNEL_LAYOUT_MONO)
#else
channel_layout_(CHANNEL_LAYOUT_STEREO)
#endif
{
const AudioParameters& params =
StreamTraits::GetDefaultAudioStreamParameters(audio_manager_);
sample_rate_ = params.sample_rate();
samples_per_packet_ = params.frames_per_buffer();
}
virtual ~StreamWrapper() = default;
StreamType* Create() {
return CreateStream();
}
int channels() const {
return ChannelLayoutToChannelCount(channel_layout_);
}
int sample_rate() const { return sample_rate_; }
int samples_per_packet() const { return samples_per_packet_; }
private:
StreamType* CreateStream() {
StreamType* stream = StreamTraits::CreateStream(
audio_manager_,
AudioParameters(format_,
ChannelLayoutConfig(channel_layout_, channels()),
sample_rate_, samples_per_packet_));
EXPECT_TRUE(stream);
return stream;
}
raw_ptr<AudioManager> audio_manager_;
AudioParameters::Format format_;
ChannelLayout channel_layout_;
int sample_rate_;
int samples_per_packet_;
};
typedef StreamWrapper<AudioInputStreamTraits> AudioInputStreamWrapper;
typedef StreamWrapper<AudioOutputStreamTraits> AudioOutputStreamWrapper;
TEST_F(AudioLowLatencyInputOutputTest, DISABLED_FullDuplexDelayMeasurement) {
AudioDeviceInfoAccessorForTests device_info_accessor(audio_manager());
ABORT_AUDIO_TEST_IF_NOT(device_info_accessor.HasAudioInputDevices() &&
device_info_accessor.HasAudioOutputDevices());
AudioInputStreamWrapper aisw(audio_manager());
AudioInputStream* ais = aisw.Create();
EXPECT_TRUE(ais);
AudioOutputStreamWrapper aosw(audio_manager());
AudioOutputStream* aos = aosw.Create();
EXPECT_TRUE(aos);
if (aisw.sample_rate() != aosw.sample_rate() ||
aisw.samples_per_packet() != aosw.samples_per_packet() ||
aisw.channels() != aosw.channels()) {
LOG(ERROR) << "This test requires symmetric input and output parameters. "
"Ensure that sample rate and number of channels are identical in "
"both directions";
aos->Close();
ais->Close();
return;
}
EXPECT_EQ(ais->Open(), AudioInputStream::OpenOutcome::kSuccess);
EXPECT_TRUE(aos->Open());
FullDuplexAudioSinkSource full_duplex(
aisw.sample_rate(), aisw.samples_per_packet(), aisw.channels());
VLOG(0) << ">> You should now be able to hear yourself in loopback...";
DVLOG(0) << " sample_rate : " << aisw.sample_rate();
DVLOG(0) << " samples_per_packet: " << aisw.samples_per_packet();
DVLOG(0) << " channels : " << aisw.channels();
ais->Start(&full_duplex);
aos->Start(&full_duplex);
base::RunLoop run_loop;
task_runner()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::action_timeout());
run_loop.Run();
aos->Stop();
ais->Stop();
aos->Close();
ais->Close();
}
}
}