#include "media/audio/audio_manager.h"
#include <algorithm>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/environment.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/synchronization/waitable_event.h"
#include "base/system/sys_info.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/test_message_loop.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_device_name.h"
#include "media/audio/audio_output_proxy.h"
#include "media/audio/audio_unittest_util.h"
#include "media/audio/fake_audio_log_factory.h"
#include "media/audio/fake_audio_manager.h"
#include "media/audio/mock_audio_debug_recording_manager.h"
#include "media/audio/test_audio_thread.h"
#include "media/base/audio_bus.h"
#include "media/base/limits.h"
#include "media/base/media_switches.h"
#include "media/media_buildflags.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "arkweb/build/features/features.h"
#if defined(USE_ALSA)
#include "media/audio/alsa/audio_manager_alsa.h"
#endif
#if BUILDFLAG(IS_MAC)
#include "media/audio/mac/audio_manager_mac.h"
#endif
#if BUILDFLAG(IS_WIN)
#include "base/win/scoped_com_initializer.h"
#include "media/audio/win/audio_manager_win.h"
#endif
#if defined(USE_PULSEAUDIO)
#include "media/audio/pulse/audio_manager_pulse.h"
#include "media/audio/pulse/pulse_util.h"
#endif
#if BUILDFLAG(USE_CRAS)
#include "media/audio/cras/audio_manager_cras.h"
#endif
namespace media {
namespace {
template <typename T>
struct TestAudioManagerFactory {
static std::unique_ptr<AudioManager> Create(
AudioLogFactory* audio_log_factory) {
return std::make_unique<T>(std::make_unique<TestAudioThread>(),
audio_log_factory);
}
};
#if defined(USE_PULSEAUDIO)
template <>
struct TestAudioManagerFactory<AudioManagerPulse> {
static std::unique_ptr<AudioManager> Create(
AudioLogFactory* audio_log_factory) {
pa_threaded_mainloop* pa_mainloop = nullptr;
pa_context* pa_context = nullptr;
if (!pulse::InitPulse(&pa_mainloop, &pa_context))
return nullptr;
return std::make_unique<AudioManagerPulse>(
std::make_unique<TestAudioThread>(), audio_log_factory, pa_mainloop,
pa_context);
}
};
#endif
template <>
struct TestAudioManagerFactory<std::nullptr_t> {
static std::unique_ptr<AudioManager> Create(
AudioLogFactory* audio_log_factory) {
return AudioManager::CreateForTesting(std::make_unique<TestAudioThread>());
}
};
const char kRealDefaultInputDeviceID[] = "input2";
const char kRealDefaultOutputDeviceID[] = "output3";
const char kRealCommunicationsInputDeviceID[] = "input1";
const char kRealCommunicationsOutputDeviceID[] = "output1";
void CheckDescriptionLabels(const AudioDeviceDescriptions& descriptions,
const std::string& real_default_id,
const std::string& real_communications_id) {
std::string real_default_label;
std::string real_communications_label;
for (const auto& description : descriptions) {
if (description.unique_id == real_default_id)
real_default_label = description.device_name;
else if (description.unique_id == real_communications_id)
real_communications_label = description.device_name;
}
for (const auto& description : descriptions) {
if (AudioDeviceDescription::IsDefaultDevice(description.unique_id)) {
EXPECT_TRUE(base::EndsWith(description.device_name, real_default_label,
base::CompareCase::SENSITIVE));
} else if (description.unique_id ==
AudioDeviceDescription::kCommunicationsDeviceId) {
EXPECT_TRUE(base::EndsWith(description.device_name,
real_communications_label,
base::CompareCase::SENSITIVE));
} else if (description.unique_id == real_default_id) {
EXPECT_TRUE(description.is_system_default);
} else if (description.unique_id == real_communications_id) {
EXPECT_TRUE(description.is_communications_device);
}
}
}
}
class AudioManagerTest : public ::testing::Test {
public:
void HandleDefaultDeviceIDsTest() {
AudioParameters params(AudioParameters::AUDIO_PCM_LOW_LATENCY,
ChannelLayoutConfig::Stereo(), 48000, 2048);
AudioOutputStream* stream =
audio_manager_->MakeAudioOutputStreamProxy(params, "");
ASSERT_TRUE(stream);
AudioOutputDispatcher* dispatcher1 =
reinterpret_cast<AudioOutputProxy*>(stream)
->get_dispatcher_for_testing();
stream->Close();
stream = audio_manager_->MakeAudioOutputStreamProxy(
params, AudioDeviceDescription::kDefaultDeviceId);
ASSERT_EQ(dispatcher1, reinterpret_cast<AudioOutputProxy*>(stream)
->get_dispatcher_for_testing());
stream->Close();
stream = audio_manager_->MakeAudioOutputStreamProxy(params, "123456");
ASSERT_NE(dispatcher1, reinterpret_cast<AudioOutputProxy*>(stream)
->get_dispatcher_for_testing());
stream->Close();
}
AudioParameters GetOutputStreamParameters(const std::string& device_id) {
return device_info_accessor_->GetOutputStreamParameters(device_id);
}
void GetAssociatedOutputDeviceID(const std::string& input_device_id,
std::string* output_device_id) {
*output_device_id =
device_info_accessor_->GetAssociatedOutputDeviceID(input_device_id);
}
protected:
AudioManagerTest() : message_loop_(base::MessagePumpType::IO) {
CreateAudioManagerForTesting();
}
~AudioManagerTest() override { audio_manager_->Shutdown(); }
static void CheckDeviceDescriptions(
const AudioDeviceDescriptions& device_descriptions) {
DVLOG(2) << "Got " << device_descriptions.size() << " audio devices.";
if (!device_descriptions.empty()) {
auto it = device_descriptions.begin();
EXPECT_EQ(std::string(AudioDeviceDescription::kDefaultDeviceId),
it->unique_id);
++it;
while (it != device_descriptions.end()) {
EXPECT_FALSE(it->device_name.empty());
EXPECT_FALSE(it->unique_id.empty());
EXPECT_FALSE(it->group_id.empty());
DVLOG(2) << "Device ID(" << it->unique_id
<< "), label: " << it->device_name
<< "group: " << it->group_id;
EXPECT_NE(AudioDeviceDescription::GetDefaultDeviceName(),
it->device_name);
EXPECT_NE(std::string(AudioDeviceDescription::kDefaultDeviceId),
it->unique_id);
++it;
}
} else {
LOG(WARNING) << "No input devices detected";
}
}
bool InputDevicesAvailable() {
#if BUILDFLAG(IS_MAC) && defined(ARCH_CPU_ARM64)
return false;
#else
return device_info_accessor_->HasAudioInputDevices();
#endif
}
bool OutputDevicesAvailable() {
return device_info_accessor_->HasAudioOutputDevices();
}
template <typename T = std::nullptr_t>
void CreateAudioManagerForTesting() {
if (audio_manager_) {
audio_manager_->Shutdown();
device_info_accessor_.reset();
audio_manager_.reset();
}
audio_manager_ =
TestAudioManagerFactory<T>::Create(&fake_audio_log_factory_);
base::RunLoop().RunUntilIdle();
device_info_accessor_ =
std::make_unique<AudioDeviceInfoAccessorForTests>(audio_manager_.get());
}
base::TestMessageLoop message_loop_;
FakeAudioLogFactory fake_audio_log_factory_;
std::unique_ptr<AudioManager> audio_manager_;
std::unique_ptr<AudioDeviceInfoAccessorForTests> device_info_accessor_;
};
TEST_F(AudioManagerTest, HandleDefaultDeviceIDs) {
CreateAudioManagerForTesting<FakeAudioManager>();
HandleDefaultDeviceIDsTest();
base::RunLoop().RunUntilIdle();
}
TEST_F(AudioManagerTest, EnumerateInputDevices) {
ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
AudioDeviceDescriptions device_descriptions;
device_info_accessor_->GetAudioInputDeviceDescriptions(&device_descriptions);
CheckDeviceDescriptions(device_descriptions);
}
TEST_F(AudioManagerTest, EnumerateOutputDevices) {
ABORT_AUDIO_TEST_IF_NOT(OutputDevicesAvailable());
AudioDeviceDescriptions device_descriptions;
device_info_accessor_->GetAudioOutputDeviceDescriptions(&device_descriptions);
CheckDeviceDescriptions(device_descriptions);
}
#if BUILDFLAG(IS_WIN)
TEST_F(AudioManagerTest, EnumerateInputDevicesWinMMDevice) {
ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
AudioDeviceDescriptions device_descriptions;
device_info_accessor_->GetAudioInputDeviceDescriptions(&device_descriptions);
CheckDeviceDescriptions(device_descriptions);
}
TEST_F(AudioManagerTest, EnumerateOutputDevicesWinMMDevice) {
ABORT_AUDIO_TEST_IF_NOT(OutputDevicesAvailable());
AudioDeviceDescriptions device_descriptions;
device_info_accessor_->GetAudioOutputDeviceDescriptions(&device_descriptions);
CheckDeviceDescriptions(device_descriptions);
}
#endif
#if defined(USE_PULSEAUDIO)
TEST_F(AudioManagerTest, EnumerateInputDevicesPulseaudio) {
ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
CreateAudioManagerForTesting<AudioManagerPulse>();
if (audio_manager_.get()) {
AudioDeviceDescriptions device_descriptions;
device_info_accessor_->GetAudioInputDeviceDescriptions(
&device_descriptions);
CheckDeviceDescriptions(device_descriptions);
} else {
LOG(WARNING) << "No pulseaudio on this system.";
}
}
TEST_F(AudioManagerTest, EnumerateOutputDevicesPulseaudio) {
ABORT_AUDIO_TEST_IF_NOT(OutputDevicesAvailable());
CreateAudioManagerForTesting<AudioManagerPulse>();
if (audio_manager_.get()) {
AudioDeviceDescriptions device_descriptions;
device_info_accessor_->GetAudioOutputDeviceDescriptions(
&device_descriptions);
CheckDeviceDescriptions(device_descriptions);
} else {
LOG(WARNING) << "No pulseaudio on this system.";
}
}
#endif
#if defined(USE_ALSA)
TEST_F(AudioManagerTest, EnumerateInputDevicesAlsa) {
ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
DVLOG(2) << "Testing AudioManagerAlsa.";
CreateAudioManagerForTesting<AudioManagerAlsa>();
AudioDeviceDescriptions device_descriptions;
device_info_accessor_->GetAudioInputDeviceDescriptions(&device_descriptions);
CheckDeviceDescriptions(device_descriptions);
}
TEST_F(AudioManagerTest, EnumerateInputDevicesAlsaWithInputDeviceSwitch) {
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kAlsaInputDevice, "switch-input-device");
DVLOG(2) << "Testing AudioManagerAlsa.";
CreateAudioManagerForTesting<AudioManagerAlsa>();
AudioDeviceDescriptions device_descriptions;
device_info_accessor_->GetAudioInputDeviceDescriptions(&device_descriptions);
CheckDeviceDescriptions(device_descriptions);
EXPECT_TRUE(base::Contains(device_descriptions, "switch-input-device",
[](const auto& device_description) {
return device_description.unique_id;
}));
}
TEST_F(AudioManagerTest, EnumerateOutputDevicesAlsa) {
ABORT_AUDIO_TEST_IF_NOT(OutputDevicesAvailable());
DVLOG(2) << "Testing AudioManagerAlsa.";
CreateAudioManagerForTesting<AudioManagerAlsa>();
AudioDeviceDescriptions device_descriptions;
device_info_accessor_->GetAudioOutputDeviceDescriptions(&device_descriptions);
CheckDeviceDescriptions(device_descriptions);
}
TEST_F(AudioManagerTest, EnumerateOutputDevicesAlsaWithOutputDeviceSwitch) {
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kAlsaOutputDevice, "switch-output-device");
DVLOG(2) << "Testing AudioManagerAlsa.";
CreateAudioManagerForTesting<AudioManagerAlsa>();
AudioDeviceDescriptions device_descriptions;
device_info_accessor_->GetAudioOutputDeviceDescriptions(&device_descriptions);
CheckDeviceDescriptions(device_descriptions);
EXPECT_TRUE(base::Contains(device_descriptions, "switch-output-device",
[](const auto& device_description) {
return device_description.unique_id;
}));
}
#endif
TEST_F(AudioManagerTest, GetOutputStreamParameters) {
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable());
std::string default_device_id = AudioDeviceDescription::kDefaultDeviceId;
AudioParameters params = GetOutputStreamParameters(default_device_id);
EXPECT_TRUE(params.IsValid());
#endif
}
TEST_F(AudioManagerTest, GetAssociatedOutputDeviceID) {
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
ABORT_AUDIO_TEST_IF_NOT(InputDevicesAvailable() && OutputDevicesAvailable());
AudioDeviceDescriptions device_descriptions;
device_info_accessor_->GetAudioInputDeviceDescriptions(&device_descriptions);
bool found_an_associated_device = false;
for (const auto& description : device_descriptions) {
EXPECT_FALSE(description.unique_id.empty());
EXPECT_FALSE(description.device_name.empty());
EXPECT_FALSE(description.group_id.empty());
std::string output_device_id;
GetAssociatedOutputDeviceID(description.unique_id, &output_device_id);
if (!output_device_id.empty()) {
DVLOG(2) << description.unique_id << " matches with " << output_device_id;
found_an_associated_device = true;
}
}
EXPECT_TRUE(found_an_associated_device);
#endif
}
class TestAudioManager : public FakeAudioManager {
public:
TestAudioManager(std::unique_ptr<AudioThread> audio_thread,
AudioLogFactory* audio_log_factory)
: FakeAudioManager(std::move(audio_thread), audio_log_factory) {}
std::string GetDefaultInputDeviceID() override {
return kRealDefaultInputDeviceID;
}
std::string GetDefaultOutputDeviceID() override {
return kRealDefaultOutputDeviceID;
}
std::string GetCommunicationsInputDeviceID() override {
return kRealCommunicationsInputDeviceID;
}
std::string GetCommunicationsOutputDeviceID() override {
return kRealCommunicationsOutputDeviceID;
}
std::string GetAssociatedOutputDeviceID(
const std::string& input_id) override {
if (input_id == "input1")
return "output1";
DCHECK_EQ(std::string(kRealDefaultInputDeviceID), "input2");
if (input_id == AudioDeviceDescription::kDefaultDeviceId ||
input_id == kRealDefaultInputDeviceID)
return "output2";
return std::string();
}
#if BUILDFLAG(ARKWEB_UNITTESTS)
protected:
AudioParameters GetPreferredOutputStreamParameters(
const std::string& output_device_id,
const AudioParameters& input_params) override {}
#if BUILDFLAG(ARKWEB_WEBRTC)
AudioParameters GetPreferredInputStreamParameters(
const std::string& input_device_id) override {}
#endif
#endif
private:
void GetAudioInputDeviceNames(AudioDeviceNames* device_names) override {
DCHECK(device_names->empty());
device_names->emplace_back(AudioDeviceName::CreateDefault());
device_names->emplace_back("Input 1", "input1");
device_names->emplace_back("Input 2", "input2");
device_names->emplace_back("Input 3", "input3");
}
void GetAudioOutputDeviceNames(AudioDeviceNames* device_names) override {
DCHECK(device_names->empty());
device_names->emplace_back(AudioDeviceName::CreateDefault());
device_names->emplace_back("Output 1", "output1");
device_names->emplace_back("Output 2", "output2");
device_names->emplace_back("Output 3", "output3");
}
};
TEST_F(AudioManagerTest, GroupId) {
CreateAudioManagerForTesting<TestAudioManager>();
AudioDeviceDescriptions inputs;
device_info_accessor_->GetAudioInputDeviceDescriptions(&inputs);
AudioDeviceDescriptions outputs;
device_info_accessor_->GetAudioOutputDeviceDescriptions(&outputs);
EXPECT_EQ(inputs[0].group_id, outputs[2].group_id);
EXPECT_NE(inputs[0].group_id, outputs[0].group_id);
EXPECT_EQ(outputs[0].group_id, outputs[3].group_id);
EXPECT_EQ(inputs[1].group_id, outputs[1].group_id);
EXPECT_EQ(inputs[2].group_id, outputs[2].group_id);
EXPECT_NE(inputs[3].group_id, outputs[3].group_id);
EXPECT_NE(inputs[1].group_id, inputs[2].group_id);
EXPECT_NE(inputs[1].group_id, inputs[3].group_id);
EXPECT_NE(inputs[2].group_id, inputs[3].group_id);
EXPECT_NE(outputs[1].group_id, outputs[2].group_id);
EXPECT_NE(outputs[1].group_id, outputs[3].group_id);
EXPECT_NE(outputs[2].group_id, outputs[3].group_id);
}
TEST_F(AudioManagerTest, DefaultCommunicationsLabelsContainRealLabels) {
CreateAudioManagerForTesting<TestAudioManager>();
std::string default_input_id =
device_info_accessor_->GetDefaultInputDeviceID();
EXPECT_EQ(default_input_id, kRealDefaultInputDeviceID);
std::string default_output_id =
device_info_accessor_->GetDefaultOutputDeviceID();
EXPECT_EQ(default_output_id, kRealDefaultOutputDeviceID);
std::string communications_input_id =
device_info_accessor_->GetCommunicationsInputDeviceID();
EXPECT_EQ(communications_input_id, kRealCommunicationsInputDeviceID);
std::string communications_output_id =
device_info_accessor_->GetCommunicationsOutputDeviceID();
EXPECT_EQ(communications_output_id, kRealCommunicationsOutputDeviceID);
AudioDeviceDescriptions inputs;
device_info_accessor_->GetAudioInputDeviceDescriptions(&inputs);
CheckDescriptionLabels(inputs, default_input_id, communications_input_id);
AudioDeviceDescriptions outputs;
device_info_accessor_->GetAudioOutputDeviceDescriptions(&outputs);
CheckDescriptionLabels(outputs, default_output_id, communications_output_id);
}
TEST_F(AudioManagerTest, CheckMakeOutputStreamWithPreferredParameters) {
ABORT_AUDIO_TEST_IF_NOT(OutputDevicesAvailable());
std::string default_device_id = AudioDeviceDescription::kDefaultDeviceId;
AudioParameters params = GetOutputStreamParameters(default_device_id);
ASSERT_TRUE(params.IsValid());
AudioOutputStream* stream =
audio_manager_->MakeAudioOutputStreamProxy(params, "");
ASSERT_TRUE(stream);
stream->Close();
}
#if BUILDFLAG(IS_MAC) || BUILDFLAG(USE_CRAS)
class TestAudioSourceCallback : public AudioOutputStream::AudioSourceCallback {
public:
TestAudioSourceCallback(int expected_frames_per_buffer,
base::WaitableEvent* event)
: expected_frames_per_buffer_(expected_frames_per_buffer),
event_(event) {}
TestAudioSourceCallback(const TestAudioSourceCallback&) = delete;
TestAudioSourceCallback& operator=(const TestAudioSourceCallback&) = delete;
~TestAudioSourceCallback() override {}
int OnMoreData(base::TimeDelta,
base::TimeTicks,
const AudioGlitchInfo&,
AudioBus* dest) override {
EXPECT_EQ(dest->frames(), expected_frames_per_buffer_);
event_->Signal();
return 0;
}
void OnError(ErrorType type) override { FAIL(); }
private:
const int expected_frames_per_buffer_;
raw_ptr<base::WaitableEvent> event_;
};
TEST_F(AudioManagerTest, CheckMinMaxAudioBufferSizeCallbacks) {
ABORT_AUDIO_TEST_IF_NOT(OutputDevicesAvailable());
#if BUILDFLAG(IS_MAC)
CreateAudioManagerForTesting<AudioManagerMac>();
#elif BUILDFLAG(USE_CRAS) && BUILDFLAG(IS_CHROMEOS)
CreateAudioManagerForTesting<AudioManagerCras>();
#endif
DCHECK(audio_manager_);
std::string default_device_id = AudioDeviceDescription::kDefaultDeviceId;
AudioParameters default_params = GetOutputStreamParameters(default_device_id);
ASSERT_LT(default_params.frames_per_buffer(),
media::limits::kMaxAudioBufferSize);
#if BUILDFLAG(IS_MAC)
ASSERT_GT(
default_params.frames_per_buffer(),
AudioManagerMac::GetMinAudioBufferSizeMacOS(
media::limits::kMinAudioBufferSize, default_params.sample_rate()));
#else
static_assert(BUILDFLAG(USE_CRAS));
ASSERT_GE(default_params.frames_per_buffer(),
media::limits::kMinAudioBufferSize);
#endif
AudioOutputStream* stream;
base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
AudioParameters min_params = default_params;
min_params.set_frames_per_buffer(media::limits::kMinAudioBufferSize);
stream = audio_manager_->MakeAudioOutputStreamProxy(min_params, "");
ASSERT_TRUE(stream);
EXPECT_TRUE(stream->Open());
event.Reset();
TestAudioSourceCallback min_source(min_params.frames_per_buffer(), &event);
stream->Start(&min_source);
event.Wait();
stream->Stop();
stream->Close();
AudioParameters max_params = default_params;
max_params.set_frames_per_buffer(media::limits::kMaxAudioBufferSize);
stream = audio_manager_->MakeAudioOutputStreamProxy(max_params, "");
ASSERT_TRUE(stream);
EXPECT_TRUE(stream->Open());
event.Reset();
TestAudioSourceCallback max_source(max_params.frames_per_buffer(), &event);
stream->Start(&max_source);
event.Wait();
stream->Stop();
stream->Close();
}
#endif
}