From 24208db609ded951628adc8d0c186b6f7d115202 Mon Sep 17 00:00:00 2001 From: Silvano Seva Date: Sat, 6 May 2023 21:46:17 +0200 Subject: [PATCH] New unified audio stream manager --- meson.build | 11 +- openrtx/include/core/audio_stream.h | 107 +++++---------- openrtx/include/interfaces/audio.h | 2 +- openrtx/src/core/audio_stream.c | 200 ++++++++++++++++++++++++++++ 4 files changed, 239 insertions(+), 81 deletions(-) create mode 100644 openrtx/src/core/audio_stream.c diff --git a/meson.build b/meson.build index b996db10..6a7f1824 100644 --- a/meson.build +++ b/meson.build @@ -48,6 +48,7 @@ openrtx_src = ['openrtx/src/core/state.c', 'openrtx/src/core/datetime.c', 'openrtx/src/core/openrtx.c', 'openrtx/src/core/audio_codec.c', + 'openrtx/src/core/audio_stream.c', 'openrtx/src/core/audio_path.cpp', 'openrtx/src/core/data_conversion.c', 'openrtx/src/core/memory_profiling.cpp', @@ -167,8 +168,6 @@ mdx_src = ['openrtx/src/core/xmodem.c', 'platform/drivers/NVM/W25Qx.c', 'platform/drivers/NVM/nvmem_settings_MDx.c', 'platform/drivers/audio/audio_MDx.c', - 'platform/drivers/audio/inputStream_MDx.cpp', - 'platform/drivers/audio/outputStream_MDx.cpp', 'platform/drivers/baseband/HR_Cx000.cpp', 'platform/drivers/tones/toneGenerator_MDx.cpp'] @@ -193,9 +192,7 @@ gdx_src = ['openrtx/src/core/xmodem.c', 'platform/drivers/baseband/HR_C6000_GDx.cpp', 'platform/drivers/display/UC1701_GDx.c', 'platform/drivers/keyboard/keyboard_GDx.c', - 'platform/drivers/audio/audio_GDx.c', - 'platform/drivers/audio/inputStream_GDx.c', - 'platform/drivers/audio/outputStream_GDx.c'] + 'platform/drivers/audio/audio_GDx.c'] gdx_src = gdx_src + openrtx_ui_default @@ -278,8 +275,6 @@ linux_platform_src = ['platform/targets/linux/emulator/emulator.c', 'platform/mcu/x86_64/drivers/delays.c', 'platform/drivers/baseband/radio_linux.cpp', 'platform/drivers/audio/audio_linux.c', - 'platform/drivers/audio/inputStream_linux.cpp', - 'platform/drivers/audio/outputStream_linux.c', 'platform/targets/linux/platform.c', 'platform/drivers/CPS/cps_io_libc.c'] @@ -386,8 +381,6 @@ mod17_src = ['platform/targets/Module17/platform.c', 'platform/drivers/NVM/nvmem_Mod17.c', 'platform/drivers/CPS/cps_io_native_Mod17.c', 'platform/drivers/baseband/radio_Mod17.cpp', - 'platform/drivers/audio/inputStream_Mod17.cpp', - 'platform/drivers/audio/outputStream_Mod17.cpp', 'platform/drivers/audio/audio_Mod17.c', 'platform/drivers/audio/MAX9814_Mod17.cpp', 'platform/drivers/baseband/MCP4551_Mod17.cpp'] diff --git a/openrtx/include/core/audio_stream.h b/openrtx/include/core/audio_stream.h index 86ca86f8..99144f66 100644 --- a/openrtx/include/core/audio_stream.h +++ b/openrtx/include/core/audio_stream.h @@ -24,12 +24,18 @@ #include #include #include +#include #include #ifdef __cplusplus extern "C" { #endif +enum StreamMode +{ + STREAM_INPUT = 0x10, ///< Open an audio stream in input mode. + STREAM_OUTPUT = 0x20 ///< Open an audio stream in output mode. +}; typedef int8_t streamId; @@ -42,28 +48,38 @@ typedef struct dataBlock_t; /** - * Start the acquisition of an incoming audio stream, also opening the - * corresponding audio path. If a stream is opened from the same source but - * with an higher priority than the one currently open, the new stream takes - * over the previous one. - * The function returns an error when the audio path is not available or the - * selected stream is already in use by another process with higher priority. + * Start an audio stream, either in input or output mode as specified by the + * corresponding parameter. * - * @param source: input source specifier. - * @param prio: priority of the requester. - * @param buf: pointer to a buffer used for management of sampled data. - * @param bufLength: length of the buffer, in elements. - * @param mode: buffer management mode. - * @param sampleRate: sample rate, in Hz. - * @return a unique identifier for the stream or -1 if the stream could not be - * opened. + * WARNING: for output streams the caller must ensure that buffer content is not + * modified while the stream is being reproduced. + * + * @param path: audio path for the stream. + * @param buf: buffer containing the audio samples. + * @param length: length of the buffer, in elements. + * @param sampleRate: sample rate in Hz. + * @param mode: operation mode of the buffer + * @return a unique identifier for the stream or a negative error code. */ -streamId inputStream_start(const enum AudioSource source, - const enum AudioPriority prio, - stream_sample_t * const buf, - const size_t bufLength, - const enum BufMode mode, - const uint32_t sampleRate); +streamId audioStream_start(const pathId path, stream_sample_t * const buf, + const size_t length, const uint32_t sampleRate, + const uint8_t mode); + +/** + * Request termination of a currently ongoing audio stream. + * Stream is effectively stopped only when all the remaining data have been + * processed, execution flow is blocked in the meantime. + * + * @param id: identifier of the stream to be stopped. + */ +void audioStream_stop(const streamId id); + +/** + * Interrupt a currently ongoing audio stream before its natural ending. + * + * @param id: identifier of the stream to be stopped. + */ +void audioStream_terminate(const streamId id); /** * Get a chunk of data from an already opened input stream, blocking function. @@ -77,42 +93,6 @@ streamId inputStream_start(const enum AudioSource source, */ dataBlock_t inputStream_getData(streamId id); -/** - * Release the current input stream, allowing for a new call of startInputStream. - * If this function is called when sampler is running, acquisition is stopped - * and any thread waiting on getData() is woken up and given a partial result. - * - * @param id: identifier of the stream to be stopped - */ -void inputStream_stop(streamId id); - -/** - * Send an audio stream to a given output. This function returns immediately if - * there is not another stream already running with the same destination and - * priority of the ones specified, otherwise it will block the caller until the - * previous stream terminates. - * If a stream is opened from the same source but with an higher priority than - * the one currently open, the new stream takes over the previous one. - * - * WARNING: the caller must ensure that buffer content is not modified while the - * stream is being reproduced. - * - * @param destination: destination of the output stream. - * @param prio: priority of the requester. - * @param buf: buffer containing the audio samples. - * @param length: length of the buffer, in elements. - * @param mode: operation mode of the buffer - * @param sampleRate: sample rate in Hz. - * @return a unique identifier for the stream or -1 if the stream could not be - * opened. - */ -streamId outputStream_start(const enum AudioSink destination, - const enum AudioPriority prio, - stream_sample_t * const buf, - const size_t length, - const enum BufMode mode, - const uint32_t sampleRate); - /** * Get a pointer to the section of the sample buffer not currently being read * by the DMA peripheral. The function is to be used primarily when the output @@ -142,21 +122,6 @@ stream_sample_t *outputStream_getIdleBuffer(const streamId id); */ bool outputStream_sync(const streamId id, const bool bufChanged); -/** - * Request termination of a currently ongoing output stream. - * Stream is effectively stopped only when all the remaining data are sent. - * - * @param id: identifier of the stream to be stopped. - */ -void outputStream_stop(const streamId id); - -/** - * Interrupt a currently ongoing output stream before its natural ending. - * - * @param id: identifier of the stream to be stopped. - */ -void outputStream_terminate(const streamId id); - #ifdef __cplusplus } #endif diff --git a/openrtx/include/interfaces/audio.h b/openrtx/include/interfaces/audio.h index ff6ed232..3db7b3f6 100644 --- a/openrtx/include/interfaces/audio.h +++ b/openrtx/include/interfaces/audio.h @@ -58,7 +58,7 @@ enum AudioPriority enum BufMode { - BUF_LINEAR, ///< Linear buffer mode, conversion stops when full. + BUF_LINEAR = 1, ///< Linear buffer mode, conversion stops when full. BUF_CIRC_DOUBLE ///< Circular double buffer mode, conversion never stops, /// thread woken up whenever half of the buffer is full. }; diff --git a/openrtx/src/core/audio_stream.c b/openrtx/src/core/audio_stream.c new file mode 100644 index 00000000..7e41f021 --- /dev/null +++ b/openrtx/src/core/audio_stream.c @@ -0,0 +1,200 @@ +/*************************************************************************** + * Copyright (C) 2023 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 * + ***************************************************************************/ + +#include +#include + +#define MAX_NUM_STREAMS 3 +#define MAX_NUM_DEVICES 3 + +struct streamState +{ + const struct audioDevice *dev; + struct streamCtx ctx; + pathId path; +}; + +static struct streamState streams[MAX_NUM_STREAMS] = {0}; + + +/** + * \internal + * Verify if the path associated to a given stream is still open and, if path is + * closed or suspended, terminate the stream. + * + * @param id: stream ID. + * @return true if the path is valid. + */ +static bool validateStream(const streamId id) +{ + if((id < 0) || (id >= MAX_NUM_STREAMS)) + return false; + + uint8_t status = audioPath_getStatus(streams[id].path); + if(status != PATH_OPEN) + { + // Path has been closed or suspended: terminate the stream and free it + streams[id].dev->driver->terminate(&(streams[id].ctx)); + streams[id].path = 0; + + return false; + } + + // Path is still open + return true; +} + +streamId audioStream_start(const pathId path, stream_sample_t * const buf, + const size_t length, const uint32_t sampleRate, + const uint8_t mode) +{ + // Check for invalid stream mode or invalid buffer handling mode + if(((mode & 0xF0) == 0) || ((mode & 0x0F) == 0)) + return -EINVAL; + + pathInfo_t pathInfo = audioPath_getInfo(path); + if(pathInfo.status != PATH_OPEN) + return -EPERM; + + // Search for an audio device serving the correct output endpoint. + const struct audioDevice *dev = NULL; + const struct audioDevice *devs = inputDevices; + uint8_t endpoint = pathInfo.source; + + if((mode & 0xF0) == STREAM_OUTPUT) + { + devs = outputDevices; + endpoint = pathInfo.sink; + } + + for(size_t i = 0; i < MAX_NUM_DEVICES; i++) + { + if(devs[i].endpoint == endpoint) + dev = &devs[i]; + } + + // No audio device found + if(dev == NULL) + return -ENODEV; + + // Search for an empty audio stream slot + streamId id = -1; + for(size_t i = 0; i < MAX_NUM_STREAMS; i++) + { + if((streams[i].path <= 0) && (streams[i].ctx.running == 0)) + id = i; + } + + // No stream slots available + if(id < 0) + return -EBUSY; + + // Setup new stream and start it + streams[id].path = path; + streams[id].dev = dev; + streams[id].ctx.buffer = buf; + streams[id].ctx.bufMode = (mode & 0x0F); + streams[id].ctx.bufSize = length; + streams[id].ctx.sampleRate = sampleRate; + + int ret = dev->driver->start(dev->instance, dev->config, &streams[id].ctx); + if(ret < 0) + { + streams[id].ctx.running = 0; + streams[id].path = 0; + return ret; + } + + return id; +} + +void audioStream_stop(const streamId id) +{ + if((id < 0) || (id >= MAX_NUM_STREAMS)) + return; + + if(streams[id].path == 0) + return; + + streams[id].dev->driver->stop(&(streams[id].ctx)); + streams[id].dev->driver->sync(&(streams[id].ctx), false); + streams[id].path = 0; +} + +void audioStream_terminate(const streamId id) +{ + if((id < 0) || (id >= MAX_NUM_STREAMS)) + return; + + if(streams[id].path == 0) + return; + + streams[id].dev->driver->terminate(&(streams[id].ctx)); + streams[id].path = 0; +} + +dataBlock_t inputStream_getData(streamId id) +{ + dataBlock_t block; + block.data = NULL; + block.len = 0; + + if(validateStream(id) == false) + return block; + + int ret = streams[id].dev->driver->sync(&(streams[id].ctx), false); + if(ret < 0) + return block; + + ret = streams[id].dev->driver->data(&(streams[id].ctx), &block.data); + if(ret < 0) + { + block.data = NULL; + return block; + } + + block.len = (size_t) ret; + return block; +} + +stream_sample_t *outputStream_getIdleBuffer(const streamId id) +{ + if(validateStream(id) == false) + return NULL; + + stream_sample_t *buf; + int ret = streams[id].dev->driver->data(&(streams[id].ctx), &buf); + if(ret < 0) + return NULL; + + return buf; +} + +bool outputStream_sync(const streamId id, const bool bufChanged) +{ + if(validateStream(id) == false) + return false; + + int ret = streams[id].dev->driver->sync(&(streams[id].ctx), bufChanged); + if(ret < 0) + return false; + + return true; +}