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 <titovartem@webrtc.org>
> > > > Reviewed-by: Henrik Andreassson <henrika@webrtc.org>
> > > > 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 <jleconte@google.com>
> > > Bot-Commit: rubber-stamper@appspot.gserviceaccount.com <rubber-stamper@appspot.gserviceaccount.com>
> > > Reviewed-by: Jeremy Leconte <jleconte@google.com>
> > > Auto-Submit: Christoffer Jansson <jansson@google.com>
> > > Owners-Override: Christoffer Jansson <jansson@google.com>
> > > 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 <henrika@webrtc.org>
> > Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
> > Commit-Queue: Artem Titov <titovartem@webrtc.org>
> > 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 <jleconte@google.com>
> Owners-Override: Jeremy Leconte <jleconte@google.com>
> Bot-Commit: rubber-stamper@appspot.gserviceaccount.com <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 <mbonadei@webrtc.org>
Reviewed-by: Tomas Gunnarsson <tommi@webrtc.org>
Commit-Queue: Artem Titov <titovartem@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#40387}
diff --git a/modules/audio_device/BUILD.gn b/modules/audio_device/BUILD.gn
index ee71f46..162981c4 100644
--- a/modules/audio_device/BUILD.gn
+++ b/modules/audio_device/BUILD.gn
@@ -186,14 +186,20 @@
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 @@
"../../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 @@
"../../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 @@
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 @@
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 @@
"../../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 @@
"../../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 91964d1..f1bd8e8 100644
--- a/modules/audio_device/audio_device_buffer.cc
+++ b/modules/audio_device/audio_device_buffer.cc
@@ -41,7 +41,8 @@
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 @@
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 f7c4ecd..1260a24 100644
--- a/modules/audio_device/audio_device_buffer.h
+++ b/modules/audio_device/audio_device_buffer.h
@@ -78,7 +78,11 @@
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 9da9c62..c177d7d 100644
--- a/modules/audio_device/audio_device_impl.cc
+++ b/modules/audio_device/audio_device_impl.cc
@@ -124,6 +124,17 @@
RTC_DLOG(LS_INFO) << __FUNCTION__;
}
+AudioDeviceModuleImpl::AudioDeviceModuleImpl(
+ AudioLayer audio_layer,
+ std::unique_ptr<AudioDeviceGeneric> 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 @@
#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::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 1737b46..46d91a4 100644
--- a/modules/audio_device/audio_device_impl.h
+++ b/modules/audio_device/audio_device_impl.h
@@ -34,7 +34,12 @@
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 @@
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<AudioDeviceGeneric> 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 7640644..fa4f3fe 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 @@
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<TestAudioDeviceModule> {
+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> capturer,
- std::unique_ptr<Renderer> 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<TestAudioDeviceModule::Capturer> capturer,
+ std::unique_ptr<TestAudioDeviceModule::Renderer> renderer,
+ float speed = 1)
+ : AudioDeviceModuleImpl(
+ AudioLayer::kDummyAudio,
+ std::make_unique<TestAudioDevice>(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<rtc::TaskQueue>(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<const int16_t>(playout_buffer_.data(), samples_out));
- if (!keep_rendering) {
- rendering_ = false;
- done_rendering_.Set();
- }
- }
- }
- TaskQueueFactory* const task_queue_factory_;
- const std::unique_ptr<Capturer> capturer_ RTC_GUARDED_BY(lock_);
- const std::unique_ptr<Renderer> 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<int16_t> playout_buffer_ RTC_GUARDED_BY(lock_);
- rtc::BufferT<int16_t> recording_buffer_ RTC_GUARDED_BY(lock_);
- std::unique_ptr<rtc::TaskQueue> task_queue_;
+ ~TestAudioDeviceModuleImpl() override = default;
};
// A fake capturer that generates pulses with random samples between
@@ -444,8 +305,26 @@
std::unique_ptr<TestAudioDeviceModule::Capturer> capturer,
std::unique_ptr<TestAudioDeviceModule::Renderer> renderer,
float speed) {
- return rtc::make_ref_counted<TestAudioDeviceModuleImpl>(
+ auto audio_device = rtc::make_ref_counted<TestAudioDeviceModuleImpl>(
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<TestAudioDeviceModule::PulsedNoiseCapturer>
diff --git a/modules/audio_device/include/test_audio_device.h b/modules/audio_device/include/test_audio_device.h
index 87ba9cf..751eae6 100644
--- a/modules/audio_device/include/test_audio_device.h
+++ b/modules/audio_device/include/test_audio_device.h
@@ -29,9 +29,10 @@
// 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 @@
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 @@
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 2975b11..54ede80 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 <algorithm>
#include <array>
+#include <memory>
+#include <utility>
+#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<int16_t>& input_samples,
@@ -64,7 +73,6 @@
remove(output_filename.c_str());
}
-} // namespace
TEST(BoundedWavFileWriterTest, NoSilence) {
static const std::vector<int16_t> kInputSamples = {
@@ -189,4 +197,185 @@
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<int64_t> 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<size_t> samples_per_channel() const {
+ MutexLock lock(&mutex_);
+ return samples_per_channel_;
+ }
+ std::vector<size_t> number_of_channels() const {
+ MutexLock lock(&mutex_);
+ return number_of_channels_;
+ }
+ std::vector<size_t> bytes_per_sample() const {
+ MutexLock lock(&mutex_);
+ return bytes_per_sample_;
+ }
+ std::vector<size_t> samples_per_second() const {
+ MutexLock lock(&mutex_);
+ return samples_per_second_;
+ }
+
+ private:
+ const Mode mode_;
+
+ mutable Mutex mutex_;
+ std::vector<size_t> samples_per_channel_ RTC_GUARDED_BY(mutex_);
+ std::vector<size_t> number_of_channels_ RTC_GUARDED_BY(mutex_);
+ std::vector<size_t> bytes_per_sample_ RTC_GUARDED_BY(mutex_);
+ std::vector<size_t> samples_per_second_ RTC_GUARDED_BY(mutex_);
+};
+
+TEST(TestAudioDeviceModuleTest, CreatedADMCanRecord) {
+ GlobalSimulatedTimeController time_controller(kStartTime);
+ TestAudioTransport audio_transport(TestAudioTransport::Mode::kRecording);
+ std::unique_ptr<TestAudioDeviceModule::PulsedNoiseCapturer> capturer =
+ TestAudioDeviceModule::CreatePulsedNoiseCapturer(
+ /*max_amplitude=*/1000,
+ /*sampling_frequency_in_hz=*/48000, /*num_channels=*/2);
+
+ rtc::scoped_refptr<AudioDeviceModule> 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<TestAudioDeviceModule::Renderer> renderer =
+ TestAudioDeviceModule::CreateDiscardRenderer(
+ /*sampling_frequency_in_hz=*/48000, /*num_channels=*/2);
+
+ rtc::scoped_refptr<AudioDeviceModule> 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 0000000..c5de40c
--- /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 <memory>
+#include <utility>
+
+#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<TestAudioDeviceModule::Capturer> capturer,
+ std::unique_ptr<TestAudioDeviceModule::Renderer> 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<rtc::TaskQueue>(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<const int16_t>(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 0000000..36192b7
--- /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 <memory>
+#include <vector>
+
+#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<TestAudioDeviceModule::Capturer> capturer,
+ std::unique_ptr<TestAudioDeviceModule::Renderer> 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<TestAudioDeviceModule::Capturer> capturer_
+ RTC_GUARDED_BY(lock_);
+ const std::unique_ptr<TestAudioDeviceModule::Renderer> 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<int16_t> playout_buffer_ RTC_GUARDED_BY(lock_);
+ rtc::BufferT<int16_t> recording_buffer_ RTC_GUARDED_BY(lock_);
+ std::unique_ptr<rtc::TaskQueue> 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 0000000..e81bb2f
--- /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 <memory>
+#include <utility>
+
+#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<int64_t> 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<size_t> samples_per_channel() const {
+ MutexLock lock(&mutex_);
+ return samples_per_channel_;
+ }
+ std::vector<size_t> number_of_channels() const {
+ MutexLock lock(&mutex_);
+ return number_of_channels_;
+ }
+ std::vector<size_t> bytes_per_sample() const {
+ MutexLock lock(&mutex_);
+ return bytes_per_sample_;
+ }
+ std::vector<size_t> samples_per_second() const {
+ MutexLock lock(&mutex_);
+ return samples_per_second_;
+ }
+
+ private:
+ const Mode mode_;
+
+ mutable Mutex mutex_;
+ std::vector<size_t> samples_per_channel_ RTC_GUARDED_BY(mutex_);
+ std::vector<size_t> number_of_channels_ RTC_GUARDED_BY(mutex_);
+ std::vector<size_t> bytes_per_sample_ RTC_GUARDED_BY(mutex_);
+ std::vector<size_t> 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<TestAudioDeviceModule::PulsedNoiseCapturer> 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<TestAudioDeviceModule::PulsedNoiseCapturer> 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<TestAudioDeviceModule::Renderer> 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<TestAudioDeviceModule::Renderer> 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