mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-19 00:27:51 +01:00

The test RunPlayoutAndRecordingInFullDuplex makes the speakers play the signal it simultaneously records from the microphone, which can cause full howling. The test itself measures buffer usage and does not depend on what signal is played through the speakers. This change mutes the speakers to prevent howling when running modules_unittests. Bug: webrtc:10704 Change-Id: I6176adb2fb5ce0e4d86f6f447476be8a88c2f2cf Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/139889 Reviewed-by: Henrik Andreassson <henrika@webrtc.org> Commit-Queue: Gustaf Ullberg <gustaf@webrtc.org> Cr-Commit-Position: refs/heads/master@{#28140}
1140 lines
45 KiB
C++
1140 lines
45 KiB
C++
/*
|
|
* Copyright (c) 2017 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 <algorithm>
|
|
#include <cstring>
|
|
#include <memory>
|
|
#include <numeric>
|
|
|
|
#include "absl/memory/memory.h"
|
|
#include "absl/types/optional.h"
|
|
#include "api/array_view.h"
|
|
#include "api/scoped_refptr.h"
|
|
#include "api/task_queue/default_task_queue_factory.h"
|
|
#include "api/task_queue/task_queue_factory.h"
|
|
#include "modules/audio_device/audio_device_impl.h"
|
|
#include "modules/audio_device/include/audio_device.h"
|
|
#include "modules/audio_device/include/mock_audio_transport.h"
|
|
#include "rtc_base/buffer.h"
|
|
#include "rtc_base/critical_section.h"
|
|
#include "rtc_base/event.h"
|
|
#include "rtc_base/logging.h"
|
|
#include "rtc_base/numerics/safe_conversions.h"
|
|
#include "rtc_base/race_checker.h"
|
|
#include "rtc_base/thread_annotations.h"
|
|
#include "rtc_base/thread_checker.h"
|
|
#include "rtc_base/time_utils.h"
|
|
#include "system_wrappers/include/sleep.h"
|
|
#include "test/gmock.h"
|
|
#include "test/gtest.h"
|
|
#ifdef WEBRTC_WIN
|
|
#include "modules/audio_device/include/audio_device_factory.h"
|
|
#include "modules/audio_device/win/core_audio_utility_win.h"
|
|
#endif
|
|
|
|
using ::testing::_;
|
|
using ::testing::AtLeast;
|
|
using ::testing::Ge;
|
|
using ::testing::Invoke;
|
|
using ::testing::NiceMock;
|
|
using ::testing::NotNull;
|
|
using ::testing::Mock;
|
|
|
|
namespace webrtc {
|
|
namespace {
|
|
|
|
// Using a #define for AUDIO_DEVICE since we will call *different* versions of
|
|
// the ADM functions, depending on the ID type.
|
|
#if defined(WEBRTC_WIN)
|
|
#define AUDIO_DEVICE_ID (AudioDeviceModule::WindowsDeviceType::kDefaultDevice)
|
|
#else
|
|
#define AUDIO_DEVICE_ID (0u)
|
|
#endif // defined(WEBRTC_WIN)
|
|
|
|
// #define ENABLE_DEBUG_PRINTF
|
|
#ifdef ENABLE_DEBUG_PRINTF
|
|
#define PRINTD(...) fprintf(stderr, __VA_ARGS__);
|
|
#else
|
|
#define PRINTD(...) ((void)0)
|
|
#endif
|
|
#define PRINT(...) fprintf(stderr, __VA_ARGS__);
|
|
|
|
// Don't run these tests in combination with sanitizers.
|
|
// TODO(webrtc:9778): Re-enable on THREAD_SANITIZER?
|
|
#if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) && \
|
|
!defined(THREAD_SANITIZER)
|
|
#define SKIP_TEST_IF_NOT(requirements_satisfied) \
|
|
do { \
|
|
if (!requirements_satisfied) { \
|
|
return; \
|
|
} \
|
|
} while (false)
|
|
#else
|
|
// Or if other audio-related requirements are not met.
|
|
#define SKIP_TEST_IF_NOT(requirements_satisfied) \
|
|
do { \
|
|
return; \
|
|
} while (false)
|
|
#endif
|
|
|
|
// Number of callbacks (input or output) the tests waits for before we set
|
|
// an event indicating that the test was OK.
|
|
static constexpr size_t kNumCallbacks = 10;
|
|
// Max amount of time we wait for an event to be set while counting callbacks.
|
|
static constexpr size_t kTestTimeOutInMilliseconds = 10 * 1000;
|
|
// Average number of audio callbacks per second assuming 10ms packet size.
|
|
static constexpr size_t kNumCallbacksPerSecond = 100;
|
|
// Run the full-duplex test during this time (unit is in seconds).
|
|
static constexpr size_t kFullDuplexTimeInSec = 5;
|
|
// Length of round-trip latency measurements. Number of deteced impulses
|
|
// shall be kImpulseFrequencyInHz * kMeasureLatencyTimeInSec - 1 since the
|
|
// last transmitted pulse is not used.
|
|
static constexpr size_t kMeasureLatencyTimeInSec = 10;
|
|
// Sets the number of impulses per second in the latency test.
|
|
static constexpr size_t kImpulseFrequencyInHz = 1;
|
|
// Utilized in round-trip latency measurements to avoid capturing noise samples.
|
|
static constexpr int kImpulseThreshold = 1000;
|
|
|
|
enum class TransportType {
|
|
kInvalid,
|
|
kPlay,
|
|
kRecord,
|
|
kPlayAndRecord,
|
|
};
|
|
|
|
// Interface for processing the audio stream. Real implementations can e.g.
|
|
// run audio in loopback, read audio from a file or perform latency
|
|
// measurements.
|
|
class AudioStream {
|
|
public:
|
|
virtual void Write(rtc::ArrayView<const int16_t> source) = 0;
|
|
virtual void Read(rtc::ArrayView<int16_t> destination) = 0;
|
|
|
|
virtual ~AudioStream() = default;
|
|
};
|
|
|
|
// Converts index corresponding to position within a 10ms buffer into a
|
|
// delay value in milliseconds.
|
|
// Example: index=240, frames_per_10ms_buffer=480 => 5ms as output.
|
|
int IndexToMilliseconds(size_t index, size_t frames_per_10ms_buffer) {
|
|
return rtc::checked_cast<int>(
|
|
10.0 * (static_cast<double>(index) / frames_per_10ms_buffer) + 0.5);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Simple first in first out (FIFO) class that wraps a list of 16-bit audio
|
|
// buffers of fixed size and allows Write and Read operations. The idea is to
|
|
// store recorded audio buffers (using Write) and then read (using Read) these
|
|
// stored buffers with as short delay as possible when the audio layer needs
|
|
// data to play out. The number of buffers in the FIFO will stabilize under
|
|
// normal conditions since there will be a balance between Write and Read calls.
|
|
// The container is a std::list container and access is protected with a lock
|
|
// since both sides (playout and recording) are driven by its own thread.
|
|
// Note that, we know by design that the size of the audio buffer will not
|
|
// change over time and that both sides will in most cases use the same size.
|
|
class FifoAudioStream : public AudioStream {
|
|
public:
|
|
void Write(rtc::ArrayView<const int16_t> source) override {
|
|
RTC_DCHECK_RUNS_SERIALIZED(&race_checker_);
|
|
const size_t size = [&] {
|
|
rtc::CritScope lock(&lock_);
|
|
fifo_.push_back(Buffer16(source.data(), source.size()));
|
|
return fifo_.size();
|
|
}();
|
|
if (size > max_size_) {
|
|
max_size_ = size;
|
|
}
|
|
// Add marker once per second to signal that audio is active.
|
|
if (write_count_++ % 100 == 0) {
|
|
PRINT(".");
|
|
}
|
|
written_elements_ += size;
|
|
}
|
|
|
|
void Read(rtc::ArrayView<int16_t> destination) override {
|
|
rtc::CritScope lock(&lock_);
|
|
if (fifo_.empty()) {
|
|
std::fill(destination.begin(), destination.end(), 0);
|
|
} else {
|
|
const Buffer16& buffer = fifo_.front();
|
|
if (buffer.size() == destination.size()) {
|
|
// Default case where input and output uses same sample rate and
|
|
// channel configuration. No conversion is needed.
|
|
std::copy(buffer.begin(), buffer.end(), destination.begin());
|
|
} else if (destination.size() == 2 * buffer.size()) {
|
|
// Recorded input signal in |buffer| is in mono. Do channel upmix to
|
|
// match stereo output (1 -> 2).
|
|
for (size_t i = 0; i < buffer.size(); ++i) {
|
|
destination[2 * i] = buffer[i];
|
|
destination[2 * i + 1] = buffer[i];
|
|
}
|
|
} else if (buffer.size() == 2 * destination.size()) {
|
|
// Recorded input signal in |buffer| is in stereo. Do channel downmix
|
|
// to match mono output (2 -> 1).
|
|
for (size_t i = 0; i < destination.size(); ++i) {
|
|
destination[i] =
|
|
(static_cast<int32_t>(buffer[2 * i]) + buffer[2 * i + 1]) / 2;
|
|
}
|
|
} else {
|
|
RTC_NOTREACHED() << "Required conversion is not support";
|
|
}
|
|
fifo_.pop_front();
|
|
}
|
|
}
|
|
|
|
size_t size() const {
|
|
rtc::CritScope lock(&lock_);
|
|
return fifo_.size();
|
|
}
|
|
|
|
size_t max_size() const {
|
|
RTC_DCHECK_RUNS_SERIALIZED(&race_checker_);
|
|
return max_size_;
|
|
}
|
|
|
|
size_t average_size() const {
|
|
RTC_DCHECK_RUNS_SERIALIZED(&race_checker_);
|
|
return 0.5 + static_cast<float>(written_elements_ / write_count_);
|
|
}
|
|
|
|
using Buffer16 = rtc::BufferT<int16_t>;
|
|
|
|
rtc::CriticalSection lock_;
|
|
rtc::RaceChecker race_checker_;
|
|
|
|
std::list<Buffer16> fifo_ RTC_GUARDED_BY(lock_);
|
|
size_t write_count_ RTC_GUARDED_BY(race_checker_) = 0;
|
|
size_t max_size_ RTC_GUARDED_BY(race_checker_) = 0;
|
|
size_t written_elements_ RTC_GUARDED_BY(race_checker_) = 0;
|
|
};
|
|
|
|
// Inserts periodic impulses and measures the latency between the time of
|
|
// transmission and time of receiving the same impulse.
|
|
class LatencyAudioStream : public AudioStream {
|
|
public:
|
|
LatencyAudioStream() {
|
|
// Delay thread checkers from being initialized until first callback from
|
|
// respective thread.
|
|
read_thread_checker_.Detach();
|
|
write_thread_checker_.Detach();
|
|
}
|
|
|
|
// Insert periodic impulses in first two samples of |destination|.
|
|
void Read(rtc::ArrayView<int16_t> destination) override {
|
|
RTC_DCHECK_RUN_ON(&read_thread_checker_);
|
|
if (read_count_ == 0) {
|
|
PRINT("[");
|
|
}
|
|
read_count_++;
|
|
std::fill(destination.begin(), destination.end(), 0);
|
|
if (read_count_ % (kNumCallbacksPerSecond / kImpulseFrequencyInHz) == 0) {
|
|
PRINT(".");
|
|
{
|
|
rtc::CritScope lock(&lock_);
|
|
if (!pulse_time_) {
|
|
pulse_time_ = rtc::TimeMillis();
|
|
}
|
|
}
|
|
constexpr int16_t impulse = std::numeric_limits<int16_t>::max();
|
|
std::fill_n(destination.begin(), 2, impulse);
|
|
}
|
|
}
|
|
|
|
// Detect received impulses in |source|, derive time between transmission and
|
|
// detection and add the calculated delay to list of latencies.
|
|
void Write(rtc::ArrayView<const int16_t> source) override {
|
|
RTC_DCHECK_RUN_ON(&write_thread_checker_);
|
|
RTC_DCHECK_RUNS_SERIALIZED(&race_checker_);
|
|
rtc::CritScope lock(&lock_);
|
|
write_count_++;
|
|
if (!pulse_time_) {
|
|
// Avoid detection of new impulse response until a new impulse has
|
|
// been transmitted (sets |pulse_time_| to value larger than zero).
|
|
return;
|
|
}
|
|
// Find index (element position in vector) of the max element.
|
|
const size_t index_of_max =
|
|
std::max_element(source.begin(), source.end()) - source.begin();
|
|
// Derive time between transmitted pulse and received pulse if the level
|
|
// is high enough (removes noise).
|
|
const size_t max = source[index_of_max];
|
|
if (max > kImpulseThreshold) {
|
|
PRINTD("(%zu, %zu)", max, index_of_max);
|
|
int64_t now_time = rtc::TimeMillis();
|
|
int extra_delay = IndexToMilliseconds(index_of_max, source.size());
|
|
PRINTD("[%d]", rtc::checked_cast<int>(now_time - pulse_time_));
|
|
PRINTD("[%d]", extra_delay);
|
|
// Total latency is the difference between transmit time and detection
|
|
// tome plus the extra delay within the buffer in which we detected the
|
|
// received impulse. It is transmitted at sample 0 but can be received
|
|
// at sample N where N > 0. The term |extra_delay| accounts for N and it
|
|
// is a value between 0 and 10ms.
|
|
latencies_.push_back(now_time - *pulse_time_ + extra_delay);
|
|
pulse_time_.reset();
|
|
} else {
|
|
PRINTD("-");
|
|
}
|
|
}
|
|
|
|
size_t num_latency_values() const {
|
|
RTC_DCHECK_RUNS_SERIALIZED(&race_checker_);
|
|
return latencies_.size();
|
|
}
|
|
|
|
int min_latency() const {
|
|
RTC_DCHECK_RUNS_SERIALIZED(&race_checker_);
|
|
if (latencies_.empty())
|
|
return 0;
|
|
return *std::min_element(latencies_.begin(), latencies_.end());
|
|
}
|
|
|
|
int max_latency() const {
|
|
RTC_DCHECK_RUNS_SERIALIZED(&race_checker_);
|
|
if (latencies_.empty())
|
|
return 0;
|
|
return *std::max_element(latencies_.begin(), latencies_.end());
|
|
}
|
|
|
|
int average_latency() const {
|
|
RTC_DCHECK_RUNS_SERIALIZED(&race_checker_);
|
|
if (latencies_.empty())
|
|
return 0;
|
|
return 0.5 + static_cast<double>(
|
|
std::accumulate(latencies_.begin(), latencies_.end(), 0)) /
|
|
latencies_.size();
|
|
}
|
|
|
|
void PrintResults() const {
|
|
RTC_DCHECK_RUNS_SERIALIZED(&race_checker_);
|
|
PRINT("] ");
|
|
for (auto it = latencies_.begin(); it != latencies_.end(); ++it) {
|
|
PRINTD("%d ", *it);
|
|
}
|
|
PRINT("\n");
|
|
PRINT("[..........] [min, max, avg]=[%d, %d, %d] ms\n", min_latency(),
|
|
max_latency(), average_latency());
|
|
}
|
|
|
|
rtc::CriticalSection lock_;
|
|
rtc::RaceChecker race_checker_;
|
|
rtc::ThreadChecker read_thread_checker_;
|
|
rtc::ThreadChecker write_thread_checker_;
|
|
|
|
absl::optional<int64_t> pulse_time_ RTC_GUARDED_BY(lock_);
|
|
std::vector<int> latencies_ RTC_GUARDED_BY(race_checker_);
|
|
size_t read_count_ RTC_GUARDED_BY(read_thread_checker_) = 0;
|
|
size_t write_count_ RTC_GUARDED_BY(write_thread_checker_) = 0;
|
|
};
|
|
|
|
// Mocks the AudioTransport object and proxies actions for the two callbacks
|
|
// (RecordedDataIsAvailable and NeedMorePlayData) to different implementations
|
|
// of AudioStreamInterface.
|
|
class MockAudioTransport : public test::MockAudioTransport {
|
|
public:
|
|
explicit MockAudioTransport(TransportType type) : type_(type) {}
|
|
~MockAudioTransport() {}
|
|
|
|
// Set default actions of the mock object. We are delegating to fake
|
|
// implementation where the number of callbacks is counted and an event
|
|
// is set after a certain number of callbacks. Audio parameters are also
|
|
// checked.
|
|
void HandleCallbacks(rtc::Event* event,
|
|
AudioStream* audio_stream,
|
|
int num_callbacks) {
|
|
event_ = event;
|
|
audio_stream_ = audio_stream;
|
|
num_callbacks_ = num_callbacks;
|
|
if (play_mode()) {
|
|
ON_CALL(*this, NeedMorePlayData(_, _, _, _, _, _, _, _))
|
|
.WillByDefault(
|
|
Invoke(this, &MockAudioTransport::RealNeedMorePlayData));
|
|
}
|
|
if (rec_mode()) {
|
|
ON_CALL(*this, RecordedDataIsAvailable(_, _, _, _, _, _, _, _, _, _))
|
|
.WillByDefault(
|
|
Invoke(this, &MockAudioTransport::RealRecordedDataIsAvailable));
|
|
}
|
|
}
|
|
|
|
// Special constructor used in manual tests where the user wants to run audio
|
|
// until e.g. a keyboard key is pressed. The event flag is set to nullptr by
|
|
// default since it is up to the user to stop the test. See e.g.
|
|
// DISABLED_RunPlayoutAndRecordingInFullDuplexAndWaitForEnterKey().
|
|
void HandleCallbacks(AudioStream* audio_stream) {
|
|
HandleCallbacks(nullptr, audio_stream, 0);
|
|
}
|
|
|
|
int32_t RealRecordedDataIsAvailable(const void* audio_buffer,
|
|
const size_t samples_per_channel,
|
|
const size_t bytes_per_frame,
|
|
const size_t channels,
|
|
const uint32_t sample_rate,
|
|
const uint32_t total_delay_ms,
|
|
const int32_t clock_drift,
|
|
const uint32_t current_mic_level,
|
|
const bool typing_status,
|
|
uint32_t& new_mic_level) {
|
|
EXPECT_TRUE(rec_mode()) << "No test is expecting these callbacks.";
|
|
// Store audio parameters once in the first callback. For all other
|
|
// callbacks, verify that the provided audio parameters are maintained and
|
|
// that each callback corresponds to 10ms for any given sample rate.
|
|
if (!record_parameters_.is_complete()) {
|
|
record_parameters_.reset(sample_rate, channels, samples_per_channel);
|
|
} else {
|
|
EXPECT_EQ(samples_per_channel, record_parameters_.frames_per_buffer());
|
|
EXPECT_EQ(bytes_per_frame, record_parameters_.GetBytesPerFrame());
|
|
EXPECT_EQ(channels, record_parameters_.channels());
|
|
EXPECT_EQ(static_cast<int>(sample_rate),
|
|
record_parameters_.sample_rate());
|
|
EXPECT_EQ(samples_per_channel,
|
|
record_parameters_.frames_per_10ms_buffer());
|
|
}
|
|
{
|
|
rtc::CritScope lock(&lock_);
|
|
rec_count_++;
|
|
}
|
|
// Write audio data to audio stream object if one has been injected.
|
|
if (audio_stream_) {
|
|
audio_stream_->Write(
|
|
rtc::MakeArrayView(static_cast<const int16_t*>(audio_buffer),
|
|
samples_per_channel * channels));
|
|
}
|
|
// Signal the event after given amount of callbacks.
|
|
if (event_ && ReceivedEnoughCallbacks()) {
|
|
event_->Set();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int32_t RealNeedMorePlayData(const size_t samples_per_channel,
|
|
const size_t bytes_per_frame,
|
|
const size_t channels,
|
|
const uint32_t sample_rate,
|
|
void* audio_buffer,
|
|
size_t& samples_out,
|
|
int64_t* elapsed_time_ms,
|
|
int64_t* ntp_time_ms) {
|
|
EXPECT_TRUE(play_mode()) << "No test is expecting these callbacks.";
|
|
// Store audio parameters once in the first callback. For all other
|
|
// callbacks, verify that the provided audio parameters are maintained and
|
|
// that each callback corresponds to 10ms for any given sample rate.
|
|
if (!playout_parameters_.is_complete()) {
|
|
playout_parameters_.reset(sample_rate, channels, samples_per_channel);
|
|
} else {
|
|
EXPECT_EQ(samples_per_channel, playout_parameters_.frames_per_buffer());
|
|
EXPECT_EQ(bytes_per_frame, playout_parameters_.GetBytesPerFrame());
|
|
EXPECT_EQ(channels, playout_parameters_.channels());
|
|
EXPECT_EQ(static_cast<int>(sample_rate),
|
|
playout_parameters_.sample_rate());
|
|
EXPECT_EQ(samples_per_channel,
|
|
playout_parameters_.frames_per_10ms_buffer());
|
|
}
|
|
{
|
|
rtc::CritScope lock(&lock_);
|
|
play_count_++;
|
|
}
|
|
samples_out = samples_per_channel * channels;
|
|
// Read audio data from audio stream object if one has been injected.
|
|
if (audio_stream_) {
|
|
audio_stream_->Read(rtc::MakeArrayView(
|
|
static_cast<int16_t*>(audio_buffer), samples_per_channel * channels));
|
|
} else {
|
|
// Fill the audio buffer with zeros to avoid disturbing audio.
|
|
const size_t num_bytes = samples_per_channel * bytes_per_frame;
|
|
std::memset(audio_buffer, 0, num_bytes);
|
|
}
|
|
// Signal the event after given amount of callbacks.
|
|
if (event_ && ReceivedEnoughCallbacks()) {
|
|
event_->Set();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool ReceivedEnoughCallbacks() {
|
|
bool recording_done = false;
|
|
if (rec_mode()) {
|
|
rtc::CritScope lock(&lock_);
|
|
recording_done = rec_count_ >= num_callbacks_;
|
|
} else {
|
|
recording_done = true;
|
|
}
|
|
bool playout_done = false;
|
|
if (play_mode()) {
|
|
rtc::CritScope lock(&lock_);
|
|
playout_done = play_count_ >= num_callbacks_;
|
|
} else {
|
|
playout_done = true;
|
|
}
|
|
return recording_done && playout_done;
|
|
}
|
|
|
|
bool play_mode() const {
|
|
return type_ == TransportType::kPlay ||
|
|
type_ == TransportType::kPlayAndRecord;
|
|
}
|
|
|
|
bool rec_mode() const {
|
|
return type_ == TransportType::kRecord ||
|
|
type_ == TransportType::kPlayAndRecord;
|
|
}
|
|
|
|
void ResetCallbackCounters() {
|
|
rtc::CritScope lock(&lock_);
|
|
if (play_mode()) {
|
|
play_count_ = 0;
|
|
}
|
|
if (rec_mode()) {
|
|
rec_count_ = 0;
|
|
}
|
|
}
|
|
|
|
private:
|
|
rtc::CriticalSection lock_;
|
|
TransportType type_ = TransportType::kInvalid;
|
|
rtc::Event* event_ = nullptr;
|
|
AudioStream* audio_stream_ = nullptr;
|
|
size_t num_callbacks_ = 0;
|
|
size_t play_count_ RTC_GUARDED_BY(lock_) = 0;
|
|
size_t rec_count_ RTC_GUARDED_BY(lock_) = 0;
|
|
AudioParameters playout_parameters_;
|
|
AudioParameters record_parameters_;
|
|
};
|
|
|
|
// AudioDeviceTest test fixture.
|
|
class AudioDeviceTest
|
|
: public ::testing::TestWithParam<webrtc::AudioDeviceModule::AudioLayer> {
|
|
protected:
|
|
AudioDeviceTest()
|
|
: audio_layer_(GetParam()),
|
|
task_queue_factory_(CreateDefaultTaskQueueFactory()) {
|
|
// TODO(webrtc:9778): Re-enable on THREAD_SANITIZER?
|
|
#if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) && \
|
|
!defined(WEBRTC_DUMMY_AUDIO_BUILD) && !defined(THREAD_SANITIZER)
|
|
rtc::LogMessage::LogToDebug(rtc::LS_INFO);
|
|
// Add extra logging fields here if needed for debugging.
|
|
rtc::LogMessage::LogTimestamps();
|
|
rtc::LogMessage::LogThreads();
|
|
audio_device_ = CreateAudioDevice();
|
|
EXPECT_NE(audio_device_.get(), nullptr);
|
|
AudioDeviceModule::AudioLayer audio_layer;
|
|
int got_platform_audio_layer =
|
|
audio_device_->ActiveAudioLayer(&audio_layer);
|
|
// First, ensure that a valid audio layer can be activated.
|
|
if (got_platform_audio_layer != 0) {
|
|
requirements_satisfied_ = false;
|
|
}
|
|
// Next, verify that the ADM can be initialized.
|
|
if (requirements_satisfied_) {
|
|
requirements_satisfied_ = (audio_device_->Init() == 0);
|
|
}
|
|
// Finally, ensure that at least one valid device exists in each direction.
|
|
if (requirements_satisfied_) {
|
|
const int16_t num_playout_devices = audio_device_->PlayoutDevices();
|
|
const int16_t num_record_devices = audio_device_->RecordingDevices();
|
|
requirements_satisfied_ =
|
|
num_playout_devices > 0 && num_record_devices > 0;
|
|
}
|
|
#else
|
|
requirements_satisfied_ = false;
|
|
#endif
|
|
if (requirements_satisfied_) {
|
|
EXPECT_EQ(0, audio_device_->SetPlayoutDevice(AUDIO_DEVICE_ID));
|
|
EXPECT_EQ(0, audio_device_->InitSpeaker());
|
|
EXPECT_EQ(0, audio_device_->StereoPlayoutIsAvailable(&stereo_playout_));
|
|
EXPECT_EQ(0, audio_device_->SetStereoPlayout(stereo_playout_));
|
|
EXPECT_EQ(0, audio_device_->SetRecordingDevice(AUDIO_DEVICE_ID));
|
|
EXPECT_EQ(0, audio_device_->InitMicrophone());
|
|
// Avoid asking for input stereo support and always record in mono
|
|
// since asking can cause issues in combination with remote desktop.
|
|
// See https://bugs.chromium.org/p/webrtc/issues/detail?id=7397 for
|
|
// details.
|
|
EXPECT_EQ(0, audio_device_->SetStereoRecording(false));
|
|
}
|
|
}
|
|
|
|
virtual ~AudioDeviceTest() {
|
|
if (audio_device_) {
|
|
EXPECT_EQ(0, audio_device_->Terminate());
|
|
}
|
|
}
|
|
|
|
bool requirements_satisfied() const { return requirements_satisfied_; }
|
|
rtc::Event* event() { return &event_; }
|
|
AudioDeviceModule::AudioLayer audio_layer() const { return audio_layer_; }
|
|
|
|
// AudioDeviceModuleForTest extends the default ADM interface with some extra
|
|
// test methods. Intended for usage in tests only and requires a unique
|
|
// factory method. See CreateAudioDevice() for details.
|
|
const rtc::scoped_refptr<AudioDeviceModuleForTest>& audio_device() const {
|
|
return audio_device_;
|
|
}
|
|
|
|
rtc::scoped_refptr<AudioDeviceModuleForTest> CreateAudioDevice() {
|
|
// Use the default factory for kPlatformDefaultAudio and a special factory
|
|
// CreateWindowsCoreAudioAudioDeviceModuleForTest() for kWindowsCoreAudio2.
|
|
// The value of |audio_layer_| is set at construction by GetParam() and two
|
|
// different layers are tested on Windows only.
|
|
if (audio_layer_ == AudioDeviceModule::kPlatformDefaultAudio) {
|
|
return AudioDeviceModule::CreateForTest(audio_layer_,
|
|
task_queue_factory_.get());
|
|
} else if (audio_layer_ == AudioDeviceModule::kWindowsCoreAudio2) {
|
|
#ifdef WEBRTC_WIN
|
|
// We must initialize the COM library on a thread before we calling any of
|
|
// the library functions. All COM functions in the ADM will return
|
|
// CO_E_NOTINITIALIZED otherwise.
|
|
com_initializer_ = absl::make_unique<webrtc_win::ScopedCOMInitializer>(
|
|
webrtc_win::ScopedCOMInitializer::kMTA);
|
|
EXPECT_TRUE(com_initializer_->Succeeded());
|
|
EXPECT_TRUE(webrtc_win::core_audio_utility::IsSupported());
|
|
EXPECT_TRUE(webrtc_win::core_audio_utility::IsMMCSSSupported());
|
|
return CreateWindowsCoreAudioAudioDeviceModuleForTest(
|
|
task_queue_factory_.get());
|
|
#else
|
|
return nullptr;
|
|
#endif
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
void StartPlayout() {
|
|
EXPECT_FALSE(audio_device()->Playing());
|
|
EXPECT_EQ(0, audio_device()->InitPlayout());
|
|
EXPECT_TRUE(audio_device()->PlayoutIsInitialized());
|
|
EXPECT_EQ(0, audio_device()->StartPlayout());
|
|
EXPECT_TRUE(audio_device()->Playing());
|
|
}
|
|
|
|
void StopPlayout() {
|
|
EXPECT_EQ(0, audio_device()->StopPlayout());
|
|
EXPECT_FALSE(audio_device()->Playing());
|
|
EXPECT_FALSE(audio_device()->PlayoutIsInitialized());
|
|
}
|
|
|
|
void StartRecording() {
|
|
EXPECT_FALSE(audio_device()->Recording());
|
|
EXPECT_EQ(0, audio_device()->InitRecording());
|
|
EXPECT_TRUE(audio_device()->RecordingIsInitialized());
|
|
EXPECT_EQ(0, audio_device()->StartRecording());
|
|
EXPECT_TRUE(audio_device()->Recording());
|
|
}
|
|
|
|
void StopRecording() {
|
|
EXPECT_EQ(0, audio_device()->StopRecording());
|
|
EXPECT_FALSE(audio_device()->Recording());
|
|
EXPECT_FALSE(audio_device()->RecordingIsInitialized());
|
|
}
|
|
|
|
bool NewWindowsAudioDeviceModuleIsUsed() {
|
|
#ifdef WEBRTC_WIN
|
|
AudioDeviceModule::AudioLayer audio_layer;
|
|
EXPECT_EQ(0, audio_device()->ActiveAudioLayer(&audio_layer));
|
|
if (audio_layer == AudioDeviceModule::kWindowsCoreAudio2) {
|
|
// Default device is always added as first element in the list and the
|
|
// default communication device as the second element. Hence, the list
|
|
// contains two extra elements in this case.
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
private:
|
|
#ifdef WEBRTC_WIN
|
|
// Windows Core Audio based ADM needs to run on a COM initialized thread.
|
|
std::unique_ptr<webrtc_win::ScopedCOMInitializer> com_initializer_;
|
|
#endif
|
|
AudioDeviceModule::AudioLayer audio_layer_;
|
|
std::unique_ptr<TaskQueueFactory> task_queue_factory_;
|
|
bool requirements_satisfied_ = true;
|
|
rtc::Event event_;
|
|
rtc::scoped_refptr<AudioDeviceModuleForTest> audio_device_;
|
|
bool stereo_playout_ = false;
|
|
};
|
|
|
|
// Instead of using the test fixture, verify that the different factory methods
|
|
// work as intended.
|
|
TEST(AudioDeviceTestWin, ConstructDestructWithFactory) {
|
|
std::unique_ptr<TaskQueueFactory> task_queue_factory =
|
|
CreateDefaultTaskQueueFactory();
|
|
rtc::scoped_refptr<AudioDeviceModule> audio_device;
|
|
// The default factory should work for all platforms when a default ADM is
|
|
// requested.
|
|
audio_device = AudioDeviceModule::Create(
|
|
AudioDeviceModule::kPlatformDefaultAudio, task_queue_factory.get());
|
|
EXPECT_TRUE(audio_device);
|
|
audio_device = nullptr;
|
|
#ifdef WEBRTC_WIN
|
|
// For Windows, the old factory method creates an ADM where the platform-
|
|
// specific parts are implemented by an AudioDeviceGeneric object. Verify
|
|
// that the old factory can't be used in combination with the latest audio
|
|
// layer AudioDeviceModule::kWindowsCoreAudio2.
|
|
audio_device = AudioDeviceModule::Create(
|
|
AudioDeviceModule::kWindowsCoreAudio2, task_queue_factory.get());
|
|
EXPECT_FALSE(audio_device);
|
|
audio_device = nullptr;
|
|
// Instead, ensure that the new dedicated factory method called
|
|
// CreateWindowsCoreAudioAudioDeviceModule() can be used on Windows and that
|
|
// it sets the audio layer to kWindowsCoreAudio2 implicitly. Note that, the
|
|
// new ADM for Windows must be created on a COM thread.
|
|
webrtc_win::ScopedCOMInitializer com_initializer(
|
|
webrtc_win::ScopedCOMInitializer::kMTA);
|
|
EXPECT_TRUE(com_initializer.Succeeded());
|
|
audio_device =
|
|
CreateWindowsCoreAudioAudioDeviceModule(task_queue_factory.get());
|
|
EXPECT_TRUE(audio_device);
|
|
AudioDeviceModule::AudioLayer audio_layer;
|
|
EXPECT_EQ(0, audio_device->ActiveAudioLayer(&audio_layer));
|
|
EXPECT_EQ(audio_layer, AudioDeviceModule::kWindowsCoreAudio2);
|
|
#endif
|
|
}
|
|
|
|
// Uses the test fixture to create, initialize and destruct the ADM.
|
|
TEST_P(AudioDeviceTest, ConstructDestructDefault) {}
|
|
|
|
TEST_P(AudioDeviceTest, InitTerminate) {
|
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
|
// Initialization is part of the test fixture.
|
|
EXPECT_TRUE(audio_device()->Initialized());
|
|
EXPECT_EQ(0, audio_device()->Terminate());
|
|
EXPECT_FALSE(audio_device()->Initialized());
|
|
}
|
|
|
|
// Enumerate all available and active output devices.
|
|
TEST_P(AudioDeviceTest, PlayoutDeviceNames) {
|
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
|
char device_name[kAdmMaxDeviceNameSize];
|
|
char unique_id[kAdmMaxGuidSize];
|
|
int num_devices = audio_device()->PlayoutDevices();
|
|
if (NewWindowsAudioDeviceModuleIsUsed()) {
|
|
num_devices += 2;
|
|
}
|
|
EXPECT_GT(num_devices, 0);
|
|
for (int i = 0; i < num_devices; ++i) {
|
|
EXPECT_EQ(0, audio_device()->PlayoutDeviceName(i, device_name, unique_id));
|
|
}
|
|
EXPECT_EQ(-1, audio_device()->PlayoutDeviceName(num_devices, device_name,
|
|
unique_id));
|
|
}
|
|
|
|
// Enumerate all available and active input devices.
|
|
TEST_P(AudioDeviceTest, RecordingDeviceNames) {
|
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
|
char device_name[kAdmMaxDeviceNameSize];
|
|
char unique_id[kAdmMaxGuidSize];
|
|
int num_devices = audio_device()->RecordingDevices();
|
|
if (NewWindowsAudioDeviceModuleIsUsed()) {
|
|
num_devices += 2;
|
|
}
|
|
EXPECT_GT(num_devices, 0);
|
|
for (int i = 0; i < num_devices; ++i) {
|
|
EXPECT_EQ(0,
|
|
audio_device()->RecordingDeviceName(i, device_name, unique_id));
|
|
}
|
|
EXPECT_EQ(-1, audio_device()->RecordingDeviceName(num_devices, device_name,
|
|
unique_id));
|
|
}
|
|
|
|
// Counts number of active output devices and ensure that all can be selected.
|
|
TEST_P(AudioDeviceTest, SetPlayoutDevice) {
|
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
|
int num_devices = audio_device()->PlayoutDevices();
|
|
if (NewWindowsAudioDeviceModuleIsUsed()) {
|
|
num_devices += 2;
|
|
}
|
|
EXPECT_GT(num_devices, 0);
|
|
// Verify that all available playout devices can be set (not enabled yet).
|
|
for (int i = 0; i < num_devices; ++i) {
|
|
EXPECT_EQ(0, audio_device()->SetPlayoutDevice(i));
|
|
}
|
|
EXPECT_EQ(-1, audio_device()->SetPlayoutDevice(num_devices));
|
|
#ifdef WEBRTC_WIN
|
|
// On Windows, verify the alternative method where the user can select device
|
|
// by role.
|
|
EXPECT_EQ(
|
|
0, audio_device()->SetPlayoutDevice(AudioDeviceModule::kDefaultDevice));
|
|
EXPECT_EQ(0, audio_device()->SetPlayoutDevice(
|
|
AudioDeviceModule::kDefaultCommunicationDevice));
|
|
#endif
|
|
}
|
|
|
|
// Counts number of active input devices and ensure that all can be selected.
|
|
TEST_P(AudioDeviceTest, SetRecordingDevice) {
|
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
|
int num_devices = audio_device()->RecordingDevices();
|
|
if (NewWindowsAudioDeviceModuleIsUsed()) {
|
|
num_devices += 2;
|
|
}
|
|
EXPECT_GT(num_devices, 0);
|
|
// Verify that all available recording devices can be set (not enabled yet).
|
|
for (int i = 0; i < num_devices; ++i) {
|
|
EXPECT_EQ(0, audio_device()->SetRecordingDevice(i));
|
|
}
|
|
EXPECT_EQ(-1, audio_device()->SetRecordingDevice(num_devices));
|
|
#ifdef WEBRTC_WIN
|
|
// On Windows, verify the alternative method where the user can select device
|
|
// by role.
|
|
EXPECT_EQ(
|
|
0, audio_device()->SetRecordingDevice(AudioDeviceModule::kDefaultDevice));
|
|
EXPECT_EQ(0, audio_device()->SetRecordingDevice(
|
|
AudioDeviceModule::kDefaultCommunicationDevice));
|
|
#endif
|
|
}
|
|
|
|
// Tests Start/Stop playout without any registered audio callback.
|
|
TEST_P(AudioDeviceTest, StartStopPlayout) {
|
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
|
StartPlayout();
|
|
StopPlayout();
|
|
}
|
|
|
|
// Tests Start/Stop recording without any registered audio callback.
|
|
TEST_P(AudioDeviceTest, StartStopRecording) {
|
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
|
StartRecording();
|
|
StopRecording();
|
|
}
|
|
|
|
// Tests Init/Stop/Init recording without any registered audio callback.
|
|
// See https://bugs.chromium.org/p/webrtc/issues/detail?id=8041 for details
|
|
// on why this test is useful.
|
|
TEST_P(AudioDeviceTest, InitStopInitRecording) {
|
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
|
EXPECT_EQ(0, audio_device()->InitRecording());
|
|
EXPECT_TRUE(audio_device()->RecordingIsInitialized());
|
|
StopRecording();
|
|
EXPECT_EQ(0, audio_device()->InitRecording());
|
|
StopRecording();
|
|
}
|
|
|
|
// Tests Init/Stop/Init recording while playout is active.
|
|
TEST_P(AudioDeviceTest, InitStopInitRecordingWhilePlaying) {
|
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
|
StartPlayout();
|
|
EXPECT_EQ(0, audio_device()->InitRecording());
|
|
EXPECT_TRUE(audio_device()->RecordingIsInitialized());
|
|
StopRecording();
|
|
EXPECT_EQ(0, audio_device()->InitRecording());
|
|
StopRecording();
|
|
StopPlayout();
|
|
}
|
|
|
|
// Tests Init/Stop/Init playout without any registered audio callback.
|
|
TEST_P(AudioDeviceTest, InitStopInitPlayout) {
|
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
|
EXPECT_EQ(0, audio_device()->InitPlayout());
|
|
EXPECT_TRUE(audio_device()->PlayoutIsInitialized());
|
|
StopPlayout();
|
|
EXPECT_EQ(0, audio_device()->InitPlayout());
|
|
StopPlayout();
|
|
}
|
|
|
|
// Tests Init/Stop/Init playout while recording is active.
|
|
TEST_P(AudioDeviceTest, InitStopInitPlayoutWhileRecording) {
|
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
|
StartRecording();
|
|
EXPECT_EQ(0, audio_device()->InitPlayout());
|
|
EXPECT_TRUE(audio_device()->PlayoutIsInitialized());
|
|
StopPlayout();
|
|
EXPECT_EQ(0, audio_device()->InitPlayout());
|
|
StopPlayout();
|
|
StopRecording();
|
|
}
|
|
|
|
// TODO(henrika): restart without intermediate destruction is currently only
|
|
// supported on Windows.
|
|
#ifdef WEBRTC_WIN
|
|
// Tests Start/Stop playout followed by a second session (emulates a restart
|
|
// triggered by a user using public APIs).
|
|
TEST_P(AudioDeviceTest, StartStopPlayoutWithExternalRestart) {
|
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
|
StartPlayout();
|
|
StopPlayout();
|
|
// Restart playout without destroying the ADM in between. Ensures that we
|
|
// support: Init(), Start(), Stop(), Init(), Start(), Stop().
|
|
StartPlayout();
|
|
StopPlayout();
|
|
}
|
|
|
|
// Tests Start/Stop recording followed by a second session (emulates a restart
|
|
// triggered by a user using public APIs).
|
|
TEST_P(AudioDeviceTest, StartStopRecordingWithExternalRestart) {
|
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
|
StartRecording();
|
|
StopRecording();
|
|
// Restart recording without destroying the ADM in between. Ensures that we
|
|
// support: Init(), Start(), Stop(), Init(), Start(), Stop().
|
|
StartRecording();
|
|
StopRecording();
|
|
}
|
|
|
|
// Tests Start/Stop playout followed by a second session (emulates a restart
|
|
// triggered by an internal callback e.g. corresponding to a device switch).
|
|
// Note that, internal restart is only supported in combination with the latest
|
|
// Windows ADM.
|
|
TEST_P(AudioDeviceTest, StartStopPlayoutWithInternalRestart) {
|
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
|
if (audio_layer() != AudioDeviceModule::kWindowsCoreAudio2) {
|
|
return;
|
|
}
|
|
MockAudioTransport mock(TransportType::kPlay);
|
|
mock.HandleCallbacks(event(), nullptr, kNumCallbacks);
|
|
EXPECT_CALL(mock, NeedMorePlayData(_, _, _, _, NotNull(), _, _, _))
|
|
.Times(AtLeast(kNumCallbacks));
|
|
EXPECT_EQ(0, audio_device()->RegisterAudioCallback(&mock));
|
|
StartPlayout();
|
|
event()->Wait(kTestTimeOutInMilliseconds);
|
|
EXPECT_TRUE(audio_device()->Playing());
|
|
// Restart playout but without stopping the internal audio thread.
|
|
// This procedure uses a non-public test API and it emulates what happens
|
|
// inside the ADM when e.g. a device is removed.
|
|
EXPECT_EQ(0, audio_device()->RestartPlayoutInternally());
|
|
|
|
// Run basic tests of public APIs while a restart attempt is active.
|
|
// These calls should now be very thin and not trigger any new actions.
|
|
EXPECT_EQ(-1, audio_device()->StopPlayout());
|
|
EXPECT_TRUE(audio_device()->Playing());
|
|
EXPECT_TRUE(audio_device()->PlayoutIsInitialized());
|
|
EXPECT_EQ(0, audio_device()->InitPlayout());
|
|
EXPECT_EQ(0, audio_device()->StartPlayout());
|
|
|
|
// Wait until audio has restarted and a new sequence of audio callbacks
|
|
// becomes active.
|
|
// TODO(henrika): is it possible to verify that the internal state transition
|
|
// is Stop->Init->Start?
|
|
ASSERT_TRUE(Mock::VerifyAndClearExpectations(&mock));
|
|
mock.ResetCallbackCounters();
|
|
EXPECT_CALL(mock, NeedMorePlayData(_, _, _, _, NotNull(), _, _, _))
|
|
.Times(AtLeast(kNumCallbacks));
|
|
event()->Wait(kTestTimeOutInMilliseconds);
|
|
EXPECT_TRUE(audio_device()->Playing());
|
|
// Stop playout and the audio thread after successful internal restart.
|
|
StopPlayout();
|
|
}
|
|
|
|
// Tests Start/Stop recording followed by a second session (emulates a restart
|
|
// triggered by an internal callback e.g. corresponding to a device switch).
|
|
// Note that, internal restart is only supported in combination with the latest
|
|
// Windows ADM.
|
|
TEST_P(AudioDeviceTest, StartStopRecordingWithInternalRestart) {
|
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
|
if (audio_layer() != AudioDeviceModule::kWindowsCoreAudio2) {
|
|
return;
|
|
}
|
|
MockAudioTransport mock(TransportType::kRecord);
|
|
mock.HandleCallbacks(event(), nullptr, kNumCallbacks);
|
|
EXPECT_CALL(mock, RecordedDataIsAvailable(NotNull(), _, _, _, _, Ge(0u), 0, _,
|
|
false, _))
|
|
.Times(AtLeast(kNumCallbacks));
|
|
EXPECT_EQ(0, audio_device()->RegisterAudioCallback(&mock));
|
|
StartRecording();
|
|
event()->Wait(kTestTimeOutInMilliseconds);
|
|
EXPECT_TRUE(audio_device()->Recording());
|
|
// Restart recording but without stopping the internal audio thread.
|
|
// This procedure uses a non-public test API and it emulates what happens
|
|
// inside the ADM when e.g. a device is removed.
|
|
EXPECT_EQ(0, audio_device()->RestartRecordingInternally());
|
|
|
|
// Run basic tests of public APIs while a restart attempt is active.
|
|
// These calls should now be very thin and not trigger any new actions.
|
|
EXPECT_EQ(-1, audio_device()->StopRecording());
|
|
EXPECT_TRUE(audio_device()->Recording());
|
|
EXPECT_TRUE(audio_device()->RecordingIsInitialized());
|
|
EXPECT_EQ(0, audio_device()->InitRecording());
|
|
EXPECT_EQ(0, audio_device()->StartRecording());
|
|
|
|
// Wait until audio has restarted and a new sequence of audio callbacks
|
|
// becomes active.
|
|
// TODO(henrika): is it possible to verify that the internal state transition
|
|
// is Stop->Init->Start?
|
|
ASSERT_TRUE(Mock::VerifyAndClearExpectations(&mock));
|
|
mock.ResetCallbackCounters();
|
|
EXPECT_CALL(mock, RecordedDataIsAvailable(NotNull(), _, _, _, _, Ge(0u), 0, _,
|
|
false, _))
|
|
.Times(AtLeast(kNumCallbacks));
|
|
event()->Wait(kTestTimeOutInMilliseconds);
|
|
EXPECT_TRUE(audio_device()->Recording());
|
|
// Stop recording and the audio thread after successful internal restart.
|
|
StopRecording();
|
|
}
|
|
#endif // #ifdef WEBRTC_WIN
|
|
|
|
// Start playout and verify that the native audio layer starts asking for real
|
|
// audio samples to play out using the NeedMorePlayData() callback.
|
|
// Note that we can't add expectations on audio parameters in EXPECT_CALL
|
|
// since parameter are not provided in the each callback. We therefore test and
|
|
// verify the parameters in the fake audio transport implementation instead.
|
|
TEST_P(AudioDeviceTest, StartPlayoutVerifyCallbacks) {
|
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
|
MockAudioTransport mock(TransportType::kPlay);
|
|
mock.HandleCallbacks(event(), nullptr, kNumCallbacks);
|
|
EXPECT_CALL(mock, NeedMorePlayData(_, _, _, _, NotNull(), _, _, _))
|
|
.Times(AtLeast(kNumCallbacks));
|
|
EXPECT_EQ(0, audio_device()->RegisterAudioCallback(&mock));
|
|
StartPlayout();
|
|
event()->Wait(kTestTimeOutInMilliseconds);
|
|
StopPlayout();
|
|
}
|
|
|
|
// Start recording and verify that the native audio layer starts providing real
|
|
// audio samples using the RecordedDataIsAvailable() callback.
|
|
TEST_P(AudioDeviceTest, StartRecordingVerifyCallbacks) {
|
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
|
MockAudioTransport mock(TransportType::kRecord);
|
|
mock.HandleCallbacks(event(), nullptr, kNumCallbacks);
|
|
EXPECT_CALL(mock, RecordedDataIsAvailable(NotNull(), _, _, _, _, Ge(0u), 0, _,
|
|
false, _))
|
|
.Times(AtLeast(kNumCallbacks));
|
|
EXPECT_EQ(0, audio_device()->RegisterAudioCallback(&mock));
|
|
StartRecording();
|
|
event()->Wait(kTestTimeOutInMilliseconds);
|
|
StopRecording();
|
|
}
|
|
|
|
// Start playout and recording (full-duplex audio) and verify that audio is
|
|
// active in both directions.
|
|
TEST_P(AudioDeviceTest, StartPlayoutAndRecordingVerifyCallbacks) {
|
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
|
MockAudioTransport mock(TransportType::kPlayAndRecord);
|
|
mock.HandleCallbacks(event(), nullptr, kNumCallbacks);
|
|
EXPECT_CALL(mock, NeedMorePlayData(_, _, _, _, NotNull(), _, _, _))
|
|
.Times(AtLeast(kNumCallbacks));
|
|
EXPECT_CALL(mock, RecordedDataIsAvailable(NotNull(), _, _, _, _, Ge(0u), 0, _,
|
|
false, _))
|
|
.Times(AtLeast(kNumCallbacks));
|
|
EXPECT_EQ(0, audio_device()->RegisterAudioCallback(&mock));
|
|
StartPlayout();
|
|
StartRecording();
|
|
event()->Wait(kTestTimeOutInMilliseconds);
|
|
StopRecording();
|
|
StopPlayout();
|
|
}
|
|
|
|
// Start playout and recording and store recorded data in an intermediate FIFO
|
|
// buffer from which the playout side then reads its samples in the same order
|
|
// as they were stored. Under ideal circumstances, a callback sequence would
|
|
// look like: ...+-+-+-+-+-+-+-..., where '+' means 'packet recorded' and '-'
|
|
// means 'packet played'. Under such conditions, the FIFO would contain max 1,
|
|
// with an average somewhere in (0,1) depending on how long the packets are
|
|
// buffered. However, under more realistic conditions, the size
|
|
// of the FIFO will vary more due to an unbalance between the two sides.
|
|
// This test tries to verify that the device maintains a balanced callback-
|
|
// sequence by running in loopback for a few seconds while measuring the size
|
|
// (max and average) of the FIFO. The size of the FIFO is increased by the
|
|
// recording side and decreased by the playout side.
|
|
TEST_P(AudioDeviceTest, RunPlayoutAndRecordingInFullDuplex) {
|
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
|
NiceMock<MockAudioTransport> mock(TransportType::kPlayAndRecord);
|
|
FifoAudioStream audio_stream;
|
|
mock.HandleCallbacks(event(), &audio_stream,
|
|
kFullDuplexTimeInSec * kNumCallbacksPerSecond);
|
|
EXPECT_EQ(0, audio_device()->RegisterAudioCallback(&mock));
|
|
// Run both sides using the same channel configuration to avoid conversions
|
|
// between mono/stereo while running in full duplex mode. Also, some devices
|
|
// (mainly on Windows) do not support mono.
|
|
EXPECT_EQ(0, audio_device()->SetStereoPlayout(true));
|
|
EXPECT_EQ(0, audio_device()->SetStereoRecording(true));
|
|
// Mute speakers to prevent howling.
|
|
EXPECT_EQ(0, audio_device()->SetSpeakerVolume(0));
|
|
StartPlayout();
|
|
StartRecording();
|
|
event()->Wait(static_cast<int>(
|
|
std::max(kTestTimeOutInMilliseconds, 1000 * kFullDuplexTimeInSec)));
|
|
StopRecording();
|
|
StopPlayout();
|
|
// This thresholds is set rather high to accommodate differences in hardware
|
|
// in several devices. The main idea is to capture cases where a very large
|
|
// latency is built up. See http://bugs.webrtc.org/7744 for examples on
|
|
// bots where relatively large average latencies can happen.
|
|
EXPECT_LE(audio_stream.average_size(), 25u);
|
|
PRINT("\n");
|
|
}
|
|
|
|
// Runs audio in full duplex until user hits Enter. Intended as a manual test
|
|
// to ensure that the audio quality is good and that real device switches works
|
|
// as intended.
|
|
TEST_P(AudioDeviceTest,
|
|
DISABLED_RunPlayoutAndRecordingInFullDuplexAndWaitForEnterKey) {
|
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
|
if (audio_layer() != AudioDeviceModule::kWindowsCoreAudio2) {
|
|
return;
|
|
}
|
|
NiceMock<MockAudioTransport> mock(TransportType::kPlayAndRecord);
|
|
FifoAudioStream audio_stream;
|
|
mock.HandleCallbacks(&audio_stream);
|
|
EXPECT_EQ(0, audio_device()->RegisterAudioCallback(&mock));
|
|
EXPECT_EQ(0, audio_device()->SetStereoPlayout(true));
|
|
EXPECT_EQ(0, audio_device()->SetStereoRecording(true));
|
|
// Ensure that the sample rate for both directions are identical so that we
|
|
// always can listen to our own voice. Will lead to rate conversion (and
|
|
// higher latency) if the native sample rate is not 48kHz.
|
|
EXPECT_EQ(0, audio_device()->SetPlayoutSampleRate(48000));
|
|
EXPECT_EQ(0, audio_device()->SetRecordingSampleRate(48000));
|
|
StartPlayout();
|
|
StartRecording();
|
|
do {
|
|
PRINT("Loopback audio is active at 48kHz. Press Enter to stop.\n");
|
|
} while (getchar() != '\n');
|
|
StopRecording();
|
|
StopPlayout();
|
|
}
|
|
|
|
// Measures loopback latency and reports the min, max and average values for
|
|
// a full duplex audio session.
|
|
// The latency is measured like so:
|
|
// - Insert impulses periodically on the output side.
|
|
// - Detect the impulses on the input side.
|
|
// - Measure the time difference between the transmit time and receive time.
|
|
// - Store time differences in a vector and calculate min, max and average.
|
|
// This test needs the '--gtest_also_run_disabled_tests' flag to run and also
|
|
// some sort of audio feedback loop. E.g. a headset where the mic is placed
|
|
// close to the speaker to ensure highest possible echo. It is also recommended
|
|
// to run the test at highest possible output volume.
|
|
TEST_P(AudioDeviceTest, DISABLED_MeasureLoopbackLatency) {
|
|
SKIP_TEST_IF_NOT(requirements_satisfied());
|
|
NiceMock<MockAudioTransport> mock(TransportType::kPlayAndRecord);
|
|
LatencyAudioStream audio_stream;
|
|
mock.HandleCallbacks(event(), &audio_stream,
|
|
kMeasureLatencyTimeInSec * kNumCallbacksPerSecond);
|
|
EXPECT_EQ(0, audio_device()->RegisterAudioCallback(&mock));
|
|
EXPECT_EQ(0, audio_device()->SetStereoPlayout(true));
|
|
EXPECT_EQ(0, audio_device()->SetStereoRecording(true));
|
|
StartPlayout();
|
|
StartRecording();
|
|
event()->Wait(static_cast<int>(
|
|
std::max(kTestTimeOutInMilliseconds, 1000 * kMeasureLatencyTimeInSec)));
|
|
StopRecording();
|
|
StopPlayout();
|
|
// Verify that a sufficient number of transmitted impulses are detected.
|
|
EXPECT_GE(audio_stream.num_latency_values(),
|
|
static_cast<size_t>(
|
|
kImpulseFrequencyInHz * kMeasureLatencyTimeInSec - 2));
|
|
// Print out min, max and average delay values for debugging purposes.
|
|
audio_stream.PrintResults();
|
|
}
|
|
|
|
#ifdef WEBRTC_WIN
|
|
// Test two different audio layers (or rather two different Core Audio
|
|
// implementations) for Windows.
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
AudioLayerWin,
|
|
AudioDeviceTest,
|
|
::testing::Values(AudioDeviceModule::kPlatformDefaultAudio,
|
|
AudioDeviceModule::kWindowsCoreAudio2));
|
|
#else
|
|
// For all platforms but Windows, only test the default audio layer.
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
AudioLayer,
|
|
AudioDeviceTest,
|
|
::testing::Values(AudioDeviceModule::kPlatformDefaultAudio));
|
|
#endif
|
|
|
|
} // namespace webrtc
|