Linux input stream driver: bugfixes and completed test
This commit is contained in:
parent
1d48e5e3e0
commit
07394cc8a0
|
|
@ -44,6 +44,9 @@ class InputStream
|
||||||
uint32_t sampleRate)
|
uint32_t sampleRate)
|
||||||
: m_run_thread(true), m_func_running(false)
|
: 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;
|
m_db_ready[0] = m_db_ready[1] = false;
|
||||||
|
|
||||||
changeId();
|
changeId();
|
||||||
|
|
@ -110,8 +113,10 @@ class InputStream
|
||||||
if (!m_run_thread) return {NULL, 0};
|
if (!m_run_thread) return {NULL, 0};
|
||||||
|
|
||||||
// Return the buffer contents
|
// Return the buffer contents
|
||||||
auto* pos = m_buf;
|
auto* pos = m_buf + id * (m_bufLength / 2);
|
||||||
m_buf += m_bufLength / 2 * id;
|
m_db_ready[id] = 0;
|
||||||
|
|
||||||
|
// Update the read buffer
|
||||||
m_db_curread = (id + 1) % 2;
|
m_db_curread = (id + 1) % 2;
|
||||||
return {pos, m_bufLength / 2};
|
return {pos, m_bufLength / 2};
|
||||||
}
|
}
|
||||||
|
|
@ -219,15 +224,14 @@ class InputStream
|
||||||
m_func_running = false;
|
m_func_running = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
using std::chrono::milliseconds;
|
using std::chrono::microseconds;
|
||||||
|
|
||||||
if (m_sampleRate > 0)
|
if (m_sampleRate > 0)
|
||||||
{
|
{
|
||||||
// Do a piecewise-sleep so that it's easily interruptible
|
// Do a piecewise-sleep so that it's easily interruptible
|
||||||
int msec = sz * 1000 / m_sampleRate;
|
uint64_t microsec = sz * 1000000 / m_sampleRate;
|
||||||
while (msec > 10)
|
while (microsec > 10000)
|
||||||
{
|
{
|
||||||
printf("Wait 10ms: %d\n", msec);
|
|
||||||
if (!m_run_thread)
|
if (!m_run_thread)
|
||||||
{
|
{
|
||||||
// Early exit if the class is being deallocated
|
// Early exit if the class is being deallocated
|
||||||
|
|
@ -235,10 +239,10 @@ class InputStream
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::this_thread::sleep_for(milliseconds(10));
|
std::this_thread::sleep_for(microseconds(10000));
|
||||||
msec -= 10;
|
microsec -= 10000;
|
||||||
}
|
}
|
||||||
std::this_thread::sleep_for(milliseconds(msec));
|
std::this_thread::sleep_for(microseconds(microsec));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_run_thread)
|
if (!m_run_thread)
|
||||||
|
|
@ -251,11 +255,9 @@ class InputStream
|
||||||
// Fill the buffer
|
// Fill the buffer
|
||||||
while (i < sz)
|
while (i < sz)
|
||||||
{
|
{
|
||||||
printf("Read: %lu/%lu\n", i, sz);
|
|
||||||
auto n = fread(dest + i, 2, sz - i, m_fp);
|
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;
|
i += n;
|
||||||
fseek(m_fp, 0, SEEK_SET);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(i == sz);
|
assert(i == sz);
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,18 @@
|
||||||
* along with this program; if not, see <http://www.gnu.org/licenses/> *
|
* along with this program; if not, see <http://www.gnu.org/licenses/> *
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "interfaces/audio_stream.h"
|
#include "interfaces/audio_stream.h"
|
||||||
|
|
||||||
|
static const char* files[] = {"MIC.raw", "RTX.raw", "MCU.raw"};
|
||||||
|
|
||||||
#define CHECK(x) \
|
#define CHECK(x) \
|
||||||
do \
|
do \
|
||||||
{ \
|
{ \
|
||||||
|
|
@ -30,21 +37,158 @@
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} 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<AudioSource>(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<AudioSource>(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<AudioSource>(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<AudioSource>(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<microseconds>(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<stream_sample_t> tmp(buf_size);
|
||||||
|
auto id = inputStream_start(
|
||||||
|
static_cast<AudioSource>(fn), AudioPriority::PRIO_BEEP, tmp.data(),
|
||||||
|
tmp.size(), BufMode::BUF_CIRC_DOUBLE, 44100);
|
||||||
|
CHECK(id != -1);
|
||||||
|
|
||||||
|
using namespace std::chrono;
|
||||||
|
time_point<steady_clock> t0 = steady_clock::now();
|
||||||
|
|
||||||
|
uint64_t ctr = 0;
|
||||||
|
std::vector<stream_sample_t> 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<microseconds>(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()
|
int main()
|
||||||
{
|
{
|
||||||
FILE* fp = fopen("MIC.raw", "wb");
|
test_linear();
|
||||||
for (int i = 0; i < 13; i++)
|
test_ring_buffer(13, 10, 256);
|
||||||
{
|
test_ring_buffer(128, 10, 256);
|
||||||
fputc(i, fp);
|
test_ring_buffer(256, 10, 128);
|
||||||
fputc(0, fp);
|
test_ring_buffer(1234, 10, 768);
|
||||||
}
|
|
||||||
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));
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue