Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3924)

Unified Diff: chromecast/media/cma/backend/alsa/stream_mixer_alsa_unittest.cc

Issue 2847673002: [Chromecast] Complete PostProcessingPipeline changes (Closed)
Patch Set: Fix deps Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chromecast/media/cma/backend/alsa/stream_mixer_alsa_unittest.cc
diff --git a/chromecast/media/cma/backend/alsa/stream_mixer_alsa_unittest.cc b/chromecast/media/cma/backend/alsa/stream_mixer_alsa_unittest.cc
index 3537d8e17a16d50266fc28f2a59359130304f7fe..2bc53149f39f0f574d1a5bbd70aea7f7fe18dab5 100644
--- a/chromecast/media/cma/backend/alsa/stream_mixer_alsa_unittest.cc
+++ b/chromecast/media/cma/backend/alsa/stream_mixer_alsa_unittest.cc
@@ -7,14 +7,18 @@
#include <algorithm>
#include <cmath>
#include <limits>
+#include <unordered_map>
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_vector.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
#include "base/threading/thread_task_runner_handle.h"
+#include "base/values.h"
#include "chromecast/media/cma/backend/alsa/mock_alsa_wrapper.h"
+#include "chromecast/media/cma/backend/alsa/post_processing_pipeline.h"
#include "media/audio/audio_device_description.h"
#include "media/base/audio_bus.h"
#include "media/base/vector_math.h"
@@ -120,6 +124,51 @@ const int32_t kTestData[NUM_DATA_SETS][NUM_SAMPLES] = {
}
};
+// Compensate for integer arithmatic errors.
+const int kMaxDelayErrorUs = 2;
+
+const char kDelayModuleSolib[] = "delay.so";
+
+// Should match # of "processors" blocks below.
+const int kNumPostProcessors = 5;
+const char kTestPipelineJsonTemplate[] = R"json(
+{
+ "output_streams": [{
+ "streams": [ "default" ],
+ "processors": [{
+ "processor": "%s",
+ "config": { "delay": %d }
+ }]
+ }, {
+ "streams": [ "assistant-tts" ],
+ "processors": [{
+ "processor": "%s",
+ "config": { "delay": %d }
+ }]
+ }, {
+ "streams": [ "communications" ],
+ "processors": []
+ }],
+ "mix": {
+ "processors": [{
+ "processor": "%s",
+ "config": { "delay": %d }
+ }]
+ },
+ "linearize": {
+ "processors": [{
+ "processor": "%s",
+ "config": { "delay": %d }
+ }]
+ }
+}
+)json";
+
+const int kDefaultProcessorDelay = 10;
+const int kTtsProcessorDelay = 100;
+const int kMixProcessorDelay = 1000;
+const int kLinearizeProcessorDelay = 10000;
+
// Return a scoped pointer filled with the data laid out at |index| above.
std::unique_ptr<::media::AudioBus> GetTestData(size_t index) {
CHECK_LT(index, NUM_DATA_SETS);
@@ -131,9 +180,9 @@ std::unique_ptr<::media::AudioBus> GetTestData(size_t index) {
class MockInputQueue : public StreamMixerAlsa::InputQueue {
public:
- explicit MockInputQueue(int samples_per_second,
- const std::string& device_id =
- ::media::AudioDeviceDescription::kDefaultDeviceId)
+ MockInputQueue(int samples_per_second,
+ const std::string& device_id =
+ ::media::AudioDeviceDescription::kDefaultDeviceId)
: paused_(true),
samples_per_second_(samples_per_second),
max_read_size_(kTestMaxReadSize),
@@ -241,6 +290,75 @@ class MockInputQueue : public StreamMixerAlsa::InputQueue {
DISALLOW_COPY_AND_ASSIGN(MockInputQueue);
};
+class MockPostProcessor : public PostProcessingPipeline {
+ public:
+ MockPostProcessor(const std::string& name,
+ const base::ListValue* filter_description_list,
+ int channels)
+ : name_(name) {
+ CHECK(instances_.insert({name_, this}).second);
+
+ if (!filter_description_list) {
+ // This happens for PostProcessingPipeline with no post-processors.
+ return;
+ }
+
+ // Parse |filter_description_list| for parameters.
+ for (size_t i = 0; i < filter_description_list->GetSize(); ++i) {
+ const base::DictionaryValue* description_dict;
+ CHECK(filter_description_list->GetDictionary(i, &description_dict));
+ std::string solib;
+ CHECK(description_dict->GetString("processor", &solib));
+ // This will initially be called with the actual pipeline on creation.
+ // Ignore and wait for the call to ResetPostProcessorsForTest.
+ if (solib == kDelayModuleSolib) {
+ const base::DictionaryValue* processor_config_dict;
+ CHECK(
+ description_dict->GetDictionary("config", &processor_config_dict));
+ int module_delay;
+ CHECK(processor_config_dict->GetInteger("delay", &module_delay));
+ rendering_delay_ += module_delay;
+ processor_config_dict->GetBoolean("ringing", &ringing_);
+ }
+ }
+ ON_CALL(*this, ProcessFrames(_, _, _, _))
+ .WillByDefault(
+ testing::Invoke(this, &MockPostProcessor::DoProcessFrames));
+ }
+ ~MockPostProcessor() override { instances_.erase(name_); }
+ MOCK_METHOD4(ProcessFrames,
+ int(const std::vector<float*>& data,
+ int num_frames,
+ float current_volume,
+ bool is_silence));
+ bool SetSampleRate(int sample_rate) override { return false; }
+ bool IsRinging() override { return ringing_; }
+ int delay() { return rendering_delay_; }
+ std::string name() const { return name_; }
+
+ static std::unordered_map<std::string, MockPostProcessor*>* instances() {
+ return &instances_;
+ }
+
+ private:
+ int DoProcessFrames(const std::vector<float*>& data,
+ int num_frames,
+ float current_volume,
+ bool is_sience) {
+ return rendering_delay_;
+ }
+
+ static std::unordered_map<std::string, MockPostProcessor*> instances_;
+ std::string name_;
+ int rendering_delay_ = 0;
+ bool ringing_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(MockPostProcessor);
+};
+
+std::unordered_map<std::string, MockPostProcessor*>
+ MockPostProcessor::instances_;
+
// Given |inputs|, returns mixed audio data according to the mixing method used
// by the mixer.
std::unique_ptr<::media::AudioBus> GetMixedAudioData(
@@ -297,15 +415,45 @@ void CompareAudioData(const ::media::AudioBus& expected,
}
}
+// Check that MediaPipelineBackendAlsa::RenderingDelay.delay_microseconds is
+// within kMaxDelayErrorUs of |delay|
+MATCHER_P2(MatchDelay, delay, id, "") {
+ bool result = std::abs(arg.delay_microseconds - delay) < kMaxDelayErrorUs;
+ if (!result) {
+ LOG(ERROR) << "Expected delay_microseconds for " << id << " to be " << delay
+ << " but got " << arg.delay_microseconds;
+ }
+ return result;
+}
+
+// Convert a number of frames at kTestSamplesPerSecond to microseconds
+int64_t FramesToDelayUs(int64_t frames) {
+ return frames * base::Time::kMicrosecondsPerSecond / kTestSamplesPerSecond;
+}
+
} // namespace
+std::unique_ptr<PostProcessingPipeline> PostProcessingPipeline::Create(
+ const std::string& name,
+ const base::ListValue* filter_description_list,
+ int channels) {
+ return base::MakeUnique<testing::NiceMock<MockPostProcessor>>(
+ name, filter_description_list, channels);
+}
+
class StreamMixerAlsaTest : public testing::Test {
protected:
StreamMixerAlsaTest()
: message_loop_(new base::MessageLoop()),
mock_alsa_(new testing::NiceMock<MockAlsaWrapper>()) {
StreamMixerAlsa::MakeSingleThreadedForTest();
- StreamMixerAlsa::Get()->DisablePostProcessingForTest();
+ std::string test_pipeline_json = base::StringPrintf(
+ kTestPipelineJsonTemplate, kDelayModuleSolib, kDefaultProcessorDelay,
+ kDelayModuleSolib, kTtsProcessorDelay, kDelayModuleSolib,
+ kMixProcessorDelay, kDelayModuleSolib, kLinearizeProcessorDelay);
+ StreamMixerAlsa::Get()->ResetPostProcessorsForTest(test_pipeline_json);
+ CHECK_EQ(MockPostProcessor::instances()->size(),
+ static_cast<size_t>(kNumPostProcessors));
StreamMixerAlsa::Get()->SetAlsaWrapperForTest(base::WrapUnique(mock_alsa_));
}
@@ -803,5 +951,232 @@ TEST_F(StreamMixerAlsaTest, StuckStreamWithLowBuffer) {
mixer->WriteFramesForTest();
}
+#define EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(map, name, times, frames, \
+ silence) \
+ do { \
+ auto itr = map->find(name); \
+ CHECK(itr != map->end()) << "Could not find processor for " << name; \
+ EXPECT_CALL(*(itr->second), ProcessFrames(_, frames, _, silence)) \
+ .Times(times); \
+ } while (0);
+
+TEST_F(StreamMixerAlsaTest, PostProcessorDelayListedDeviceId) {
+ int common_delay = kMixProcessorDelay + kLinearizeProcessorDelay;
+ std::vector<testing::StrictMock<MockInputQueue>*> inputs;
+ std::vector<int64_t> delays;
+ inputs.push_back(new testing::StrictMock<MockInputQueue>(
+ kTestSamplesPerSecond, "default"));
+ delays.push_back(common_delay + kDefaultProcessorDelay);
+
+ inputs.push_back(new testing::StrictMock<MockInputQueue>(
+ kTestSamplesPerSecond, "communications"));
+ delays.push_back(common_delay);
+
+ inputs.push_back(new testing::StrictMock<MockInputQueue>(
+ kTestSamplesPerSecond, "assistant-tts"));
+ delays.push_back(common_delay + kTtsProcessorDelay);
+
+ // Convert delay from frames to microseconds.
+ std::transform(delays.begin(), delays.end(), delays.begin(),
+ &FramesToDelayUs);
+
+ const int kNumFrames = 10;
+ for (auto* input : inputs) {
+ input->SetMaxReadSize(kNumFrames);
+ input->SetPaused(false);
+ }
+
+ StreamMixerAlsa* mixer = StreamMixerAlsa::Get();
+ for (size_t i = 0; i < inputs.size(); ++i) {
+ EXPECT_CALL(*inputs[i], Initialize(_)).Times(1);
+ mixer->AddInput(base::WrapUnique(inputs[i]));
+ }
+
+ mock_alsa()->set_avail(4086);
+
+ auto* post_processors = MockPostProcessor::instances();
+ EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "default", 1,
+ kNumFrames, false);
+ EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "mix", 1, kNumFrames,
+ false);
+ EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "linearize", 1,
+ kNumFrames, false);
+ EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "communications", 1,
+ kNumFrames, false);
+ EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "assistant-tts", 1,
+ kNumFrames, false);
+
+ // Poll the inputs for data. Each input will get a different
+ // rendering delay based on their device type.
+ for (size_t i = 0; i < inputs.size(); ++i) {
+ EXPECT_CALL(*inputs[i], GetResampledData(_, kNumFrames));
+ EXPECT_CALL(*inputs[i], VolumeScaleAccumulate(_, _, kNumFrames, _))
+ .Times(kNumChannels);
+ EXPECT_CALL(*inputs[i], AfterWriteFrames(
+ MatchDelay(delays[i], inputs[i]->device_id())));
+ }
+ mixer->WriteFramesForTest();
+}
+
+TEST_F(StreamMixerAlsaTest, PostProcessorDelayUnlistedDevice) {
+ const std::string device_id = "not-a-device-id";
+ testing::StrictMock<MockInputQueue>* input =
+ new testing::StrictMock<MockInputQueue>(kTestSamplesPerSecond, device_id);
+
+ // Delay should be based on default processor
+ int64_t delay = FramesToDelayUs(
+ kDefaultProcessorDelay + kLinearizeProcessorDelay + kMixProcessorDelay);
+ const int kNumFrames = 10;
+ input->SetMaxReadSize(kNumFrames);
+ input->SetPaused(false);
+
+ auto* post_processors = MockPostProcessor::instances();
+ EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "default", 1,
+ kNumFrames, false);
+ EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "mix", 1, kNumFrames,
+ false);
+ EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "linearize", 1,
+ kNumFrames, false);
+ EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "communications", 0,
+ _, _);
+ EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "assistant-tts", 0,
+ _, _);
+
+ StreamMixerAlsa* mixer = StreamMixerAlsa::Get();
+ EXPECT_CALL(*input, Initialize(_));
+ mixer->AddInput(base::WrapUnique(input));
+
+ EXPECT_CALL(*input, GetResampledData(_, kNumFrames));
+ EXPECT_CALL(*input, VolumeScaleAccumulate(_, _, kNumFrames, _))
+ .Times(kNumChannels);
+ EXPECT_CALL(*input, AfterWriteFrames(MatchDelay(delay, device_id)));
+ mixer->WriteFramesForTest();
+}
+
+TEST_F(StreamMixerAlsaTest, PostProcessorRingingWithoutInput) {
+ const char kTestPipelineJson[] = R"json(
+{
+ "output_streams": [{
+ "streams": [ "default" ],
+ "processors": [{
+ "processor": "%s",
+ "config": { "delay": 0, "ringing": true}
+ }]
+ }, {
+ "streams": [ "assistant-tts" ],
+ "processors": [{
+ "processor": "%s",
+ "config": { "delay": 0, "ringing": true}
+ }]
+ }]
+}
+)json";
+
+ const int kNumFrames = 32;
+ testing::NiceMock<MockInputQueue>* input =
+ new testing::NiceMock<MockInputQueue>(kTestSamplesPerSecond, "default");
+ input->SetMaxReadSize(kNumFrames);
+ input->SetPaused(false);
+
+ StreamMixerAlsa* mixer = StreamMixerAlsa::Get();
+ std::string test_pipeline_json = base::StringPrintf(
+ kTestPipelineJson, kDelayModuleSolib, kDelayModuleSolib);
+ mixer->ResetPostProcessorsForTest(test_pipeline_json);
+ mixer->AddInput(base::WrapUnique(input));
+
+ // "mix" + "linearize" should be automatic
+ CHECK_EQ(MockPostProcessor::instances()->size(), 4u);
+
+ mock_alsa()->set_avail(4086);
+
+ auto* post_processors = MockPostProcessor::instances();
+ EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "default", 1,
+ kNumFrames, false);
+ EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "mix", 1, kNumFrames,
+ false);
+ EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "linearize", 1,
+ kNumFrames, false);
+ EXPECT_POSTPROCESSOR_CALL_PROCESSFRAMES(post_processors, "assistant-tts", 1,
+ kNumFrames, true);
+
+ mixer->WriteFramesForTest();
+}
+
+TEST_F(StreamMixerAlsaTest, PostProcessorProvidesDefaultPipeline) {
+ StreamMixerAlsa* mixer = StreamMixerAlsa::Get();
+ mixer->ResetPostProcessorsForTest("{}");
+
+ auto* instances = MockPostProcessor::instances();
+ CHECK(instances->find("default") != instances->end());
+ CHECK(instances->find("mix") != instances->end());
+ CHECK(instances->find("linearize") != instances->end());
+ CHECK_EQ(MockPostProcessor::instances()->size(), 3u);
+}
+
+TEST_F(StreamMixerAlsaTest, InvalidStreamTypeCrashes) {
+ const char json[] = R"json(
+{
+ "output_streams": [{
+ "streams": [ "foobar" ],
+ "processors": [{
+ "processor": "dont_care.so",
+ "config": { "delay": 0 }
+ }]
+ }]
+}
+)json";
+
+ EXPECT_DEATH(StreamMixerAlsa::Get()->ResetPostProcessorsForTest(json),
+ "foobar is not a stream type");
+}
+
+TEST_F(StreamMixerAlsaTest, BadJsonCrashes) {
+ const std::string json("{{");
+ EXPECT_DEATH(StreamMixerAlsa::Get()->ResetPostProcessorsForTest(json),
+ "Invalid JSON");
+}
+
+TEST_F(StreamMixerAlsaTest, MultiplePostProcessorsInOneStream) {
+ const char kJsonTemplate[] = R"json(
+{
+ "output_streams": [{
+ "streams": [ "default" ],
+ "processors": [{
+ "processor": "%s",
+ "config": { "delay": 10 }
+ }, {
+ "processor": "%s",
+ "config": { "delay": 100 }
+ }]
+ }],
+ "mix": {
+ "processors": [{
+ "processor": "%s",
+ "config": { "delay": 1000 }
+ }, {
+ "processor": "%s",
+ "config": { "delay": 10000 }
+ }]
+ }
+}
+)json";
+
+ std::string json =
+ base::StringPrintf(kJsonTemplate, kDelayModuleSolib, kDelayModuleSolib,
+ kDelayModuleSolib, kDelayModuleSolib);
+
+ StreamMixerAlsa* mixer = StreamMixerAlsa::Get();
+ mixer->ResetPostProcessorsForTest(json);
+
+ // "mix" + "linearize" + "default"
+ CHECK_EQ(MockPostProcessor::instances()->size(), 3u);
+
+ auto* post_processors = MockPostProcessor::instances();
+ CHECK_EQ(post_processors->find("default")->second->delay(), 110);
+ CHECK_EQ(post_processors->find("mix")->second->delay(), 11000);
+ CHECK_EQ(post_processors->find("linearize")->second->delay(), 0);
+ mixer->WriteFramesForTest();
+}
+
} // namespace media
} // namespace chromecast
« no previous file with comments | « chromecast/media/cma/backend/alsa/stream_mixer_alsa.cc ('k') | chromecast/public/media/audio_post_processor_shlib.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698