// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cstdint>
#include <memory>
#include <vector>
#include "base/check.h"
#include "base/no_destructor.h"
#include "base/ranges/algorithm.h"
#include "chromecast/media/audio/fake_external_audio_pipeline_support.h"
#include "chromecast/public/cast_media_shlib.h"
#include "chromecast/public/media/external_audio_pipeline_shlib.h"
#include "chromecast/public/media/mixer_output_stream.h"
namespace chromecast {
namespace media {
namespace {
// Library implementation for media volume/mute testing part. It stores
// observers for sending volume/mute change requests and stores volume/muted.
class TestMediaVolumeMute {
public:
TestMediaVolumeMute() = default;
TestMediaVolumeMute(const TestMediaVolumeMute&) = delete;
TestMediaVolumeMute& operator=(const TestMediaVolumeMute&) = delete;
// Called by library.
void AddExternalMediaVolumeChangeRequestObserver(
ExternalAudioPipelineShlib::ExternalMediaVolumeChangeRequestObserver*
observer) {
volume_change_request_observer_ = observer;
}
void RemoveExternalMediaVolumeChangeRequestObserver(
ExternalAudioPipelineShlib::ExternalMediaVolumeChangeRequestObserver*
observer) {
volume_change_request_observer_ = nullptr;
}
void SetVolume(float volume) { volume_ = volume; }
void SetMuted(bool muted) { muted_ = muted; }
protected:
// Used by derived class for FakeExternalAudioPipelineSupport.
ExternalAudioPipelineShlib::ExternalMediaVolumeChangeRequestObserver*
volume_change_request_observer_ = nullptr;
float volume_ = 0;
bool muted_ = false;
};
// Library implementation for loopback data testing part. It stores
// CastMediaShlib observers and does loopback audio data to it.
class TestLoopBack {
public:
TestLoopBack() = default;
TestLoopBack(const TestLoopBack&) = delete;
TestLoopBack& operator=(const TestLoopBack&) = delete;
// Called from FakeMixerOutputStream.
void OnData(const float* data,
int data_size,
MixerOutputStream* stream,
int channels) {
auto delay = stream->GetRenderingDelay();
int64_t delay_microseconds =
delay.timestamp_microseconds + delay.delay_microseconds;
for (auto* observer : observers_) {
observer->OnLoopbackAudio(
delay_microseconds, kSampleFormatF32, stream->GetSampleRate(),
channels, reinterpret_cast<uint8_t*>(const_cast<float*>(data)),
data_size * sizeof(float));
}
}
// Called from library.
void AddExternalLoopbackAudioObserver(
ExternalAudioPipelineShlib::LoopbackAudioObserver* observer) {
observers_.push_back(observer);
}
void RemoveExternalLoopbackAudioObserver(
ExternalAudioPipelineShlib::LoopbackAudioObserver* observer) {
auto it = base::ranges::find(observers_, observer);
if (it != observers_.end()) {
observers_.erase(it);
}
observer->OnRemoved();
}
protected:
// Used by derived class for FakeExternalAudioPipelineSupport.
std::vector<ExternalAudioPipelineShlib::LoopbackAudioObserver*> observers_;
};
class TestMediaMetadata {
public:
TestMediaMetadata() = default;
TestMediaMetadata(const TestMediaMetadata&) = delete;
TestMediaMetadata& operator=(const TestMediaMetadata&) = delete;
// Called from library.
void AddExternalMediaMetadataChangeObserver(
ExternalAudioPipelineShlib::ExternalMediaMetadataChangeObserver*
observer) {
media_metadata_change_observer_ = observer;
}
void RemoveExternalMediaMetadataChangeObserver(
ExternalAudioPipelineShlib::ExternalMediaMetadataChangeObserver*) {
media_metadata_change_observer_ = nullptr;
}
protected:
ExternalAudioPipelineShlib::ExternalMediaMetadataChangeObserver*
media_metadata_change_observer_ = nullptr;
};
// Final class includes library implementation for testing media volume/mute
// + loopback and FakeExternalAudioPipelineSupport implementation.
class TestMedia : public TestMediaVolumeMute,
public TestLoopBack,
public TestMediaMetadata,
public testing::FakeExternalAudioPipelineSupport {
public:
TestMedia() = default;
TestMedia(const TestMedia&) = delete;
TestMedia& operator=(const TestMedia&) = delete;
bool supported() const { return supported_; }
// FakeExternalAudioPipelineSupport implementation:
void SetSupported() override { supported_ = true; }
void Reset() override {
supported_ = false;
volume_change_request_observer_ = nullptr;
volume_ = 0;
muted_ = false;
observers_.clear();
}
float GetVolume() const override { return volume_; }
bool IsMuted() const override { return muted_; }
void OnVolumeChangeRequest(float level) override {
CHECK(volume_change_request_observer_);
volume_change_request_observer_->OnVolumeChangeRequest(level);
}
void OnMuteChangeRequest(bool muted) override {
CHECK(volume_change_request_observer_);
volume_change_request_observer_->OnMuteChangeRequest(muted);
}
void UpdateExternalMediaMetadata(
const ExternalAudioPipelineShlib::ExternalMediaMetadata& metadata)
override {
if (media_metadata_change_observer_) {
media_metadata_change_observer_->OnExternalMediaMetadataChanged(metadata);
}
}
private:
bool supported_ = false;
};
TestMedia* GetTestMedia() {
static base::NoDestructor<TestMedia> g_test_media;
return g_test_media.get();
}
// MixerOutputStream implementation, it will be created by
// ExternalAudioPipelineShlib::CreateMixerOutputStream.
class FakeMixerOutputStream : public MixerOutputStream {
public:
FakeMixerOutputStream() : test_loop_back_(GetTestMedia()) {}
FakeMixerOutputStream(const FakeMixerOutputStream&) = delete;
FakeMixerOutputStream& operator=(const FakeMixerOutputStream&) = delete;
// MixerOutputStream implementation:
bool Start(int requested_sample_rate, int channels) override {
sample_rate_ = requested_sample_rate;
channels_ = channels;
return true;
}
void Stop() override {}
int GetNumChannels() override { return channels_; }
int GetSampleRate() override { return sample_rate_; }
MediaPipelineBackend::AudioDecoder::RenderingDelay GetRenderingDelay()
override {
return MediaPipelineBackend::AudioDecoder::RenderingDelay();
}
int OptimalWriteFramesCount() override { return 256; }
bool Write(const float* data,
int data_size,
bool* out_playback_interrupted) override {
// To check OnLoopbackInterrupted.
*out_playback_interrupted = true;
// Loopback data.
test_loop_back_->OnData(data, data_size, this, channels_);
return true;
}
private:
int sample_rate_ = 0;
int channels_ = 0;
TestLoopBack* const test_loop_back_;
};
} // namespace
namespace testing {
// Get the interface for interaction with the library from unittests.
FakeExternalAudioPipelineSupport* GetFakeExternalAudioPipelineSupport() {
return GetTestMedia();
}
} // namespace testing
// Library implementation.
bool ExternalAudioPipelineShlib::IsSupported() {
return GetTestMedia()->supported();
}
void ExternalAudioPipelineShlib::AddExternalMediaVolumeChangeRequestObserver(
ExternalMediaVolumeChangeRequestObserver* observer) {
GetTestMedia()->AddExternalMediaVolumeChangeRequestObserver(observer);
}
void ExternalAudioPipelineShlib::RemoveExternalMediaVolumeChangeRequestObserver(
ExternalMediaVolumeChangeRequestObserver* observer) {
GetTestMedia()->RemoveExternalMediaVolumeChangeRequestObserver(observer);
}
void ExternalAudioPipelineShlib::SetExternalMediaVolume(float level) {
GetTestMedia()->SetVolume(level);
}
void ExternalAudioPipelineShlib::SetExternalMediaMuted(bool muted) {
GetTestMedia()->SetMuted(muted);
}
void ExternalAudioPipelineShlib::AddExternalLoopbackAudioObserver(
LoopbackAudioObserver* observer) {
GetTestMedia()->AddExternalLoopbackAudioObserver(observer);
}
void ExternalAudioPipelineShlib::RemoveExternalLoopbackAudioObserver(
LoopbackAudioObserver* observer) {
GetTestMedia()->RemoveExternalLoopbackAudioObserver(observer);
}
void ExternalAudioPipelineShlib::AddExternalMediaMetadataChangeObserver(
ExternalMediaMetadataChangeObserver* observer) {
GetTestMedia()->AddExternalMediaMetadataChangeObserver(observer);
}
void ExternalAudioPipelineShlib::RemoveExternalMediaMetadataChangeObserver(
ExternalMediaMetadataChangeObserver* observer) {
GetTestMedia()->RemoveExternalMediaMetadataChangeObserver(observer);
}
std::unique_ptr<MixerOutputStream>
ExternalAudioPipelineShlib::CreateMixerOutputStream() {
return std::make_unique<FakeMixerOutputStream>();
}
} // namespace media
} // namespace chromecast