#include <windows.h>
#include <mmsystem.h>
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include "base/base_paths.h"
#include "base/containers/span.h"
#include "base/logging.h"
#include "base/memory/aligned_memory.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/memory/raw_span.h"
#include "base/run_loop.h"
#include "base/sync_socket.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "base/win/scoped_com_initializer.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/simple_sources.h"
#include "media/audio/test_audio_thread.h"
#include "media/base/audio_bus.h"
#include "media/base/limits.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::DoAll;
using ::testing::Field;
using ::testing::InSequence;
using ::testing::NiceMock;
using ::testing::NotNull;
using ::testing::Return;
namespace media {
static int ClearData(base::TimeDelta ,
base::TimeTicks ,
const AudioGlitchInfo& ,
AudioBus* dest) {
dest->Zero();
return dest->frames();
}
class TestSourceBasic : public AudioOutputStream::AudioSourceCallback {
public:
TestSourceBasic() = default;
int OnMoreData(base::TimeDelta ,
base::TimeTicks ,
const AudioGlitchInfo& ,
AudioBus* dest) override {
++callback_count_;
dest->Zero();
return dest->frames();
}
void OnError(ErrorType type) override { ++had_error_; }
int callback_count() const {
return callback_count_;
}
int had_error() const {
return had_error_;
}
void set_error(bool error) {
had_error_ += error ? 1 : 0;
}
private:
int callback_count_ = 0;
int had_error_ = 0;
};
constexpr int kMaxNumBuffers = 3;
class TestSourceLaggy : public TestSourceBasic {
public:
explicit TestSourceLaggy(int lag_in_ms)
: lag_in_ms_(lag_in_ms) {
}
int OnMoreData(base::TimeDelta delay,
base::TimeTicks delay_timestamp,
const AudioGlitchInfo& glitch_info,
AudioBus* dest) override {
TestSourceBasic::OnMoreData(delay, delay_timestamp, glitch_info, dest);
if (callback_count() > kMaxNumBuffers) {
::Sleep(lag_in_ms_);
}
return dest->frames();
}
private:
int lag_in_ms_;
};
class WinAudioTest : public ::testing::Test {
public:
WinAudioTest() {
audio_manager_ =
AudioManager::CreateForTesting(std::make_unique<TestAudioThread>());
audio_manager_device_info_ =
std::make_unique<AudioDeviceInfoAccessorForTests>(audio_manager_.get());
base::RunLoop().RunUntilIdle();
}
~WinAudioTest() override { audio_manager_->Shutdown(); }
protected:
base::test::SingleThreadTaskEnvironment task_environment_;
std::unique_ptr<AudioManager> audio_manager_;
std::unique_ptr<AudioDeviceInfoAccessorForTests> audio_manager_device_info_;
};
TEST_F(WinAudioTest, PCMWaveStreamGetAndClose) {
ABORT_AUDIO_TEST_IF_NOT(audio_manager_device_info_->HasAudioOutputDevices());
AudioOutputStream* oas = audio_manager_->MakeAudioOutputStream(
AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
ChannelLayoutConfig::Stereo(), 8000, 256),
std::string(), AudioManager::LogCallback());
ASSERT_TRUE(NULL != oas);
oas->Close();
}
TEST_F(WinAudioTest, PCMWaveStreamOpenAndClose) {
ABORT_AUDIO_TEST_IF_NOT(audio_manager_device_info_->HasAudioOutputDevices());
AudioOutputStream* oas = audio_manager_->MakeAudioOutputStream(
AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
ChannelLayoutConfig::Stereo(), 8000, 256),
std::string(), AudioManager::LogCallback());
ASSERT_TRUE(NULL != oas);
EXPECT_TRUE(oas->Open());
oas->Close();
}
TEST_F(WinAudioTest, PCMWaveSlowSource) {
ABORT_AUDIO_TEST_IF_NOT(audio_manager_device_info_->HasAudioOutputDevices());
AudioOutputStream* oas = audio_manager_->MakeAudioOutputStream(
AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
ChannelLayoutConfig::Mono(), 16000, 256),
std::string(), AudioManager::LogCallback());
ASSERT_TRUE(NULL != oas);
TestSourceLaggy test_laggy(90);
EXPECT_TRUE(oas->Open());
oas->Start(&test_laggy);
::Sleep(500);
EXPECT_GT(test_laggy.callback_count(), 2);
EXPECT_FALSE(test_laggy.had_error());
oas->Stop();
::Sleep(500);
oas->Close();
}
TEST_F(WinAudioTest, PCMWaveStreamPlaySlowLoop) {
ABORT_AUDIO_TEST_IF_NOT(audio_manager_device_info_->HasAudioOutputDevices());
uint32_t samples_100_ms = AudioParameters::kAudioCDSampleRate / 10;
AudioOutputStream* oas = audio_manager_->MakeAudioOutputStream(
AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
ChannelLayoutConfig::Mono(),
AudioParameters::kAudioCDSampleRate, samples_100_ms),
std::string(), AudioManager::LogCallback());
ASSERT_TRUE(NULL != oas);
SineWaveAudioSource source(1, 200.0, AudioParameters::kAudioCDSampleRate);
EXPECT_TRUE(oas->Open());
oas->SetVolume(1.0);
for (int ix = 0; ix != 5; ++ix) {
oas->Start(&source);
::Sleep(10);
oas->Stop();
}
oas->Close();
}
TEST_F(WinAudioTest, PCMWaveStreamPlay200HzTone44Kss) {
if (!audio_manager_device_info_->HasAudioOutputDevices()) {
LOG(WARNING) << "No output device detected.";
return;
}
uint32_t samples_100_ms = AudioParameters::kAudioCDSampleRate / 10;
AudioOutputStream* oas = audio_manager_->MakeAudioOutputStream(
AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
ChannelLayoutConfig::Mono(),
AudioParameters::kAudioCDSampleRate, samples_100_ms),
std::string(), AudioManager::LogCallback());
ASSERT_TRUE(NULL != oas);
SineWaveAudioSource source(1, 200.0, AudioParameters::kAudioCDSampleRate);
EXPECT_TRUE(oas->Open());
oas->SetVolume(1.0);
oas->Start(&source);
::Sleep(500);
oas->Stop();
oas->Close();
}
TEST_F(WinAudioTest, PCMWaveStreamPlay200HzTone22Kss) {
ABORT_AUDIO_TEST_IF_NOT(audio_manager_device_info_->HasAudioOutputDevices());
uint32_t samples_100_ms = AudioParameters::kAudioCDSampleRate / 20;
AudioOutputStream* oas = audio_manager_->MakeAudioOutputStream(
AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
ChannelLayoutConfig::Mono(),
AudioParameters::kAudioCDSampleRate / 2, samples_100_ms),
std::string(), AudioManager::LogCallback());
ASSERT_TRUE(NULL != oas);
SineWaveAudioSource source(1, 200.0, AudioParameters::kAudioCDSampleRate/2);
EXPECT_TRUE(oas->Open());
oas->SetVolume(0.5);
oas->Start(&source);
::Sleep(500);
double volume = 0.0;
oas->GetVolume(&volume);
EXPECT_LT(volume, 0.51);
EXPECT_GT(volume, 0.49);
oas->Stop();
oas->Close();
}
TEST_F(WinAudioTest, PushSourceFile16KHz) {
ABORT_AUDIO_TEST_IF_NOT(audio_manager_device_info_->HasAudioOutputDevices());
static constexpr int kSampleRate = 16000;
SineWaveAudioSource source(1, 200.0, kSampleRate);
constexpr uint32_t kSamples100ms = (kSampleRate / 1000) * 100;
source.CapSamples(kSamples100ms);
AudioOutputStream* oas = audio_manager_->MakeAudioOutputStream(
AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
ChannelLayoutConfig::Mono(), kSampleRate, kSamples100ms),
std::string(), AudioManager::LogCallback());
ASSERT_TRUE(NULL != oas);
EXPECT_TRUE(oas->Open());
oas->SetVolume(1.0);
oas->Start(&source);
for (uint32_t ix = 0; ix != 100; ++ix) {
::Sleep(10);
source.Reset();
}
::Sleep(500);
oas->Stop();
oas->Close();
}
TEST_F(WinAudioTest, PCMWaveStreamPlayTwice200HzTone44Kss) {
ABORT_AUDIO_TEST_IF_NOT(audio_manager_device_info_->HasAudioOutputDevices());
uint32_t samples_100_ms = AudioParameters::kAudioCDSampleRate / 10;
AudioOutputStream* oas = audio_manager_->MakeAudioOutputStream(
AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
ChannelLayoutConfig::Mono(),
AudioParameters::kAudioCDSampleRate, samples_100_ms),
std::string(), AudioManager::LogCallback());
ASSERT_TRUE(NULL != oas);
SineWaveAudioSource source(1, 200.0, AudioParameters::kAudioCDSampleRate);
EXPECT_TRUE(oas->Open());
oas->SetVolume(1.0);
oas->Start(&source);
::Sleep(500);
oas->Stop();
::Sleep(250);
oas->Start(&source);
::Sleep(500);
oas->Stop();
oas->Close();
}
TEST_F(WinAudioTest, PCMWaveStreamPlay200HzToneLowLatency) {
ABORT_AUDIO_TEST_IF_NOT(audio_manager_device_info_->HasAudioOutputDevices());
std::string default_device_id =
audio_manager_device_info_->GetDefaultOutputDeviceID();
const AudioParameters params =
audio_manager_device_info_->GetOutputStreamParameters(default_device_id);
int sample_rate = params.sample_rate();
uint32_t samples_10_ms = sample_rate / 100;
AudioOutputStream* oas = audio_manager_->MakeAudioOutputStream(
AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
ChannelLayoutConfig::Mono(), sample_rate, samples_10_ms),
std::string(), AudioManager::LogCallback());
ASSERT_TRUE(NULL != oas);
SineWaveAudioSource source(1, 200, sample_rate);
bool opened = oas->Open();
if (!opened) {
LOG(WARNING) << "Mono is not supported. Skipping test.";
oas->Close();
return;
}
oas->SetVolume(1.0);
oas->Start(&source);
::Sleep(800);
oas->Stop();
oas->Close();
}
TEST_F(WinAudioTest, PCMWaveStreamPendingBytes) {
ABORT_AUDIO_TEST_IF_NOT(audio_manager_device_info_->HasAudioOutputDevices());
uint32_t samples_100_ms = AudioParameters::kAudioCDSampleRate / 10;
AudioOutputStream* oas = audio_manager_->MakeAudioOutputStream(
AudioParameters(AudioParameters::AUDIO_PCM_LINEAR,
ChannelLayoutConfig::Mono(),
AudioParameters::kAudioCDSampleRate, samples_100_ms),
std::string(), AudioManager::LogCallback());
ASSERT_TRUE(NULL != oas);
NiceMock<MockAudioSourceCallback> source;
EXPECT_TRUE(oas->Open());
constexpr base::TimeDelta delay_100_ms = base::Milliseconds(100);
constexpr base::TimeDelta delay_200_ms = base::Milliseconds(200);
InSequence s;
EXPECT_CALL(source,
OnMoreData(base::TimeDelta(), _, AudioGlitchInfo(), NotNull()))
.WillOnce(ClearData);
EXPECT_CALL(source, OnMoreData(delay_100_ms, _, AudioGlitchInfo(), NotNull()))
.WillOnce(ClearData);
EXPECT_CALL(source, OnMoreData(delay_200_ms, _, AudioGlitchInfo(), NotNull()))
.WillOnce(ClearData);
EXPECT_CALL(source, OnMoreData(delay_200_ms, _, AudioGlitchInfo(), NotNull()))
.Times(AnyNumber())
.WillRepeatedly(Return(0));
EXPECT_CALL(source, OnMoreData(delay_100_ms, _, AudioGlitchInfo(), NotNull()))
.Times(AnyNumber())
.WillRepeatedly(Return(0));
EXPECT_CALL(source,
OnMoreData(base::TimeDelta(), _, AudioGlitchInfo(), NotNull()))
.Times(AnyNumber())
.WillRepeatedly(Return(0));
oas->Start(&source);
::Sleep(500);
oas->Stop();
oas->Close();
}
class SyncSocketSource : public AudioOutputStream::AudioSourceCallback {
public:
SyncSocketSource(base::SyncSocket* socket,
const AudioParameters& params,
int expected_packet_count)
: socket_(socket),
params_(params),
expected_packet_count_(expected_packet_count) {
packet_size_ = AudioBus::CalculateMemorySize(params);
data_ = base::AlignedUninit<uint8_t>(
packet_size_ + sizeof(AudioOutputBufferParameters),
AudioBus::kChannelAlignment);
audio_data_ =
data_.as_span().subspan<sizeof(AudioOutputBufferParameters)>();
audio_bus_ = AudioBus::WrapMemory(params, audio_data_);
}
~SyncSocketSource() override {}
int OnMoreData(base::TimeDelta delay,
base::TimeTicks delay_timestamp,
const AudioGlitchInfo& glitch_info,
AudioBus* dest) override {
if (current_packet_count_ < expected_packet_count_) {
uint32_t control_signal = 0;
socket_->Send(base::byte_span_from_ref(control_signal));
output_buffer()->params.delay_us = delay.InMicroseconds();
output_buffer()->params.delay_timestamp_us =
(delay_timestamp - base::TimeTicks()).InMicroseconds();
uint32_t size = socket_->Receive(audio_data_);
++current_packet_count_;
DCHECK_EQ(static_cast<size_t>(size) % sizeof(float), 0U);
audio_bus_->CopyTo(dest);
return audio_bus_->frames();
}
return 0;
}
int packet_size() const { return packet_size_; }
AudioOutputBuffer* output_buffer() const {
return reinterpret_cast<AudioOutputBuffer*>(
const_cast<uint8_t*>(data_.data()));
}
void OnError(ErrorType type) override {}
private:
raw_ptr<base::SyncSocket> socket_;
const AudioParameters params_;
size_t packet_size_;
base::AlignedHeapArray<uint8_t> data_;
base::raw_span<uint8_t> audio_data_;
std::unique_ptr<AudioBus> audio_bus_;
const int expected_packet_count_;
int current_packet_count_ = 0;
};
struct SyncThreadContext {
RAW_PTR_EXCLUSION base::SyncSocket* socket;
int sample_rate;
int channels;
int frames;
double sine_freq;
uint32_t packet_size_bytes;
RAW_PTR_EXCLUSION AudioOutputBuffer* buffer;
int total_packets;
};
DWORD __stdcall SyncSocketThread(void* context) {
SyncThreadContext& ctx = *(reinterpret_cast<SyncThreadContext*>(context));
CHECK_EQ(ctx.packet_size_bytes % sizeof(float), 0U);
auto data = base::AlignedUninit<float>(ctx.packet_size_bytes / sizeof(float),
AudioBus::kChannelAlignment);
std::unique_ptr<AudioBus> audio_bus =
AudioBus::WrapMemory(ctx.channels, ctx.frames, data);
SineWaveAudioSource sine(1, ctx.sine_freq, ctx.sample_rate);
uint32_t control_signal = 0;
for (int ix = 0; ix < ctx.total_packets; ++ix) {
if (ctx.socket->Receive(base::byte_span_from_ref(control_signal)) == 0) {
break;
}
base::TimeDelta delay = base::Microseconds(ctx.buffer->params.delay_us);
base::TimeTicks delay_timestamp =
base::TimeTicks() +
base::Microseconds(ctx.buffer->params.delay_timestamp_us);
sine.OnMoreData(delay, delay_timestamp, {}, audio_bus.get());
ctx.socket->Send(
base::as_bytes(base::allow_nonunique_obj, base::span(data)));
}
return 0;
}
TEST_F(WinAudioTest, SyncSocketBasic) {
ABORT_AUDIO_TEST_IF_NOT(audio_manager_device_info_->HasAudioOutputDevices());
static constexpr int sample_rate = AudioParameters::kAudioCDSampleRate;
static constexpr uint32_t kSamples20ms = sample_rate / 50;
static constexpr int kPackets2s = 100;
AudioParameters params(AudioParameters::AUDIO_PCM_LINEAR,
ChannelLayoutConfig::Mono(), sample_rate,
kSamples20ms);
AudioOutputStream* oas = audio_manager_->MakeAudioOutputStream(
params, std::string(), AudioManager::LogCallback());
ASSERT_TRUE(NULL != oas);
ASSERT_TRUE(oas->Open());
base::SyncSocket sockets[2];
ASSERT_TRUE(base::SyncSocket::CreatePair(&sockets[0], &sockets[1]));
SyncSocketSource source(&sockets[0], params, kPackets2s);
SyncThreadContext thread_context;
thread_context.sample_rate = params.sample_rate();
thread_context.sine_freq = 200.0;
thread_context.packet_size_bytes = source.packet_size();
thread_context.frames = params.frames_per_buffer();
thread_context.channels = params.channels();
thread_context.socket = &sockets[1];
thread_context.buffer = source.output_buffer();
thread_context.total_packets = kPackets2s;
HANDLE thread = ::CreateThread(NULL, 0, SyncSocketThread,
&thread_context, 0, NULL);
oas->Start(&source);
::WaitForSingleObject(thread, INFINITE);
::CloseHandle(thread);
oas->Stop();
oas->Close();
}
}