unplugged-system/external/tinyalsa_new/tests/src/pcm_loopback_test.cc

267 lines
8.8 KiB
C++

/* pcm_loopback_test.c
**
** Copyright 2020, The Android Open Source Project
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** * Neither the name of The Android Open Source Project nor the names of
** its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
** DAMAGE.
*/
#include "pcm_test_device.h"
#include <chrono>
#include <cmath>
#include <cstring>
#include <iostream>
#include <thread>
#include <gtest/gtest.h>
#include "tinyalsa/pcm.h"
namespace tinyalsa {
namespace testing {
template<int32_t CH, int32_t SR, pcm_format F>
class SilenceGenerator {
public:
pcm_format GetFormat() {
return F;
}
int32_t GetChannels() {
return CH;
};
int32_t GetSamplingRate() {
return SR;
};
virtual int32_t Read(void *buffer, int32_t size) {
std::memset(buffer, 0, size);
return size;
}
};
template<pcm_format F>
struct PcmFormat {
using Type = void;
static constexpr pcm_format kFormat = F;
static constexpr int32_t kMax = 0;
static constexpr int32_t kMin = 0;
};
template<>
struct PcmFormat<PCM_FORMAT_S16_LE> {
using Type = int16_t;
static constexpr pcm_format kFormat = PCM_FORMAT_S16_LE;
static constexpr Type kMax = std::numeric_limits<Type>::max();
static constexpr Type kMin = std::numeric_limits<Type>::min();
};
template<>
struct PcmFormat<PCM_FORMAT_FLOAT_LE> {
using Type = float;
static constexpr pcm_format kFormat = PCM_FORMAT_FLOAT_LE;
static constexpr Type kMax = 1.0;
static constexpr Type kMin = -1.0;
};
// CH: channels
// SR: sampling rate
// FQ: sine wave frequency
// L: max level
template<int32_t CH, int32_t SR, int32_t FQ, int32_t L, pcm_format F>
class SineToneGenerator : public SilenceGenerator<CH, SR, F> {
private:
using Type = typename PcmFormat<F>::Type;
static constexpr double kPi = M_PI;
static constexpr double kStep = FQ * CH * kPi / SR;
double channels[CH];
double gain;
Type GetSample(double radian) {
double sine = std::sin(radian) * gain;
if (sine >= 1.0) {
return PcmFormat<F>::kMax;
} else if (sine <= -1.0) {
return PcmFormat<F>::kMin;
}
return static_cast<Type>(sine * PcmFormat<F>::kMax);
}
public:
SineToneGenerator() {
constexpr double phase = (CH == 1) ? 0 : kPi / 2 / (CH - 1);
channels[0] = 0.0;
for (int32_t i = 1; i < CH; ++i) {
channels[i] = channels[i - 1] + phase;
}
gain = std::pow(M_E, std::log(10) * static_cast<double>(L) / 20.0);
}
~SineToneGenerator() = default;
int32_t Read(void *buffer, int32_t size) override {
Type *pcm_buffer = reinterpret_cast<Type *>(buffer);
size = (size / (CH * sizeof(Type))) * (CH * sizeof(Type));
int32_t samples = size / sizeof(Type);
int32_t s = 0;
while (s < samples) {
for (int32_t i = 0; i < CH; ++i) {
pcm_buffer[s++] = GetSample(channels[i]);
channels[i] += kStep;
}
}
return size;
}
};
template<typename T>
static double Energy(T *buffer, size_t samples) {
double sum = 0.0;
for (size_t i = 0; i < samples; i++) {
sum += static_cast<double>(buffer[i]) * static_cast<double>(buffer[i]);
}
return sum;
}
template<typename F>
class PcmLoopbackTest : public ::testing::Test {
protected:
PcmLoopbackTest() = default;
virtual ~PcmLoopbackTest() = default;
void SetUp() override {
static constexpr pcm_config kInConfig = {
.channels = kDefaultChannels,
.rate = kDefaultSamplingRate,
.period_size = kDefaultPeriodSize,
.period_count = kDefaultPeriodCount,
.format = kPcmForamt,
.start_threshold = 0,
.stop_threshold = 0,
.silence_threshold = 0,
.silence_size = 0,
};
pcm_in = pcm_open(kLoopbackCard, kLoopbackCaptureDevice, PCM_IN, &kInConfig);
ASSERT_TRUE(pcm_is_ready(pcm_in));
static constexpr pcm_config kOutConfig = {
.channels = kDefaultChannels,
.rate = kDefaultSamplingRate,
.period_size = kDefaultPeriodSize,
.period_count = kDefaultPeriodCount,
.format = kPcmForamt,
.start_threshold = kDefaultPeriodSize,
.stop_threshold = kDefaultPeriodSize * kDefaultPeriodCount,
.silence_threshold = 0,
.silence_size = 0,
};
pcm_out = pcm_open(kLoopbackCard, kLoopbackPlaybackDevice, PCM_OUT, &kOutConfig);
ASSERT_TRUE(pcm_is_ready(pcm_out));
ASSERT_EQ(pcm_link(pcm_in, pcm_out), 0);
}
void TearDown() override {
ASSERT_EQ(pcm_unlink(pcm_in), 0);
pcm_close(pcm_in);
pcm_close(pcm_out);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
static constexpr unsigned int kDefaultPeriodTimeInMs =
kDefaultPeriodSize * 1000 / kDefaultSamplingRate;
static constexpr pcm_format kPcmForamt = F::kFormat;
pcm *pcm_in;
pcm *pcm_out;
};
using S16bitlePcmFormat = PcmFormat<PCM_FORMAT_S16_LE>;
using FloatPcmFormat = PcmFormat<PCM_FORMAT_FLOAT_LE>;
using Formats = ::testing::Types<S16bitlePcmFormat, FloatPcmFormat>;
TYPED_TEST_SUITE(PcmLoopbackTest, Formats);
TYPED_TEST(PcmLoopbackTest, Loopback) {
static constexpr unsigned int kDefaultPeriodTimeInMs = this->kDefaultPeriodTimeInMs;
static constexpr pcm_format kPcmForamt = this->kPcmForamt;
pcm *pcm_in = this->pcm_in;
pcm *pcm_out = this->pcm_out;
bool stopping = false;
ASSERT_EQ(pcm_get_subdevice(pcm_in), pcm_get_subdevice(pcm_out));
std::thread capture([pcm_in, &stopping] {
size_t buffer_size = pcm_frames_to_bytes(pcm_in, kDefaultPeriodSize);
unsigned int frames = pcm_bytes_to_frames(pcm_in, buffer_size);
auto buffer = std::make_unique<unsigned char[]>(buffer_size);
int32_t counter = 0;
while (!stopping) {
int res = pcm_readi(pcm_in, buffer.get(), frames);
if (res == -1) {
std::cout << pcm_get_error(pcm_in) << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(kDefaultPeriodTimeInMs));
counter++;
continue;
}
// Test the energy of the buffer after the sine tone samples fill in the buffer.
// Therefore, check the buffer 5 times later.
if (counter >= 5) {
double e = Energy(buffer.get(), frames * kDefaultChannels);
EXPECT_GT(e, 0.0) << counter;
}
counter++;
}
std::cout << "read count = " << counter << std::endl;
});
std::thread playback([pcm_out, &stopping] {
SineToneGenerator<kDefaultChannels, kDefaultSamplingRate, 1000, 0, kPcmForamt> generator;
size_t buffer_size = pcm_frames_to_bytes(pcm_out, kDefaultPeriodSize);
unsigned int frames = pcm_bytes_to_frames(pcm_out, buffer_size);
std::cout << buffer_size << std::endl;
auto buffer = std::make_unique<unsigned char[]>(buffer_size);
int32_t counter = 0;
while (!stopping) {
generator.Read(buffer.get(), buffer_size);
EXPECT_EQ(pcm_writei(pcm_out, buffer.get(), frames), frames) << counter;
counter++;
}
std::cout << "write count = " << counter << std::endl;
});
std::this_thread::sleep_for(std::chrono::milliseconds(500));
stopping = true;
capture.join();
playback.join();
}
} // namespace testing
} // namespace tinyalsa