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);
+}