UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
portaudio.c
Go to the documentation of this file.
1/*
2 * FILE: audio/capture/portaudio.c
3 * AUTHORS: Martin Benes <martinbenesh@gmail.com>
4 * Lukas Hejtmanek <xhejtman@ics.muni.cz>
5 * Petr Holub <hopet@ics.muni.cz>
6 * Milos Liska <xliska@fi.muni.cz>
7 * Jiri Matela <matela@ics.muni.cz>
8 * Dalibor Matura <255899@mail.muni.cz>
9 * Ian Wesley-Smith <iwsmith@cct.lsu.edu>
10 * Martin Pulec <pulec@cesnet.cz>
11 *
12 * Copyright (c) 2005-2024 CESNET
13 *
14 * Redistribution and use in source and binary forms, with or without
15 * modification, is permitted provided that the following conditions
16 * are met:
17 *
18 * 1. Redistributions of source code must retain the above copyright
19 * notice, this list of conditions and the following disclaimer.
20 *
21 * 2. Redistributions in binary form must reproduce the above copyright
22 * notice, this list of conditions and the following disclaimer in the
23 * documentation and/or other materials provided with the distribution.
24 *
25 * 3. All advertising materials mentioning features or use of this software
26 * must display the following acknowledgement:
27 *
28 * This product includes software developed by CESNET z.s.p.o.
29 *
30 * 4. Neither the name of CESNET nor the names of its contributors may be used
31 * to endorse or promote products derived from this software without specific
32 * prior written permission.
33 *
34 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
35 * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
36 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
37 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
38 * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
39 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
40 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
41 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
42 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
43 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
44 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
45 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
46 *
47 */
48
49#include <ctype.h> // for isdigit
50#include <portaudio.h> /* from PortAudio API */
51#include <pthread.h>
52#include <stdio.h>
53#include <stdlib.h>
54#include <string.h>
55struct device_info;
56struct module;
57
58#include "audio/audio_capture.h"
60#include "audio/types.h"
61#include "compat/strings.h" // strdupa
62#include "debug.h"
63#include "host.h"
64#include "lib_common.h"
65#include "utils/color_out.h"
66#include "utils/macros.h"
67#include "utils/ring_buffer.h"
68
69/* default variables for sender */
70#define BUF_MS 100
71
72#define MOD_NAME "[Portaudio capture] "
73
76 PaStream *stream;
77
78 pthread_mutex_t lock;
79 pthread_cond_t cv;
81};
82
83/*
84 * For Portaudio threads-related issues see
85 * http://www.portaudio.com/trac/wiki/tips/Threading
86 */
87static int portaudio_start_stream(PaStream *stream);
88static void portaudio_close(PaStream *stream); // closes and frees all audio resources ( according to valgrind this is not true.. )
89static int callback( const void *inputBuffer, void *outputBuffer,
90 unsigned long framesPerBuffer,
91 const PaStreamCallbackTimeInfo* timeInfo,
92 PaStreamCallbackFlags statusFlags,
93 void *userData );
94
95static int portaudio_start_stream(PaStream *stream)
96{
97 PaError error = Pa_StartStream(stream);
98 if (error != paNoError) {
99 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Error starting stream:%s\n", Pa_GetErrorText(error));
100 log_msg(LOG_LEVEL_ERROR, "\tPortAudio error: %s\n", Pa_GetErrorText( error ) );
101 return -1;
102 }
103
104 return 0;
105}
106
107static void audio_cap_portaudio_probe(struct device_info **available_devices, int *count, void (**deleter)(void *))
108{
109 *deleter = free;
110 audio_portaudio_probe(available_devices, count, PORTAUDIO_IN);
111}
112
117
118static void portaudio_close(PaStream * stream) // closes and frees all audio resources
119{
120 Pa_StopStream(stream); // may not be necessary
121 Pa_CloseStream(stream);
122 Pa_Terminate();
123}
124
125static _Bool
126parse_fmt(const char *cfg, PaTime *latency, int *input_device_idx,
127 char device_name[static STR_LEN])
128{
129 if (isdigit(cfg[0])) {
130 *input_device_idx = atoi(cfg);
131 cfg = strchr(cfg, ':') ? strchr(cfg, ':') + 1 : cfg + strlen(cfg);
132 }
133 char *ccfg = strdupa(cfg);
134 char *item = NULL;
135 char *saveptr = NULL;
136 while ((item = strtok_r(ccfg, ":", &saveptr)) != NULL) {
137 if (IS_KEY_PREFIX(item, "latency")) {
138 *latency = atof(strchr(item, '=') + 1);
139 } else if (IS_KEY_PREFIX(item, "device")) {
140 const char *dev = strchr(item, '=') + 1;
141 if (isdigit(dev[0])) {
142 *input_device_idx = atoi(dev);
143 } else {
144 snprintf(device_name, STR_LEN, "%s", dev);
145 }
146 } else {
147 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Unknown option: %s!\n", item);
148 return 0;
149 }
150 ccfg = NULL;
151 }
152 return 1;
153}
154
155static void usage() {
156 printf("PortAudio capture usage:\n");
157 color_printf("\t" TBOLD(TRED("-s portaudio") "[:<index>[:latency=<l>]]") "\n");
158 printf("or\n");
159 color_printf("\t" TBOLD(TRED("-s portaudio") "[:device=<dev>][:latency=<l>]") "\n\n");
160 printf("options:\n");
161 color_printf("\t" TBOLD(" <l> ") "\tsuggested latency in sec (experimental, use in case of problems)\n");
162 color_printf("\t" TBOLD("<dev>") "\tdevice name (or a part of it); device index is also accepted here\n");
163 printf("\nAvailable PortAudio capture devices:\n");
164
166}
167
168static void * audio_cap_portaudio_init(struct module *parent, const char *cfg)
169{
170 UNUSED(parent);
172
173 if (strcmp(cfg, "help") == 0) {
174 usage();
175 return INIT_NOERR;
176 }
177
178 int input_device_idx = -1;
179 char input_device_name[STR_LEN] = "";
180 PaError error;
181 const PaDeviceInfo *device_info = NULL;
182 PaTime latency = -1.0;
183
184 if (!parse_fmt(cfg, &latency, &input_device_idx, input_device_name)) {
185 return NULL;
186 }
187
188 struct state_portaudio_capture *s = calloc(1, sizeof *s);
189
190 log_msg(LOG_LEVEL_INFO, "Initializing portaudio capture.\n");
191
192 pthread_mutex_init(&s->lock, NULL);
193 pthread_cond_init(&s->cv, NULL);
194
195 error = Pa_Initialize();
196 if (error != paNoError) {
197 log_msg(LOG_LEVEL_ERROR, MOD_NAME "error initializing portaudio\n");
198 log_msg(LOG_LEVEL_ERROR, "\tPortAudio error: %s\n", Pa_GetErrorText( error ) );
199 free(s);
200 return NULL;
201 }
202
203 log_msg(LOG_LEVEL_INFO, "Using PortAudio version: %s\n", Pa_GetVersionText());
204
205 PaStreamParameters inputParameters;
206
207 // default device
208 if (strlen(input_device_name) > 0) {
209 input_device_idx = portaudio_select_device_by_name(input_device_name);
210 }
211 if (input_device_idx == -1) {
212 log_msg(LOG_LEVEL_NOTICE, MOD_NAME "Using default input audio device: %s\n",
213 portaudio_get_device_name(Pa_GetDefaultInputDevice()));
214 inputParameters.device = Pa_GetDefaultInputDevice();
215 device_info = Pa_GetDeviceInfo(Pa_GetDefaultInputDevice());
216 } else if (input_device_idx >= 0) {
217 log_msg(LOG_LEVEL_NOTICE, MOD_NAME "Using input audio device: %s\n",
218 portaudio_get_device_name(input_device_idx));
219 inputParameters.device = input_device_idx;
220 device_info = Pa_GetDeviceInfo(input_device_idx);
221 }
222
223 if(device_info == NULL) {
224 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Couldn't obtain requested portaudio device index %d.\n"
225 MOD_NAME "Follows list of available Portaudio devices.\n", input_device_idx);
227 free(s);
228 Pa_Terminate();
229 return NULL;
230 }
231
232 inputParameters.channelCount = s->frame.ch_count = audio_capture_channels > 0 ? (int) audio_capture_channels : MIN(device_info->maxInputChannels, DEFAULT_AUDIO_CAPTURE_CHANNELS);
233 if (s->frame.ch_count > device_info->maxInputChannels) {
234 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Requested %d input channels, device offers only %d.\n",
235 s->frame.ch_count,
236 device_info->maxInputChannels);
237 free(s);
238 return NULL;
239 }
240 if (audio_capture_bps == 4) {
241 inputParameters.sampleFormat = paInt32;
242 s->frame.bps = 4;
243 } else {
244 if (audio_capture_bps != 0 && audio_capture_bps != 2) {
245 log_msg(LOG_LEVEL_WARNING, MOD_NAME "Ignoring unsupported Bps %d!\n",
247 }
248 inputParameters.sampleFormat = paInt16;
249 s->frame.bps = 2;
250 }
251 inputParameters.suggestedLatency = latency == -1.0 ? Pa_GetDeviceInfo(inputParameters.device)->defaultLowInputLatency : latency;
252 inputParameters.hostApiSpecificStreamInfo = NULL;
253
255 48000;
256
257 error = Pa_OpenStream( &s->stream, &inputParameters, NULL, s->frame.sample_rate,
258 paFramesPerBufferUnspecified, // frames per buffer // TODO decide on the amount
259 paNoFlag,
260 callback, // callback function; NULL, because we use blocking functions
261 s // user data - none, because we use blocking functions
262 );
263 if (error != paNoError) {
264 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Error opening audio stream\n");
265 log_msg(LOG_LEVEL_ERROR, "\tPortAudio error: %s\n", Pa_GetErrorText( error ) );
266 return NULL;
267 }
268
269 s->frame.max_size = (s->frame.bps * s->frame.ch_count) * s->frame.sample_rate / 1000 * BUF_MS;
270
271 s->frame.data = (char*)malloc(s->frame.max_size);
272
274
275 memset(s->frame.data, 0, s->frame.max_size);
276
278
279 return s;
280}
281
282static int callback( const void *inputBuffer, void *outputBuffer,
283 unsigned long framesPerBuffer,
284 const PaStreamCallbackTimeInfo* timeInfo,
285 PaStreamCallbackFlags statusFlags,
286 void *userData)
287{
288 struct state_portaudio_capture * s =
289 (struct state_portaudio_capture *) userData;
290 UNUSED(outputBuffer);
291 UNUSED(timeInfo);
292 UNUSED(statusFlags);
293
294 pthread_mutex_lock(&s->lock);
295 ring_buffer_write(s->buffer, inputBuffer, framesPerBuffer * s->frame.ch_count *
296 s->frame.bps);
297 pthread_cond_signal(&s->cv);
298 pthread_mutex_unlock(&s->lock);
299
300 return paContinue;
301
302}
303
304// read from input device
306{
307 struct state_portaudio_capture *s =
309
310 int ret = 0;
311
312 pthread_mutex_lock(&s->lock);
313 while((ret = ring_buffer_read(s->buffer, s->frame.data, s->frame.max_size)) == 0) {
314 pthread_cond_wait(&s->cv, &s->lock);
315 }
316 pthread_mutex_unlock(&s->lock);
317
318 s->frame.data_len = ret;
319
320 return &s->frame;
321}
322
324{
327 free(s->frame.data);
328 pthread_mutex_destroy(&s->lock);
329 pthread_cond_destroy(&s->cv);
331 free(s);
332}
333
340
342
#define UNUSED(x)
void ** state
int void(** deleter)(void *))
This file contains definition of some common audio types.
#define AUDIO_CAPTURE_ABI_VERSION
static void portaudio_close(PaStream *stream)
Definition portaudio.c:118
static const struct audio_capture_info acap_portaudio_info
Definition portaudio.c:334
static _Bool parse_fmt(const char *cfg, PaTime *latency, int *input_device_idx, char device_name[static STR_LEN])
Definition portaudio.c:126
static void usage()
Definition portaudio.c:155
static void * audio_cap_portaudio_init(struct module *parent, const char *cfg)
Definition portaudio.c:168
static void audio_cap_portaudio_done(void *state)
Definition portaudio.c:323
static void audio_cap_portaudio_help()
Definition portaudio.c:113
#define MOD_NAME
Definition portaudio.c:72
static void audio_cap_portaudio_probe(struct device_info **available_devices, int *count, void(**deleter)(void *))
Definition portaudio.c:107
static struct audio_frame * audio_cap_portaudio_read(void *state)
Definition portaudio.c:305
#define BUF_MS
Definition portaudio.c:70
static int portaudio_start_stream(PaStream *stream)
Definition portaudio.c:95
int color_printf(const char *format,...)
#define TRED(x)
Definition color_out.h:75
#define TBOLD(x)
Definition color_out.h:70
void log_msg(int level, const char *format,...)
Definition debug.cpp:130
#define LOG_LEVEL_NOTICE
information that may be interesting
Definition debug.h:61
#define LOG_LEVEL_INFO
normal reporting
Definition debug.h:62
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
#define LOG_LEVEL_WARNING
less severe errors
Definition debug.h:60
int count
Definition hd-rum.c:44
unsigned int audio_capture_bps
Definition host.cpp:164
unsigned int audio_capture_sample_rate
Definition host.cpp:165
unsigned int audio_capture_channels
Definition host.cpp:163
#define DEFAULT_AUDIO_CAPTURE_CHANNELS
Definition host.h:89
#define INIT_NOERR
Definition host.h:73
@ LIBRARY_CLASS_AUDIO_CAPTURE
Definition lib_common.h:76
#define REGISTER_MODULE(name, info, lclass, abi)
Registers module to global modules' registry.
Definition lib_common.h:144
#define MIN(a, b)
Definition macros.h:63
#define IS_KEY_PREFIX(tok, key)
expands to true value if <k> from tok in format <k>=<v> is prefix of key
Definition macros.h:138
@ STR_LEN
"standard" string length placeholder
Definition macros.h:134
int portaudio_select_device_by_name(const char *name)
void portaudio_print_version()
void audio_portaudio_probe(struct device_info **available_devices, int *count, enum portaudio_device_direction dir)
const char * portaudio_get_device_name(PaDeviceIndex device)
void portaudio_print_help(enum portaudio_device_direction kind)
@ PORTAUDIO_IN
void ring_buffer_destroy(struct ring_buffer *ring)
struct ring_buffer * ring_buffer_init(int size)
int ring_buffer_read(struct ring_buffer *ring, char *out, int max_len)
void ring_buffer_write(struct ring_buffer *ring, const char *in, int len)
const char * parent
Definition settings.cpp:198
Option::Callback::fcn_type callback
Definition settings.cpp:251
#define strdupa(s)
Definition strings.h:77
struct used to comunicate with audio capture/playback device
Definition types.h:99
int sample_rate
Definition types.h:101
char * data
Definition types.h:102
int ch_count
Definition types.h:104
int data_len
Definition types.h:103
int bps
Definition types.h:100
int max_size
Definition types.h:105
Definition hd-rum.c:27
pthread_mutex_t lock
Definition portaudio.c:78
pthread_cond_t cv
Definition portaudio.c:79
struct ring_buffer * buffer
Definition portaudio.c:80
struct audio_frame frame
Definition portaudio.c:75