/*************************************************************************** * Copyright (C) 2021 - 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 #include #include #include #include #include #include #include // Expand opaque pa_simple struct struct pa_simple { pa_threaded_mainloop *mainloop; pa_context *context; pa_stream *stream; pa_stream_direction_t direction; const void *read_data; size_t read_index; size_t read_length; int operation_success; }; static enum BufMode bufMode; // Buffer operation mode static enum AudioPriority priority = PRIO_BEEP; // Priority level static bool running = false; // Stream is running static size_t bufLen = 0; // Total buffer length static stream_sample_t *playBuf = NULL; // Buffer being reproduced static stream_sample_t *idleBuf = NULL; // Idle buffer available to be filled static pa_simple *paInstance = NULL; // Pulseaudio instance static size_t remaining = 0; static pthread_cond_t barrier; static pthread_mutex_t mutex; static void buf_circ_write_cb(pa_stream* s, size_t length, void* userdata) { (void) userdata; if((s == NULL) || (length <= 0)) return; if(length > remaining) { // We can play all the rest of the buffer pa_stream_write(s, playBuf, remaining * sizeof(stream_sample_t), NULL, 0, PA_SEEK_RELATIVE); remaining = 0; } else { pa_stream_write(s, playBuf, length, NULL, 0, PA_SEEK_RELATIVE); if (remaining > length) remaining -= length; else remaining = 0; } // All data in playBuffer has been sent if(remaining == 0) { // Reload counter remaining = bufLen/2; pthread_mutex_lock(&mutex); // Swap idle and play buffers stream_sample_t *tmp = idleBuf; playBuf = idleBuf; idleBuf = tmp; // Unlock waiting threads pthread_cond_signal(&barrier); pthread_mutex_unlock(&mutex); } } streamId outputStream_start(const enum AudioSink destination, const enum AudioPriority prio, stream_sample_t* const buffer, const size_t length, const enum BufMode mode, const uint32_t sampleRate) { if(destination != SINK_SPK) return -1; // Check if an output stream is already opened and, in case, handle // priority. if(running) { if(prio < priority) return -1; // Lower priority, reject. if(prio > priority) outputStream_stop(0); // Higher priority, takes over. while(running) ; // Same priority, wait. } // Assign priority and set stream as running running = true; priority = prio; bufMode = mode; playBuf = buffer; idleBuf = buffer + (length/2); bufLen = length; remaining = length/2; int paError = 0; bool success = true; if(paInstance == NULL) { // Stream data sample format static pa_sample_spec spec; spec.format = PA_SAMPLE_S16LE; spec.rate = 0; spec.channels = 1; spec.rate = sampleRate; paInstance = pa_simple_new(NULL, "OpenRTX", PA_STREAM_PLAYBACK, NULL, "Audio out", &spec, NULL, NULL, &paError); if(paInstance == NULL) { fprintf(stderr, __FILE__ ": pa_simple_new() failed: %s\n", pa_strerror(paError)); success = false; } else { pthread_mutex_init(&mutex, NULL); pthread_cond_init(&barrier, NULL); } } switch(mode) { case BUF_LINEAR: if(pa_simple_write(paInstance, buffer, length, &paError) < 0) success = false; break; case BUF_CIRC_DOUBLE: { if(paInstance->stream == NULL) { success = false; break; } // Register write callback pa_stream_set_write_callback(paInstance->stream, buf_circ_write_cb, NULL); // Set minimal prebuffering const pa_buffer_attr attr = { .fragsize = -1, .maxlength = -1, .minreq = -1, .prebuf = 320, .tlength = -1, }; pa_stream_set_buffer_attr(paInstance->stream, &attr, NULL, NULL); // Get maximum pulse buffer size size_t wsize = pa_stream_writable_size(paInstance->stream); if(wsize > (length / 2)) wsize = length / 2; // Start writing loop pa_stream_write(paInstance->stream, playBuf, wsize, NULL, 0, PA_SEEK_RELATIVE); } break; } if(success == false) { running = false; priority = PRIO_BEEP; return -1; } return 0; } stream_sample_t *outputStream_getIdleBuffer(const streamId id) { (void) id; stream_sample_t *ptr = NULL; if(bufMode == BUF_CIRC_DOUBLE) { pthread_mutex_lock(&mutex); ptr = idleBuf; pthread_mutex_unlock(&mutex); } return ptr; } bool outputStream_sync(const streamId id, const bool bufChanged) { (void) id; (void) bufChanged; if(bufMode == BUF_CIRC_DOUBLE) { pthread_mutex_lock(&mutex); pthread_cond_wait(&barrier, &mutex); pthread_mutex_unlock(&mutex); } // // TODO syncronisation barrrier also for linear buffer mode // return true; } void outputStream_stop(const streamId id) { (void) id; int error = 0; if (pa_simple_flush(paInstance, &error) < 0) { fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(error)); } running = false; priority = PRIO_BEEP; } void outputStream_terminate(const streamId id) { (void) id; running = false; priority = PRIO_BEEP; if(paInstance != NULL) { pa_simple_free(paInstance); pthread_mutex_destroy(&mutex); pthread_cond_destroy(&barrier); } }