Skip to content

Commit d8b4866

Browse files
authored
Merge pull request #234 from Libvisual/portaudio-input-plugin
libvisual-plugins: Add PortAudio input plugin (e.g. for macOS, non-blocking I/O)
2 parents c321cec + a70cd36 commit d8b4866

File tree

7 files changed

+192
-1
lines changed

7 files changed

+192
-1
lines changed

.github/workflows/linux.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ jobs:
7070
libasound2-dev \
7171
libsdl1.2-dev \
7272
libgl1-mesa-dev \
73-
pkg-config
73+
pkg-config \
74+
portaudio19-dev
7475
7576
- name: Install build dependency Clang ${{ matrix.clang_major_version }}
7677
if: "${{ contains(matrix.cxx, 'clang') }}"

.github/workflows/windows.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ jobs:
5353
mingw-w64-x86_64-ninja `
5454
mingw-w64-x86_64-gstreamer `
5555
mingw-w64-x86_64-libpng `
56+
mingw-w64-x86_64-portaudio `
5657
mingw-w64-x86_64-SDL `
5758
mingw-w64-x86_64-gettext
5859

libvisual-plugins/CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ OPTION(ENABLE_NEBULUS "Build the Nebulus plugin" yes)
148148
OPTION(ENABLE_MPLAYER "Build the MPlayer input plugin" yes)
149149
OPTION(ENABLE_NASTYFFT "Build the NastyFFT plugin" yes)
150150
OPTION(ENABLE_PLAZMA "Build the Plazma plugin" yes)
151+
OPTION(ENABLE_PORTAUDIO "Build the PortAudio input plugin" yes)
151152
OPTION(ENABLE_PULSEAUDIO "Build the PulseAudio input plugin" yes)
152153
OPTION(ENABLE_OINKSIE "Build the Oinksie plugin" yes)
153154
OPTION(ENABLE_SLIDE "Build the Slide morph plugin" yes)
@@ -260,6 +261,14 @@ IF(ENABLE_NEBULUS)
260261
ENDIF()
261262
ENDIF()
262263

264+
IF(ENABLE_PORTAUDIO)
265+
PKG_CHECK_MODULES(PORTAUDIO portaudio-2.0 IMPORTED_TARGET)
266+
IF(NOT PORTAUDIO_FOUND)
267+
MESSAGE(WARNING "PortAudio not found. The PortAudio input plugin will not be built.")
268+
SET(ENABLE_PORTAUDIO no)
269+
ENDIF()
270+
ENDIF()
271+
263272
IF(ENABLE_PULSEAUDIO)
264273
PKG_CHECK_MODULES(PULSE libpulse>=${PULSE_REQUIRED_VERSION} IMPORTED_TARGET)
265274
IF(NOT PULSE_FOUND)

libvisual-plugins/plugins/input/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ IF(ENABLE_MPLAYER)
1414
ADD_SUBDIRECTORY(mplayer)
1515
ENDIF()
1616

17+
IF(ENABLE_PORTAUDIO)
18+
ADD_SUBDIRECTORY(portaudio)
19+
ENDIF()
20+
1721
IF(ENABLE_PULSEAUDIO)
1822
ADD_SUBDIRECTORY(pulseaudio)
1923
ENDIF()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
LV_BUILD_INPUT_PLUGIN(portaudio
2+
SOURCES input_portaudio.c
3+
LINK_LIBS PkgConfig::PORTAUDIO
4+
)
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/* Libvisual-plugins - Standard plugins for libvisual
2+
*
3+
* Copyright (C) 2023 Sebastian Pipping <[email protected]>
4+
*
5+
* Authors: Sebastian Pipping <[email protected]>
6+
*
7+
* This program is free software; you can redistribute it and/or modify
8+
* it under the terms of the GNU Lesser General Public License as
9+
* published by the Free Software Foundation; either version 2.1
10+
* of the License, or (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public License
18+
* along with this program; if not, write to the Free Software
19+
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20+
*/
21+
22+
#include <stdlib.h> // malloc
23+
24+
#include <libvisual/libvisual.h>
25+
#include "gettext.h"
26+
#include <portaudio.h>
27+
28+
VISUAL_PLUGIN_API_VERSION_VALIDATOR
29+
30+
#define SAMPLE_RATE 44100
31+
#define SAMPLE_RATE_TYPE_LV VISUAL_AUDIO_SAMPLE_RATE_44100
32+
#define CHANNELS 2
33+
#define CHANNELS_TYPE VISUAL_AUDIO_SAMPLE_CHANNEL_STEREO
34+
#define SAMPLE_FORMAT_BITS 16
35+
#define SAMPLE_FORMAT_PA paInt16
36+
#define SAMPLE_FORMAT_LV VISUAL_AUDIO_SAMPLE_FORMAT_S16
37+
38+
#define FRAMES 1024
39+
40+
typedef struct {
41+
PaStream *stream;
42+
void * buffer;
43+
} PortAudioPrivate;
44+
45+
static int inp_portaudio_init (VisPluginData *plugin);
46+
static void inp_portaudio_cleanup (VisPluginData *plugin);
47+
static int inp_portaudio_upload (VisPluginData *plugin, VisAudio *audio);
48+
49+
const VisPluginInfo *get_plugin_info (void)
50+
{
51+
static VisInputPlugin input = {
52+
.upload = inp_portaudio_upload
53+
};
54+
55+
static VisPluginInfo info = {
56+
.type = VISUAL_PLUGIN_TYPE_INPUT,
57+
58+
.plugname = "portaudio",
59+
.name = "portaudio",
60+
.author = "Sebastian Pipping <[email protected]>",
61+
.version = "1.0",
62+
.about = N_("PortAudio capture plugin"),
63+
.help = N_("Use this plugin to capture PCM data from the PortAudio record device"),
64+
.license = VISUAL_PLUGIN_LICENSE_LGPL,
65+
66+
.init = inp_portaudio_init,
67+
.cleanup = inp_portaudio_cleanup,
68+
69+
.plugin = &input
70+
};
71+
72+
return &info;
73+
}
74+
75+
int inp_portaudio_init (VisPluginData *plugin)
76+
{
77+
#if ENABLE_NLS
78+
bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
79+
#endif
80+
81+
PortAudioPrivate * const priv = visual_mem_new0 (PortAudioPrivate, 1);
82+
visual_return_val_if_fail (priv != NULL, FALSE);
83+
visual_plugin_set_private (plugin, priv);
84+
85+
// Init PortAudio
86+
const PaError init_error = Pa_Initialize();
87+
visual_return_val_if_fail (init_error == paNoError, FALSE);
88+
89+
// Select input device
90+
const PaDeviceIndex input_device = Pa_GetDefaultInputDevice ();
91+
visual_return_val_if_fail (input_device != paNoDevice, FALSE);
92+
93+
// Open stream
94+
const double latency_seconds = 1.0 / SAMPLE_RATE * FRAMES;
95+
const PaStreamParameters input_parameters = {
96+
.device = input_device,
97+
.channelCount = CHANNELS,
98+
.sampleFormat = SAMPLE_FORMAT_PA,
99+
.suggestedLatency = latency_seconds,
100+
.hostApiSpecificStreamInfo = NULL};
101+
const PaError open_error =
102+
Pa_OpenStream (&priv->stream, &input_parameters, NULL,
103+
SAMPLE_RATE, FRAMES, paClipOff, NULL, NULL);
104+
visual_return_val_if_fail (open_error == paNoError, FALSE);
105+
106+
// Allocate buffer
107+
const visual_size_t buffer_size_bytes = FRAMES * CHANNELS * (SAMPLE_FORMAT_BITS / 8);
108+
priv->buffer = malloc (buffer_size_bytes);
109+
visual_return_val_if_fail (priv->buffer != NULL, FALSE);
110+
111+
// Start stream
112+
const PaError input_start_error = Pa_StartStream (priv->stream);
113+
visual_return_val_if_fail (input_start_error == paNoError, FALSE);
114+
115+
return TRUE;
116+
}
117+
118+
void inp_portaudio_cleanup (VisPluginData *plugin)
119+
{
120+
visual_return_if_fail (plugin != NULL);
121+
122+
PortAudioPrivate * const priv = visual_plugin_get_private (plugin);
123+
visual_return_if_fail (priv != NULL);
124+
125+
free (priv->buffer);
126+
priv->buffer = NULL;
127+
128+
Pa_StopStream (priv->stream);
129+
Pa_CloseStream (priv->stream);
130+
priv->stream = NULL;
131+
132+
Pa_Terminate ();
133+
134+
visual_mem_free (priv);
135+
}
136+
137+
int inp_portaudio_upload (VisPluginData *plugin, VisAudio *audio)
138+
{
139+
visual_return_val_if_fail (plugin != NULL, FALSE);
140+
visual_return_val_if_fail (audio != NULL, FALSE);
141+
142+
PortAudioPrivate * const priv = visual_plugin_get_private (plugin);
143+
visual_return_val_if_fail (priv != NULL, FALSE);
144+
visual_return_val_if_fail (priv->stream != NULL, FALSE);
145+
146+
for (;;) {
147+
long frames_to_read = Pa_GetStreamReadAvailable (priv->stream);
148+
if (frames_to_read < 1) {
149+
return TRUE;
150+
}
151+
if (frames_to_read > FRAMES) {
152+
frames_to_read = FRAMES; // because that's all that priv->buffer has space for
153+
}
154+
155+
const PaError read_error = Pa_ReadStream(priv->stream, priv->buffer, frames_to_read);
156+
if (read_error != paNoError) {
157+
return TRUE;
158+
}
159+
160+
const visual_size_t bytes_to_write = frames_to_read * CHANNELS * (SAMPLE_FORMAT_BITS / 8);
161+
162+
VisBuffer * const buffer = visual_buffer_new_wrap_data (priv->buffer, bytes_to_write, FALSE);
163+
visual_return_val_if_fail (priv != NULL, FALSE);
164+
165+
visual_audio_input(audio, buffer, SAMPLE_RATE_TYPE_LV, SAMPLE_FORMAT_LV, CHANNELS_TYPE);
166+
167+
visual_buffer_unref (buffer);
168+
}
169+
170+
// we never get here
171+
}

libvisual-plugins/po/POTFILES.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ plugins/input/alsa/input_alsa.c
2828
plugins/input/debug/input_debug.c
2929
plugins/input/jack/input_jack.cpp
3030
plugins/input/mplayer/input_mplayer.c
31+
plugins/input/portaudio/input_portaudio.c
3132
plugins/input/pulseaudio/input_pulseaudio.c
3233
plugins/input/wavein/input_wavein.cpp
3334
plugins/morph/alphablend/morph_alphablend.c

0 commit comments

Comments
 (0)