miniDSP
A small C library for audio DSP
Loading...
Searching...
No Matches
liveio.c
Go to the documentation of this file.
1
15#include "liveio.h"
16
17/* -----------------------------------------------------------------------
18 * Internal data structures
19 * -----------------------------------------------------------------------*/
20
28typedef struct {
29 int16_t *audiodata;
30 unsigned long len;
31 unsigned long pos_r;
32 unsigned long pos_w;
34
35/* -----------------------------------------------------------------------
36 * Module-level state
37 * -----------------------------------------------------------------------*/
38
39static int LA_InitDone = 0;
40static PaError LA_LastError = paNoError;
41static PaStream *LA_PlayStream = nullptr;
42static PaStream *LA_RecordStream = nullptr;
45/* -----------------------------------------------------------------------
46 * Stream status queries
47 * -----------------------------------------------------------------------*/
48
50{
51 if (Pa_IsStreamActive(LA_RecordStream) == 1) {
52 return Pa_GetStreamTime(LA_RecordStream);
53 }
54 return -1.0;
55}
56
58{
59 if (Pa_IsStreamActive(LA_PlayStream) == 1) {
60 return Pa_GetStreamTime(LA_PlayStream);
61 }
62 return -1.0;
63}
64
66{
67 if (LA_is_recording() == 1) {
68 Pa_CloseStream(LA_RecordStream);
69 }
70}
71
73{
74 if (LA_is_playing() == 1) {
75 Pa_CloseStream(LA_PlayStream);
76 }
77}
78
84{
85 int res = Pa_IsStreamActive(LA_RecordStream);
86 if (res == 1 || res == 0) return res;
87 return -1;
88}
89
95{
96 int res = Pa_IsStreamActive(LA_PlayStream);
97 if (res == 1 || res == 0) return res;
98 return -1;
99}
100
101/* -----------------------------------------------------------------------
102 * PortAudio callbacks
103 *
104 * These functions are called by PortAudio on a dedicated audio thread.
105 * They must be fast and must not block (no malloc, no printf, no locks).
106 * -----------------------------------------------------------------------*/
107
115static int _ContinuousRecordCallback(const void *inputBuffer,
116 void *outputBuffer,
117 unsigned long framesPerBuffer,
118 const PaStreamCallbackTimeInfo *timeInfo,
119 PaStreamCallbackFlags statusFlags,
120 void *userData)
121{
122 LA_simpleData *data = (LA_simpleData *)userData;
123 const int16_t *rptr = (const int16_t *)inputBuffer;
124
125 /* Suppress unused-parameter warnings */
126 (void)outputBuffer;
127 (void)timeInfo;
128 (void)statusFlags;
129
130 for (unsigned long i = 0; i < framesPerBuffer; i++) {
131 data->audiodata[data->pos_w] = (inputBuffer != nullptr) ? *rptr++ : 0;
132 data->pos_w++;
133 if (data->pos_w >= data->len) {
134 data->pos_w = 0; /* Wrap around */
135 }
136 }
137
138 return paContinue;
139}
140
147static int _RecordCallback(const void *inputBuffer,
148 void *outputBuffer,
149 unsigned long framesPerBuffer,
150 const PaStreamCallbackTimeInfo *timeInfo,
151 PaStreamCallbackFlags statusFlags,
152 void *userData)
153{
154 LA_simpleData *data = (LA_simpleData *)userData;
155 const int16_t *rptr = (const int16_t *)inputBuffer;
156 int16_t *wptr = &data->audiodata[data->pos_w];
157
158 (void)outputBuffer;
159 (void)timeInfo;
160 (void)statusFlags;
161
162 /* How many frames can we still fit in the buffer? */
163 unsigned long framesLeft = data->len - data->pos_w;
164 unsigned long framesToRec;
165 int finished;
166
167 if (framesLeft <= framesPerBuffer) {
168 framesToRec = framesLeft;
169 finished = paComplete;
170 } else {
171 framesToRec = framesPerBuffer;
172 finished = paContinue;
173 }
174
175 /* Copy samples (or write silence if input is nullptr) */
176 for (unsigned long i = 0; i < framesToRec; i++) {
177 *wptr++ = (inputBuffer != nullptr) ? *rptr++ : 0;
178 }
179 data->pos_w += framesToRec;
180
181 return finished;
182}
183
188static int _PlayCallback(const void *inputBuffer,
189 void *outputBuffer,
190 unsigned long framesPerBuffer,
191 const PaStreamCallbackTimeInfo *timeInfo,
192 PaStreamCallbackFlags statusFlags,
193 void *userData)
194{
195 LA_simpleData *data = (LA_simpleData *)userData;
196 int16_t *out = (int16_t *)outputBuffer;
197
198 (void)timeInfo;
199 (void)statusFlags;
200 (void)inputBuffer;
201
202 int finished = paContinue;
203 unsigned long nout = framesPerBuffer;
204
205 /* Check if we are near the end of the buffer */
206 if (data->pos_r + framesPerBuffer >= data->len) {
207 finished = paComplete;
208 nout = data->len - data->pos_r;
209 }
210
211 /* Write each mono sample to both stereo channels */
212 for (unsigned long i = 0; i < nout; i++) {
213 *out++ = data->audiodata[data->pos_r]; /* Left channel */
214 *out++ = data->audiodata[data->pos_r]; /* Right channel */
215 data->pos_r++;
216 }
217
218 return finished;
219}
220
225static void _RecordStreamFinished(void *userData)
226{
227 (void)userData;
228 PaError err = Pa_CloseStream(LA_RecordStream);
229 if (err != paNoError) {
230 fprintf(stderr, "PortAudio Error: %s\n", Pa_GetErrorText(err));
231 }
232}
233
238static void _PlayStreamFinished(void *userData)
239{
240 (void)userData;
241 PaError err = Pa_CloseStream(LA_PlayStream);
242 if (err != paNoError) {
243 fprintf(stderr, "PortAudio Error: %s\n", Pa_GetErrorText(err));
244 }
245}
246
247/* -----------------------------------------------------------------------
248 * Internal helpers for stream setup
249 * -----------------------------------------------------------------------*/
250
253{
254 if (LA_InitDone == 0) {
255 if (LA_init() != LA_OK) return LA_ERROR;
256 }
257 return LA_OK;
258}
259
261static LaError_t _setup_input_params(PaStreamParameters *p)
262{
263 p->device = Pa_GetDefaultInputDevice();
264 if (p->device == paNoDevice) {
265 LA_LastError = paNoDevice;
266 return LA_ERROR;
267 }
268 p->channelCount = 1; /* Mono input */
269 p->sampleFormat = paInt16;
270 p->suggestedLatency = Pa_GetDeviceInfo(p->device)->defaultHighInputLatency;
271 p->hostApiSpecificStreamInfo = nullptr;
272 return LA_OK;
273}
274
276static LaError_t _setup_output_params(PaStreamParameters *p)
277{
278 p->device = Pa_GetDefaultOutputDevice();
279 if (p->device == paNoDevice) {
280 LA_LastError = paNoDevice;
281 return LA_ERROR;
282 }
283 p->channelCount = 2; /* Stereo output */
284 p->sampleFormat = paInt16;
285 p->suggestedLatency = Pa_GetDeviceInfo(p->device)->defaultHighOutputLatency;
286 p->hostApiSpecificStreamInfo = nullptr;
287 return LA_OK;
288}
289
290/* -----------------------------------------------------------------------
291 * Public API: recording
292 * -----------------------------------------------------------------------*/
293
294LaError_t LA_record_callback(unsigned samprate, PaStreamCallback *cb, void *cb_data)
295{
296 if (_ensure_init() != LA_OK) return LA_ERROR;
297 if (LA_is_recording() == 1) return LA_ERROR;
298
299 PaStreamParameters inputParams;
300 if (_setup_input_params(&inputParams) != LA_OK) return LA_ERROR;
301
302 PaError err;
303 err = Pa_OpenStream(&LA_RecordStream, &inputParams, nullptr,
304 samprate, paFramesPerBufferUnspecified,
305 paClipOff, cb, cb_data);
306 if (err != paNoError) return LA_ERROR;
307
308 err = Pa_StartStream(LA_RecordStream);
309 if (err != paNoError) return LA_ERROR;
310
311 return LA_OK;
312}
313
314LaError_t LA_record(void *buffer, unsigned long bufsize,
315 unsigned samprate, int rectype)
316{
317 if (_ensure_init() != LA_OK) return LA_ERROR;
318 if (LA_is_recording() == 1) return LA_ERROR;
319
320 LA_data.audiodata = (int16_t *)buffer;
321 LA_data.len = bufsize;
322 LA_data.pos_w = 0;
323
324 PaStreamParameters inputParams;
325 if (_setup_input_params(&inputParams) != LA_OK) return LA_ERROR;
326
327 PaStreamCallback *cb;
328 if (rectype == LA_REC_ONCE) {
329 cb = _RecordCallback;
330 } else if (rectype == LA_REC_CONT) {
332 } else {
333 return LA_ERROR;
334 }
335
336 PaError err;
337 err = Pa_OpenStream(&LA_RecordStream, &inputParams, nullptr,
338 samprate, paFramesPerBufferUnspecified,
339 paClipOff, cb, &LA_data);
340 if (err != paNoError) return LA_ERROR;
341
342 /* For one-shot recording, register a finished callback to auto-close */
343 if (rectype == LA_REC_ONCE) {
344 err = Pa_SetStreamFinishedCallback(LA_RecordStream, _RecordStreamFinished);
345 if (err != paNoError) return LA_ERROR;
346 }
347
348 err = Pa_StartStream(LA_RecordStream);
349 if (err != paNoError) return LA_ERROR;
350
351 return LA_OK;
352}
353
354/* -----------------------------------------------------------------------
355 * Public API: playback
356 * -----------------------------------------------------------------------*/
357
358LaError_t LA_play_callback(unsigned samprate, PaStreamCallback *cb, void *cb_data)
359{
360 if (_ensure_init() != LA_OK) return LA_ERROR;
361 if (LA_is_playing() == 1) return LA_ERROR;
362
363 PaStreamParameters outputParams;
364 if (_setup_output_params(&outputParams) != LA_OK) return LA_ERROR;
365
366 PaError err;
367 err = Pa_OpenStream(&LA_PlayStream, nullptr, &outputParams,
368 samprate, paFramesPerBufferUnspecified,
369 paNoFlag, cb, cb_data);
370 if (err != paNoError) return LA_ERROR;
371
372 err = Pa_StartStream(LA_PlayStream);
373 if (err != paNoError) return LA_ERROR;
374
375 return LA_OK;
376}
377
378LaError_t LA_play(const void *buffer, unsigned long bufsize, unsigned samprate)
379{
380 if (_ensure_init() != LA_OK) return LA_ERROR;
381 if (LA_is_playing() == 1) return LA_ERROR;
382
383 LA_data.audiodata = (int16_t *)buffer;
384 LA_data.len = bufsize;
385 LA_data.pos_r = 0;
386
387 PaStreamParameters outputParams;
388 if (_setup_output_params(&outputParams) != LA_OK) return LA_ERROR;
389
390 PaError err;
391 err = Pa_OpenStream(&LA_PlayStream, nullptr, &outputParams,
392 samprate, paFramesPerBufferUnspecified,
393 paNoFlag, _PlayCallback, &LA_data);
394 if (err != paNoError) return LA_ERROR;
395
396 err = Pa_SetStreamFinishedCallback(LA_PlayStream, _PlayStreamFinished);
397 if (err != paNoError) return LA_ERROR;
398
399 err = Pa_StartStream(LA_PlayStream);
400 if (err != paNoError) return LA_ERROR;
401
402 return LA_OK;
403}
404
405/* -----------------------------------------------------------------------
406 * Public API: error reporting and lifecycle
407 * -----------------------------------------------------------------------*/
408
409void LA_print_last_error(FILE *stream)
410{
411 fprintf(stream, "PortAudio Error: %s\n", Pa_GetErrorText(LA_LastError));
412}
413
421{
422 if (LA_InitDone) {
423 if (LA_terminate() != LA_OK) return LA_ERROR;
424 }
425
426 LA_LastError = Pa_Initialize();
427 if (LA_LastError != paNoError) {
428 LA_InitDone = 0;
429 return LA_ERROR;
430 }
431
432 memset(&LA_data, 0, sizeof(LA_data));
433 LA_InitDone = 1;
434 return LA_OK;
435}
436
442{
443 if (LA_InitDone == 0) {
444 LA_LastError = paNotInitialized;
445 return LA_ERROR;
446 }
447
448 LA_LastError = Pa_Terminate();
449 if (LA_LastError != paNoError) {
450 LA_InitDone = 0;
451 return LA_ERROR;
452 }
453
454 /* BUG FIX: previously this was incorrectly set to 1 after terminating,
455 * which meant the system thought it was still initialised. */
456 LA_InitDone = 0;
457 return LA_OK;
458}
static int _ContinuousRecordCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
Callback for continuous (circular buffer) recording.
Definition liveio.c:115
static LaError_t _setup_input_params(PaStreamParameters *p)
Configure mono 16-bit input parameters for the default device.
Definition liveio.c:261
double LA_get_cur_play_time(void)
Get the current time (in seconds) of the playback stream.
Definition liveio.c:57
int LA_is_playing(void)
Check if the playback stream is currently active.
Definition liveio.c:94
static int LA_InitDone
Has PA been initialised?
Definition liveio.c:39
static void _PlayStreamFinished(void *userData)
Called by PortAudio when a playback stream finishes.
Definition liveio.c:238
static LA_simpleData LA_data
Shared buffer descriptor
Definition liveio.c:43
static LaError_t _ensure_init(void)
Auto-initialise PortAudio if not already done.
Definition liveio.c:252
static int _RecordCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
Callback for one-shot recording (fill the buffer once, then stop).
Definition liveio.c:147
LaError_t LA_terminate(void)
Terminate the PortAudio system and release resources.
Definition liveio.c:441
static PaError LA_LastError
Most recent PA error code.
Definition liveio.c:40
LaError_t LA_init(void)
Initialise the PortAudio system.
Definition liveio.c:420
static PaStream * LA_RecordStream
Active recording stream
Definition liveio.c:42
LaError_t LA_record(void *buffer, unsigned long bufsize, unsigned samprate, int rectype)
Record audio from the default input device into a buffer.
Definition liveio.c:314
LaError_t LA_play(const void *buffer, unsigned long bufsize, unsigned samprate)
Play audio from a buffer to the default output device.
Definition liveio.c:378
LaError_t LA_record_callback(unsigned samprate, PaStreamCallback *cb, void *cb_data)
Record using a user-supplied PortAudio callback function.
Definition liveio.c:294
void LA_stop_recording(void)
Stop the current recording.
Definition liveio.c:65
LaError_t LA_play_callback(unsigned samprate, PaStreamCallback *cb, void *cb_data)
Play using a user-supplied PortAudio callback function.
Definition liveio.c:358
static void _RecordStreamFinished(void *userData)
Called by PortAudio when a one-shot recording stream finishes.
Definition liveio.c:225
int LA_is_recording(void)
Check if the recording stream is currently active.
Definition liveio.c:83
static int _PlayCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
Callback for playback: reads mono samples and duplicates them to both stereo output channels (left an...
Definition liveio.c:188
void LA_print_last_error(FILE *stream)
Print the last PortAudio error message to the given stream.
Definition liveio.c:409
static PaStream * LA_PlayStream
Active playback stream
Definition liveio.c:41
void LA_stop_playing(void)
Stop the current playback.
Definition liveio.c:72
static LaError_t _setup_output_params(PaStreamParameters *p)
Configure stereo 16-bit output parameters for the default device.
Definition liveio.c:276
double LA_get_cur_record_time(void)
Get the current time (in seconds) of the recording stream.
Definition liveio.c:49
Live audio recording and playback via PortAudio.
int LaError_t
Return type for live audio functions.
Definition liveio.h:27
@ LA_REC_CONT
Record continuously in a circular buffer.
Definition liveio.h:38
@ LA_REC_ONCE
Record until the buffer is full, then stop.
Definition liveio.h:37
@ LA_OK
Operation succeeded.
Definition liveio.h:31
@ LA_ERROR
Something went wrong.
Definition liveio.h:32
Simple audio buffer with read/write positions.
Definition liveio.c:28
unsigned long pos_w
Current write position (recording).
Definition liveio.c:32
int16_t * audiodata
Pointer to the sample buffer.
Definition liveio.c:29
unsigned long pos_r
Current read position (playback).
Definition liveio.c:31
unsigned long len
Total number of samples in the buffer.
Definition liveio.c:30