Chromium Code Reviews| 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 |