blob: 3234be467d343befeea8c2142a403df907b6f801 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/host/chromeos/audio_helper_chromeos_impl.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/protobuf_matchers.h"
#include "base/test/run_until.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "media/audio/audio_device_description.h"
#include "media/audio/audio_io.h"
#include "media/audio/fake_audio_log_factory.h"
#include "media/audio/fake_audio_manager.h"
#include "media/audio/mock_audio_manager.h"
#include "media/audio/test_audio_thread.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_parameters.h"
#include "remoting/proto/audio.pb.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace remoting {
namespace {
constexpr int kSampleRate = 48000;
constexpr int kFramesPerBuffer = kSampleRate / 100;
media::AudioParameters GetTestAudioParams() {
return media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::ChannelLayoutConfig::Stereo(),
kSampleRate, kFramesPerBuffer);
}
} // namespace
class FakeAudioInputStream : public media::AudioInputStream {
public:
using OpenOutcome = media::AudioInputStream::OpenOutcome;
FakeAudioInputStream() = default;
void SetOpenOutcome(OpenOutcome outcome) { open_outcome_ = outcome; }
bool IsStarted() const { return callback_ != nullptr; }
bool IsClosed() const { return closed_; }
void SimulateData(const media::AudioBus* audio_bus,
base::TimeTicks capture_time,
double volume) {
callback_->OnData(audio_bus, capture_time, volume, {});
}
void SimulateError() { callback_->OnError(); }
OpenOutcome Open() override { return open_outcome_; }
void Start(AudioInputCallback* callback) override { callback_ = callback; }
void Stop() override { callback_ = nullptr; }
void Close() override {
Stop();
closed_ = true;
}
// Unused but required for overrides.
double GetMaxVolume() override { return 0; }
void SetVolume(double volume) override {}
double GetVolume() override { return 0; }
bool SetAutomaticGainControl(bool enabled) override { return false; }
bool GetAutomaticGainControl() override { return false; }
bool IsMuted() override { return false; }
void SetOutputDeviceForAec(const std::string& input_device_id) override {}
private:
raw_ptr<AudioInputCallback> callback_ = nullptr;
OpenOutcome open_outcome_ = OpenOutcome::kSuccess;
bool closed_ = false;
};
class CustomFakeAudioManager : public media::FakeAudioManager {
public:
CustomFakeAudioManager()
: media::FakeAudioManager(std::make_unique<media::TestAudioThread>(
/* use_real_thread= */ true),
/*audio_log_factory=*/nullptr) {}
~CustomFakeAudioManager() override = default;
media::AudioInputStream* MakeAudioInputStream(
const media::AudioParameters& params,
const std::string& device_id,
const LogCallback& log_callback) override {
if (fail_stream_creation_) {
return nullptr;
}
params_ = params;
device_id_ = device_id;
input_stream_ = std::make_unique<FakeAudioInputStream>();
if (fail_stream_open_) {
input_stream_->SetOpenOutcome(
media::AudioInputStream::OpenOutcome::kFailed);
}
return input_stream_.get();
}
void SetFailStreamCreation(bool fail) { fail_stream_creation_ = fail; }
void SetFailStreamOpen(bool fail) { fail_stream_open_ = fail; }
FakeAudioInputStream* GetInputStream() { return input_stream_.get(); }
media::AudioParameters params() { return params_; }
std::string device_id() { return device_id_; }
private:
std::unique_ptr<FakeAudioInputStream> input_stream_;
bool fail_stream_creation_ = false;
bool fail_stream_open_ = false;
media::AudioParameters params_;
std::string device_id_;
};
class AudioHelperChromeOsImplTest : public testing::Test {
public:
AudioHelperChromeOsImplTest() = default;
~AudioHelperChromeOsImplTest() override = default;
void SetUp() override {
audio_manager_ = std::make_unique<CustomFakeAudioManager>();
audio_runner_ = audio_manager_->GetTaskRunner();
audio_helper_chromeos_ = std::make_unique<AudioHelperChromeOsImpl>();
}
void TearDown() override {
// AudioHelperChromeOS needs to be destroyed before the AudioManager because
// on destruction, AudioHelperChromeOS attempts to close the audio stream.
// AudioHelperChromeOS also needs to be destroyed on the `audio_runner_`
// sequence.
base::RunLoop run_loop;
audio_runner_->DeleteSoon(FROM_HERE, std::move(audio_helper_chromeos_));
audio_runner_->PostTask(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
audio_manager_->Shutdown();
}
void OnDataCallback(std::unique_ptr<AudioPacket> packet) {
captured_audio_packets_.push_back(std::move(packet));
}
void OnErrorCallback() { ++on_error_called_count_; }
protected:
base::test::TaskEnvironment task_environment_;
std::unique_ptr<CustomFakeAudioManager> audio_manager_;
std::unique_ptr<AudioHelperChromeOsImpl> audio_helper_chromeos_;
std::vector<std::unique_ptr<AudioPacket>> captured_audio_packets_;
int on_data_called_count_ = 0;
int on_error_called_count_ = 0;
// Runner for AudioManager.
scoped_refptr<base::SequencedTaskRunner> audio_runner_;
};
TEST_F(AudioHelperChromeOsImplTest, SuccessfulStartWithPackets) {
// Simulate `audio_helper_chromeos_` being called on the `audio_runner_`.
base::RunLoop start_loop;
audio_runner_->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
audio_helper_chromeos_->StartAudioStream(
base::BindRepeating(&AudioHelperChromeOsImplTest::OnDataCallback,
base::Unretained(this)),
base::BindRepeating(&AudioHelperChromeOsImplTest::OnErrorCallback,
base::Unretained(this)));
}));
audio_runner_->PostTask(FROM_HERE, start_loop.QuitClosure());
start_loop.Run();
auto audio_bus = media::AudioBus::Create(GetTestAudioParams());
audio_bus->Zero();
base::TimeTicks capture_time = base::TimeTicks::Now();
audio_manager_->GetInputStream()->SimulateData(audio_bus.get(), capture_time,
/* volume= */ 50);
EXPECT_TRUE(base::test::RunUntil(
[&]() { return captured_audio_packets_.size() == 1; }));
const auto& packet = captured_audio_packets_[0];
EXPECT_EQ(AudioPacket::ENCODING_RAW, packet->encoding());
EXPECT_EQ(AudioPacket::SAMPLING_RATE_48000, packet->sampling_rate());
EXPECT_EQ(AudioPacket::BYTES_PER_SAMPLE_2, packet->bytes_per_sample());
EXPECT_EQ(AudioPacket::CHANNELS_STEREO, packet->channels());
}
TEST_F(AudioHelperChromeOsImplTest, VerifyStreamParams) {
// Simulate `audio_helper_chromeos_` being called on the `audio_runner_`.
base::RunLoop start_loop;
audio_runner_->PostTask(FROM_HERE, base::BindLambdaForTesting([&]() {
audio_helper_chromeos_->StartAudioStream(
base::DoNothing(), base::DoNothing());
}));
audio_runner_->PostTask(FROM_HERE, start_loop.QuitClosure());
start_loop.Run();
EXPECT_EQ(media::AudioDeviceDescription::kLoopbackWithMuteDeviceId,
audio_manager_->device_id());
EXPECT_EQ(GetTestAudioParams().sample_rate(),
audio_manager_->params().sample_rate());
EXPECT_EQ(GetTestAudioParams().channels(),
audio_manager_->params().channels());
EXPECT_EQ(GetTestAudioParams().frames_per_buffer(),
audio_manager_->params().frames_per_buffer());
EXPECT_EQ(GetTestAudioParams().format(), audio_manager_->params().format());
}
TEST_F(AudioHelperChromeOsImplTest, SuccessfulStartWithStreamFailure) {
// Simulate `audio_helper_chromeos_` being called on the `audio_runner_`.
base::RunLoop start_loop;
audio_runner_->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
audio_helper_chromeos_->StartAudioStream(
base::BindRepeating(&AudioHelperChromeOsImplTest::OnDataCallback,
base::Unretained(this)),
base::BindRepeating(&AudioHelperChromeOsImplTest::OnErrorCallback,
base::Unretained(this)));
EXPECT_TRUE(audio_manager_->GetInputStream()->IsStarted());
audio_manager_->GetInputStream()->SimulateError();
}));
audio_runner_->PostTask(FROM_HERE, start_loop.QuitClosure());
start_loop.Run();
EXPECT_TRUE(
base::test::RunUntil([&]() { return on_error_called_count_ == 1; }));
}
TEST_F(AudioHelperChromeOsImplTest, FailedStartStreamNotCreated) {
audio_manager_->SetFailStreamCreation(/* fail= */ true);
// Simulate `audio_helper_chromeos_` being called on the `audio_runner_`.
base::RunLoop start_loop;
audio_runner_->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
audio_helper_chromeos_->StartAudioStream(
base::BindRepeating(&AudioHelperChromeOsImplTest::OnDataCallback,
base::Unretained(this)),
base::BindRepeating(&AudioHelperChromeOsImplTest::OnErrorCallback,
base::Unretained(this)));
}));
audio_runner_->PostTask(FROM_HERE, start_loop.QuitClosure());
start_loop.Run();
EXPECT_TRUE(
base::test::RunUntil([&]() { return on_error_called_count_ == 1; }));
}
TEST_F(AudioHelperChromeOsImplTest, FailedStartStreamNotOpened) {
audio_manager_->SetFailStreamOpen(/* fail= */ true);
// Simulate `audio_helper_chromeos_` being called on the `audio_runner_`.
base::RunLoop start_loop;
audio_runner_->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
audio_helper_chromeos_->StartAudioStream(
base::BindRepeating(&AudioHelperChromeOsImplTest::OnDataCallback,
base::Unretained(this)),
base::BindRepeating(&AudioHelperChromeOsImplTest::OnErrorCallback,
base::Unretained(this)));
start_loop.Quit();
}));
audio_runner_->PostTask(FROM_HERE, start_loop.QuitClosure());
start_loop.Run();
EXPECT_TRUE(
base::test::RunUntil([&]() { return on_error_called_count_ == 1; }));
}
} // namespace remoting