
This CL contains major modifications of the audio output parts for WebRTC on iOS: - general code cleanup - improves thread handling (added thread checks, remove critical section, atomic ops etc.) - reduces loopback latency of iPhone 6 from ~90ms to ~60ms ;-) - improves selection of audio parameters on iOS - reduces complexity by removing complex and redundant delay estimates - now instead uses fixed delay estimates if for some reason the SW EAC must be used - adds AudioFineBuffer to compensate for differences in native output buffer size and the 10ms size used by WebRTC. Same class as is used today on Android and we have unit tests for this class (the old code was buggy and we have several issue reports of crashes related to it) Similar improvements will be done for the recording sid as well in a separate CL. I will also add support for 48kHz in an upcoming CL since that will improve Opus performance. BUG=webrtc:4796,webrtc:4817,webrtc:4954, webrtc:4212 TEST=AppRTC demo and iOS modules_unittests using --gtest_filter=AudioDevice* R=pbos@webrtc.org, tkchin@webrtc.org Review URL: https://codereview.webrtc.org/1254883002 . Cr-Commit-Position: refs/heads/master@{#9875}
148 lines
5.6 KiB
C++
148 lines
5.6 KiB
C++
/*
|
|
* Copyright (c) 2013 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 "webrtc/modules/audio_device/fine_audio_buffer.h"
|
|
|
|
#include <limits.h>
|
|
#include <memory>
|
|
|
|
#include "testing/gmock/include/gmock/gmock.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
#include "webrtc/base/scoped_ptr.h"
|
|
#include "webrtc/modules/audio_device/mock_audio_device_buffer.h"
|
|
|
|
using ::testing::_;
|
|
using ::testing::AtLeast;
|
|
using ::testing::InSequence;
|
|
using ::testing::Return;
|
|
|
|
namespace webrtc {
|
|
|
|
// The fake audio data is 0,1,..SCHAR_MAX-1,0,1,... This is to make it easy
|
|
// to detect errors. This function verifies that the buffers contain such data.
|
|
// E.g. if there are two buffers of size 3, buffer 1 would contain 0,1,2 and
|
|
// buffer 2 would contain 3,4,5. Note that SCHAR_MAX is 127 so wrap-around
|
|
// will happen.
|
|
// |buffer| is the audio buffer to verify.
|
|
bool VerifyBuffer(const int8_t* buffer, int buffer_number, int size) {
|
|
int start_value = (buffer_number * size) % SCHAR_MAX;
|
|
for (int i = 0; i < size; ++i) {
|
|
if (buffer[i] != (i + start_value) % SCHAR_MAX) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// This function replaces the real AudioDeviceBuffer::GetPlayoutData when it's
|
|
// called (which is done implicitly when calling GetBufferData). It writes the
|
|
// sequence 0,1,..SCHAR_MAX-1,0,1,... to the buffer. Note that this is likely a
|
|
// buffer of different size than the one VerifyBuffer verifies.
|
|
// |iteration| is the number of calls made to UpdateBuffer prior to this call.
|
|
// |samples_per_10_ms| is the number of samples that should be written to the
|
|
// buffer (|arg0|).
|
|
ACTION_P2(UpdateBuffer, iteration, samples_per_10_ms) {
|
|
int8_t* buffer = static_cast<int8_t*>(arg0);
|
|
int bytes_per_10_ms = samples_per_10_ms * static_cast<int>(sizeof(int16_t));
|
|
int start_value = (iteration * bytes_per_10_ms) % SCHAR_MAX;
|
|
for (int i = 0; i < bytes_per_10_ms; ++i) {
|
|
buffer[i] = (i + start_value) % SCHAR_MAX;
|
|
}
|
|
return samples_per_10_ms;
|
|
}
|
|
|
|
// Writes a periodic ramp pattern to the supplied |buffer|. See UpdateBuffer()
|
|
// for details.
|
|
void UpdateInputBuffer(int8_t* buffer, int iteration, int size) {
|
|
int start_value = (iteration * size) % SCHAR_MAX;
|
|
for (int i = 0; i < size; ++i) {
|
|
buffer[i] = (i + start_value) % SCHAR_MAX;
|
|
}
|
|
}
|
|
|
|
// Action macro which verifies that the recorded 10ms chunk of audio data
|
|
// (in |arg0|) contains the correct reference values even if they have been
|
|
// supplied using a buffer size that is smaller or larger than 10ms.
|
|
// See VerifyBuffer() for details.
|
|
ACTION_P2(VerifyInputBuffer, iteration, samples_per_10_ms) {
|
|
const int8_t* buffer = static_cast<const int8_t*>(arg0);
|
|
int bytes_per_10_ms = samples_per_10_ms * static_cast<int>(sizeof(int16_t));
|
|
int start_value = (iteration * bytes_per_10_ms) % SCHAR_MAX;
|
|
for (int i = 0; i < bytes_per_10_ms; ++i) {
|
|
EXPECT_EQ(buffer[i], (i + start_value) % SCHAR_MAX);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void RunFineBufferTest(int sample_rate, int frame_size_in_samples) {
|
|
const int kSamplesPer10Ms = sample_rate * 10 / 1000;
|
|
const int kFrameSizeBytes =
|
|
frame_size_in_samples * static_cast<int>(sizeof(int16_t));
|
|
const int kNumberOfFrames = 5;
|
|
// Ceiling of integer division: 1 + ((x - 1) / y)
|
|
const int kNumberOfUpdateBufferCalls =
|
|
1 + ((kNumberOfFrames * frame_size_in_samples - 1) / kSamplesPer10Ms);
|
|
|
|
MockAudioDeviceBuffer audio_device_buffer;
|
|
EXPECT_CALL(audio_device_buffer, RequestPlayoutData(_))
|
|
.WillRepeatedly(Return(kSamplesPer10Ms));
|
|
{
|
|
InSequence s;
|
|
for (int i = 0; i < kNumberOfUpdateBufferCalls; ++i) {
|
|
EXPECT_CALL(audio_device_buffer, GetPlayoutData(_))
|
|
.WillOnce(UpdateBuffer(i, kSamplesPer10Ms))
|
|
.RetiresOnSaturation();
|
|
}
|
|
}
|
|
{
|
|
InSequence s;
|
|
for (int j = 0; j < kNumberOfUpdateBufferCalls - 1; ++j) {
|
|
EXPECT_CALL(audio_device_buffer, SetRecordedBuffer(_, kSamplesPer10Ms))
|
|
.WillOnce(VerifyInputBuffer(j, kSamplesPer10Ms))
|
|
.RetiresOnSaturation();
|
|
}
|
|
}
|
|
EXPECT_CALL(audio_device_buffer, SetVQEData(_, _, _))
|
|
.Times(kNumberOfUpdateBufferCalls - 1);
|
|
EXPECT_CALL(audio_device_buffer, DeliverRecordedData())
|
|
.Times(kNumberOfUpdateBufferCalls - 1)
|
|
.WillRepeatedly(Return(kSamplesPer10Ms));
|
|
|
|
FineAudioBuffer fine_buffer(&audio_device_buffer, kFrameSizeBytes,
|
|
sample_rate);
|
|
|
|
rtc::scoped_ptr<int8_t[]> out_buffer;
|
|
out_buffer.reset(new int8_t[fine_buffer.RequiredPlayoutBufferSizeBytes()]);
|
|
rtc::scoped_ptr<int8_t[]> in_buffer;
|
|
in_buffer.reset(new int8_t[kFrameSizeBytes]);
|
|
for (int i = 0; i < kNumberOfFrames; ++i) {
|
|
fine_buffer.GetPlayoutData(out_buffer.get());
|
|
EXPECT_TRUE(VerifyBuffer(out_buffer.get(), i, kFrameSizeBytes));
|
|
UpdateInputBuffer(in_buffer.get(), i, kFrameSizeBytes);
|
|
fine_buffer.DeliverRecordedData(in_buffer.get(), kFrameSizeBytes, 0, 0);
|
|
}
|
|
}
|
|
|
|
TEST(FineBufferTest, BufferLessThan10ms) {
|
|
const int kSampleRate = 44100;
|
|
const int kSamplesPer10Ms = kSampleRate * 10 / 1000;
|
|
const int kFrameSizeSamples = kSamplesPer10Ms - 50;
|
|
RunFineBufferTest(kSampleRate, kFrameSizeSamples);
|
|
}
|
|
|
|
TEST(FineBufferTest, GreaterThan10ms) {
|
|
const int kSampleRate = 44100;
|
|
const int kSamplesPer10Ms = kSampleRate * 10 / 1000;
|
|
const int kFrameSizeSamples = kSamplesPer10Ms + 50;
|
|
RunFineBufferTest(kSampleRate, kFrameSizeSamples);
|
|
}
|
|
|
|
} // namespace webrtc
|