From 081b19e52cba48fca47b0ba97f3642440fd902c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Izzo?= Date: Tue, 28 Jun 2022 22:49:52 +0200 Subject: [PATCH] Implemented output audio stream driver for linux. Implement outputStream backend on linux using Pulseaudio simple API. TG-250 --- meson.build | 10 ++- platform/drivers/audio/outputStream_linux.c | 82 +++++++++++++++++++-- tests/unit/play_sine.c | 66 +++++++++++++++++ 3 files changed, 148 insertions(+), 10 deletions(-) create mode 100644 tests/unit/play_sine.c diff --git a/meson.build b/meson.build index e9bddeed..238607bc 100644 --- a/meson.build +++ b/meson.build @@ -278,7 +278,8 @@ linux_inc = inc + ['platform/targets/linux', if not meson.is_cross_build() sdl_dep = dependency('SDL2') threads_dep = dependency('threads') - linux_dep = [sdl_dep, threads_dep, codec2_dep] + pulse_dep = dependency('libpulse') + linux_dep = [sdl_dep, threads_dep, pulse_dep, codec2_dep] else linux_dep = [ ] endif @@ -371,7 +372,7 @@ mod17_def = def + stm32f405_def + {'PLATFORM_MOD17': ''} linux_c_args = ['-DPLATFORM_LINUX'] linux_cpp_args = ['-std=c++14', '-DPLATFORM_LINUX'] -linux_l_args = ['-lm', '-lreadline'] +linux_l_args = ['-lm', '-lreadline', '-lpulse-simple'] # Add AddressSanitizer if required if get_option('asan') @@ -689,9 +690,14 @@ linux_inputStream_test = executable('linux_inputStream_test', sources : unit_test_src + ['tests/unit/linux_inputStream_test.cpp'], kwargs : unit_test_opts) +sine_test = executable('sine_test', + sources : unit_test_src + ['tests/unit/play_sine.c'], + kwargs : unit_test_opts) + test('M17 Golay Unit Test', m17_golay_test) test('M17 Viterbi Unit Test', m17_viterbi_test) test('M17 Demodulator Test', m17_demodulator_test) test('M17 RRC Test', m17_rrc_test) test('Codeplug Test', cps_test) test('Linux InputStream Test', linux_inputStream_test) +test('Sine Test', sine_test) diff --git a/platform/drivers/audio/outputStream_linux.c b/platform/drivers/audio/outputStream_linux.c index 598587fd..273b69b6 100644 --- a/platform/drivers/audio/outputStream_linux.c +++ b/platform/drivers/audio/outputStream_linux.c @@ -18,8 +18,17 @@ * along with this program; if not, see * ***************************************************************************/ +#include #include +#include +#include #include +#include + +static int priority = PRIO_BEEP; // Current priority +static bool running = false; // Stream is running +pa_simple *s = NULL; // Pulseaudio instance +int error; // Error code streamId outputStream_start(const enum AudioSink destination, const enum AudioPriority prio, @@ -28,14 +37,53 @@ streamId outputStream_start(const enum AudioSink destination, const enum BufMode mode, const uint32_t sampleRate) { - (void) destination; - (void) prio; - (void) buf; - (void) length; - (void) mode; - (void) sampleRate; + assert(destination == SINK_SPK && "Only speaker sink was implemented!\n"); + assert(mode == BUF_LINEAR && "Only linear buffering was implemented!\n"); - return -1; + /* The Sample format to use */ + static pa_sample_spec ss = { + .format = PA_SAMPLE_S16LE, + .rate = 0, + .channels = 1 + }; + + ss.rate = sampleRate; + + // Check if an output stream is already opened and, in case, handle priority. + if(running) + { + if((int) prio < priority) return -1; // Lower priority, reject. + if((int) prio > priority) outputStream_stop(0); // Higher priority, takes over. + outputStream_sync(0, false); // Same priority, wait. + } + + // Assign priority and set stream as running + priority = prio; + running = true; + + if (!s) + { + if (!(s = pa_simple_new(NULL, + "OpenRTX", + PA_STREAM_PLAYBACK, + NULL, + "playback", + &ss, + NULL, + NULL, + &error))) + { + fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error)); + return -1; + } + } + + if (pa_simple_write(s, buf, length, &error) < 0) { + fprintf(stderr, __FILE__": pa_simple_write() failed: %s\n", pa_strerror(error)); + return -1; + } + + return 0; } stream_sample_t *outputStream_getIdleBuffer(const streamId id) @@ -50,15 +98,33 @@ bool outputStream_sync(const streamId id, const bool bufChanged) (void) id; (void) bufChanged; - return false; + /* Make sure that every single sample was played */ + if (pa_simple_drain(s, &error) < 0) { + fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(error)); + return false; + } + + running = false; + + return true; } void outputStream_stop(const streamId id) { (void) id; + + /* Make sure that every single sample was played */ + if (pa_simple_flush(s, &error) < 0) { + fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(error)); + } + + running = false; } void outputStream_terminate(const streamId id) { (void) id; + + if (s) + pa_simple_free(s); } diff --git a/tests/unit/play_sine.c b/tests/unit/play_sine.c new file mode 100644 index 00000000..9c581094 --- /dev/null +++ b/tests/unit/play_sine.c @@ -0,0 +1,66 @@ +/*************************************************************************** + * Copyright (C) 2021 by Federico Amedeo Izzo IU2NUO, * + * Niccolò Izzo IU2KIN * + * Frederik Saraci IU2NRO * + * Silvano Seva IU2KWO * + * * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +// Test private methods +#define private public + +#include +#include + +#define BUF_LEN 4096 * 10 +#define AMPLITUDE 20000 +#define PI 3.14159 +#define SAMPLE_RATE 48000 +#define FREQUENCY 440 + +/** + * Test sine playback + */ + +int16_t buffer[BUF_LEN] = { 0 }; + +int main() +{ + int id = 0; + + // Create 440 Hz sine wave + for(int i = 0; i < BUF_LEN; i++) + { + buffer[i] = AMPLITUDE * sin(2 * PI * i * + ((float) FREQUENCY / SAMPLE_RATE)); + } + + // Play back sine wave + for(int i = 0; i < 10; i++) + { + id = outputStream_start(SINK_SPK, + PRIO_PROMPT, + buffer, + BUF_LEN, + BUF_LINEAR, + SAMPLE_RATE); + }; + outputStream_sync(id, false); + + // Sync, flush, terminate + outputStream_stop(id); + outputStream_terminate(id); +}