From 2cf8eb9f78bb7ca19f821c18eabff98771741dc2 Mon Sep 17 00:00:00 2001 From: Artem Titov Date: Fri, 30 Jun 2023 15:26:09 +0200 Subject: [PATCH] Reland "Migrate TestAudioDeviceModule on AudioDeviceModuleImpl" This CL will add AudioDeviceBuffer into the SUT increasing test coverage for audio quality regression detection. This reverts commit b035dcc0a274e6cdde3e0fc465244bc0e9e3d70e. Reason for revert: reland with a fix Original change's description: > Revert "Reland "Migrate TestAudioDeviceModule on AudioDeviceModuleImpl"" > > This reverts commit eeae96299784515f573379a64655eb07a5973a3a. > > Reason for revert: breaks WebRTC Chromium FYI ios-device > https://ci.chromium.org/ui/p/chromium/builders/webrtc.fyi/WebRTC%20Chromium%20FYI%20ios-device/14896/overview > > Original change's description: > > Reland "Migrate TestAudioDeviceModule on AudioDeviceModuleImpl" > > > > This reverts commit 69c8d3c843326aff9dee32cc639741c1cd7f8ae9. > > > > Reason for revert: Reland with a fix > > > > Original change's description: > > > Revert "Migrate TestAudioDeviceModule on AudioDeviceModuleImpl" > > > > > > This reverts commit e42bf81486d2f08b6dcbf1442287202e937ce52b. > > > > > > Reason for revert: Breaks iOS simulator bots and thus blocks chromium roll, https://chromium-review.googlesource.com/c/chromium/src/+/4433814 > > > > > > Original change's description: > > > > Migrate TestAudioDeviceModule on AudioDeviceModuleImpl > > > > > > > > Bug: b/272350185 > > > > Change-Id: Ia3d85d6fa3b0d4809e987a39d60d3eb022687132 > > > > Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/300363 > > > > Commit-Queue: Artem Titov > > > > Reviewed-by: Henrik Andreassson > > > > Cr-Commit-Position: refs/heads/main@{#39877} > > > > > > Bug: b/272350185 > > > Change-Id: I1e3b542fc1278797f283afedeae01cbb7412d353 > > > No-Presubmit: true > > > No-Tree-Checks: true > > > No-Try: true > > > Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/301701 > > > Commit-Queue: Jeremy Leconte > > > Bot-Commit: rubber-stamper@appspot.gserviceaccount.com > > > Reviewed-by: Jeremy Leconte > > > Auto-Submit: Christoffer Jansson > > > Owners-Override: Christoffer Jansson > > > Cr-Commit-Position: refs/heads/main@{#39881} > > > > Bug: b/272350185 > > Change-Id: I809466306b2e1fd54c44b90311059c98a53ef8ee > > Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/301704 > > Reviewed-by: Henrik Andreassson > > Reviewed-by: Mirko Bonadei > > Commit-Queue: Artem Titov > > Cr-Commit-Position: refs/heads/main@{#39936} > > Bug: b/272350185 > Change-Id: If0a10717bf14a0a618e52728fc3a61b9c55f3bd2 > No-Presubmit: true > No-Tree-Checks: true > No-Try: true > Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/303460 > Commit-Queue: Jeremy Leconte > Owners-Override: Jeremy Leconte > Bot-Commit: rubber-stamper@appspot.gserviceaccount.com > Cr-Commit-Position: refs/heads/main@{#39947} Bug: b/272350185 Change-Id: I7cf7c6bc25561f4eb722957f318c2af9ce20726d Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/311101 Reviewed-by: Mirko Bonadei Reviewed-by: Tomas Gunnarsson Commit-Queue: Artem Titov Cr-Commit-Position: refs/heads/main@{#40387} --- modules/audio_device/BUILD.gn | 18 +- modules/audio_device/audio_device_buffer.cc | 6 +- modules/audio_device/audio_device_buffer.h | 6 +- modules/audio_device/audio_device_impl.cc | 18 ++ modules/audio_device/audio_device_impl.h | 13 +- .../audio_device/include/test_audio_device.cc | 193 +++--------- .../audio_device/include/test_audio_device.h | 24 +- .../include/test_audio_device_unittest.cc | 193 +++++++++++- .../audio_device/test_audio_device_impl.cc | 212 ++++++++++++++ modules/audio_device/test_audio_device_impl.h | 198 +++++++++++++ .../test_audio_device_impl_test.cc | 275 ++++++++++++++++++ 11 files changed, 975 insertions(+), 181 deletions(-) create mode 100644 modules/audio_device/test_audio_device_impl.cc create mode 100644 modules/audio_device/test_audio_device_impl.h create mode 100644 modules/audio_device/test_audio_device_impl_test.cc diff --git a/modules/audio_device/BUILD.gn b/modules/audio_device/BUILD.gn index ee71f46e15..162981c427 100644 --- a/modules/audio_device/BUILD.gn +++ b/modules/audio_device/BUILD.gn @@ -186,14 +186,20 @@ if (!build_with_chromium) { sources = [ "include/test_audio_device.cc", "include/test_audio_device.h", + "test_audio_device_impl.cc", + "test_audio_device_impl.h", ] deps = [ ":audio_device_api", + ":audio_device_buffer", ":audio_device_default", + ":audio_device_generic", + ":audio_device_impl", "../../api:array_view", "../../api:make_ref_counted", "../../api:scoped_refptr", "../../api/task_queue", + "../../api/units:time_delta", "../../common_audio", "../../rtc_base:buffer", "../../rtc_base:checks", @@ -208,7 +214,10 @@ if (!build_with_chromium) { "../../rtc_base/synchronization:mutex", "../../rtc_base/task_utils:repeating_task", ] - absl_deps = [ "//third_party/abseil-cpp/absl/strings" ] + absl_deps = [ + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + ] } } @@ -270,6 +279,7 @@ rtc_library("audio_device_impl") { "../../api:scoped_refptr", "../../api:sequence_checker", "../../api/task_queue", + "../../api/units:time_delta", "../../common_audio", "../../common_audio:common_audio_c", "../../rtc_base:buffer", @@ -295,6 +305,7 @@ rtc_library("audio_device_impl") { absl_deps = [ "//third_party/abseil-cpp/absl/base:core_headers", "//third_party/abseil-cpp/absl/strings:strings", + "//third_party/abseil-cpp/absl/types:optional", ] if (rtc_include_internal_audio_device && is_ios) { deps += [ "../../sdk:audio_device" ] @@ -451,10 +462,12 @@ if (rtc_include_tests && !build_with_chromium) { sources = [ "fine_audio_buffer_unittest.cc", "include/test_audio_device_unittest.cc", + "test_audio_device_impl_test.cc", ] deps = [ ":audio_device", ":audio_device_buffer", + ":audio_device_generic", ":audio_device_impl", ":mock_audio_device", ":test_audio_device_module", @@ -463,6 +476,8 @@ if (rtc_include_tests && !build_with_chromium) { "../../api:sequence_checker", "../../api/task_queue", "../../api/task_queue:default_task_queue_factory", + "../../api/units:time_delta", + "../../api/units:timestamp", "../../common_audio", "../../rtc_base:buffer", "../../rtc_base:checks", @@ -477,6 +492,7 @@ if (rtc_include_tests && !build_with_chromium) { "../../system_wrappers", "../../test:fileutils", "../../test:test_support", + "../../test/time_controller", ] absl_deps = [ "//third_party/abseil-cpp/absl/strings", diff --git a/modules/audio_device/audio_device_buffer.cc b/modules/audio_device/audio_device_buffer.cc index 91964d1edd..f1bd8e823b 100644 --- a/modules/audio_device/audio_device_buffer.cc +++ b/modules/audio_device/audio_device_buffer.cc @@ -41,7 +41,8 @@ static const size_t kMinValidCallTimeTimeInMilliseconds = static const double k2Pi = 6.28318530717959; #endif -AudioDeviceBuffer::AudioDeviceBuffer(TaskQueueFactory* task_queue_factory) +AudioDeviceBuffer::AudioDeviceBuffer(TaskQueueFactory* task_queue_factory, + bool create_detached) : task_queue_(task_queue_factory->CreateTaskQueue( kTimerQueueName, TaskQueueFactory::Priority::NORMAL)), @@ -67,6 +68,9 @@ AudioDeviceBuffer::AudioDeviceBuffer(TaskQueueFactory* task_queue_factory) phase_ = 0.0; RTC_LOG(LS_WARNING) << "AUDIO_DEVICE_PLAYS_SINUS_TONE is defined!"; #endif + if (create_detached) { + main_thread_checker_.Detach(); + } } AudioDeviceBuffer::~AudioDeviceBuffer() { diff --git a/modules/audio_device/audio_device_buffer.h b/modules/audio_device/audio_device_buffer.h index f7c4ecdcff..1260a24c61 100644 --- a/modules/audio_device/audio_device_buffer.h +++ b/modules/audio_device/audio_device_buffer.h @@ -78,7 +78,11 @@ class AudioDeviceBuffer { int16_t max_play_level = 0; }; - explicit AudioDeviceBuffer(TaskQueueFactory* task_queue_factory); + // If `create_detached` is true, the created buffer can be used on another + // thread compared to the one on which it was created. It's useful for + // testing. + explicit AudioDeviceBuffer(TaskQueueFactory* task_queue_factory, + bool create_detached = false); virtual ~AudioDeviceBuffer(); int32_t RegisterAudioCallback(AudioTransport* audio_callback); diff --git a/modules/audio_device/audio_device_impl.cc b/modules/audio_device/audio_device_impl.cc index 9da9c62381..c177d7df71 100644 --- a/modules/audio_device/audio_device_impl.cc +++ b/modules/audio_device/audio_device_impl.cc @@ -124,6 +124,17 @@ AudioDeviceModuleImpl::AudioDeviceModuleImpl( RTC_DLOG(LS_INFO) << __FUNCTION__; } +AudioDeviceModuleImpl::AudioDeviceModuleImpl( + AudioLayer audio_layer, + std::unique_ptr audio_device, + TaskQueueFactory* task_queue_factory, + bool create_detached) + : audio_layer_(audio_layer), + audio_device_buffer_(task_queue_factory, create_detached), + audio_device_(std::move(audio_device)) { + RTC_DLOG(LS_INFO) << __FUNCTION__; +} + int32_t AudioDeviceModuleImpl::CheckPlatform() { RTC_DLOG(LS_INFO) << __FUNCTION__; // Ensure that the current platform is supported @@ -143,6 +154,9 @@ int32_t AudioDeviceModuleImpl::CheckPlatform() { #elif defined(WEBRTC_MAC) platform = kPlatformMac; RTC_LOG(LS_INFO) << "current platform is Mac"; +#elif defined(WEBRTC_FUCHSIA) + platform = kPlatformFuchsia; + RTC_LOG(LS_INFO) << "current platform is Fuchsia"; #endif if (platform == kPlatformNotSupported) { RTC_LOG(LS_ERROR) @@ -156,6 +170,10 @@ int32_t AudioDeviceModuleImpl::CheckPlatform() { int32_t AudioDeviceModuleImpl::CreatePlatformSpecificObjects() { RTC_LOG(LS_INFO) << __FUNCTION__; + if (audio_device_ != nullptr) { + RTC_LOG(LS_INFO) << "Reusing provided audio device"; + return 0; + } // Dummy ADM implementations if build flags are set. #if defined(WEBRTC_DUMMY_AUDIO_BUILD) audio_device_.reset(new AudioDeviceDummy()); diff --git a/modules/audio_device/audio_device_impl.h b/modules/audio_device/audio_device_impl.h index 1737b4613d..46d91a46c8 100644 --- a/modules/audio_device/audio_device_impl.h +++ b/modules/audio_device/audio_device_impl.h @@ -34,7 +34,12 @@ class AudioDeviceModuleImpl : public AudioDeviceModuleForTest { kPlatformLinux = 3, kPlatformMac = 4, kPlatformAndroid = 5, - kPlatformIOS = 6 + kPlatformIOS = 6, + // Fuchsia isn't fully supported, as there is no implementation for + // AudioDeviceGeneric which will be created for Fuchsia, so + // `CreatePlatformSpecificObjects()` call will fail unless usable + // implementation will be provided by the user. + kPlatformFuchsia = 7, }; int32_t CheckPlatform(); @@ -43,6 +48,12 @@ class AudioDeviceModuleImpl : public AudioDeviceModuleForTest { AudioDeviceModuleImpl(AudioLayer audio_layer, TaskQueueFactory* task_queue_factory); + // If `create_detached` is true, created ADM can be used on another thread + // compared to the one on which it was created. It's useful for testing. + AudioDeviceModuleImpl(AudioLayer audio_layer, + std::unique_ptr audio_device, + TaskQueueFactory* task_queue_factory, + bool create_detached); ~AudioDeviceModuleImpl() override; // Retrieve the currently utilized audio layer diff --git a/modules/audio_device/include/test_audio_device.cc b/modules/audio_device/include/test_audio_device.cc index 76406448a0..fa4f3fe9d3 100644 --- a/modules/audio_device/include/test_audio_device.cc +++ b/modules/audio_device/include/test_audio_device.cc @@ -22,7 +22,9 @@ #include "api/array_view.h" #include "api/make_ref_counted.h" #include "common_audio/wav_file.h" +#include "modules/audio_device/audio_device_impl.h" #include "modules/audio_device/include/audio_device_default.h" +#include "modules/audio_device/test_audio_device_impl.h" #include "rtc_base/buffer.h" #include "rtc_base/checks.h" #include "rtc_base/event.h" @@ -43,164 +45,23 @@ namespace { constexpr int kFrameLengthUs = 10000; constexpr int kFramesPerSecond = rtc::kNumMicrosecsPerSec / kFrameLengthUs; -// TestAudioDeviceModule implements an AudioDevice module that can act both as a -// capturer and a renderer. It will use 10ms audio frames. -class TestAudioDeviceModuleImpl - : public webrtc_impl::AudioDeviceModuleDefault { +class TestAudioDeviceModuleImpl : public AudioDeviceModuleImpl { public: - // Creates a new TestAudioDeviceModule. When capturing or playing, 10 ms audio - // frames will be processed every 10ms / `speed`. - // `capturer` is an object that produces audio data. Can be nullptr if this - // device is never used for recording. - // `renderer` is an object that receives audio data that would have been - // played out. Can be nullptr if this device is never used for playing. - // Use one of the Create... functions to get these instances. - TestAudioDeviceModuleImpl(TaskQueueFactory* task_queue_factory, - std::unique_ptr capturer, - std::unique_ptr renderer, - float speed = 1) - : task_queue_factory_(task_queue_factory), - capturer_(std::move(capturer)), - renderer_(std::move(renderer)), - process_interval_us_(kFrameLengthUs / speed), - audio_callback_(nullptr), - rendering_(false), - capturing_(false) { - auto good_sample_rate = [](int sr) { - return sr == 8000 || sr == 16000 || sr == 32000 || sr == 44100 || - sr == 48000; - }; + TestAudioDeviceModuleImpl( + TaskQueueFactory* task_queue_factory, + std::unique_ptr capturer, + std::unique_ptr renderer, + float speed = 1) + : AudioDeviceModuleImpl( + AudioLayer::kDummyAudio, + std::make_unique(task_queue_factory, + std::move(capturer), + std::move(renderer), + speed), + task_queue_factory, + /*create_detached=*/true) {} - if (renderer_) { - const int sample_rate = renderer_->SamplingFrequency(); - playout_buffer_.resize( - SamplesPerFrame(sample_rate) * renderer_->NumChannels(), 0); - RTC_CHECK(good_sample_rate(sample_rate)); - } - if (capturer_) { - RTC_CHECK(good_sample_rate(capturer_->SamplingFrequency())); - } - } - - ~TestAudioDeviceModuleImpl() override { - StopPlayout(); - StopRecording(); - } - - int32_t Init() override { - task_queue_ = - std::make_unique(task_queue_factory_->CreateTaskQueue( - "TestAudioDeviceModuleImpl", TaskQueueFactory::Priority::NORMAL)); - - RepeatingTaskHandle::Start(task_queue_->Get(), [this]() { - ProcessAudio(); - return TimeDelta::Micros(process_interval_us_); - }); - return 0; - } - - int32_t RegisterAudioCallback(AudioTransport* callback) override { - MutexLock lock(&lock_); - RTC_DCHECK(callback || audio_callback_); - audio_callback_ = callback; - return 0; - } - - int32_t StartPlayout() override { - MutexLock lock(&lock_); - RTC_CHECK(renderer_); - rendering_ = true; - return 0; - } - - int32_t StopPlayout() override { - MutexLock lock(&lock_); - rendering_ = false; - return 0; - } - - int32_t StartRecording() override { - MutexLock lock(&lock_); - RTC_CHECK(capturer_); - capturing_ = true; - return 0; - } - - int32_t StopRecording() override { - MutexLock lock(&lock_); - capturing_ = false; - return 0; - } - - bool Playing() const override { - MutexLock lock(&lock_); - return rendering_; - } - - bool Recording() const override { - MutexLock lock(&lock_); - return capturing_; - } - - // Blocks forever until the Recorder stops producing data. - void WaitForRecordingEnd() override { - done_capturing_.Wait(rtc::Event::kForever); - } - - private: - void ProcessAudio() { - MutexLock lock(&lock_); - if (capturing_) { - // Capture 10ms of audio. 2 bytes per sample. - const bool keep_capturing = capturer_->Capture(&recording_buffer_); - uint32_t new_mic_level = 0; - if (recording_buffer_.size() > 0) { - audio_callback_->RecordedDataIsAvailable( - recording_buffer_.data(), - recording_buffer_.size() / capturer_->NumChannels(), - 2 * capturer_->NumChannels(), capturer_->NumChannels(), - capturer_->SamplingFrequency(), /*totalDelayMS=*/0, - /*clockDrift=*/0, - /*currentMicLevel=*/0, /*keyPressed=*/false, new_mic_level, - absl::make_optional(rtc::TimeNanos())); - } - if (!keep_capturing) { - capturing_ = false; - done_capturing_.Set(); - } - } - if (rendering_) { - size_t samples_out = 0; - int64_t elapsed_time_ms = -1; - int64_t ntp_time_ms = -1; - const int sampling_frequency = renderer_->SamplingFrequency(); - audio_callback_->NeedMorePlayData( - SamplesPerFrame(sampling_frequency), 2 * renderer_->NumChannels(), - renderer_->NumChannels(), sampling_frequency, playout_buffer_.data(), - samples_out, &elapsed_time_ms, &ntp_time_ms); - const bool keep_rendering = renderer_->Render( - rtc::ArrayView(playout_buffer_.data(), samples_out)); - if (!keep_rendering) { - rendering_ = false; - done_rendering_.Set(); - } - } - } - TaskQueueFactory* const task_queue_factory_; - const std::unique_ptr capturer_ RTC_GUARDED_BY(lock_); - const std::unique_ptr renderer_ RTC_GUARDED_BY(lock_); - const int64_t process_interval_us_; - - mutable Mutex lock_; - AudioTransport* audio_callback_ RTC_GUARDED_BY(lock_); - bool rendering_ RTC_GUARDED_BY(lock_); - bool capturing_ RTC_GUARDED_BY(lock_); - rtc::Event done_rendering_; - rtc::Event done_capturing_; - - std::vector playout_buffer_ RTC_GUARDED_BY(lock_); - rtc::BufferT recording_buffer_ RTC_GUARDED_BY(lock_); - std::unique_ptr task_queue_; + ~TestAudioDeviceModuleImpl() override = default; }; // A fake capturer that generates pulses with random samples between @@ -444,8 +305,26 @@ rtc::scoped_refptr TestAudioDeviceModule::Create( std::unique_ptr capturer, std::unique_ptr renderer, float speed) { - return rtc::make_ref_counted( + auto audio_device = rtc::make_ref_counted( task_queue_factory, std::move(capturer), std::move(renderer), speed); + + // Ensure that the current platform is supported. + if (audio_device->CheckPlatform() == -1) { + return nullptr; + } + + // Create the platform-dependent implementation. + if (audio_device->CreatePlatformSpecificObjects() == -1) { + return nullptr; + } + + // Ensure that the generic audio buffer can communicate with the platform + // specific parts. + if (audio_device->AttachAudioBuffer() == -1) { + return nullptr; + } + + return audio_device; } std::unique_ptr diff --git a/modules/audio_device/include/test_audio_device.h b/modules/audio_device/include/test_audio_device.h index 87ba9cfbae..751eae6b7b 100644 --- a/modules/audio_device/include/test_audio_device.h +++ b/modules/audio_device/include/test_audio_device.h @@ -29,9 +29,10 @@ namespace webrtc { // This is test API and is in development, so it can be changed/removed without // notice. -// TestAudioDeviceModule implements an AudioDevice module that can act both as a -// capturer and a renderer. It will use 10ms audio frames. -class TestAudioDeviceModule : public AudioDeviceModule { +// This class exists for historical reasons. For now it only contains static +// methods to create test AudioDeviceModule. Implementation details of that +// module are considered private. This class isn't intended to be instantiated. +class TestAudioDeviceModule { public: // Returns the number of samples that Capturers and Renderers with this // sampling frequency will work with every time Capture or Render is called. @@ -73,8 +74,6 @@ class TestAudioDeviceModule : public AudioDeviceModule { virtual void SetMaxAmplitude(int16_t amplitude) = 0; }; - ~TestAudioDeviceModule() override {} - // Creates a new TestAudioDeviceModule. When capturing or playing, 10 ms audio // frames will be processed every 10ms / `speed`. // `capturer` is an object that produces audio data. Can be nullptr if this @@ -132,19 +131,8 @@ class TestAudioDeviceModule : public AudioDeviceModule { int sampling_frequency_in_hz, int num_channels = 1); - int32_t Init() override = 0; - int32_t RegisterAudioCallback(AudioTransport* callback) override = 0; - - int32_t StartPlayout() override = 0; - int32_t StopPlayout() override = 0; - int32_t StartRecording() override = 0; - int32_t StopRecording() override = 0; - - bool Playing() const override = 0; - bool Recording() const override = 0; - - // Blocks forever until the Recorder stops producing data. - virtual void WaitForRecordingEnd() = 0; + private: + TestAudioDeviceModule() = default; }; } // namespace webrtc diff --git a/modules/audio_device/include/test_audio_device_unittest.cc b/modules/audio_device/include/test_audio_device_unittest.cc index 2975b11325..54ede808b1 100644 --- a/modules/audio_device/include/test_audio_device_unittest.cc +++ b/modules/audio_device/include/test_audio_device_unittest.cc @@ -12,17 +12,26 @@ #include #include +#include +#include +#include "absl/types/optional.h" #include "api/array_view.h" +#include "api/task_queue/task_queue_factory.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" #include "common_audio/wav_file.h" #include "common_audio/wav_header.h" +#include "modules/audio_device/include/audio_device_defines.h" +#include "rtc_base/checks.h" #include "rtc_base/logging.h" +#include "rtc_base/synchronization/mutex.h" #include "test/gmock.h" #include "test/gtest.h" #include "test/testsupport/file_utils.h" +#include "test/time_controller/simulated_time_controller.h" namespace webrtc { - namespace { void RunTest(const std::vector& input_samples, @@ -64,7 +73,6 @@ void RunTest(const std::vector& input_samples, remove(output_filename.c_str()); } -} // namespace TEST(BoundedWavFileWriterTest, NoSilence) { static const std::vector kInputSamples = { @@ -189,4 +197,185 @@ TEST(PulsedNoiseCapturerTest, SetMaxAmplitude) { EXPECT_GT(max_sample, kAmplitude); } +using ::testing::ElementsAre; + +constexpr Timestamp kStartTime = Timestamp::Millis(10000); + +class TestAudioTransport : public AudioTransport { + public: + enum class Mode { kPlaying, kRecording }; + + explicit TestAudioTransport(Mode mode) : mode_(mode) {} + ~TestAudioTransport() override = default; + + int32_t RecordedDataIsAvailable( + const void* audioSamples, + size_t samples_per_channel, + size_t bytes_per_sample, + size_t number_of_channels, + uint32_t samples_per_second, + uint32_t total_delay_ms, + int32_t clock_drift, + uint32_t current_mic_level, + bool key_pressed, + uint32_t& new_mic_level, + absl::optional estimated_capture_time_ns) override { + new_mic_level = 1; + + if (mode_ != Mode::kRecording) { + EXPECT_TRUE(false) + << "NeedMorePlayData mustn't be called when mode isn't kRecording"; + return -1; + } + + MutexLock lock(&mutex_); + samples_per_channel_.push_back(samples_per_channel); + number_of_channels_.push_back(number_of_channels); + bytes_per_sample_.push_back(bytes_per_sample); + samples_per_second_.push_back(samples_per_second); + return 0; + } + + int32_t NeedMorePlayData(size_t samples_per_channel, + size_t bytes_per_sample, + size_t number_of_channels, + uint32_t samples_per_second, + void* audio_samples, + size_t& samples_out, + int64_t* elapsed_time_ms, + int64_t* ntp_time_ms) override { + const size_t num_bytes = samples_per_channel * number_of_channels; + std::memset(audio_samples, 1, num_bytes); + samples_out = samples_per_channel * number_of_channels; + *elapsed_time_ms = 0; + *ntp_time_ms = 0; + + if (mode_ != Mode::kPlaying) { + EXPECT_TRUE(false) + << "NeedMorePlayData mustn't be called when mode isn't kPlaying"; + return -1; + } + + MutexLock lock(&mutex_); + samples_per_channel_.push_back(samples_per_channel); + number_of_channels_.push_back(number_of_channels); + bytes_per_sample_.push_back(bytes_per_sample); + samples_per_second_.push_back(samples_per_second); + return 0; + } + + int32_t RecordedDataIsAvailable(const void* audio_samples, + size_t samples_per_channel, + size_t bytes_per_sample, + size_t number_of_channels, + uint32_t samples_per_second, + uint32_t total_delay_ms, + int32_t clockDrift, + uint32_t current_mic_level, + bool key_pressed, + uint32_t& new_mic_level) override { + RTC_CHECK(false) << "This methods should be never executed"; + } + + void PullRenderData(int bits_per_sample, + int sample_rate, + size_t number_of_channels, + size_t number_of_frames, + void* audio_data, + int64_t* elapsed_time_ms, + int64_t* ntp_time_ms) override { + RTC_CHECK(false) << "This methods should be never executed"; + } + + std::vector samples_per_channel() const { + MutexLock lock(&mutex_); + return samples_per_channel_; + } + std::vector number_of_channels() const { + MutexLock lock(&mutex_); + return number_of_channels_; + } + std::vector bytes_per_sample() const { + MutexLock lock(&mutex_); + return bytes_per_sample_; + } + std::vector samples_per_second() const { + MutexLock lock(&mutex_); + return samples_per_second_; + } + + private: + const Mode mode_; + + mutable Mutex mutex_; + std::vector samples_per_channel_ RTC_GUARDED_BY(mutex_); + std::vector number_of_channels_ RTC_GUARDED_BY(mutex_); + std::vector bytes_per_sample_ RTC_GUARDED_BY(mutex_); + std::vector samples_per_second_ RTC_GUARDED_BY(mutex_); +}; + +TEST(TestAudioDeviceModuleTest, CreatedADMCanRecord) { + GlobalSimulatedTimeController time_controller(kStartTime); + TestAudioTransport audio_transport(TestAudioTransport::Mode::kRecording); + std::unique_ptr capturer = + TestAudioDeviceModule::CreatePulsedNoiseCapturer( + /*max_amplitude=*/1000, + /*sampling_frequency_in_hz=*/48000, /*num_channels=*/2); + + rtc::scoped_refptr adm = TestAudioDeviceModule::Create( + time_controller.GetTaskQueueFactory(), std::move(capturer), + /*renderer=*/nullptr); + + ASSERT_EQ(adm->RegisterAudioCallback(&audio_transport), 0); + ASSERT_EQ(adm->Init(), 0); + + EXPECT_FALSE(adm->RecordingIsInitialized()); + ASSERT_EQ(adm->InitRecording(), 0); + EXPECT_TRUE(adm->RecordingIsInitialized()); + ASSERT_EQ(adm->StartRecording(), 0); + time_controller.AdvanceTime(TimeDelta::Millis(10)); + ASSERT_TRUE(adm->Recording()); + time_controller.AdvanceTime(TimeDelta::Millis(10)); + ASSERT_EQ(adm->StopRecording(), 0); + + EXPECT_THAT(audio_transport.samples_per_channel(), + ElementsAre(480, 480, 480)); + EXPECT_THAT(audio_transport.number_of_channels(), ElementsAre(2, 2, 2)); + EXPECT_THAT(audio_transport.bytes_per_sample(), ElementsAre(4, 4, 4)); + EXPECT_THAT(audio_transport.samples_per_second(), + ElementsAre(48000, 48000, 48000)); +} + +TEST(TestAudioDeviceModuleTest, CreatedADMCanPlay) { + GlobalSimulatedTimeController time_controller(kStartTime); + TestAudioTransport audio_transport(TestAudioTransport::Mode::kPlaying); + std::unique_ptr renderer = + TestAudioDeviceModule::CreateDiscardRenderer( + /*sampling_frequency_in_hz=*/48000, /*num_channels=*/2); + + rtc::scoped_refptr adm = + TestAudioDeviceModule::Create(time_controller.GetTaskQueueFactory(), + /*capturer=*/nullptr, std::move(renderer)); + + ASSERT_EQ(adm->RegisterAudioCallback(&audio_transport), 0); + ASSERT_EQ(adm->Init(), 0); + + EXPECT_FALSE(adm->PlayoutIsInitialized()); + ASSERT_EQ(adm->InitPlayout(), 0); + EXPECT_TRUE(adm->PlayoutIsInitialized()); + ASSERT_EQ(adm->StartPlayout(), 0); + time_controller.AdvanceTime(TimeDelta::Millis(10)); + ASSERT_TRUE(adm->Playing()); + time_controller.AdvanceTime(TimeDelta::Millis(10)); + ASSERT_EQ(adm->StopPlayout(), 0); + + EXPECT_THAT(audio_transport.samples_per_channel(), + ElementsAre(480, 480, 480)); + EXPECT_THAT(audio_transport.number_of_channels(), ElementsAre(2, 2, 2)); + EXPECT_THAT(audio_transport.bytes_per_sample(), ElementsAre(4, 4, 4)); + EXPECT_THAT(audio_transport.samples_per_second(), + ElementsAre(48000, 48000, 48000)); +} + +} // namespace } // namespace webrtc diff --git a/modules/audio_device/test_audio_device_impl.cc b/modules/audio_device/test_audio_device_impl.cc new file mode 100644 index 0000000000..c5de40c79a --- /dev/null +++ b/modules/audio_device/test_audio_device_impl.cc @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2023 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 "modules/audio_device/test_audio_device_impl.h" + +#include +#include + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/task_queue/task_queue_factory.h" +#include "api/units/time_delta.h" +#include "modules/audio_device/include/test_audio_device.h" +#include "rtc_base/checks.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/task_queue.h" +#include "rtc_base/task_utils/repeating_task.h" + +namespace webrtc { +namespace { + +constexpr int kFrameLengthUs = 10000; + +} + +TestAudioDevice::TestAudioDevice( + TaskQueueFactory* task_queue_factory, + std::unique_ptr capturer, + std::unique_ptr renderer, + float speed) + : task_queue_factory_(task_queue_factory), + capturer_(std::move(capturer)), + renderer_(std::move(renderer)), + process_interval_us_(kFrameLengthUs / speed), + audio_buffer_(nullptr), + rendering_(false), + capturing_(false) { + auto good_sample_rate = [](int sr) { + return sr == 8000 || sr == 16000 || sr == 32000 || sr == 44100 || + sr == 48000; + }; + + if (renderer_) { + const int sample_rate = renderer_->SamplingFrequency(); + playout_buffer_.resize(TestAudioDeviceModule::SamplesPerFrame(sample_rate) * + renderer_->NumChannels(), + 0); + RTC_CHECK(good_sample_rate(sample_rate)); + } + if (capturer_) { + RTC_CHECK(good_sample_rate(capturer_->SamplingFrequency())); + } +} + +AudioDeviceGeneric::InitStatus TestAudioDevice::Init() { + task_queue_ = + std::make_unique(task_queue_factory_->CreateTaskQueue( + "TestAudioDeviceModuleImpl", TaskQueueFactory::Priority::NORMAL)); + + RepeatingTaskHandle::Start(task_queue_->Get(), [this]() { + ProcessAudio(); + return TimeDelta::Micros(process_interval_us_); + }); + return InitStatus::OK; +} + +int32_t TestAudioDevice::PlayoutIsAvailable(bool& available) { + MutexLock lock(&lock_); + available = renderer_ != nullptr; + return 0; +} + +int32_t TestAudioDevice::InitPlayout() { + MutexLock lock(&lock_); + + if (rendering_) { + return -1; + } + + if (audio_buffer_ != nullptr && renderer_ != nullptr) { + // Update webrtc audio buffer with the selected parameters + audio_buffer_->SetPlayoutSampleRate(renderer_->SamplingFrequency()); + audio_buffer_->SetPlayoutChannels(renderer_->NumChannels()); + } + rendering_initialized_ = true; + return 0; +} + +bool TestAudioDevice::PlayoutIsInitialized() const { + MutexLock lock(&lock_); + return rendering_initialized_; +} + +int32_t TestAudioDevice::StartPlayout() { + MutexLock lock(&lock_); + RTC_CHECK(renderer_); + rendering_ = true; + return 0; +} + +int32_t TestAudioDevice::StopPlayout() { + MutexLock lock(&lock_); + rendering_ = false; + return 0; +} + +int32_t TestAudioDevice::RecordingIsAvailable(bool& available) { + MutexLock lock(&lock_); + available = capturer_ != nullptr; + return 0; +} + +int32_t TestAudioDevice::InitRecording() { + MutexLock lock(&lock_); + + if (capturing_) { + return -1; + } + + if (audio_buffer_ != nullptr && capturer_ != nullptr) { + // Update webrtc audio buffer with the selected parameters + audio_buffer_->SetRecordingSampleRate(capturer_->SamplingFrequency()); + audio_buffer_->SetRecordingChannels(capturer_->NumChannels()); + } + capturing_initialized_ = true; + return 0; +} + +bool TestAudioDevice::RecordingIsInitialized() const { + MutexLock lock(&lock_); + return capturing_initialized_; +} + +int32_t TestAudioDevice::StartRecording() { + MutexLock lock(&lock_); + RTC_CHECK(capturer_); + capturing_ = true; + return 0; +} + +int32_t TestAudioDevice::StopRecording() { + MutexLock lock(&lock_); + capturing_ = false; + return 0; +} + +bool TestAudioDevice::Playing() const { + MutexLock lock(&lock_); + return rendering_; +} + +bool TestAudioDevice::Recording() const { + MutexLock lock(&lock_); + return capturing_; +} + +void TestAudioDevice::ProcessAudio() { + MutexLock lock(&lock_); + if (audio_buffer_ == nullptr) { + return; + } + if (capturing_) { + // Capture 10ms of audio. 2 bytes per sample. + const bool keep_capturing = capturer_->Capture(&recording_buffer_); + if (recording_buffer_.size() > 0) { + audio_buffer_->SetRecordedBuffer( + recording_buffer_.data(), + recording_buffer_.size() / capturer_->NumChannels(), + absl::make_optional(rtc::TimeNanos())); + audio_buffer_->DeliverRecordedData(); + } + if (!keep_capturing) { + capturing_ = false; + } + } + if (rendering_) { + const int sampling_frequency = renderer_->SamplingFrequency(); + int32_t samples_per_channel = audio_buffer_->RequestPlayoutData( + TestAudioDeviceModule::SamplesPerFrame(sampling_frequency)); + audio_buffer_->GetPlayoutData(playout_buffer_.data()); + size_t samples_out = samples_per_channel * renderer_->NumChannels(); + RTC_CHECK_LE(samples_out, playout_buffer_.size()); + const bool keep_rendering = renderer_->Render( + rtc::ArrayView(playout_buffer_.data(), samples_out)); + if (!keep_rendering) { + rendering_ = false; + } + } +} + +void TestAudioDevice::AttachAudioBuffer(AudioDeviceBuffer* audio_buffer) { + MutexLock lock(&lock_); + RTC_DCHECK(audio_buffer || audio_buffer_); + audio_buffer_ = audio_buffer; + + if (renderer_ != nullptr) { + audio_buffer_->SetPlayoutSampleRate(renderer_->SamplingFrequency()); + audio_buffer_->SetPlayoutChannels(renderer_->NumChannels()); + } + if (capturer_ != nullptr) { + audio_buffer_->SetRecordingSampleRate(capturer_->SamplingFrequency()); + audio_buffer_->SetRecordingChannels(capturer_->NumChannels()); + } +} + +} // namespace webrtc diff --git a/modules/audio_device/test_audio_device_impl.h b/modules/audio_device/test_audio_device_impl.h new file mode 100644 index 0000000000..36192b7f7f --- /dev/null +++ b/modules/audio_device/test_audio_device_impl.h @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2023 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. + */ + +#ifndef MODULES_AUDIO_DEVICE_TEST_AUDIO_DEVICE_IMPL_H_ +#define MODULES_AUDIO_DEVICE_TEST_AUDIO_DEVICE_IMPL_H_ + +#include +#include + +#include "api/task_queue/task_queue_factory.h" +#include "modules/audio_device/audio_device_buffer.h" +#include "modules/audio_device/audio_device_generic.h" +#include "modules/audio_device/include/audio_device.h" +#include "modules/audio_device/include/audio_device_defines.h" +#include "modules/audio_device/include/test_audio_device.h" +#include "rtc_base/buffer.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/task_queue.h" + +namespace webrtc { + +class TestAudioDevice : public AudioDeviceGeneric { + public: + // Creates a new TestAudioDevice. When capturing or playing, 10 ms audio + // frames will be processed every 10ms / `speed`. + // `capturer` is an object that produces audio data. Can be nullptr if this + // device is never used for recording. + // `renderer` is an object that receives audio data that would have been + // played out. Can be nullptr if this device is never used for playing. + TestAudioDevice(TaskQueueFactory* task_queue_factory, + std::unique_ptr capturer, + std::unique_ptr renderer, + float speed = 1); + TestAudioDevice(const TestAudioDevice&) = delete; + TestAudioDevice& operator=(const TestAudioDevice&) = delete; + ~TestAudioDevice() override = default; + + // Retrieve the currently utilized audio layer + int32_t ActiveAudioLayer( + AudioDeviceModule::AudioLayer& audioLayer) const override { + return 0; + } + + // Main initializaton and termination + InitStatus Init() override; + int32_t Terminate() override { return 0; } + bool Initialized() const override { return true; } + + // Device enumeration + int16_t PlayoutDevices() override { return 0; } + int16_t RecordingDevices() override { return 0; } + int32_t PlayoutDeviceName(uint16_t index, + char name[kAdmMaxDeviceNameSize], + char guid[kAdmMaxGuidSize]) override { + return 0; + } + int32_t RecordingDeviceName(uint16_t index, + char name[kAdmMaxDeviceNameSize], + char guid[kAdmMaxGuidSize]) override { + return 0; + } + + // Device selection + int32_t SetPlayoutDevice(uint16_t index) override { return 0; } + int32_t SetPlayoutDevice( + AudioDeviceModule::WindowsDeviceType device) override { + return 0; + } + int32_t SetRecordingDevice(uint16_t index) override { return 0; } + int32_t SetRecordingDevice( + AudioDeviceModule::WindowsDeviceType device) override { + return 0; + } + + // Audio transport initialization + int32_t PlayoutIsAvailable(bool& available) override; + int32_t InitPlayout() override; + bool PlayoutIsInitialized() const override; + int32_t RecordingIsAvailable(bool& available) override; + int32_t InitRecording() override; + bool RecordingIsInitialized() const override; + + // Audio transport control + int32_t StartPlayout() override; + int32_t StopPlayout() override; + bool Playing() const override; + int32_t StartRecording() override; + int32_t StopRecording() override; + bool Recording() const override; + + // Audio mixer initialization + int32_t InitSpeaker() override { return 0; } + bool SpeakerIsInitialized() const override { return true; } + int32_t InitMicrophone() override { return 0; } + bool MicrophoneIsInitialized() const override { return true; } + + // Speaker volume controls + int32_t SpeakerVolumeIsAvailable(bool& available) override { return 0; } + int32_t SetSpeakerVolume(uint32_t volume) override { return 0; } + int32_t SpeakerVolume(uint32_t& volume) const override { return 0; } + int32_t MaxSpeakerVolume(uint32_t& maxVolume) const override { return 0; } + int32_t MinSpeakerVolume(uint32_t& minVolume) const override { return 0; } + + // Microphone volume controls + int32_t MicrophoneVolumeIsAvailable(bool& available) override { return 0; } + int32_t SetMicrophoneVolume(uint32_t volume) override { return 0; } + int32_t MicrophoneVolume(uint32_t& volume) const override { return 0; } + int32_t MaxMicrophoneVolume(uint32_t& maxVolume) const override { return 0; } + int32_t MinMicrophoneVolume(uint32_t& minVolume) const override { return 0; } + + // Speaker mute control + int32_t SpeakerMuteIsAvailable(bool& available) override { return 0; } + int32_t SetSpeakerMute(bool enable) override { return 0; } + int32_t SpeakerMute(bool& enabled) const override { return 0; } + + // Microphone mute control + int32_t MicrophoneMuteIsAvailable(bool& available) override { return 0; } + int32_t SetMicrophoneMute(bool enable) override { return 0; } + int32_t MicrophoneMute(bool& enabled) const override { return 0; } + + // Stereo support + int32_t StereoPlayoutIsAvailable(bool& available) override { + available = false; + return 0; + } + int32_t SetStereoPlayout(bool enable) override { return 0; } + int32_t StereoPlayout(bool& enabled) const override { return 0; } + int32_t StereoRecordingIsAvailable(bool& available) override { + available = false; + return 0; + } + int32_t SetStereoRecording(bool enable) override { return 0; } + int32_t StereoRecording(bool& enabled) const override { return 0; } + + // Delay information and control + int32_t PlayoutDelay(uint16_t& delayMS) const override { + delayMS = 0; + return 0; + } + + // Android only + bool BuiltInAECIsAvailable() const override { return false; } + bool BuiltInAGCIsAvailable() const override { return false; } + bool BuiltInNSIsAvailable() const override { return false; } + + // Windows Core Audio and Android only. + int32_t EnableBuiltInAEC(bool enable) override { return -1; } + int32_t EnableBuiltInAGC(bool enable) override { return -1; } + int32_t EnableBuiltInNS(bool enable) override { return -1; } + + // Play underrun count. + int32_t GetPlayoutUnderrunCount() const override { return -1; } + +// iOS only. +// TODO(henrika): add Android support. +#if defined(WEBRTC_IOS) + int GetPlayoutAudioParameters(AudioParameters* params) const override { + return -1; + } + int GetRecordAudioParameters(AudioParameters* params) const override { + return -1; + } +#endif // WEBRTC_IOS + + void AttachAudioBuffer(AudioDeviceBuffer* audio_buffer) override; + + private: + void ProcessAudio(); + + TaskQueueFactory* const task_queue_factory_; + const std::unique_ptr capturer_ + RTC_GUARDED_BY(lock_); + const std::unique_ptr renderer_ + RTC_GUARDED_BY(lock_); + const int64_t process_interval_us_; + + mutable Mutex lock_; + AudioDeviceBuffer* audio_buffer_ RTC_GUARDED_BY(lock_) = nullptr; + bool rendering_ RTC_GUARDED_BY(lock_) = false; + bool capturing_ RTC_GUARDED_BY(lock_) = false; + bool rendering_initialized_ RTC_GUARDED_BY(lock_) = false; + bool capturing_initialized_ RTC_GUARDED_BY(lock_) = false; + + std::vector playout_buffer_ RTC_GUARDED_BY(lock_); + rtc::BufferT recording_buffer_ RTC_GUARDED_BY(lock_); + std::unique_ptr task_queue_; +}; + +} // namespace webrtc + +#endif // MODULES_AUDIO_DEVICE_TEST_AUDIO_DEVICE_IMPL_H_ diff --git a/modules/audio_device/test_audio_device_impl_test.cc b/modules/audio_device/test_audio_device_impl_test.cc new file mode 100644 index 0000000000..e81bb2f807 --- /dev/null +++ b/modules/audio_device/test_audio_device_impl_test.cc @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2023 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 "modules/audio_device/test_audio_device_impl.h" + +#include +#include + +#include "absl/types/optional.h" +#include "api/task_queue/task_queue_factory.h" +#include "api/units/time_delta.h" +#include "api/units/timestamp.h" +#include "modules/audio_device/audio_device_buffer.h" +#include "modules/audio_device/audio_device_generic.h" +#include "modules/audio_device/include/audio_device.h" +#include "modules/audio_device/include/audio_device_defines.h" +#include "modules/audio_device/include/test_audio_device.h" +#include "rtc_base/checks.h" +#include "rtc_base/synchronization/mutex.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/time_controller/simulated_time_controller.h" + +namespace webrtc { +namespace { + +using ::testing::ElementsAre; + +constexpr Timestamp kStartTime = Timestamp::Millis(10000); + +class TestAudioTransport : public AudioTransport { + public: + enum class Mode { kPlaying, kRecording }; + + explicit TestAudioTransport(Mode mode) : mode_(mode) {} + ~TestAudioTransport() override = default; + + int32_t RecordedDataIsAvailable( + const void* audioSamples, + size_t samples_per_channel, + size_t bytes_per_sample, + size_t number_of_channels, + uint32_t samples_per_second, + uint32_t total_delay_ms, + int32_t clock_drift, + uint32_t current_mic_level, + bool key_pressed, + uint32_t& new_mic_level, + absl::optional estimated_capture_time_ns) override { + new_mic_level = 1; + + if (mode_ != Mode::kRecording) { + EXPECT_TRUE(false) << "RecordedDataIsAvailable mustn't be called when " + "mode isn't kRecording"; + return -1; + } + + MutexLock lock(&mutex_); + samples_per_channel_.push_back(samples_per_channel); + number_of_channels_.push_back(number_of_channels); + bytes_per_sample_.push_back(bytes_per_sample); + samples_per_second_.push_back(samples_per_second); + return 0; + } + + int32_t NeedMorePlayData(size_t samples_per_channel, + size_t bytes_per_sample, + size_t number_of_channels, + uint32_t samples_per_second, + void* audio_samples, + size_t& samples_out, + int64_t* elapsed_time_ms, + int64_t* ntp_time_ms) override { + const size_t num_bytes = samples_per_channel * number_of_channels; + std::memset(audio_samples, 1, num_bytes); + samples_out = samples_per_channel * number_of_channels; + *elapsed_time_ms = 0; + *ntp_time_ms = 0; + + if (mode_ != Mode::kPlaying) { + EXPECT_TRUE(false) + << "NeedMorePlayData mustn't be called when mode isn't kPlaying"; + return -1; + } + + MutexLock lock(&mutex_); + samples_per_channel_.push_back(samples_per_channel); + number_of_channels_.push_back(number_of_channels); + bytes_per_sample_.push_back(bytes_per_sample); + samples_per_second_.push_back(samples_per_second); + return 0; + } + + int32_t RecordedDataIsAvailable(const void* audio_samples, + size_t samples_per_channel, + size_t bytes_per_sample, + size_t number_of_channels, + uint32_t samples_per_second, + uint32_t total_delay_ms, + int32_t clockDrift, + uint32_t current_mic_level, + bool key_pressed, + uint32_t& new_mic_level) override { + RTC_CHECK(false) << "This methods should be never executed"; + } + + void PullRenderData(int bits_per_sample, + int sample_rate, + size_t number_of_channels, + size_t number_of_frames, + void* audio_data, + int64_t* elapsed_time_ms, + int64_t* ntp_time_ms) override { + RTC_CHECK(false) << "This methods should be never executed"; + } + + std::vector samples_per_channel() const { + MutexLock lock(&mutex_); + return samples_per_channel_; + } + std::vector number_of_channels() const { + MutexLock lock(&mutex_); + return number_of_channels_; + } + std::vector bytes_per_sample() const { + MutexLock lock(&mutex_); + return bytes_per_sample_; + } + std::vector samples_per_second() const { + MutexLock lock(&mutex_); + return samples_per_second_; + } + + private: + const Mode mode_; + + mutable Mutex mutex_; + std::vector samples_per_channel_ RTC_GUARDED_BY(mutex_); + std::vector number_of_channels_ RTC_GUARDED_BY(mutex_); + std::vector bytes_per_sample_ RTC_GUARDED_BY(mutex_); + std::vector samples_per_second_ RTC_GUARDED_BY(mutex_); +}; + +TEST(TestAudioDeviceTest, EnablingRecordingProducesAudio) { + GlobalSimulatedTimeController time_controller(kStartTime); + TestAudioTransport audio_transport(TestAudioTransport::Mode::kRecording); + AudioDeviceBuffer audio_buffer(time_controller.GetTaskQueueFactory()); + ASSERT_EQ(audio_buffer.RegisterAudioCallback(&audio_transport), 0); + std::unique_ptr capturer = + TestAudioDeviceModule::CreatePulsedNoiseCapturer( + /*max_amplitude=*/1000, + /*sampling_frequency_in_hz=*/48000, /*num_channels=*/2); + + TestAudioDevice audio_device(time_controller.GetTaskQueueFactory(), + std::move(capturer), + /*renderer=*/nullptr); + ASSERT_EQ(audio_device.Init(), AudioDeviceGeneric::InitStatus::OK); + audio_device.AttachAudioBuffer(&audio_buffer); + + EXPECT_FALSE(audio_device.RecordingIsInitialized()); + ASSERT_EQ(audio_device.InitRecording(), 0); + EXPECT_TRUE(audio_device.RecordingIsInitialized()); + audio_buffer.StartRecording(); + ASSERT_EQ(audio_device.StartRecording(), 0); + time_controller.AdvanceTime(TimeDelta::Millis(10)); + ASSERT_TRUE(audio_device.Recording()); + time_controller.AdvanceTime(TimeDelta::Millis(10)); + ASSERT_EQ(audio_device.StopRecording(), 0); + audio_buffer.StopRecording(); + + EXPECT_THAT(audio_transport.samples_per_channel(), + ElementsAre(480, 480, 480)); + EXPECT_THAT(audio_transport.number_of_channels(), ElementsAre(2, 2, 2)); + EXPECT_THAT(audio_transport.bytes_per_sample(), ElementsAre(4, 4, 4)); + EXPECT_THAT(audio_transport.samples_per_second(), + ElementsAre(48000, 48000, 48000)); +} + +TEST(TestAudioDeviceTest, RecordingIsAvailableWhenCapturerIsSet) { + GlobalSimulatedTimeController time_controller(kStartTime); + std::unique_ptr capturer = + TestAudioDeviceModule::CreatePulsedNoiseCapturer( + /*max_amplitude=*/1000, + /*sampling_frequency_in_hz=*/48000, /*num_channels=*/2); + + TestAudioDevice audio_device(time_controller.GetTaskQueueFactory(), + std::move(capturer), + /*renderer=*/nullptr); + ASSERT_EQ(audio_device.Init(), AudioDeviceGeneric::InitStatus::OK); + + bool available; + EXPECT_EQ(audio_device.RecordingIsAvailable(available), 0); + EXPECT_TRUE(available); +} + +TEST(TestAudioDeviceTest, RecordingIsNotAvailableWhenCapturerIsNotSet) { + GlobalSimulatedTimeController time_controller(kStartTime); + TestAudioDevice audio_device(time_controller.GetTaskQueueFactory(), + /*capturer=*/nullptr, + /*renderer=*/nullptr); + ASSERT_EQ(audio_device.Init(), AudioDeviceGeneric::InitStatus::OK); + + bool available; + EXPECT_EQ(audio_device.RecordingIsAvailable(available), 0); + EXPECT_FALSE(available); +} + +TEST(TestAudioDeviceTest, EnablingPlayoutProducesAudio) { + GlobalSimulatedTimeController time_controller(kStartTime); + TestAudioTransport audio_transport(TestAudioTransport::Mode::kPlaying); + AudioDeviceBuffer audio_buffer(time_controller.GetTaskQueueFactory()); + ASSERT_EQ(audio_buffer.RegisterAudioCallback(&audio_transport), 0); + std::unique_ptr renderer = + TestAudioDeviceModule::CreateDiscardRenderer( + /*sampling_frequency_in_hz=*/48000, /*num_channels=*/2); + + TestAudioDevice audio_device(time_controller.GetTaskQueueFactory(), + /*capturer=*/nullptr, std::move(renderer)); + ASSERT_EQ(audio_device.Init(), AudioDeviceGeneric::InitStatus::OK); + audio_device.AttachAudioBuffer(&audio_buffer); + + EXPECT_FALSE(audio_device.PlayoutIsInitialized()); + ASSERT_EQ(audio_device.InitPlayout(), 0); + EXPECT_TRUE(audio_device.PlayoutIsInitialized()); + audio_buffer.StartPlayout(); + ASSERT_EQ(audio_device.StartPlayout(), 0); + time_controller.AdvanceTime(TimeDelta::Millis(10)); + ASSERT_TRUE(audio_device.Playing()); + time_controller.AdvanceTime(TimeDelta::Millis(10)); + ASSERT_EQ(audio_device.StopPlayout(), 0); + audio_buffer.StopPlayout(); + + EXPECT_THAT(audio_transport.samples_per_channel(), + ElementsAre(480, 480, 480)); + EXPECT_THAT(audio_transport.number_of_channels(), ElementsAre(2, 2, 2)); + EXPECT_THAT(audio_transport.bytes_per_sample(), ElementsAre(4, 4, 4)); + EXPECT_THAT(audio_transport.samples_per_second(), + ElementsAre(48000, 48000, 48000)); +} + +TEST(TestAudioDeviceTest, PlayoutIsAvailableWhenRendererIsSet) { + GlobalSimulatedTimeController time_controller(kStartTime); + std::unique_ptr renderer = + TestAudioDeviceModule::CreateDiscardRenderer( + /*sampling_frequency_in_hz=*/48000, /*num_channels=*/2); + + TestAudioDevice audio_device(time_controller.GetTaskQueueFactory(), + /*capturer=*/nullptr, std::move(renderer)); + ASSERT_EQ(audio_device.Init(), AudioDeviceGeneric::InitStatus::OK); + + bool available; + EXPECT_EQ(audio_device.PlayoutIsAvailable(available), 0); + EXPECT_TRUE(available); +} + +TEST(TestAudioDeviceTest, PlayoutIsNotAvailableWhenRendererIsNotSet) { + GlobalSimulatedTimeController time_controller(kStartTime); + TestAudioDevice audio_device(time_controller.GetTaskQueueFactory(), + /*capturer=*/nullptr, + /*renderer=*/nullptr); + ASSERT_EQ(audio_device.Init(), AudioDeviceGeneric::InitStatus::OK); + + bool available; + EXPECT_EQ(audio_device.PlayoutIsAvailable(available), 0); + EXPECT_FALSE(available); +} + +} // namespace +} // namespace webrtc