#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/metrics/histogram_tester.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;
constexpr char kAudioPlaybackModeHistogramName[] =
"Remoting.Host.ChromeOs.AudioStream.AudioPlaybackMode";
constexpr char kAudioStreamErrorHistogramName[] =
"Remoting.Host.ChromeOs.AudioStream.OnError";
constexpr char kAudioStreamOpenOutcomeHistogramName[] =
"Remoting.Host.ChromeOs.AudioStream.OpenOutcome";
constexpr char kStartAudioStreamHistogramName[] =
"Remoting.Host.ChromeOs.AudioStream.StartResult";
media::AudioParameters GetTestAudioParams() {
return media::AudioParameters(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
media::ChannelLayoutConfig::Stereo(),
kSampleRate, kFramesPerBuffer);
}
}
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;
}
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>(
true),
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 {
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;
scoped_refptr<base::SequencedTaskRunner> audio_runner_;
};
TEST_F(AudioHelperChromeOsImplTest, SuccessfulStartWithPackets) {
base::HistogramTester histogram_tester;
base::RunLoop start_loop;
audio_runner_->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
audio_helper_chromeos_->StartAudioStream(
AudioPlaybackMode::kRemoteOnly,
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();
histogram_tester.ExpectUniqueSample(kStartAudioStreamHistogramName,
AudioHelperStartStreamResult::kSuccess,
1);
histogram_tester.ExpectUniqueSample(kAudioStreamOpenOutcomeHistogramName,
OpenOutcomeChromeOs::kSuccess,
1);
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,
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) {
base::RunLoop start_loop;
audio_runner_->PostTask(FROM_HERE, base::BindLambdaForTesting([&]() {
audio_helper_chromeos_->StartAudioStream(
AudioPlaybackMode::kRemoteOnly,
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, StartAudioStreamWithRemoteOnly) {
base::HistogramTester histogram_tester;
base::RunLoop run_loop;
audio_runner_->PostTask(FROM_HERE, base::BindLambdaForTesting([&]() {
audio_helper_chromeos_->StartAudioStream(
AudioPlaybackMode::kRemoteOnly,
base::DoNothing(), base::DoNothing());
}));
audio_runner_->PostTask(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
EXPECT_EQ(media::AudioDeviceDescription::kLoopbackWithMuteDeviceId,
audio_manager_->device_id());
histogram_tester.ExpectUniqueSample(kAudioPlaybackModeHistogramName,
AudioPlaybackMode::kRemoteOnly,
1);
}
TEST_F(AudioHelperChromeOsImplTest, StartAudioStreamWithRemoteAndLocal) {
base::HistogramTester histogram_tester;
base::RunLoop run_loop;
audio_runner_->PostTask(FROM_HERE, base::BindLambdaForTesting([&]() {
audio_helper_chromeos_->StartAudioStream(
AudioPlaybackMode::kRemoteAndLocal,
base::DoNothing(), base::DoNothing());
}));
audio_runner_->PostTask(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
EXPECT_EQ(media::AudioDeviceDescription::kDefaultDeviceId,
audio_manager_->device_id());
histogram_tester.ExpectUniqueSample(kAudioPlaybackModeHistogramName,
AudioPlaybackMode::kRemoteAndLocal,
1);
}
TEST_F(AudioHelperChromeOsImplTest, SuccessfulStartWithStreamFailure) {
base::HistogramTester histogram_tester;
base::RunLoop start_loop;
audio_runner_->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
audio_helper_chromeos_->StartAudioStream(
AudioPlaybackMode::kRemoteOnly,
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; }));
histogram_tester.ExpectUniqueSample(kAudioStreamErrorHistogramName,
1,
1);
}
TEST_F(AudioHelperChromeOsImplTest, FailedStartStreamNotCreated) {
base::HistogramTester histogram_tester;
audio_manager_->SetFailStreamCreation( true);
base::RunLoop start_loop;
audio_runner_->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
audio_helper_chromeos_->StartAudioStream(
AudioPlaybackMode::kRemoteOnly,
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; }));
histogram_tester.ExpectUniqueSample(
kStartAudioStreamHistogramName,
AudioHelperStartStreamResult::kFailedToCreateStream,
1);
}
TEST_F(AudioHelperChromeOsImplTest, FailedStartStreamNotOpened) {
base::HistogramTester histogram_tester;
audio_manager_->SetFailStreamOpen( true);
base::RunLoop start_loop;
audio_runner_->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
audio_helper_chromeos_->StartAudioStream(
AudioPlaybackMode::kRemoteOnly,
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; }));
histogram_tester.ExpectUniqueSample(
kStartAudioStreamHistogramName,
AudioHelperStartStreamResult::kFailedToOpenStream,
1);
histogram_tester.ExpectUniqueSample(kAudioStreamOpenOutcomeHistogramName,
OpenOutcomeChromeOs::kFailed,
1);
}
TEST_F(AudioHelperChromeOsImplTest, StreamAlreadyStarted) {
base::HistogramTester histogram_tester;
base::RunLoop start_loop;
audio_runner_->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
audio_helper_chromeos_->StartAudioStream(AudioPlaybackMode::kRemoteOnly,
base::DoNothing(),
base::DoNothing());
audio_helper_chromeos_->StartAudioStream(AudioPlaybackMode::kRemoteOnly,
base::DoNothing(),
base::DoNothing());
histogram_tester.ExpectBucketCount(
kStartAudioStreamHistogramName,
AudioHelperStartStreamResult::kStreamAlreadyStarted,
1);
}));
audio_runner_->PostTask(FROM_HERE, start_loop.QuitClosure());
start_loop.Run();
}
}