From 07394cc8a078a93ac0d349206794ddb00e01f855 Mon Sep 17 00:00:00 2001 From: Alain Carlucci Date: Fri, 3 Jun 2022 01:36:32 +0200 Subject: [PATCH] Linux input stream driver: bugfixes and completed test --- platform/drivers/audio/inputStream_linux.cpp | 26 +-- tests/unit/linux_inputStream_test.cpp | 172 +++++++++++++++++-- 2 files changed, 172 insertions(+), 26 deletions(-) diff --git a/platform/drivers/audio/inputStream_linux.cpp b/platform/drivers/audio/inputStream_linux.cpp index 96821db0..cda9eb49 100644 --- a/platform/drivers/audio/inputStream_linux.cpp +++ b/platform/drivers/audio/inputStream_linux.cpp @@ -44,6 +44,9 @@ class InputStream uint32_t sampleRate) : m_run_thread(true), m_func_running(false) { + if (bufLength % 2) + throw std::runtime_error("Invalid bufLength: " + + std::to_string(bufLength)); m_db_ready[0] = m_db_ready[1] = false; changeId(); @@ -110,8 +113,10 @@ class InputStream if (!m_run_thread) return {NULL, 0}; // Return the buffer contents - auto* pos = m_buf; - m_buf += m_bufLength / 2 * id; + auto* pos = m_buf + id * (m_bufLength / 2); + m_db_ready[id] = 0; + + // Update the read buffer m_db_curread = (id + 1) % 2; return {pos, m_bufLength / 2}; } @@ -219,15 +224,14 @@ class InputStream m_func_running = false; }; - using std::chrono::milliseconds; + using std::chrono::microseconds; if (m_sampleRate > 0) { // Do a piecewise-sleep so that it's easily interruptible - int msec = sz * 1000 / m_sampleRate; - while (msec > 10) + uint64_t microsec = sz * 1000000 / m_sampleRate; + while (microsec > 10000) { - printf("Wait 10ms: %d\n", msec); if (!m_run_thread) { // Early exit if the class is being deallocated @@ -235,10 +239,10 @@ class InputStream return false; } - std::this_thread::sleep_for(milliseconds(10)); - msec -= 10; + std::this_thread::sleep_for(microseconds(10000)); + microsec -= 10000; } - std::this_thread::sleep_for(milliseconds(msec)); + std::this_thread::sleep_for(microseconds(microsec)); } if (!m_run_thread) @@ -251,11 +255,9 @@ class InputStream // Fill the buffer while (i < sz) { - printf("Read: %lu/%lu\n", i, sz); auto n = fread(dest + i, 2, sz - i, m_fp); - printf("Ret: %lu\n", n); + if (n < (sz - i)) fseek(m_fp, 0, SEEK_SET); i += n; - fseek(m_fp, 0, SEEK_SET); } assert(i == sz); diff --git a/tests/unit/linux_inputStream_test.cpp b/tests/unit/linux_inputStream_test.cpp index 0724b332..20e3dd1c 100644 --- a/tests/unit/linux_inputStream_test.cpp +++ b/tests/unit/linux_inputStream_test.cpp @@ -15,11 +15,18 @@ * along with this program; if not, see * ***************************************************************************/ +#include + +#include #include #include +#include +#include #include "interfaces/audio_stream.h" +static const char* files[] = {"MIC.raw", "RTX.raw", "MCU.raw"}; + #define CHECK(x) \ do \ { \ @@ -30,21 +37,158 @@ } \ } while (0) +void test_linear() +{ + for (int fn = 0; fn < 3; fn++) + { + // Write a mock audio file + FILE* fp = fopen(files[fn], "wb"); + CHECK(fp); + for (int i = 0; i < 13; i++) + { + CHECK(fputc(i, fp) == i); + CHECK(fputc(0, fp) == 0); + } + fclose(fp); + + // Start inputStream using that file as source + stream_sample_t tmp[128]; + auto id = inputStream_start( + static_cast(fn), AudioPriority::PRIO_PROMPT, tmp, + sizeof(tmp) / sizeof(tmp[0]), BufMode::BUF_LINEAR, 44100); + CHECK(id != -1); + + // Should fail + auto id_2 = inputStream_start( + static_cast(fn), AudioPriority::PRIO_BEEP, tmp, + sizeof(tmp) / sizeof(tmp[0]), BufMode::BUF_LINEAR, 44100); + CHECK(id_2 == -1); + + auto id_3 = inputStream_start( + static_cast(fn), AudioPriority::PRIO_RX, tmp, + sizeof(tmp) / sizeof(tmp[0]), BufMode::BUF_LINEAR, 44100); + CHECK(id_3 == -1); + + // This should work, as it has higher priority + auto id_4 = inputStream_start( + static_cast(fn), AudioPriority::PRIO_TX, tmp, + sizeof(tmp) / sizeof(tmp[0]), BufMode::BUF_LINEAR, 44100); + CHECK(id_4 != -1); + + { + // id is now invalid (overridden by id_4), getData should fail + auto should_fail = inputStream_getData(id); + CHECK(should_fail.data == NULL); + CHECK(should_fail.len == 0); + } + + // id_4 is now the only valid pointer, should work + id = id_4; + + int c_ptr = 0; + // getData multiple times + for (int i = 0; i < 20; i++) + { + using namespace std::chrono; + auto t1 = steady_clock::now(); + auto db = inputStream_getData(id); + auto t2 = steady_clock::now(); + const uint64_t delta = duration_cast(t2 - t1).count(); + const uint64_t expected = (128 * 1000000 / 44100); + + // Check that the getData() sleeps the right amount of time + CHECK((delta > expected) && delta < (expected + 10000)); + + // Check the contents + CHECK(db.len == 128); + for (int i = 0; i < 128; i++) + { + CHECK(tmp[i] == (c_ptr % 13)); + CHECK(db.data[i] == (c_ptr % 13)); + c_ptr++; + } + } + + // Close inputStream and remove the temporary file + inputStream_stop(id); + CHECK(remove(files[fn]) == 0); + } +} + +void test_ring_buffer(uint64_t n_bytes, + uint64_t n_iter, + const uint64_t buf_size) +{ + for (int fn = 0; fn < 3; fn++) + { + FILE* fp = fopen(files[fn], "wb"); + CHECK(fp); + + for (uint64_t i = 0; i < n_bytes; i++) + { + uint16_t j = i; + uint8_t* buf = (uint8_t*)&j; + CHECK(fputc(buf[0], fp) == buf[0]); + CHECK(fputc(buf[1], fp) == buf[1]); + } + fclose(fp); + + std::vector tmp(buf_size); + auto id = inputStream_start( + static_cast(fn), AudioPriority::PRIO_BEEP, tmp.data(), + tmp.size(), BufMode::BUF_CIRC_DOUBLE, 44100); + CHECK(id != -1); + + using namespace std::chrono; + time_point t0 = steady_clock::now(); + + uint64_t ctr = 0; + std::vector tmp2(buf_size / 2); + for (uint64_t i = 0; i < n_iter; i++) + { + { + auto db = inputStream_getData(id); + + CHECK(db.len > 0); + CHECK(db.data == &tmp[0]); + memcpy(tmp2.data(), db.data, db.len * sizeof(stream_sample_t)); + for (uint64_t i = 0; i < db.len; i++) + { + CHECK(uint16_t(tmp2[i]) == uint16_t(ctr % n_bytes)); + ctr++; + } + } + + { + auto db = inputStream_getData(id); + + CHECK(db.len > 0); + CHECK(db.data == &tmp[buf_size / 2]); + memcpy(tmp2.data(), db.data, db.len * sizeof(stream_sample_t)); + for (uint64_t i = 0; i < db.len; i++) + { + CHECK(uint16_t(tmp2[i]) == uint16_t(ctr % n_bytes)); + ctr++; + } + } + } + + auto t2 = steady_clock::now(); + const uint64_t delta = duration_cast(t2 - t0).count(); + const uint64_t expected = (buf_size * n_iter * 1000000lu / 44100); + CHECK(delta > expected && delta < expected * 2); + + inputStream_stop(id); + CHECK(remove(files[fn]) == 0); + } +} + int main() { - FILE* fp = fopen("MIC.raw", "wb"); - for (int i = 0; i < 13; i++) - { - fputc(i, fp); - fputc(0, fp); - } - fclose(fp); - - stream_sample_t tmp[128]; - auto id = inputStream_start( - AudioSource::SOURCE_MIC, AudioPriority::PRIO_BEEP, tmp, - sizeof(tmp) / sizeof(tmp[0]), BufMode::BUF_LINEAR, 44100); - inputStream_getData(id); - for (int i = 0; i < 128; i++) CHECK(tmp[i] == (i % 13)); + test_linear(); + test_ring_buffer(13, 10, 256); + test_ring_buffer(128, 10, 256); + test_ring_buffer(256, 10, 128); + test_ring_buffer(1234, 10, 768); return 0; }