Initial obs-studio jack support

This commit is contained in:
Bernd Buschinski 2015-01-17 00:01:50 +01:00
parent aabb911e9f
commit e04dc57e8a
8 changed files with 506 additions and 0 deletions

View File

@ -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 <mail@cynapses.org>
# Modified for other libraries by Lasse Kärkkäinen <tronic>
#
# 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)

View File

@ -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)

View File

@ -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)

View File

@ -0,0 +1,3 @@
StartJACKServer="Start JACK Server"
Channels="Number of Channels"
JACKInput="JACK Input Client"

View File

@ -0,0 +1,149 @@
/*
Copyright (C) 2015 by Bernd Buschinski <b.buschinski@gmail.com>
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 <http://www.gnu.org/licenses/>.
*/
#include "jack-wrapper.h"
#include <obs-module.h>
/**
* 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
};

View File

@ -0,0 +1,160 @@
/*
Copyright (C) 2015 by Bernd Buschinski <b.buschinski@gmail.com>
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 <http://www.gnu.org/licenses/>.
*/
#include "jack-wrapper.h"
#include <pthread.h>
#include <stdio.h>
#include <util/platform.h>
#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);
}

View File

@ -0,0 +1,51 @@
/*
Copyright (C) 2015 by Bernd Buschinski <b.buschinski@gmail.com>
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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <jack/jack.h>
#include <obs.h>
#include <pthread.h>
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);

View File

@ -0,0 +1,28 @@
/*
Copyright (C) 2015 by Bernd Buschinski <b.buschinski@gmail.com>
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 <http://www.gnu.org/licenses/>.
*/
#include <obs-module.h>
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;
}