diff --git a/cmake/Modules/FindJack.cmake b/cmake/Modules/FindJack.cmake new file mode 100644 index 000000000..4bdfe0a7e --- /dev/null +++ b/cmake/Modules/FindJack.cmake @@ -0,0 +1,82 @@ +# - Try to find jack-2.6 +# Once done this will define +# +# JACK_FOUND - system has jack +# JACK_INCLUDE_DIRS - the jack include directory +# JACK_LIBRARIES - Link these to use jack +# JACK_DEFINITIONS - Compiler switches required for using jack +# +# Copyright (c) 2008 Andreas Schneider +# Modified for other libraries by Lasse Kärkkäinen +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +if (JACK_LIBRARIES AND JACK_INCLUDE_DIRS) + # in cache already + set(JACK_FOUND TRUE) +else (JACK_LIBRARIES AND JACK_INCLUDE_DIRS) + # use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + if (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4) + include(UsePkgConfig) + pkgconfig(jack _JACK_INCLUDEDIR _JACK_LIBDIR _JACK_LDFLAGS _JACK_CFLAGS) + else (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4) + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(_JACK jack) + endif (PKG_CONFIG_FOUND) + endif (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4) + find_path(JACK_INCLUDE_DIR + NAMES + jack/jack.h + PATHS + ${_JACK_INCLUDEDIR} + /usr/include + /usr/local/include + /opt/local/include + /sw/include + ) + + find_library(JACK_LIBRARY + NAMES + jack + PATHS + ${_JACK_LIBDIR} + /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib + ) + + if (JACK_LIBRARY AND JACK_INCLUDE_DIR) + set(JACK_FOUND TRUE) + + set(JACK_INCLUDE_DIRS + ${JACK_INCLUDE_DIR} + ) + + set(JACK_LIBRARIES + ${JACK_LIBRARIES} + ${JACK_LIBRARY} + ) + + endif (JACK_LIBRARY AND JACK_INCLUDE_DIR) + + if (JACK_FOUND) + if (NOT JACK_FIND_QUIETLY) + message(STATUS "Found jack: ${JACK_LIBRARY}") + endif (NOT JACK_FIND_QUIETLY) + else (JACK_FOUND) + if (JACK_FIND_REQUIRED) + message(FATAL_ERROR "Could not find JACK") + endif (JACK_FIND_REQUIRED) + endif (JACK_FOUND) + + # show the JACK_INCLUDE_DIRS and JACK_LIBRARIES variables only in the advanced view + mark_as_advanced(JACK_INCLUDE_DIRS JACK_LIBRARIES) + +endif (JACK_LIBRARIES AND JACK_INCLUDE_DIRS) + diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 010871eae..66a45ca0c 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -11,6 +11,7 @@ elseif("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") add_subdirectory(linux-capture) add_subdirectory(linux-pulseaudio) add_subdirectory(linux-v4l2) + add_subdirectory(linux-jack) endif() add_subdirectory(image-source) diff --git a/plugins/linux-jack/CMakeLists.txt b/plugins/linux-jack/CMakeLists.txt new file mode 100644 index 000000000..4ac17cbf9 --- /dev/null +++ b/plugins/linux-jack/CMakeLists.txt @@ -0,0 +1,32 @@ +project(linux-jack) + +if(DISABLE_JACK) + message(STATUS "JACK support disabled") + return() +endif() + +find_package(Jack) +if(NOT JACK_FOUND AND ENABLE_JACK) + message(FATAL_ERROR "JACK Audio Connection Kit not found but set as enabled") +elseif(NOT JACK_FOUND) + message(STATUS "JACK Audio Connection Kit not found, disabling JACK plugin") + return() +endif() + +include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/libobs") + +set(linux-jack_SOURCES + linux-jack.c + jack-wrapper.c + jack-input.c +) + +add_library(linux-jack MODULE + ${linux-jack_SOURCES} +) +target_link_libraries(linux-jack + libobs + ${JACK_LIBRARIES} +) + +install_obs_plugin_with_data(linux-jack data) diff --git a/plugins/linux-jack/data/locale/en-US.ini b/plugins/linux-jack/data/locale/en-US.ini new file mode 100644 index 000000000..5fc1db6a3 --- /dev/null +++ b/plugins/linux-jack/data/locale/en-US.ini @@ -0,0 +1,3 @@ +StartJACKServer="Start JACK Server" +Channels="Number of Channels" +JACKInput="JACK Input Client" diff --git a/plugins/linux-jack/jack-input.c b/plugins/linux-jack/jack-input.c new file mode 100644 index 000000000..6ceece25a --- /dev/null +++ b/plugins/linux-jack/jack-input.c @@ -0,0 +1,149 @@ +/* +Copyright (C) 2015 by Bernd Buschinski + +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 2 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 "jack-wrapper.h" + +#include + +/** + * Returns the name of the plugin + */ +static const char *jack_input_getname(void) +{ + return obs_module_text("JACKInput"); +} + +/** + * Destroy the plugin object and free all memory + */ +static void jack_destroy(void *vptr) +{ + struct jack_data* data = (struct jack_data*)vptr; + + if (!data) + return; + + deactivate_jack(data); + + if (data->device) + bfree(data->device); + pthread_mutex_destroy(&data->jack_mutex); + bfree(data); +} + +/** + * Update the input settings + */ +static void jack_update(void *vptr, obs_data_t *settings) +{ + struct jack_data* data = (struct jack_data*)vptr; + if (!data) + return; + + const char *new_device; + bool settings_changed = false; + bool new_jack_start_server = obs_data_get_bool(settings, "startjack"); + int new_channel_count = obs_data_get_int(settings, "channels"); + + if (new_jack_start_server != data->start_jack_server) { + data->start_jack_server = new_jack_start_server; + settings_changed = true; + } + + if (new_channel_count != data->channels) + /* + * keep "old" channel count for now, + * we need to destroy the correct number of channels + */ + settings_changed = true; + + new_device = obs_source_get_name(data->source); + if (!data->device || strcmp(data->device, new_device) != 0) { + if (data->device) + bfree(data->device); + data->device = bstrdup(new_device); + settings_changed = true; + } + + if (settings_changed) { + deactivate_jack(data); + + data->channels = new_channel_count; + + if (jack_init(data) != 0) { + deactivate_jack(data); + } + } +} + +/** + * Create the plugin object + */ +static void *jack_create(obs_data_t *settings, obs_source_t *source) +{ + struct jack_data *data = bzalloc(sizeof(struct jack_data)); + + pthread_mutex_init(&data->jack_mutex, NULL); + data->source = source; + data->channels = -1; + + jack_update(data, settings); + + if (data->jack_client == NULL) { + jack_destroy(data); + return NULL; + } + return data; +} + +/** + * Get plugin defaults + */ +static void jack_input_defaults(obs_data_t *settings) +{ + obs_data_set_default_int(settings, "channels", 2); + obs_data_set_default_bool(settings, "startjack", false); +} + +/** + * Get plugin properties + */ +static obs_properties_t *jack_input_properties(void *unused) +{ + (void)unused; + + obs_properties_t *props = obs_properties_create(); + + obs_properties_add_int(props, "channels", + obs_module_text("Channels"), 1, 8, 1); + obs_properties_add_bool(props, "startjack", + obs_module_text("StartJACKServer")); + + return props; +} + +struct obs_source_info jack_output_capture = { + .id = "jack_output_capture", + .type = OBS_SOURCE_TYPE_INPUT, + .output_flags = OBS_SOURCE_AUDIO, + .get_name = jack_input_getname, + .create = jack_create, + .destroy = jack_destroy, + .update = jack_update, + .get_defaults = jack_input_defaults, + .get_properties = jack_input_properties +}; diff --git a/plugins/linux-jack/jack-wrapper.c b/plugins/linux-jack/jack-wrapper.c new file mode 100644 index 000000000..f55d1ea20 --- /dev/null +++ b/plugins/linux-jack/jack-wrapper.c @@ -0,0 +1,160 @@ +/* +Copyright (C) 2015 by Bernd Buschinski + +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 2 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 "jack-wrapper.h" + +#include +#include + +#include + +#define blog(level, msg, ...) blog(level, "jack-input: " msg, ##__VA_ARGS__) + +/** + * Get obs speaker layout from number of channels + * + * @param channels number of channels reported by jack + * + * @return obs speaker_layout id + * + * @note This *might* not work for some rather unusual setups, but should work + * fine for the majority of cases. + */ +static enum speaker_layout jack_channels_to_obs_speakers(uint_fast32_t channels) +{ + switch(channels) { + case 1: return SPEAKERS_MONO; + case 2: return SPEAKERS_STEREO; + case 3: return SPEAKERS_2POINT1; + case 4: return SPEAKERS_SURROUND; + case 5: return SPEAKERS_4POINT1; + case 6: return SPEAKERS_5POINT1; + /* What should we do with 7 channels? */ + /* case 7: return SPEAKERS_...; */ + case 8: return SPEAKERS_7POINT1; + } + + return SPEAKERS_UNKNOWN; +} + +int jack_process_callback(jack_nframes_t nframes, void* arg) +{ + struct jack_data* data = (struct jack_data*)arg; + if (data == 0) + return 0; + + pthread_mutex_lock(&data->jack_mutex); + + struct obs_source_audio out; + out.speakers = jack_channels_to_obs_speakers(data->channels); + out.samples_per_sec = jack_get_sample_rate (data->jack_client); + /* format is always 32 bit float for jack */ + out.format = AUDIO_FORMAT_FLOAT_PLANAR; + + for (unsigned int i = 0; i < data->channels; ++i) { + jack_default_audio_sample_t *jack_buffer = + (jack_default_audio_sample_t *)jack_port_get_buffer( + data->jack_ports[i], nframes); + out.data[i] = (uint8_t *)jack_buffer; + } + + out.frames = nframes; + out.timestamp = os_gettime_ns() - + jack_frames_to_time(data->jack_client, nframes); + + obs_source_output_audio(data->source, &out); + pthread_mutex_unlock(&data->jack_mutex); + return 0; +} + +int_fast32_t jack_init(struct jack_data* data) +{ + pthread_mutex_lock(&data->jack_mutex); + + if (data->jack_client != NULL) + goto good; + + jack_options_t jack_option = data->start_jack_server ? + JackNullOption : JackNoStartServer; + + data->jack_client = jack_client_open(data->device, jack_option, 0); + if (data->jack_client == NULL) { + blog(LOG_ERROR, + "jack_client_open Error:" + "Could not create JACK client! %s", + data->device); + goto error; + } + + data->jack_ports = (jack_port_t**)bzalloc( + sizeof(jack_port_t*) * data->channels); + for (unsigned int i = 0; i < data->channels; ++i) { + char port_name[10] = {'\0'}; + snprintf(port_name, sizeof(port_name), "in_%d", i+1); + + data->jack_ports[i] = jack_port_register(data->jack_client, + port_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); + if (data->jack_ports[i] == NULL) { + blog(LOG_ERROR, + "jack_port_register Error:" + "Could not create JACK port! %s", + port_name); + goto error; + } + } + + if (jack_set_process_callback(data->jack_client, + jack_process_callback, data) != 0) { + blog(LOG_ERROR, "jack_set_process_callback Error"); + goto error; + } + + if (jack_activate(data->jack_client) != 0) { + blog(LOG_ERROR, + "jack_activate Error:" + "Could not activate JACK client!"); + goto error; + } + +good: + pthread_mutex_unlock(&data->jack_mutex); + return 0; + +error: + pthread_mutex_unlock(&data->jack_mutex); + return 1; +} + +void deactivate_jack(struct jack_data* data) +{ + pthread_mutex_lock(&data->jack_mutex); + + if (data->jack_client) { + if (data->jack_ports != NULL) { + for (int i = 0; i < data->channels; ++i) { + if (data->jack_ports[i] != NULL) + jack_port_unregister(data->jack_client, + data->jack_ports[i]); + } + bfree(data->jack_ports); + data->jack_ports = NULL; + } + jack_client_close(data->jack_client); + data->jack_client = NULL; + } + pthread_mutex_unlock(&data->jack_mutex); +} diff --git a/plugins/linux-jack/jack-wrapper.h b/plugins/linux-jack/jack-wrapper.h new file mode 100644 index 000000000..85202fff8 --- /dev/null +++ b/plugins/linux-jack/jack-wrapper.h @@ -0,0 +1,51 @@ +/* +Copyright (C) 2015 by Bernd Buschinski + +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 2 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 . +*/ + +#pragma once + +#include +#include +#include + +struct jack_data { + obs_source_t *source; + + /* user settings */ + char *device; + uint_fast8_t channels; + bool start_jack_server; + + /* server info */ + enum speaker_layout speakers; + uint_fast32_t samples_per_sec; + uint_fast32_t bytes_per_frame; + + jack_client_t *jack_client; + jack_port_t **jack_ports; + + pthread_mutex_t jack_mutex; +}; + +/** + * Initialize the jack client and register the ports + */ +int_fast32_t jack_init(struct jack_data* data); + +/** + * Destroys the jack client and unregisters the ports + */ +void deactivate_jack(struct jack_data* data); diff --git a/plugins/linux-jack/linux-jack.c b/plugins/linux-jack/linux-jack.c new file mode 100644 index 000000000..349a20584 --- /dev/null +++ b/plugins/linux-jack/linux-jack.c @@ -0,0 +1,28 @@ +/* +Copyright (C) 2015 by Bernd Buschinski + +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 2 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 + +OBS_DECLARE_MODULE() +OBS_MODULE_USE_DEFAULT_LOCALE("linux-jack", "en-US") + +extern struct obs_source_info jack_output_capture; + +bool obs_module_load(void) +{ + obs_register_source(&jack_output_capture); + return true; +}