diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 1a40a768e..2cd0f2d72 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -70,7 +70,8 @@ jobs: libasound2-dev \ libsdl1.2-dev \ libgl1-mesa-dev \ - pkg-config + pkg-config \ + portaudio19-dev - name: Install build dependency Clang ${{ matrix.clang_major_version }} if: "${{ contains(matrix.cxx, 'clang') }}" diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index cdc9e72c1..908e2cf84 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -53,6 +53,7 @@ jobs: mingw-w64-x86_64-ninja ` mingw-w64-x86_64-gstreamer ` mingw-w64-x86_64-libpng ` + mingw-w64-x86_64-portaudio ` mingw-w64-x86_64-SDL ` mingw-w64-x86_64-gettext diff --git a/libvisual-plugins/CMakeLists.txt b/libvisual-plugins/CMakeLists.txt index 14b7b1b23..5b30e91a7 100644 --- a/libvisual-plugins/CMakeLists.txt +++ b/libvisual-plugins/CMakeLists.txt @@ -148,6 +148,7 @@ OPTION(ENABLE_NEBULUS "Build the Nebulus plugin" yes) OPTION(ENABLE_MPLAYER "Build the MPlayer input plugin" yes) OPTION(ENABLE_NASTYFFT "Build the NastyFFT plugin" yes) OPTION(ENABLE_PLAZMA "Build the Plazma plugin" yes) +OPTION(ENABLE_PORTAUDIO "Build the PortAudio input plugin" yes) OPTION(ENABLE_PULSEAUDIO "Build the PulseAudio input plugin" yes) OPTION(ENABLE_OINKSIE "Build the Oinksie plugin" yes) OPTION(ENABLE_SLIDE "Build the Slide morph plugin" yes) @@ -260,6 +261,14 @@ IF(ENABLE_NEBULUS) ENDIF() ENDIF() +IF(ENABLE_PORTAUDIO) + PKG_CHECK_MODULES(PORTAUDIO portaudio-2.0 IMPORTED_TARGET) + IF(NOT PORTAUDIO_FOUND) + MESSAGE(WARNING "PortAudio not found. The PortAudio input plugin will not be built.") + SET(ENABLE_PORTAUDIO no) + ENDIF() +ENDIF() + IF(ENABLE_PULSEAUDIO) PKG_CHECK_MODULES(PULSE libpulse>=${PULSE_REQUIRED_VERSION} IMPORTED_TARGET) IF(NOT PULSE_FOUND) diff --git a/libvisual-plugins/plugins/input/CMakeLists.txt b/libvisual-plugins/plugins/input/CMakeLists.txt index dec51f638..8fd315792 100644 --- a/libvisual-plugins/plugins/input/CMakeLists.txt +++ b/libvisual-plugins/plugins/input/CMakeLists.txt @@ -14,6 +14,10 @@ IF(ENABLE_MPLAYER) ADD_SUBDIRECTORY(mplayer) ENDIF() +IF(ENABLE_PORTAUDIO) + ADD_SUBDIRECTORY(portaudio) +ENDIF() + IF(ENABLE_PULSEAUDIO) ADD_SUBDIRECTORY(pulseaudio) ENDIF() diff --git a/libvisual-plugins/plugins/input/portaudio/CMakeLists.txt b/libvisual-plugins/plugins/input/portaudio/CMakeLists.txt new file mode 100644 index 000000000..7ab87510c --- /dev/null +++ b/libvisual-plugins/plugins/input/portaudio/CMakeLists.txt @@ -0,0 +1,4 @@ +LV_BUILD_INPUT_PLUGIN(portaudio + SOURCES input_portaudio.c + LINK_LIBS PkgConfig::PORTAUDIO +) diff --git a/libvisual-plugins/plugins/input/portaudio/input_portaudio.c b/libvisual-plugins/plugins/input/portaudio/input_portaudio.c new file mode 100644 index 000000000..56f3f1b44 --- /dev/null +++ b/libvisual-plugins/plugins/input/portaudio/input_portaudio.c @@ -0,0 +1,171 @@ +/* Libvisual-plugins - Standard plugins for libvisual + * + * Copyright (C) 2023 Sebastian Pipping + * + * Authors: Sebastian Pipping + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 + * 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 Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include // malloc + +#include +#include "gettext.h" +#include + +VISUAL_PLUGIN_API_VERSION_VALIDATOR + +#define SAMPLE_RATE 44100 +#define SAMPLE_RATE_TYPE_LV VISUAL_AUDIO_SAMPLE_RATE_44100 +#define CHANNELS 2 +#define CHANNELS_TYPE VISUAL_AUDIO_SAMPLE_CHANNEL_STEREO +#define SAMPLE_FORMAT_BITS 16 +#define SAMPLE_FORMAT_PA paInt16 +#define SAMPLE_FORMAT_LV VISUAL_AUDIO_SAMPLE_FORMAT_S16 + +#define FRAMES 1024 + +typedef struct { + PaStream *stream; + void * buffer; +} PortAudioPrivate; + +static int inp_portaudio_init (VisPluginData *plugin); +static void inp_portaudio_cleanup (VisPluginData *plugin); +static int inp_portaudio_upload (VisPluginData *plugin, VisAudio *audio); + +const VisPluginInfo *get_plugin_info (void) +{ + static VisInputPlugin input = { + .upload = inp_portaudio_upload + }; + + static VisPluginInfo info = { + .type = VISUAL_PLUGIN_TYPE_INPUT, + + .plugname = "portaudio", + .name = "portaudio", + .author = "Sebastian Pipping ", + .version = "1.0", + .about = N_("PortAudio capture plugin"), + .help = N_("Use this plugin to capture PCM data from the PortAudio record device"), + .license = VISUAL_PLUGIN_LICENSE_LGPL, + + .init = inp_portaudio_init, + .cleanup = inp_portaudio_cleanup, + + .plugin = &input + }; + + return &info; +} + +int inp_portaudio_init (VisPluginData *plugin) +{ +#if ENABLE_NLS + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); +#endif + + PortAudioPrivate * const priv = visual_mem_new0 (PortAudioPrivate, 1); + visual_return_val_if_fail (priv != NULL, FALSE); + visual_plugin_set_private (plugin, priv); + + // Init PortAudio + const PaError init_error = Pa_Initialize(); + visual_return_val_if_fail (init_error == paNoError, FALSE); + + // Select input device + const PaDeviceIndex input_device = Pa_GetDefaultInputDevice (); + visual_return_val_if_fail (input_device != paNoDevice, FALSE); + + // Open stream + const double latency_seconds = 1.0 / SAMPLE_RATE * FRAMES; + const PaStreamParameters input_parameters = { + .device = input_device, + .channelCount = CHANNELS, + .sampleFormat = SAMPLE_FORMAT_PA, + .suggestedLatency = latency_seconds, + .hostApiSpecificStreamInfo = NULL}; + const PaError open_error = + Pa_OpenStream (&priv->stream, &input_parameters, NULL, + SAMPLE_RATE, FRAMES, paClipOff, NULL, NULL); + visual_return_val_if_fail (open_error == paNoError, FALSE); + + // Allocate buffer + const visual_size_t buffer_size_bytes = FRAMES * CHANNELS * (SAMPLE_FORMAT_BITS / 8); + priv->buffer = malloc (buffer_size_bytes); + visual_return_val_if_fail (priv->buffer != NULL, FALSE); + + // Start stream + const PaError input_start_error = Pa_StartStream (priv->stream); + visual_return_val_if_fail (input_start_error == paNoError, FALSE); + + return TRUE; +} + +void inp_portaudio_cleanup (VisPluginData *plugin) +{ + visual_return_if_fail (plugin != NULL); + + PortAudioPrivate * const priv = visual_plugin_get_private (plugin); + visual_return_if_fail (priv != NULL); + + free (priv->buffer); + priv->buffer = NULL; + + Pa_StopStream (priv->stream); + Pa_CloseStream (priv->stream); + priv->stream = NULL; + + Pa_Terminate (); + + visual_mem_free (priv); +} + +int inp_portaudio_upload (VisPluginData *plugin, VisAudio *audio) +{ + visual_return_val_if_fail (plugin != NULL, FALSE); + visual_return_val_if_fail (audio != NULL, FALSE); + + PortAudioPrivate * const priv = visual_plugin_get_private (plugin); + visual_return_val_if_fail (priv != NULL, FALSE); + visual_return_val_if_fail (priv->stream != NULL, FALSE); + + for (;;) { + long frames_to_read = Pa_GetStreamReadAvailable (priv->stream); + if (frames_to_read < 1) { + return TRUE; + } + if (frames_to_read > FRAMES) { + frames_to_read = FRAMES; // because that's all that priv->buffer has space for + } + + const PaError read_error = Pa_ReadStream(priv->stream, priv->buffer, frames_to_read); + if (read_error != paNoError) { + return TRUE; + } + + const visual_size_t bytes_to_write = frames_to_read * CHANNELS * (SAMPLE_FORMAT_BITS / 8); + + VisBuffer * const buffer = visual_buffer_new_wrap_data (priv->buffer, bytes_to_write, FALSE); + visual_return_val_if_fail (priv != NULL, FALSE); + + visual_audio_input(audio, buffer, SAMPLE_RATE_TYPE_LV, SAMPLE_FORMAT_LV, CHANNELS_TYPE); + + visual_buffer_unref (buffer); + } + + // we never get here +} diff --git a/libvisual-plugins/po/POTFILES.in b/libvisual-plugins/po/POTFILES.in index 8a41e4bc0..c8f21b236 100644 --- a/libvisual-plugins/po/POTFILES.in +++ b/libvisual-plugins/po/POTFILES.in @@ -28,6 +28,7 @@ plugins/input/alsa/input_alsa.c plugins/input/debug/input_debug.c plugins/input/jack/input_jack.cpp plugins/input/mplayer/input_mplayer.c +plugins/input/portaudio/input_portaudio.c plugins/input/pulseaudio/input_pulseaudio.c plugins/input/wavein/input_wavein.cpp plugins/morph/alphablend/morph_alphablend.c