From 99a2c5dcb677d8833cba9256b4e31ce8e604b75e Mon Sep 17 00:00:00 2001 From: Alex Loiko Date: Tue, 27 Feb 2018 14:09:47 +0100 Subject: [PATCH] New test binary for the AudioMixer. Allows mixing up to 4 input streams. Useful for profiling and manual tests. Allows testing different combinations of input/output rates and number of channels. Reads and writes WAV files. Can also configure whether to use the Limiter component of the AudioMixer. Bug: webrtc:8925 Change-Id: Iaf4fee5284980f6ed01f4bb721e49bb1af8dd392 Reviewed-on: https://webrtc-review.googlesource.com/56842 Commit-Queue: Alex Loiko Reviewed-by: Alessio Bazzica Cr-Commit-Position: refs/heads/master@{#22209} --- modules/audio_mixer/BUILD.gn | 15 ++ modules/audio_mixer/audio_mixer_impl.cc | 5 + modules/audio_mixer/audio_mixer_impl.h | 2 + modules/audio_mixer/audio_mixer_test.cc | 179 ++++++++++++++++++++++++ modules/audio_mixer/frame_combiner.cc | 10 ++ modules/audio_mixer/frame_combiner.h | 4 +- 6 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 modules/audio_mixer/audio_mixer_test.cc diff --git a/modules/audio_mixer/BUILD.gn b/modules/audio_mixer/BUILD.gn index 9b74f5d0f6..644c8ab265 100644 --- a/modules/audio_mixer/BUILD.gn +++ b/modules/audio_mixer/BUILD.gn @@ -100,4 +100,19 @@ if (rtc_include_tests) { "../../test:test_support", ] } + + rtc_executable("audio_mixer_test") { + testonly = true + sources = [ + "audio_mixer_test.cc", + ] + + deps = [ + ":audio_mixer_impl", + "../../api/audio:audio_mixer_api", + "../../common_audio", + "../../rtc_base:rtc_base_approved", + "../../system_wrappers:system_wrappers_default", + ] + } } diff --git a/modules/audio_mixer/audio_mixer_impl.cc b/modules/audio_mixer/audio_mixer_impl.cc index de0f1c3064..14c4e35645 100644 --- a/modules/audio_mixer/audio_mixer_impl.cc +++ b/modules/audio_mixer/audio_mixer_impl.cc @@ -127,6 +127,11 @@ rtc::scoped_refptr AudioMixerImpl::Create( std::move(output_rate_calculator), use_limiter)); } +void AudioMixerImpl::SetLimiterType(FrameCombiner::LimiterType limiter_type) { + RTC_DCHECK_RUNS_SERIALIZED(&race_checker_); + frame_combiner_.SetLimiterType(limiter_type); +} + void AudioMixerImpl::Mix(size_t number_of_channels, AudioFrame* audio_frame_for_mixing) { RTC_DCHECK(number_of_channels == 1 || number_of_channels == 2); diff --git a/modules/audio_mixer/audio_mixer_impl.h b/modules/audio_mixer/audio_mixer_impl.h index 8d49e913e4..cb74ad7b27 100644 --- a/modules/audio_mixer/audio_mixer_impl.h +++ b/modules/audio_mixer/audio_mixer_impl.h @@ -55,6 +55,8 @@ class AudioMixerImpl : public AudioMixer { ~AudioMixerImpl() override; + void SetLimiterType(FrameCombiner::LimiterType limiter_type); + // AudioMixer functions bool AddSource(Source* audio_source) override; void RemoveSource(Source* audio_source) override; diff --git a/modules/audio_mixer/audio_mixer_test.cc b/modules/audio_mixer/audio_mixer_test.cc new file mode 100644 index 0000000000..614c47fda8 --- /dev/null +++ b/modules/audio_mixer/audio_mixer_test.cc @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include + +#include "api/audio/audio_mixer.h" +#include "common_audio/wav_file.h" +#include "modules/audio_mixer/audio_mixer_impl.h" +#include "modules/audio_mixer/default_output_rate_calculator.h" +#include "rtc_base/flags.h" + +DEFINE_bool(help, false, "Prints this message"); +DEFINE_int(sampling_rate, + 16000, + "Rate at which to mix (all input streams must have this rate)"); + +DEFINE_bool( + stereo, + false, + "Enable stereo (interleaved). Inputs need not be as this parameter."); + +DEFINE_int(limiter, 0, "0-2. No limiter, AGC1, AGC2"); +DEFINE_string(output_file, + "mixed_file.wav", + "File in which to store the mixed result."); +DEFINE_string(input_file_1, "", "First input. Default none."); +DEFINE_string(input_file_2, "", "Second input. Default none."); +DEFINE_string(input_file_3, "", "Third input. Default none."); +DEFINE_string(input_file_4, "", "Fourth input. Default none."); + +namespace webrtc { +namespace test { + +class FilePlayingSource : public AudioMixer::Source { + public: + explicit FilePlayingSource(std::string filename) + : wav_reader_(new WavReader(filename)), + sample_rate_hz_(wav_reader_->sample_rate()), + samples_per_channel_(sample_rate_hz_ / 100), + number_of_channels_(wav_reader_->num_channels()) {} + + AudioFrameInfo GetAudioFrameWithInfo(int target_rate_hz, + AudioFrame* frame) override { + frame->samples_per_channel_ = samples_per_channel_; + frame->num_channels_ = number_of_channels_; + frame->sample_rate_hz_ = target_rate_hz; + + RTC_CHECK_EQ(target_rate_hz, sample_rate_hz_); + + const size_t num_to_read = number_of_channels_ * samples_per_channel_; + const size_t num_read = + wav_reader_->ReadSamples(num_to_read, frame->mutable_data()); + + file_has_ended_ = num_to_read != num_read; + if (file_has_ended_) { + frame->Mute(); + } + return file_has_ended_ ? AudioFrameInfo::kMuted : AudioFrameInfo::kNormal; + } + + int Ssrc() const override { return 0; } + + int PreferredSampleRate() const override { return sample_rate_hz_; } + + bool FileHasEnded() const { return file_has_ended_; } + + std::string ToString() const { + std::stringstream ss; + ss << "{rate: " << sample_rate_hz_ << ", channels: " << number_of_channels_ + << ", samples_tot: " << wav_reader_->num_samples() << "}"; + return ss.str(); + } + + private: + std::unique_ptr wav_reader_; + int sample_rate_hz_; + int samples_per_channel_; + int number_of_channels_; + bool file_has_ended_ = false; +}; +} // namespace test +} // namespace webrtc + +namespace { + +const std::vector parse_input_files() { + std::vector result; + for (auto* x : {FLAG_input_file_1, FLAG_input_file_2, FLAG_input_file_3, + FLAG_input_file_4}) { + if (strcmp(x, "") != 0) { + result.push_back(x); + } + } + return result; +} +} // namespace + +int main(int argc, char* argv[]) { + rtc::FlagList::SetFlagsFromCommandLine(&argc, argv, false); + if (FLAG_help) { + rtc::FlagList::Print(nullptr, false); + return 0; + } + + rtc::scoped_refptr mixer( + webrtc::AudioMixerImpl::Create( + std::unique_ptr( + new webrtc::DefaultOutputRateCalculator()), + false)); + mixer->SetLimiterType( + static_cast(FLAG_limiter)); + + const std::vector input_files = parse_input_files(); + std::vector sources; + const int num_channels = FLAG_stereo ? 2 : 1; + for (auto input_file : input_files) { + sources.emplace_back(input_file); + } + + for (auto& source : sources) { + auto error = mixer->AddSource(&source); + RTC_CHECK(error); + } + + if (sources.empty()) { + std::cout << "Need at least one source!\n"; + rtc::FlagList::Print(nullptr, false); + return 1; + } + + const size_t sample_rate = sources[0].PreferredSampleRate(); + for (const auto& source : sources) { + RTC_CHECK_EQ(sample_rate, source.PreferredSampleRate()); + } + + // Print stats. + std::cout << "Limiting is: " + << (FLAG_limiter == 0 ? "off" + : (FLAG_limiter == 1 ? "agc" : "agc2")) + << "\n" + << "Channels: " << num_channels << "\n" + << "Rate: " << sample_rate << "\n" + << "Number of input streams: " << input_files.size() << "\n"; + for (const auto& source : sources) { + std::cout << "\t" << source.ToString() << "\n"; + } + std::cout << "Now mixing\n...\n"; + + webrtc::WavWriter wav_writer(FLAG_output_file, sample_rate, num_channels); + + webrtc::AudioFrame frame; + + bool all_streams_finished = false; + while (!all_streams_finished) { + mixer->Mix(num_channels, &frame); + RTC_CHECK_EQ(sample_rate / 100, frame.samples_per_channel_); + RTC_CHECK_EQ(sample_rate, frame.sample_rate_hz_); + RTC_CHECK_EQ(num_channels, frame.num_channels_); + wav_writer.WriteSamples(frame.data(), + num_channels * frame.samples_per_channel_); + + all_streams_finished = + std::all_of(sources.begin(), sources.end(), + [](const webrtc::test::FilePlayingSource& source) { + return source.FileHasEnded(); + }); + } + + std::cout << "Done!\n" << std::endl; +} diff --git a/modules/audio_mixer/frame_combiner.cc b/modules/audio_mixer/frame_combiner.cc index 6d26fdd8ba..faca0908ae 100644 --- a/modules/audio_mixer/frame_combiner.cc +++ b/modules/audio_mixer/frame_combiner.cc @@ -197,6 +197,16 @@ FrameCombiner::FrameCombiner(bool use_limiter) FrameCombiner::~FrameCombiner() = default; +void FrameCombiner::SetLimiterType(LimiterType limiter_type) { + // TODO(aleloi): remove this method and make limiter_type_ const + // when we have finished moved to APM-AGC2. + limiter_type_ = limiter_type; + if (limiter_type_ == LimiterType::kApmAgcLimiter && + apm_agc_limiter_ == nullptr) { + apm_agc_limiter_ = CreateLimiter(); + } +} + void FrameCombiner::Combine(const std::vector& mix_list, size_t number_of_channels, int sample_rate, diff --git a/modules/audio_mixer/frame_combiner.h b/modules/audio_mixer/frame_combiner.h index 3a603226a6..3d43128d6a 100644 --- a/modules/audio_mixer/frame_combiner.h +++ b/modules/audio_mixer/frame_combiner.h @@ -29,6 +29,8 @@ class FrameCombiner { explicit FrameCombiner(bool use_limiter); ~FrameCombiner(); + void SetLimiterType(LimiterType limiter_type); + // Combine several frames into one. Assumes sample_rate, // samples_per_channel of the input frames match the parameters. The // parameters 'number_of_channels' and 'sample_rate' are needed @@ -42,7 +44,7 @@ class FrameCombiner { AudioFrame* audio_frame_for_mixing); private: - const LimiterType limiter_type_; + LimiterType limiter_type_; std::unique_ptr apm_agc_limiter_; std::unique_ptr data_dumper_; FixedGainController apm_agc2_limiter_;