#include "media/audio/win/audio_low_latency_output_win.h"
#include <windows.h>
#include <mmsystem.h>
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include "base/compiler_specific.h"
#include "base/environment.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/task_environment.h"
#include "base/test/test_timeouts.h"
#include "base/time/time.h"
#include "base/win/scoped_com_initializer.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/mock_audio_source_callback.h"
#include "media/audio/test_audio_thread.h"
#include "media/audio/win/core_audio_util_win.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_sample_types.h"
#include "media/base/decoder_buffer.h"
#include "media/base/seekable_buffer.h"
#include "media/base/test_data_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::DoAll;
using ::testing::NotNull;
using ::testing::Return;
namespace media {
static const int kBitsPerSample = 16;
static const size_t kMaxDeltaSamples = 1000;
static const char kDeltaTimeMsFileName[] = "delta_times_ms.txt";
MATCHER_P(HasValidDelay, value, "") {
return arg >= value;
}
class ReadFromFileAudioSource : public AudioOutputStream::AudioSourceCallback {
public:
explicit ReadFromFileAudioSource(const std::string& name)
: pos_(0),
previous_call_time_(base::TimeTicks::Now()),
text_file_(nullptr),
elements_to_write_(0) {
file_ = ReadTestDataFile(name);
delta_times_.reset(new int[kMaxDeltaSamples]);
}
~ReadFromFileAudioSource() override {
base::FilePath file_name;
EXPECT_TRUE(base::PathService::Get(base::DIR_EXE, &file_name));
file_name = file_name.AppendASCII(kDeltaTimeMsFileName);
EXPECT_TRUE(!text_file_);
text_file_ = base::OpenFile(file_name, "wt");
DLOG_IF(ERROR, !text_file_) << "Failed to open log file.";
size_t elements_written = 0;
while (elements_written < elements_to_write_) {
fprintf(text_file_.get(), "%d\n", UNSAFE_TODO(delta_times_[elements_written]));
++elements_written;
}
base::CloseFile(text_file_);
}
int OnMoreData(base::TimeDelta ,
base::TimeTicks ,
const AudioGlitchInfo& ,
AudioBus* dest) override {
const base::TimeTicks now_time = base::TimeTicks::Now();
const int diff = (now_time - previous_call_time_).InMilliseconds();
previous_call_time_ = now_time;
if (elements_to_write_ < kMaxDeltaSamples) {
UNSAFE_TODO(delta_times_[elements_to_write_]) = diff;
++elements_to_write_;
}
size_t max_size = dest->frames() * dest->channels() * kBitsPerSample / 8;
if (pos_ + max_size > file_size())
max_size = file_size() - pos_;
int frames = max_size / (dest->channels() * kBitsPerSample / 8);
if (max_size) {
static_assert(kBitsPerSample == 16, "FromInterleaved expects 2 bytes.");
dest->FromInterleaved<SignedInt16SampleTypeTraits>(
reinterpret_cast<const int16_t*>(
base::span(*file_).subspan(pos_).data()),
frames);
pos_ += max_size;
}
return frames;
}
void OnError(ErrorType type) override {}
size_t file_size() { return base::checked_cast<int>(file_->size()); }
private:
scoped_refptr<DecoderBuffer> file_;
std::unique_ptr<int[]> delta_times_;
size_t pos_;
base::TimeTicks previous_call_time_;
raw_ptr<FILE> text_file_;
size_t elements_to_write_;
};
static bool ExclusiveModeIsEnabled() {
return (WASAPIAudioOutputStream::GetShareMode() ==
AUDCLNT_SHAREMODE_EXCLUSIVE);
}
static bool HasCoreAudioAndOutputDevices(AudioManager* audio_man) {
return CoreAudioUtil::IsSupported() &&
AudioDeviceInfoAccessorForTests(audio_man).HasAudioOutputDevices();
}
class AudioOutputStreamWrapper {
public:
explicit AudioOutputStreamWrapper(AudioManager* audio_manager)
: audio_man_(audio_manager),
format_(AudioParameters::AUDIO_PCM_LOW_LATENCY) {
AudioParameters preferred_params;
EXPECT_TRUE(SUCCEEDED(CoreAudioUtil::GetPreferredAudioParameters(
AudioDeviceDescription::kDefaultDeviceId, true, &preferred_params)));
channels_ = preferred_params.channels();
channel_layout_ = preferred_params.channel_layout();
sample_rate_ = preferred_params.sample_rate();
samples_per_packet_ = preferred_params.frames_per_buffer();
}
~AudioOutputStreamWrapper() {}
AudioOutputStream* Create() { return CreateOutputStream(); }
AudioOutputStream* Create(int samples_per_packet) {
samples_per_packet_ = samples_per_packet;
return CreateOutputStream();
}
AudioOutputStream* Create(int sample_rate, int samples_per_packet) {
sample_rate_ = sample_rate;
samples_per_packet_ = samples_per_packet;
return CreateOutputStream();
}
AudioOutputStream* Create(int sample_rate,
int samples_per_packet,
bool audio_offload) {
sample_rate_ = sample_rate;
samples_per_packet_ = samples_per_packet;
AudioParameters::HardwareCapabilities hardware_cap(0, true);
hardware_cap.require_audio_offload = true;
hardware_capabilities_ = hardware_cap;
return CreateOutputStream();
}
AudioParameters::Format format() const { return format_; }
int channels() const { return channels_; }
int sample_rate() const { return sample_rate_; }
int samples_per_packet() const { return samples_per_packet_; }
private:
AudioOutputStream* CreateOutputStream() {
AudioParameters params(format_, {channel_layout_, channels_}, sample_rate_,
samples_per_packet_);
if (hardware_capabilities_) {
params.set_hardware_capabilities(hardware_capabilities_.value());
}
DVLOG(1) << params.AsHumanReadableString();
AudioOutputStream* aos = audio_man_->MakeAudioOutputStream(
params, std::string(), AudioManager::LogCallback());
EXPECT_TRUE(aos);
return aos;
}
raw_ptr<AudioManager> audio_man_;
AudioParameters::Format format_;
int channels_;
ChannelLayout channel_layout_;
int sample_rate_;
int samples_per_packet_;
std::optional<AudioParameters::HardwareCapabilities> hardware_capabilities_;
};
static AudioOutputStream* CreateDefaultAudioOutputStream(
AudioManager* audio_manager) {
AudioOutputStreamWrapper aosw(audio_manager);
AudioOutputStream* aos = aosw.Create();
return aos;
}
class WASAPIAudioOutputStreamTest : public ::testing::Test {
public:
WASAPIAudioOutputStreamTest() {
audio_manager_ =
AudioManager::CreateForTesting(std::make_unique<TestAudioThread>());
base::RunLoop().RunUntilIdle();
}
~WASAPIAudioOutputStreamTest() override { audio_manager_->Shutdown(); }
protected:
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::SingleThreadTaskEnvironment::MainThreadType::UI};
std::unique_ptr<AudioManager> audio_manager_;
};
TEST_F(WASAPIAudioOutputStreamTest, CreateAndClose) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndOutputDevices(audio_manager_.get()));
AudioOutputStream* aos = CreateDefaultAudioOutputStream(audio_manager_.get());
aos->Close();
}
TEST_F(WASAPIAudioOutputStreamTest, OpenAndClose) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndOutputDevices(audio_manager_.get()));
AudioOutputStream* aos = CreateDefaultAudioOutputStream(audio_manager_.get());
EXPECT_TRUE(aos->Open());
aos->Close();
}
TEST_F(WASAPIAudioOutputStreamTest, OpenStartAndClose) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndOutputDevices(audio_manager_.get()));
AudioOutputStream* aos = CreateDefaultAudioOutputStream(audio_manager_.get());
EXPECT_TRUE(aos->Open());
MockAudioSourceCallback source;
EXPECT_CALL(source, OnError(_)).Times(0);
aos->Start(&source);
aos->Close();
}
TEST_F(WASAPIAudioOutputStreamTest, OpenStartStopAndClose) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndOutputDevices(audio_manager_.get()));
AudioOutputStream* aos = CreateDefaultAudioOutputStream(audio_manager_.get());
EXPECT_TRUE(aos->Open());
MockAudioSourceCallback source;
EXPECT_CALL(source, OnError(_)).Times(0);
aos->Start(&source);
aos->Stop();
aos->Close();
}
TEST_F(WASAPIAudioOutputStreamTest, Volume) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndOutputDevices(audio_manager_.get()));
AudioOutputStream* aos = CreateDefaultAudioOutputStream(audio_manager_.get());
double volume = 0.0;
aos->GetVolume(&volume);
EXPECT_EQ(1.0, volume);
aos->SetVolume(0.0);
aos->GetVolume(&volume);
EXPECT_EQ(0.0, volume);
aos->SetVolume(0.5);
aos->GetVolume(&volume);
EXPECT_EQ(0.5, volume);
aos->SetVolume(1.0);
aos->GetVolume(&volume);
EXPECT_EQ(1.0, volume);
aos->SetVolume(1.5);
aos->GetVolume(&volume);
EXPECT_EQ(1.0, volume);
aos->SetVolume(-0.5);
aos->GetVolume(&volume);
EXPECT_EQ(1.0, volume);
aos->Close();
}
TEST_F(WASAPIAudioOutputStreamTest, MiscCallingSequences) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndOutputDevices(audio_manager_.get()));
AudioOutputStream* aos = CreateDefaultAudioOutputStream(audio_manager_.get());
WASAPIAudioOutputStream* waos = static_cast<WASAPIAudioOutputStream*>(aos);
EXPECT_TRUE(aos->Open());
EXPECT_TRUE(aos->Open());
MockAudioSourceCallback source;
aos->Start(&source);
EXPECT_TRUE(waos->started());
aos->Start(&source);
EXPECT_TRUE(waos->started());
aos->Stop();
EXPECT_FALSE(waos->started());
aos->Stop();
EXPECT_FALSE(waos->started());
aos->Start(&source);
EXPECT_TRUE(waos->started());
aos->Stop();
EXPECT_FALSE(waos->started());
aos->Start(&source);
EXPECT_TRUE(waos->started());
aos->Stop();
EXPECT_FALSE(waos->started());
aos->Close();
}
TEST_F(WASAPIAudioOutputStreamTest, ValidPacketSize) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndOutputDevices(audio_manager_.get()));
MockAudioSourceCallback source;
AudioOutputStreamWrapper aosw(audio_manager_.get());
AudioOutputStream* aos = aosw.Create();
EXPECT_TRUE(aos->Open());
base::RunLoop loop;
base::TimeDelta packet_duration = base::Seconds(
static_cast<double>(aosw.samples_per_packet()) / aosw.sample_rate());
EXPECT_CALL(source, OnMoreData(HasValidDelay(packet_duration), _,
AudioGlitchInfo(), NotNull()))
.WillOnce(DoAll(base::test::RunClosure(loop.QuitWhenIdleClosure()),
Return(aosw.samples_per_packet())))
.WillRepeatedly(Return(0));
aos->Start(&source);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, loop.QuitWhenIdleClosure(), TestTimeouts::action_timeout());
loop.Run();
aos->Stop();
aos->Close();
}
TEST_F(WASAPIAudioOutputStreamTest, ExclusiveModeWithAudioOffload) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndOutputDevices(audio_manager_.get()) &&
ExclusiveModeIsEnabled());
AudioOutputStreamWrapper aosw(audio_manager_.get());
AudioOutputStream* aos = aosw.Create(48000, 160, true);
EXPECT_FALSE(aos->Open());
aos->Close();
}
TEST_F(WASAPIAudioOutputStreamTest, DISABLED_ExclusiveModeBufferSizesAt48kHz) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndOutputDevices(audio_manager_.get()) &&
ExclusiveModeIsEnabled());
AudioOutputStreamWrapper aosw(audio_manager_.get());
AudioOutputStream* aos = aosw.Create(48000, 480);
EXPECT_TRUE(aos->Open());
aos->Close();
aos = aosw.Create(48000, 240);
EXPECT_FALSE(aos->Open());
aos->Close();
aos = aosw.Create(48000, 256);
EXPECT_TRUE(aos->Open());
aos->Close();
aos = aosw.Create(48000, 128);
EXPECT_FALSE(aos->Open());
aos->Close();
aos = aosw.Create(48000, 144);
EXPECT_FALSE(aos->Open());
aos->Close();
aos = aosw.Create(48000, 160);
EXPECT_TRUE(aos->Open());
aos->Close();
}
TEST_F(WASAPIAudioOutputStreamTest, DISABLED_ExclusiveModeBufferSizesAt44kHz) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndOutputDevices(audio_manager_.get()) &&
ExclusiveModeIsEnabled());
AudioOutputStreamWrapper aosw(audio_manager_.get());
AudioOutputStream* aos = aosw.Create(44100, 441);
EXPECT_FALSE(aos->Open());
aos->Close();
aos = aosw.Create(44100, 448);
EXPECT_TRUE(aos->Open());
aos->Close();
aos = aosw.Create(44100, 256);
EXPECT_TRUE(aos->Open());
aos->Close();
aos = aosw.Create(44100, 220);
EXPECT_FALSE(aos->Open());
aos->Close();
aos = aosw.Create(44100, 224);
EXPECT_TRUE(aos->Open());
aos->Close();
aos = aosw.Create(44100, 132);
EXPECT_FALSE(aos->Open());
aos->Close();
aos = aosw.Create(44100, 133);
EXPECT_FALSE(aos->Open());
aos->Close();
aos = aosw.Create(44100, 160);
EXPECT_TRUE(aos->Open());
aos->Close();
}
TEST_F(WASAPIAudioOutputStreamTest,
DISABLED_ExclusiveModeMinBufferSizeAt48kHz) {
ABORT_AUDIO_TEST_IF_NOT(HasCoreAudioAndOutputDevices(audio_manager_.get()) &&
ExclusiveModeIsEnabled());
MockAudioSourceCallback source;
AudioOutputStreamWrapper aosw(audio_manager_.get());
AudioOutputStream* aos = aosw.Create(48000, 160);
EXPECT_TRUE(aos->Open());
base::RunLoop loop;
base::TimeDelta packet_duration = base::Seconds(
static_cast<double>(aosw.samples_per_packet()) / aosw.sample_rate());
EXPECT_CALL(source, OnMoreData(HasValidDelay(packet_duration), _,
AudioGlitchInfo(), NotNull()))
.WillOnce(DoAll(base::test::RunClosure(loop.QuitWhenIdleClosure()),
Return(aosw.samples_per_packet())))
.WillRepeatedly(Return(aosw.samples_per_packet()));
aos->Start(&source);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, loop.QuitWhenIdleClosure(), TestTimeouts::action_timeout());
loop.Run();
aos->Stop();
aos->Close();
}
TEST_F(WASAPIAudioOutputStreamTest,
DISABLED_ExclusiveModeMinBufferSizeAt44kHz) {
ABORT_AUDIO_TEST_IF_NOT(ExclusiveModeIsEnabled());
MockAudioSourceCallback source;
AudioOutputStreamWrapper aosw(audio_manager_.get());
AudioOutputStream* aos = aosw.Create(44100, 160);
EXPECT_TRUE(aos->Open());
base::RunLoop loop;
base::TimeDelta packet_duration = base::Seconds(
static_cast<double>(aosw.samples_per_packet()) / aosw.sample_rate());
EXPECT_CALL(source, OnMoreData(HasValidDelay(packet_duration), _,
AudioGlitchInfo(), NotNull()))
.WillOnce(DoAll(base::test::RunClosure(loop.QuitWhenIdleClosure()),
Return(aosw.samples_per_packet())))
.WillRepeatedly(Return(aosw.samples_per_packet()));
aos->Start(&source);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, loop.QuitWhenIdleClosure(), TestTimeouts::action_timeout());
loop.Run();
aos->Stop();
aos->Close();
}
}