Index: webrtc/modules/audio_device/audio_device_unittest.cc |
diff --git a/webrtc/modules/audio_device/audio_device_unittest.cc b/webrtc/modules/audio_device/audio_device_unittest.cc |
index de9c197945d87ba3b2e681ed204c1a7e8844604d..a2c05f40a9492fb3304679def17bb4cb3490e995 100644 |
--- a/webrtc/modules/audio_device/audio_device_unittest.cc |
+++ b/webrtc/modules/audio_device/audio_device_unittest.cc |
@@ -10,6 +10,7 @@ |
#include <cstring> |
+#include "webrtc/base/criticalsection.h" |
#include "webrtc/base/event.h" |
#include "webrtc/base/logging.h" |
#include "webrtc/base/scoped_ref_ptr.h" |
@@ -51,6 +52,10 @@ namespace { |
static const size_t kNumCallbacks = 10; |
// Max amount of time we wait for an event to be set while counting callbacks. |
static const int kTestTimeOutInMilliseconds = 10 * 1000; |
+// Average number of audio callbacks per second assuming 10ms packet size. |
+static const size_t kNumCallbacksPerSecond = 100; |
+// Run the full-duplex test during this time (unit is in seconds). |
+static const int kFullDuplexTimeInSec = 5; |
kwiberg-webrtc
2017/04/03 08:40:11
These two can be constexpr instead of const. Not t
henrika_webrtc
2017/04/03 15:14:30
Done.
|
enum class TransportType { |
kInvalid, |
@@ -58,8 +63,107 @@ enum class TransportType { |
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 AudioStreamInterface { |
kwiberg-webrtc
2017/04/03 08:40:12
Suggestion: Just "AudioStream". The "Interface" su
henrika_webrtc
2017/04/03 15:14:30
Removed "Interface"
|
+ public: |
+ virtual void Write(const void* source, |
+ size_t samples_per_channel, |
+ size_t channels) = 0; |
+ virtual void Read(void* destination, |
+ size_t samples_per_channel, |
+ size_t channels) = 0; |
kwiberg-webrtc
2017/04/03 08:40:12
Can you make this a concrete type instead of void?
henrika_webrtc
2017/04/03 15:14:29
Done.
|
+ |
+ AudioStreamInterface() = default; |
kwiberg-webrtc
2017/04/03 08:40:11
This is a pure interface. No constructor needed.
henrika_webrtc
2017/04/03 15:14:30
Done.
|
+ virtual ~AudioStreamInterface() = default; |
+}; |
+ |
} // 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 use the same size. |
+class FifoAudioStream : public AudioStreamInterface { |
+ public: |
+ FifoAudioStream() : fifo_(new AudioBufferList()) { |
+ EXPECT_NE(fifo_.get(), nullptr); |
+ } |
+ virtual ~FifoAudioStream() { Flush(); } |
+ |
+ void Write(const void* source, |
+ size_t samples_per_channel, |
+ size_t channels) override { |
+ EXPECT_EQ(channels, 1u); |
+ const size_t samples_per_buffer = samples_per_channel * channels; |
+ const size_t bytes_per_buffer = sizeof(int16_t) * samples_per_buffer; |
+ int16_t* memory = new int16_t[samples_per_buffer]; |
kwiberg-webrtc
2017/04/03 08:40:11
Raw pointers shouldn't own stuff. Use unique_ptr i
henrika_webrtc
2017/04/03 15:14:29
Switched to unique_ptr. I am unable to see any rea
kwiberg-webrtc
2017/04/03 20:44:01
You avoid having to name the type twice. That's mo
|
+ memcpy(static_cast<int16_t*>(&memory[0]), source, bytes_per_buffer); |
kwiberg-webrtc
2017/04/03 08:40:12
This cast looks unnecessary. (But if you use rtc::
henrika_webrtc
2017/04/03 15:14:30
Let me add first round without using rtc::Buffer f
|
+ size_t size(0); |
+ { |
+ rtc::CritScope lock(&lock_); |
+ fifo_->push_back(memory); |
+ size = fifo_->size(); |
+ } |
kwiberg-webrtc
2017/04/03 08:40:11
You can avoid mutating the size variable:
const s
henrika_webrtc
2017/04/03 15:14:29
Yes we can :-)
|
+ if (size > max_size_) { |
+ max_size_ = size; |
+ } |
+ write_count_++; |
+ written_elements_ += size; |
+ } |
+ |
+ void Read(void* destination, |
+ size_t samples_per_channel, |
+ size_t channels) override { |
+ EXPECT_EQ(channels, 1u); |
+ const size_t bytes_per_buffer = |
+ sizeof(int16_t) * samples_per_channel * channels; |
+ rtc::CritScope lock(&lock_); |
+ if (fifo_->empty()) { |
+ memset(destination, 0, bytes_per_buffer); |
+ } else { |
+ int16_t* memory = fifo_->front(); |
kwiberg-webrtc
2017/04/03 08:40:11
Again, definitely use unique_ptr instead of a raw
henrika_webrtc
2017/04/03 15:14:30
Using unique_ptr to see if you like it :-)
|
+ memcpy(destination, static_cast<int16_t*>(&memory[0]), bytes_per_buffer); |
+ delete memory; |
+ fifo_->pop_front(); |
+ } |
+ } |
+ |
+ size_t size() const { return fifo_->size(); } |
kwiberg-webrtc
2017/04/03 08:40:11
You should take lock_ before touching fifo_. See t
henrika_webrtc
2017/04/03 15:14:30
Done.
|
+ |
+ size_t max_size() const { return max_size_; } |
+ |
+ size_t average_size() const { |
+ return 0.5 + static_cast<float>(written_elements_ / write_count_); |
+ } |
+ |
+ private: |
+ void Flush() { |
+ if (fifo_) { |
+ while (!fifo_->empty()) { |
+ delete fifo_->front(); |
+ fifo_->pop_front(); |
+ } |
+ fifo_->clear(); |
+ } |
+ } |
+ |
+ using AudioBufferList = std::list<int16_t*>; |
kwiberg-webrtc
2017/04/03 08:40:12
This should be std::list<std::unique_ptr<int16_t[]
henrika_webrtc
2017/04/03 15:14:29
Done.
|
+ rtc::CriticalSection lock_; |
+ std::unique_ptr<AudioBufferList> fifo_; |
kwiberg-webrtc
2017/04/03 08:40:11
Why the indirection? Can't you have the AudioBuffe
henrika_webrtc
2017/04/03 15:14:29
Acknowledged.
|
+ size_t write_count_ = 0; |
+ size_t max_size_ = 0; |
+ size_t written_elements_ = 0; |
kwiberg-webrtc
2017/04/03 08:40:12
Please annotate which variables are protected by w
henrika_webrtc
2017/04/03 15:14:30
Done.
|
+}; |
+ |
// Mocks the AudioTransport object and proxies actions for the two callbacks |
// (RecordedDataIsAvailable and NeedMorePlayData) to different implementations |
// of AudioStreamInterface. |
@@ -72,8 +176,11 @@ class MockAudioTransport : public test::MockAudioTransport { |
// 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, int num_callbacks) { |
+ void HandleCallbacks(rtc::Event* event, |
+ AudioStreamInterface* audio_stream, |
+ int num_callbacks) { |
event_ = event; |
+ audio_stream_ = audio_stream; |
num_callbacks_ = num_callbacks; |
if (play_mode()) { |
ON_CALL(*this, NeedMorePlayData(_, _, _, _, _, _, _, _)) |
@@ -114,6 +221,10 @@ class MockAudioTransport : public test::MockAudioTransport { |
record_parameters_.frames_per_10ms_buffer()); |
} |
rec_count_++; |
+ // Write audio data to audio stream object if one has been injected. |
+ if (audio_stream_) { |
+ audio_stream_->Write(audio_buffer, samples_per_channel, channels); |
+ } |
// Signal the event after given amount of callbacks. |
if (ReceivedEnoughCallbacks()) { |
event_->Set(); |
@@ -147,9 +258,14 @@ class MockAudioTransport : public test::MockAudioTransport { |
} |
play_count_++; |
samples_per_channel_out = samples_per_channel; |
- // 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); |
+ // Read audio data from audio stream object if one has been injected. |
+ if (audio_stream_) { |
+ audio_stream_->Read(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 (ReceivedEnoughCallbacks()) { |
event_->Set(); |
@@ -186,6 +302,7 @@ class MockAudioTransport : public test::MockAudioTransport { |
private: |
TransportType type_ = TransportType::kInvalid; |
rtc::Event* event_ = nullptr; |
+ AudioStreamInterface* audio_stream_ = nullptr; |
size_t num_callbacks_ = 0; |
size_t play_count_ = 0; |
size_t rec_count_ = 0; |
@@ -324,7 +441,7 @@ TEST_F(AudioDeviceTest, StartStopRecording) { |
TEST_F(AudioDeviceTest, StartPlayoutVerifyCallbacks) { |
SKIP_TEST_IF_NOT(requirements_satisfied()); |
MockAudioTransport mock(TransportType::kPlay); |
- mock.HandleCallbacks(event(), kNumCallbacks); |
+ mock.HandleCallbacks(event(), nullptr, kNumCallbacks); |
EXPECT_CALL(mock, NeedMorePlayData(_, _, _, _, NotNull(), _, _, _)) |
.Times(AtLeast(kNumCallbacks)); |
EXPECT_EQ(0, audio_device()->RegisterAudioCallback(&mock)); |
@@ -338,7 +455,7 @@ TEST_F(AudioDeviceTest, StartPlayoutVerifyCallbacks) { |
TEST_F(AudioDeviceTest, StartRecordingVerifyCallbacks) { |
SKIP_TEST_IF_NOT(requirements_satisfied()); |
MockAudioTransport mock(TransportType::kRecord); |
- mock.HandleCallbacks(event(), kNumCallbacks); |
+ mock.HandleCallbacks(event(), nullptr, kNumCallbacks); |
EXPECT_CALL(mock, RecordedDataIsAvailable(NotNull(), _, _, _, _, Ge(0u), 0, _, |
false, _)) |
.Times(AtLeast(kNumCallbacks)); |
@@ -353,7 +470,7 @@ TEST_F(AudioDeviceTest, StartRecordingVerifyCallbacks) { |
TEST_F(AudioDeviceTest, StartPlayoutAndRecordingVerifyCallbacks) { |
SKIP_TEST_IF_NOT(requirements_satisfied()); |
MockAudioTransport mock(TransportType::kPlayAndRecord); |
- mock.HandleCallbacks(event(), kNumCallbacks); |
+ mock.HandleCallbacks(event(), nullptr, kNumCallbacks); |
EXPECT_CALL(mock, NeedMorePlayData(_, _, _, _, NotNull(), _, _, _)) |
.Times(AtLeast(kNumCallbacks)); |
EXPECT_CALL(mock, RecordedDataIsAvailable(NotNull(), _, _, _, _, Ge(0u), 0, _, |
@@ -367,4 +484,37 @@ TEST_F(AudioDeviceTest, StartPlayoutAndRecordingVerifyCallbacks) { |
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 only contain |
+// one packet on average. However, under more realistic conditions, the size |
kwiberg-webrtc
2017/04/03 08:40:11
Max 1, average somewhere in (0,1) depending on how
henrika_webrtc
2017/04/03 15:14:30
Tried to rewrite as you suggest but I might have m
kwiberg-webrtc
2017/04/03 20:44:01
It may be better to just say
"Under such condit
|
+// 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_F(AudioDeviceTest, RunPlayoutAndRecordingInFullDuplex) { |
+ SKIP_TEST_IF_NOT(requirements_satisfied()); |
+ NiceMock<MockAudioTransport> mock(TransportType::kPlayAndRecord); |
+ std::unique_ptr<FifoAudioStream> audio_stream(new FifoAudioStream()); |
kwiberg-webrtc
2017/04/03 08:40:12
Why the indirection? It looks like you could put t
henrika_webrtc
2017/04/03 15:14:29
Done.
|
+ mock.HandleCallbacks(event(), audio_stream.get(), |
+ kFullDuplexTimeInSec * kNumCallbacksPerSecond); |
+ EXPECT_EQ(0, audio_device()->RegisterAudioCallback(&mock)); |
+ // Run both sides in mono to make the loopback packet handling less complex. |
+ EXPECT_EQ(0, audio_device()->SetStereoPlayout(false)); |
+ EXPECT_EQ(0, audio_device()->SetStereoRecording(false)); |
+ StartPlayout(); |
+ StartRecording(); |
+ event()->Wait( |
+ std::max(kTestTimeOutInMilliseconds, 1000 * kFullDuplexTimeInSec)); |
+ StopRecording(); |
+ StopPlayout(); |
+ // This thresholds is set rather high to accomodate differences in hardware |
+ // in several devices. The main idea is to capture cases where a very large |
+ // latency is built up. |
+ EXPECT_LE(audio_stream->average_size(), 5u); |
kwiberg-webrtc
2017/04/03 08:40:11
This looks like a test that can be expected to fla
henrika_webrtc
2017/04/03 15:14:29
I use rather strict conditions to be able to tun t
kwiberg-webrtc
2017/04/03 20:44:01
Acknowledged.
|
+} |
+ |
} // namespace webrtc |