Commit b100ef19 authored by Sergei Istomin's avatar Sergei Istomin Committed by Commit Bot

[Chromecast] Add unit test cases for external audio pipeline library

This CL adds test cases to "cast_audio_backend_unittests" for checking external
audio pipeline library. Tests check "StreamMixer" interaction with the library
when library returns IsSupported() true.

"Cast_audio_backend_unittests" unittest now is built with
"fake_external_audio_pipeline" that has library implementation. For test reason
the library supports additional functions (returns interface) that are used
only from unittests. Additional functions are defined in
"fake_external_audio_pipeline_support.h" "testing" namespace.

There are the following test cases:
- "SetMediaVolume"/"SetMediaMuted" for testing media volume/mute notification
   when the volume/mute is changed.
- "SetVolumeChangeRequest"/"SetMuteChangeRequest" for testing a change request
   for volume/mute from the library.
- "ExternalAudioPipelineLoopbackData" for testing loopback data.

Bug: internal b/113337099
Depends-On: I755aaa94470dc8ba33a458d6139ef9254655d3e4
Change-Id: Ie8ebaf878e0d1c0d1f8485e645a2e05142745928
Reviewed-on: https://chromium-review.googlesource.com/1218045
Commit-Queue: Sergei Istomin <sistomin@chromium.org>
Reviewed-by: default avatarKenneth MacKay <kmackay@chromium.org>
Cr-Commit-Position: refs/heads/master@{#593554}
parent 44f585c8
...@@ -48,3 +48,16 @@ cast_shared_library("libcast_external_audio_pipeline_1.0") { ...@@ -48,3 +48,16 @@ cast_shared_library("libcast_external_audio_pipeline_1.0") {
"//chromecast/public/media", "//chromecast/public/media",
] ]
} }
source_set("fake_external_audio_pipeline") {
sources = [
"fake_external_audio_pipeline.cc",
"fake_external_audio_pipeline_support.h",
]
deps = [
"//base",
"//chromecast/public",
"//chromecast/public/media",
]
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <cstdint>
#include <memory>
#include <vector>
#include "base/logging.h"
#include "base/macros.h"
#include "base/no_destructor.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;
// 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;
private:
DISALLOW_COPY_AND_ASSIGN(TestMediaVolumeMute);
};
// Library implementation for loopback data testing part. It stores
// CastMediaShlib observers and does loopback audio data to it.
class TestLoopBack {
public:
TestLoopBack() = default;
// 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(
CastMediaShlib::LoopbackAudioObserver* observer) {
observers_.push_back(observer);
}
void RemoveExternalLoopbackAudioObserver(
CastMediaShlib::LoopbackAudioObserver* observer) {
auto it = std::find(observers_.begin(), observers_.end(), observer);
if (it != observers_.end()) {
observers_.erase(it);
}
}
protected:
// Used by derived class for FakeExternalAudioPipelineSupport.
std::vector<CastMediaShlib::LoopbackAudioObserver*> observers_;
private:
DISALLOW_COPY_AND_ASSIGN(TestLoopBack);
};
// Final class includes library implementation for testing media volume/mute
// + loopback and FakeExternalAudioPipelineSupport implementation.
class TestMedia : public TestMediaVolumeMute,
public TestLoopBack,
public testing::FakeExternalAudioPipelineSupport {
public:
TestMedia() = default;
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);
}
private:
bool supported_ = false;
DISALLOW_COPY_AND_ASSIGN(TestMedia);
};
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()) {}
// MixerOutputStream implementation:
bool Start(int requested_sample_rate, int channels) override {
sample_rate_ = requested_sample_rate;
channels_ = channels;
return true;
}
void Stop() override {}
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_;
DISALLOW_COPY_AND_ASSIGN(FakeMixerOutputStream);
};
} // 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(
CastMediaShlib::LoopbackAudioObserver* observer) {
GetTestMedia()->AddExternalLoopbackAudioObserver(observer);
}
void ExternalAudioPipelineShlib::RemoveExternalLoopbackAudioObserver(
CastMediaShlib::LoopbackAudioObserver* observer) {
GetTestMedia()->RemoveExternalLoopbackAudioObserver(observer);
}
void ExternalAudioPipelineShlib::AddExternalMediaMetadataChangeObserver(
ExternalMediaMetadataChangeObserver* observer) {}
void ExternalAudioPipelineShlib::RemoveExternalMediaMetadataChangeObserver(
ExternalMediaMetadataChangeObserver* observer) {}
std::unique_ptr<MixerOutputStream>
ExternalAudioPipelineShlib::CreateMixerOutputStream() {
return std::make_unique<FakeMixerOutputStream>();
}
} // namespace media
} // namespace chromecast
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMECAST_MEDIA_AUDIO_FAKE_EXTERNAL_AUDIO_PIPELINE_SUPPORT_H
#define CHROMECAST_MEDIA_AUDIO_FAKE_EXTERNAL_AUDIO_PIPELINE_SUPPORT_H
namespace chromecast {
namespace media {
namespace testing {
// Interface class for fake external pipeline library to interact with the
// library from unit tests.
class FakeExternalAudioPipelineSupport {
public:
virtual void SetSupported() = 0;
virtual void Reset() = 0;
// Get last received values.
virtual float GetVolume() const = 0;
virtual bool IsMuted() const = 0;
// Request for change values.
virtual void OnVolumeChangeRequest(float level) = 0;
virtual void OnMuteChangeRequest(bool muted) = 0;
virtual ~FakeExternalAudioPipelineSupport() {}
};
FakeExternalAudioPipelineSupport* GetFakeExternalAudioPipelineSupport();
} // namespace testing
} // namespace media
} // namespace chromecast
#endif // CHROMECAST_MEDIA_AUDIO_FAKE_EXTERNAL_AUDIO_PIPELINE_SUPPORT_H
...@@ -259,6 +259,7 @@ test("cast_audio_backend_unittests") { ...@@ -259,6 +259,7 @@ test("cast_audio_backend_unittests") {
"mock_mixer_source.h", "mock_mixer_source.h",
"mock_redirected_audio_output.cc", "mock_redirected_audio_output.cc",
"mock_redirected_audio_output.h", "mock_redirected_audio_output.h",
"stream_mixer_external_audio_pipeline_unittest.cc",
"stream_mixer_unittest.cc", "stream_mixer_unittest.cc",
] ]
...@@ -270,6 +271,7 @@ test("cast_audio_backend_unittests") { ...@@ -270,6 +271,7 @@ test("cast_audio_backend_unittests") {
":public", ":public",
"//base", "//base",
"//base/test:run_all_unittests", "//base/test:run_all_unittests",
"//chromecast/media/audio:fake_external_audio_pipeline",
"//chromecast/media/cma/backend/post_processors:unittests", "//chromecast/media/cma/backend/post_processors:unittests",
"//chromecast/public", "//chromecast/public",
"//chromecast/public/media", "//chromecast/public/media",
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <memory>
#include <vector>
#include "base/location.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chromecast/media/audio/fake_external_audio_pipeline_support.h"
#include "chromecast/media/cma/backend/mock_mixer_source.h"
#include "chromecast/media/cma/backend/stream_mixer.h"
#include "chromecast/public/media/external_audio_pipeline_shlib.h"
#include "chromecast/public/media/mixer_output_stream.h"
#include "media/base/audio_bus.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromecast {
namespace media {
namespace {
using ::testing::_;
using ::testing::AtLeast;
// Mock for saving and checking loopback audio data.
class MockLoopbackAudioObserver : public CastMediaShlib::LoopbackAudioObserver {
public:
MockLoopbackAudioObserver() {
ON_CALL(*this, OnLoopbackAudio(_, _, _, _, _, _))
.WillByDefault(::testing::Invoke(
this, &MockLoopbackAudioObserver::OnLoopbackAudioImpl));
}
MOCK_METHOD6(OnLoopbackAudio,
void(int64_t timestamp,
SampleFormat sample_format,
int sample_rate,
int num_channels,
uint8_t* data,
int length));
MOCK_METHOD0(OnLoopbackInterrupted, void());
MOCK_METHOD0(OnRemoved, void());
const std::vector<float>& data() const { return data_; }
private:
void OnLoopbackAudioImpl(int64_t timestamp,
SampleFormat sample_format,
int sample_rate,
int num_channels,
uint8_t* data,
int length) {
data_.clear();
// Save received data to local.
float* float_data = reinterpret_cast<float*>(const_cast<uint8_t*>(data));
const size_t size = length / sizeof(float);
data_.insert(data_.end(), float_data, float_data + size);
}
std::vector<float> data_;
DISALLOW_COPY_AND_ASSIGN(MockLoopbackAudioObserver);
};
class ExternalAudioPipelineTest : public ::testing::Test {
public:
ExternalAudioPipelineTest()
: external_audio_pipeline_support_(
testing::GetFakeExternalAudioPipelineSupport()),
message_loop_(std::make_unique<base::MessageLoop>()) {}
void SetUp() override {
// Set that external library is supported.
external_audio_pipeline_support_->SetSupported();
mixer_ = std::make_unique<StreamMixer>(nullptr, nullptr,
base::ThreadTaskRunnerHandle::Get());
}
void TearDown() override {
// Reset library internal state to use it for other unit tests.
external_audio_pipeline_support_->Reset();
}
// Run async operations in the stream mixer.
void RunLoopForMixer() {
// SendLoopbackData.
base::RunLoop run_loop1;
message_loop_->task_runner()->PostTask(FROM_HERE, run_loop1.QuitClosure());
run_loop1.Run();
// Playbackloop.
base::RunLoop run_loop2;
message_loop_->task_runner()->PostTask(FROM_HERE, run_loop2.QuitClosure());
run_loop2.Run();
}
protected:
std::unique_ptr<StreamMixer> mixer_;
testing::FakeExternalAudioPipelineSupport* const
external_audio_pipeline_support_;
private:
const std::unique_ptr<base::MessageLoop> message_loop_;
DISALLOW_COPY_AND_ASSIGN(ExternalAudioPipelineTest);
};
// Check that |expected| matches |actual| exactly.
void CompareAudioData(const ::media::AudioBus& expected,
const ::media::AudioBus& actual) {
ASSERT_EQ(expected.channels(), actual.channels());
ASSERT_EQ(expected.frames(), actual.frames());
for (int c = 0; c < expected.channels(); ++c) {
const float* expected_data = expected.channel(c);
const float* actual_data = actual.channel(c);
for (int f = 0; f < expected.frames(); ++f) {
EXPECT_FLOAT_EQ(*expected_data++, *actual_data++) << c << " " << f;
}
}
}
// Unit tests for ExternalAudioPipelineShlib library.
// Test media volume notification.
TEST_F(ExternalAudioPipelineTest, SetMediaVolume) {
ASSERT_EQ(external_audio_pipeline_support_->GetVolume(), 0.0f);
mixer_->SetVolume(AudioContentType::kMedia, 0.02);
RunLoopForMixer();
ASSERT_EQ(external_audio_pipeline_support_->GetVolume(), 0.02f);
}
// Test media muted notification.
TEST_F(ExternalAudioPipelineTest, SetMediaMuted) {
ASSERT_EQ(external_audio_pipeline_support_->IsMuted(), false);
mixer_->SetMuted(AudioContentType::kMedia, true);
RunLoopForMixer();
ASSERT_EQ(external_audio_pipeline_support_->IsMuted(), true);
}
// Set media volume from library, check notification.
TEST_F(ExternalAudioPipelineTest, SetVolumeChangeRequest) {
ASSERT_EQ(external_audio_pipeline_support_->GetVolume(), 0.0f);
external_audio_pipeline_support_->OnVolumeChangeRequest(0.03);
RunLoopForMixer();
ASSERT_EQ(external_audio_pipeline_support_->GetVolume(), 0.03f);
}
// Set media mute from library, check notification.
TEST_F(ExternalAudioPipelineTest, SetMuteChangeRequest) {
ASSERT_EQ(external_audio_pipeline_support_->IsMuted(), false);
external_audio_pipeline_support_->OnMuteChangeRequest(true);
RunLoopForMixer();
ASSERT_EQ(external_audio_pipeline_support_->IsMuted(), true);
}
// Check external library loopback data. Check that passed input to StreamMixer
// comes to CastMediaShlib::LoopbackAudioObserver w/o changes.
TEST_F(ExternalAudioPipelineTest, ExternalAudioPipelineLoopbackData) {
// Set Volume to 1, because we'd like the input to be w/o changes.
mixer_->SetVolume(AudioContentType::kMedia, 1);
// CastMediaShlib::LoopbackAudioObserver mock observer.
MockLoopbackAudioObserver mock_loopback_observer;
EXPECT_CALL(mock_loopback_observer, OnLoopbackAudio(_, _, _, _, _, _))
.Times(AtLeast(1));
EXPECT_CALL(mock_loopback_observer, OnLoopbackInterrupted())
.Times(AtLeast(1));
EXPECT_CALL(mock_loopback_observer, OnRemoved()).Times(1);
// Input.
MockMixerSource input(48000);
EXPECT_CALL(input, InitializeAudioPlayback(_, _)).Times(1);
EXPECT_CALL(input, FinalizeAudioPlayback()).Times(1);
EXPECT_CALL(input, FillAudioPlaybackFrames(_, _, _)).Times(AtLeast(1));
// Prepare data for test.
const size_t kSampleSize = 64;
char test_data[kSampleSize];
for (size_t i = 0; i < kSampleSize; ++i)
test_data[i] = i;
// Set test data in AudioBus.
const int kNumChannels = 2;
const auto kNumFrames = kSampleSize / kNumChannels;
auto data = ::media::AudioBus::Create(kNumChannels, kNumFrames);
const size_t kBytesPerSample = sizeof(test_data[0]);
data->FromInterleaved(&test_data, kNumFrames, kBytesPerSample);
// Prepare data for compare.
auto expected = ::media::AudioBus::Create(kNumChannels, kNumFrames);
data->CopyTo(expected.get());
// Start the test. Set loopback observer.
mixer_->AddLoopbackAudioObserver(&mock_loopback_observer);
mixer_->AddInput(&input);
RunLoopForMixer();
// Send data to the stream mixer.
input.SetData(std::move(data));
RunLoopForMixer();
// Get actual data from our mocked loopback observer.
ASSERT_GE(mock_loopback_observer.data().size(), kNumFrames);
auto actual = ::media::AudioBus::Create(kNumChannels, kNumFrames);
using FloatType = ::media::Float32SampleTypeTraits;
actual->FromInterleaved<FloatType>(mock_loopback_observer.data().data(),
kNumFrames);
CompareAudioData(*expected, *actual);
// Check OnRemoved.
mixer_->RemoveLoopbackAudioObserver(&mock_loopback_observer);
mixer_->RemoveInput(&input);
RunLoopForMixer();
}
} // namespace
} // namespace media
} // namespace chromecast
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment