Browse Source

plugins: Add oss-audio plugin

This implements OSS audio input capturing support for OSS-capable OSes.
FreeBSD and DragonFly (not yet tested on) supports are added as a
starting point.
Ka Ho Ng 5 năm trước cách đây
mục cha
commit
a5a8cc261c

+ 33 - 0
cmake/Modules/FindOSS.cmake

@@ -0,0 +1,33 @@
+# Try to find OSS on a *nix system
+#
+#   OSS_FOUND        - True if OSS is available
+#   OSS_INCLUDE_DIR  - Include directory of OSS header
+#   OSS_HEADER_NAME  - OSS header file name
+#
+
+IF (CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
+        set(OSS_HEADER_NAME "sys/soundcard.h")
+ELSEIF (CMAKE_SYSTEM_NAME MATCHES "DragonFly")
+        set(OSS_HEADER_NAME "sys/soundcard.h")
+ENDIF()
+
+find_path(OSS_INCLUDE_DIR "${OSS_HEADER_NAME}"
+          "/usr/include"
+          "/usr/local/include"
+)
+
+if (OSS_INCLUDE_DIR)
+        set(OSS_FOUND True)
+else (OSS_INCLUDE_DIR)
+        set(OSS_FOUND)
+endif (OSS_INCLUDE_DIR)
+
+if (OSS_FOUND)
+        message(STATUS "Found OSS header: ${OSS_INCLUDE_DIR}/${OSS_HEADER_NAME}")
+else (OSS_FOUND)
+        if (OSS_FIND_REQUIRED)
+                message(FATAL_ERROR "Could not find OSS header file")
+        endif (OSS_FIND_REQUIRED)
+endif (OSS_FOUND)
+
+mark_as_advanced(OSS_FOUND OSS_INCLUDE_DIR OSS_HEADER_NAME)

+ 1 - 0
plugins/CMakeLists.txt

@@ -48,6 +48,7 @@ elseif("${CMAKE_SYSTEM_NAME}" MATCHES "FreeBSD")
 	add_subdirectory(linux-jack)
 	add_subdirectory(linux-alsa)
 	add_subdirectory(vlc-video)
+	add_subdirectory(oss-audio)
 endif()
 
 option(BUILD_BROWSER "Build browser plugin" OFF)

+ 36 - 0
plugins/oss-audio/CMakeLists.txt

@@ -0,0 +1,36 @@
+project(oss-audio)
+
+if(DISABLE_OSS)
+	message(STATUS "OSS support disabled")
+	return()
+endif()
+
+find_package(OSS)
+if(NOT OSS_FOUND AND ENABLE_OSS)
+	message(FATAL_ERROR "OSS not found but set as enabled")
+elseif(NOT OSS_FOUND)
+	message(STATUS "OSS not found, disabling OSS plugin")
+	return()
+endif()
+
+configure_file(oss-platform.h.in oss-platform.h)
+
+include_directories(
+	SYSTEM "${CMAKE_SOURCE_DIR}/libobs"
+	"${OSS_INCLUDE_DIR}"
+	"${CMAKE_CURRENT_BINARY_DIR}"
+)
+
+set(oss-audio_SOURCES
+	oss-audio.c
+	oss-input.c
+)
+
+add_library(oss-audio MODULE
+	${oss-audio_SOURCES}
+)
+target_link_libraries(oss-audio
+	libobs
+)
+
+install_obs_plugin(oss-audio)

+ 33 - 0
plugins/oss-audio/oss-audio.c

@@ -0,0 +1,33 @@
+/*
+Copyright (C) 2020. Ka Ho Ng <[email protected]>
+
+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("oss-audio", "en-US")
+
+MODULE_EXPORT const char *obs_module_description(void)
+{
+	return "OSS audio input capture";
+}
+
+extern struct obs_source_info oss_input_capture;
+
+bool obs_module_load(void)
+{
+	obs_register_source(&oss_input_capture);
+	return true;
+}

+ 676 - 0
plugins/oss-audio/oss-input.c

@@ -0,0 +1,676 @@
+/*
+Copyright (C) 2020. Ka Ho Ng <[email protected]>
+Copyright (C) 2020. Ed Maste <[email protected]>
+
+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 <util/bmem.h>
+#include <util/platform.h>
+#include <util/threading.h>
+#include <obs-module.h>
+
+#include <poll.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include "oss-platform.h"
+
+#define blog(level, msg, ...) blog(level, "oss-audio: " msg, ##__VA_ARGS__)
+
+#define NSEC_PER_SEC 1000000000LL
+
+#define OSS_MAX_CHANNELS 8
+
+#define OSS_DSP_DEFAULT "/dev/dsp"
+#define OSS_SNDSTAT_PATH "/dev/sndstat"
+#define OSS_RATE_DEFAULT 48000
+#define OSS_CHANNELS_DEFAULT 2
+
+/**
+ * Control block of plugin instance
+ */
+struct oss_input_data {
+	obs_source_t *source;
+
+	char *device;
+	int channels;
+	int rate;
+	int sample_fmt;
+
+	pthread_t reader_thr;
+	int notify_pipe[2];
+
+	int dsp_fd;
+	void *dsp_buf;
+	size_t dsp_fragsize;
+};
+#define OBS_PROPS_DSP "dsp"
+#define OBS_PROPS_CUSTOM_DSP "custom_dsp"
+#define OBS_PATH_DSP_CUSTOM "/"
+#define OBS_PROPS_CHANNELS "channels"
+#define OBS_PROPS_RATE "rate"
+#define OBS_PROPS_SAMPLE_FMT "sample_fmt"
+
+/**
+ * Common sampling rate table
+ */
+struct rate_option {
+	int rate;
+	char *desc;
+} rate_table[] = {
+	{8000, "8000 Hz"},     {11025, "11025 Hz"}, {16000, "16000 Hz"},
+	{22050, "22050 Hz"},   {32000, "32000 Hz"}, {44100, "44100 Hz"},
+	{48000, "48000 Hz"},   {96000, "96000 Hz"}, {192000, "192000 Hz"},
+	{384000, "384000 Hz"},
+};
+
+static unsigned int oss_sample_size(unsigned int sample_fmt)
+{
+	switch (sample_fmt) {
+	case AFMT_U8:
+	case AFMT_S8:
+		return 8;
+	case AFMT_S16_LE:
+	case AFMT_S16_BE:
+	case AFMT_U16_LE:
+	case AFMT_U16_BE:
+		return 16;
+	case AFMT_S32_LE:
+	case AFMT_S32_BE:
+	case AFMT_U32_LE:
+	case AFMT_U32_BE:
+	case AFMT_S24_LE:
+	case AFMT_S24_BE:
+	case AFMT_U24_LE:
+	case AFMT_U24_BE:
+		return 32;
+	}
+	return 0;
+}
+
+static size_t oss_calc_framesize(unsigned int channels, unsigned int sample_fmt)
+{
+	return oss_sample_size(sample_fmt) * channels / 8;
+}
+
+static enum audio_format oss_fmt_to_obs_audio_format(int fmt)
+{
+	switch (fmt) {
+	case AFMT_U8:
+		return AUDIO_FORMAT_U8BIT;
+	case AFMT_S16_LE:
+		return AUDIO_FORMAT_16BIT;
+	case AFMT_S32_LE:
+		return AUDIO_FORMAT_32BIT;
+	}
+
+	return AUDIO_FORMAT_UNKNOWN;
+}
+
+static enum speaker_layout oss_channels_to_obs_speakers(unsigned int channels)
+{
+	switch (channels) {
+	case 1:
+		return SPEAKERS_MONO;
+	case 2:
+		return SPEAKERS_STEREO;
+	case 3:
+		return SPEAKERS_2POINT1;
+	case 4:
+		return SPEAKERS_4POINT0;
+	case 5:
+		return SPEAKERS_4POINT1;
+	case 6:
+		return SPEAKERS_5POINT1;
+	case 8:
+		return SPEAKERS_7POINT1;
+	}
+
+	return SPEAKERS_UNKNOWN;
+}
+
+static int oss_setup_device(struct oss_input_data *handle)
+{
+	size_t dsp_fragsize;
+	void *dsp_buf = NULL;
+	int fd = -1, err;
+	audio_buf_info bi;
+
+	fd = open(handle->device, O_RDONLY);
+	if (fd < 0) {
+		blog(LOG_ERROR, "Failed to open device '%s'.", handle->device);
+		return -1;
+	}
+	int val = handle->channels;
+	err = ioctl(fd, SNDCTL_DSP_CHANNELS, &val);
+	if (err) {
+		blog(LOG_ERROR, "Failed to set number of channels on DSP '%s'.",
+		     handle->device);
+		goto failed_state;
+	}
+	val = handle->sample_fmt;
+	err = ioctl(fd, SNDCTL_DSP_SETFMT, &val);
+	if (err) {
+		blog(LOG_ERROR, "Failed to set format on DSP '%s'.",
+		     handle->device);
+		goto failed_state;
+	}
+	val = handle->rate;
+	err = ioctl(fd, SNDCTL_DSP_SPEED, &val);
+	if (err) {
+		blog(LOG_ERROR, "Failed to set sample rate on DSP '%s'.",
+		     handle->device);
+		goto failed_state;
+	}
+	err = ioctl(fd, SNDCTL_DSP_GETISPACE, &bi);
+	if (err) {
+		blog(LOG_ERROR, "Failed to get fragment size on DSP '%s'.",
+		     handle->device);
+		goto failed_state;
+	}
+
+	dsp_fragsize = bi.fragsize;
+	dsp_buf = bmalloc(dsp_fragsize);
+	if (dsp_buf == NULL)
+		goto failed_state;
+
+	handle->dsp_buf = dsp_buf;
+	handle->dsp_fragsize = dsp_fragsize;
+	handle->dsp_fd = fd;
+	return 0;
+
+failed_state:
+	if (fd != -1)
+		close(fd);
+	bfree(dsp_buf);
+	return -1;
+}
+
+static void oss_close_device(struct oss_input_data *handle)
+{
+	if (handle->dsp_fd != -1)
+		close(handle->dsp_fd);
+	bfree(handle->dsp_buf);
+	handle->dsp_fd = -1;
+	handle->dsp_buf = NULL;
+	handle->dsp_fragsize = 0;
+}
+
+static void *oss_reader_thr(void *vptr)
+{
+	struct oss_input_data *handle = vptr;
+	struct pollfd fds[2] = {0};
+	size_t framesize;
+
+	framesize = oss_calc_framesize(handle->channels, handle->sample_fmt);
+	fds[0].fd = handle->dsp_fd;
+	fds[0].events = POLLIN;
+	fds[1].fd = handle->notify_pipe[0];
+	fds[1].events = POLLIN;
+
+	assert(handle->dsp_buf);
+
+	while (poll(fds, 2, INFTIM) >= 0) {
+		if (fds[0].revents & POLLIN) {
+			/*
+			 * Incoming audio frames
+			 */
+
+			struct obs_source_audio out;
+			ssize_t nbytes;
+
+			do {
+				nbytes = read(handle->dsp_fd, handle->dsp_buf,
+					      handle->dsp_fragsize);
+			} while (nbytes < 0 && errno == EINTR);
+
+			if (nbytes < 0) {
+				blog(LOG_ERROR,
+				     "%s: Failed to read buffer on DSP '%s'. Errno %d",
+				     __func__, handle->device, errno);
+				break;
+			} else if (!nbytes) {
+				blog(LOG_ERROR,
+				     "%s: Unexpected EOF on DSP '%s'.",
+				     __func__, handle->device);
+				break;
+			}
+
+			out.data[0] = handle->dsp_buf;
+			out.format =
+				oss_fmt_to_obs_audio_format(handle->sample_fmt);
+			out.speakers =
+				oss_channels_to_obs_speakers(handle->channels);
+			out.samples_per_sec = handle->rate;
+			out.frames = nbytes / framesize;
+			out.timestamp =
+				os_gettime_ns() -
+				((out.frames * NSEC_PER_SEC) / handle->rate);
+			obs_source_output_audio(handle->source, &out);
+		}
+		if (fds[1].revents & POLLIN) {
+			char buf;
+			ssize_t nbytes;
+
+			do {
+				nbytes = read(handle->notify_pipe[0], &buf, 1);
+				assert(nbytes != 0);
+			} while (nbytes < 0 && errno == EINTR);
+
+			break;
+		}
+	}
+
+	return NULL;
+}
+
+static int oss_start_reader(struct oss_input_data *handle)
+{
+	int pfd[2];
+	int err;
+	pthread_t thr;
+
+	err = pipe(pfd);
+	if (err)
+		return -1;
+
+	err = pthread_create(&thr, NULL, oss_reader_thr, handle);
+	if (err) {
+		close(pfd[0]);
+		close(pfd[1]);
+		return -1;
+	}
+
+	handle->notify_pipe[0] = pfd[0];
+	handle->notify_pipe[1] = pfd[1];
+	handle->reader_thr = thr;
+	return 0;
+}
+
+static void oss_stop_reader(struct oss_input_data *handle)
+{
+	if (handle->reader_thr) {
+		char buf = 0x0;
+
+		write(handle->notify_pipe[1], &buf, 1);
+		pthread_join(handle->reader_thr, NULL);
+	}
+	if (handle->notify_pipe[0] != -1) {
+		close(handle->notify_pipe[0]);
+		close(handle->notify_pipe[1]);
+	}
+	handle->notify_pipe[0] = -1;
+	handle->notify_pipe[1] = -1;
+	handle->reader_thr = NULL;
+}
+
+/**
+ * Returns the name of the plugin
+ */
+static const char *oss_getname(void *unused)
+{
+	UNUSED_PARAMETER(unused);
+	return obs_module_text("OSS Input");
+}
+
+/**
+ * Create the plugin object
+ */
+static void *oss_create(obs_data_t *settings, obs_source_t *source)
+{
+	const char *dsp;
+	const char *custom_dsp;
+	struct oss_input_data *handle;
+
+	dsp = obs_data_get_string(settings, OBS_PROPS_DSP);
+	custom_dsp = obs_data_get_string(settings, OBS_PROPS_CUSTOM_DSP);
+	handle = bmalloc(sizeof(struct oss_input_data));
+	if (handle == NULL)
+		return NULL;
+
+	handle->source = source;
+	handle->device = NULL;
+	handle->channels = 0;
+	handle->rate = 0;
+	handle->sample_fmt = 0;
+
+	handle->dsp_buf = NULL;
+	handle->dsp_fragsize = 0;
+	handle->dsp_fd = -1;
+
+	handle->notify_pipe[0] = -1;
+	handle->notify_pipe[1] = -1;
+	handle->reader_thr = NULL;
+
+	if (dsp == NULL)
+		return handle;
+	if (!strcmp(dsp, OBS_PATH_DSP_CUSTOM)) {
+		if (custom_dsp == NULL)
+			return handle;
+
+		handle->device = bstrdup(custom_dsp);
+	} else
+		handle->device = bstrdup(dsp);
+	if (handle->device == NULL)
+		goto failed_state;
+
+	handle->channels = obs_data_get_int(settings, OBS_PROPS_CHANNELS);
+	handle->rate = obs_data_get_int(settings, OBS_PROPS_RATE);
+	handle->sample_fmt = obs_data_get_int(settings, OBS_PROPS_SAMPLE_FMT);
+
+	int err = oss_setup_device(handle);
+	if (err)
+		goto failed_state;
+	err = oss_start_reader(handle);
+	if (err) {
+		oss_close_device(handle);
+		goto failed_state;
+	}
+
+	return handle;
+
+failed_state:
+	bfree(handle);
+	return NULL;
+}
+
+/**
+ * Destroy the plugin object and free all memory
+ */
+static void oss_destroy(void *vptr)
+{
+	struct oss_input_data *handle = vptr;
+
+	oss_stop_reader(handle);
+	oss_close_device(handle);
+	bfree(handle->device);
+	bfree(handle);
+}
+
+/**
+ * Update the input settings
+ */
+static void oss_update(void *vptr, obs_data_t *settings)
+{
+	struct oss_input_data *handle = vptr;
+
+	oss_stop_reader(handle);
+	oss_close_device(handle);
+
+	const char *dsp = obs_data_get_string(settings, OBS_PROPS_DSP);
+	const char *custom_dsp =
+		obs_data_get_string(settings, OBS_PROPS_CUSTOM_DSP);
+	if (dsp == NULL) {
+		bfree(handle->device);
+		handle->device = NULL;
+		return;
+	}
+
+	bfree(handle->device);
+	handle->device = NULL;
+	if (!strcmp(dsp, OBS_PATH_DSP_CUSTOM)) {
+		if (custom_dsp == NULL)
+			return;
+		handle->device = bstrdup(custom_dsp);
+	} else
+		handle->device = bstrdup(dsp);
+	if (handle->device == NULL)
+		return;
+
+	handle->channels = obs_data_get_int(settings, OBS_PROPS_CHANNELS);
+	handle->rate = obs_data_get_int(settings, OBS_PROPS_RATE);
+	handle->sample_fmt = obs_data_get_int(settings, OBS_PROPS_SAMPLE_FMT);
+
+	int err = oss_setup_device(handle);
+	if (err) {
+		goto failed_state;
+		return;
+	}
+	err = oss_start_reader(handle);
+	if (err) {
+		oss_close_device(handle);
+		goto failed_state;
+	}
+
+	return;
+
+failed_state:
+	bfree(handle->device);
+	handle->device = NULL;
+}
+
+/**
+ * Add audio devices to property
+ */
+static void oss_prop_add_devices(obs_property_t *p)
+{
+#if defined(__FreeBSD__) || defined(__DragonFly__)
+	char *line = NULL;
+	size_t linecap = 0;
+	FILE *fp;
+
+	fp = fopen(OSS_SNDSTAT_PATH, "r");
+	if (fp == NULL) {
+		blog(LOG_ERROR, "Failed to open sndstat at '%s'.",
+		     OSS_SNDSTAT_PATH);
+		return;
+	}
+
+	while (getline(&line, &linecap, fp) > 0) {
+		int pcm;
+		char *ptr, *pdesc, *descr, *devname, *pmode;
+
+		if (sscanf(line, "pcm%i: ", &pcm) != 1)
+			continue;
+		if ((ptr = strchr(line, '<')) == NULL)
+			continue;
+		pdesc = ptr + 1;
+		if ((ptr = strrchr(pdesc, '>')) == NULL)
+			continue;
+		*ptr++ = '\0';
+		if (*ptr++ != ' ' || *ptr++ != '(')
+			continue;
+		pmode = ptr;
+		if ((ptr = strrchr(pmode, ')')) == NULL)
+			continue;
+		*ptr++ = '\0';
+		if (strcmp(pmode, "rec") != 0 && strcmp(pmode, "play/rec") != 0)
+			continue;
+		if (asprintf(&descr, "pcm%i: %s", pcm, pdesc) == -1)
+			continue;
+		if (asprintf(&devname, "/dev/dsp%i", pcm) == -1) {
+			free(descr);
+			continue;
+		}
+
+		obs_property_list_add_string(p, descr, devname);
+
+		free(descr);
+		free(devname);
+	}
+	free(line);
+
+	fclose(fp);
+#endif /* defined(__FreeBSD__) || defined(__DragonFly__) */
+}
+
+/**
+ * Get plugin defaults
+ */
+static void oss_defaults(obs_data_t *settings)
+{
+	obs_data_set_default_int(settings, OBS_PROPS_CHANNELS,
+				 OSS_CHANNELS_DEFAULT);
+	obs_data_set_default_int(settings, OBS_PROPS_RATE, OSS_RATE_DEFAULT);
+	obs_data_set_default_int(settings, OBS_PROPS_SAMPLE_FMT, AFMT_S16_LE);
+	obs_data_set_default_string(settings, OBS_PROPS_DSP, OSS_DSP_DEFAULT);
+}
+
+/**
+ * Get plugin properties:
+ *
+ * Fetch the engine information of the corresponding DSP
+ */
+static bool oss_fill_device_info(obs_property_t *rate, obs_property_t *channels,
+				 const char *device)
+{
+	oss_audioinfo ai;
+	int fd = -1;
+	int err;
+
+	obs_property_list_clear(rate);
+	obs_property_list_clear(channels);
+
+	if (!strcmp(device, OBS_PATH_DSP_CUSTOM))
+		goto cleanup;
+
+	fd = open(device, O_RDONLY);
+	if (fd < 0) {
+		blog(LOG_ERROR, "Failed to open device '%s'.", device);
+		goto cleanup;
+	}
+
+	ai.dev = -1;
+	err = ioctl(fd, SNDCTL_ENGINEINFO, &ai);
+	if (err) {
+		blog(LOG_ERROR,
+		     "Failed to issue ioctl(SNDCTL_ENGINEINFO) on device '%s'. Errno: %d",
+		     device, errno);
+		goto cleanup;
+	}
+
+	for (int i = ai.min_channels;
+	     i <= ai.max_channels && i <= OSS_MAX_CHANNELS; i++) {
+		enum speaker_layout layout = oss_channels_to_obs_speakers(i);
+
+		if (layout != SPEAKERS_UNKNOWN) {
+			char name[] = "xxx";
+			snprintf(name, sizeof(name), "%d", i);
+			obs_property_list_add_int(channels, name, i);
+		}
+	}
+
+	for (size_t i = 0; i < sizeof(rate_table) / sizeof(rate_table[0]);
+	     i++) {
+		if (ai.min_rate <= rate_table[i].rate &&
+		    ai.max_rate >= rate_table[i].rate)
+			obs_property_list_add_int(rate, rate_table[i].desc,
+						  rate_table[i].rate);
+	}
+
+cleanup:
+	if (!obs_property_list_item_count(rate))
+		obs_property_list_add_int(rate, "48000 Hz", OSS_RATE_DEFAULT);
+	if (!obs_property_list_item_count(channels))
+		obs_property_list_add_int(channels, "2", OSS_CHANNELS_DEFAULT);
+	if (fd != -1)
+		close(fd);
+	return true;
+}
+
+/**
+ * Get plugin properties
+ */
+static bool oss_on_devices_changed(obs_properties_t *props, obs_property_t *p,
+				   obs_data_t *settings)
+{
+	obs_property_t *rate, *channels;
+	obs_property_t *custom_dsp;
+	const char *device;
+
+	UNUSED_PARAMETER(p);
+
+	device = obs_data_get_string(settings, OBS_PROPS_DSP);
+	custom_dsp = obs_properties_get(props, OBS_PROPS_CUSTOM_DSP);
+	rate = obs_properties_get(props, OBS_PROPS_RATE);
+	channels = obs_properties_get(props, OBS_PROPS_CHANNELS);
+
+	if (!strcmp(device, OBS_PATH_DSP_CUSTOM))
+		obs_property_set_visible(custom_dsp, true);
+	else
+		obs_property_set_visible(custom_dsp, false);
+	oss_fill_device_info(rate, channels, device);
+	obs_property_modified(rate, settings);
+	obs_property_modified(channels, settings);
+
+	obs_property_modified(custom_dsp, settings);
+
+	return true;
+}
+
+/**
+ * Get plugin properties
+ */
+static obs_properties_t *oss_properties(void *unused)
+{
+	obs_properties_t *props;
+	obs_property_t *devices;
+	obs_property_t *rate;
+	obs_property_t *sample_fmt;
+	obs_property_t *channels;
+
+	UNUSED_PARAMETER(unused);
+
+	props = obs_properties_create();
+
+	devices = obs_properties_add_list(props, OBS_PROPS_DSP,
+					  obs_module_text("DSP"),
+					  OBS_COMBO_TYPE_LIST,
+					  OBS_COMBO_FORMAT_STRING);
+
+	obs_property_list_add_string(devices, "Default", OSS_DSP_DEFAULT);
+	obs_property_list_add_string(devices, "Custom", OBS_PATH_DSP_CUSTOM);
+	obs_property_set_modified_callback(devices, oss_on_devices_changed);
+
+	obs_properties_add_text(props, OBS_PROPS_CUSTOM_DSP,
+				obs_module_text("Custom DSP Path"),
+				OBS_TEXT_DEFAULT);
+
+	rate = obs_properties_add_list(props, OBS_PROPS_RATE,
+				       obs_module_text("Sample rate"),
+				       OBS_COMBO_TYPE_LIST,
+				       OBS_COMBO_FORMAT_INT);
+	channels = obs_properties_add_list(props, OBS_PROPS_CHANNELS,
+					   obs_module_text("Channels"),
+					   OBS_COMBO_TYPE_LIST,
+					   OBS_COMBO_FORMAT_INT);
+	oss_fill_device_info(rate, channels, OSS_DSP_DEFAULT);
+
+	sample_fmt = obs_properties_add_list(props, OBS_PROPS_SAMPLE_FMT,
+					     obs_module_text("Sample format"),
+					     OBS_COMBO_TYPE_LIST,
+					     OBS_COMBO_FORMAT_INT);
+
+	obs_property_list_add_int(sample_fmt, "pcm8", AFMT_U8);
+	obs_property_list_add_int(sample_fmt, "pcm16le", AFMT_S16_LE);
+	obs_property_list_add_int(sample_fmt, "pcm32le", AFMT_S32_LE);
+
+	oss_prop_add_devices(devices);
+
+	return props;
+}
+
+struct obs_source_info oss_input_capture = {
+	.id = "oss_input_capture",
+	.type = OBS_SOURCE_TYPE_INPUT,
+	.output_flags = OBS_SOURCE_AUDIO,
+	.get_name = oss_getname,
+	.create = oss_create,
+	.destroy = oss_destroy,
+	.update = oss_update,
+	.get_defaults = oss_defaults,
+	.get_properties = oss_properties,
+	.icon_type = OBS_ICON_TYPE_AUDIO_INPUT,
+};

+ 3 - 0
plugins/oss-audio/oss-platform.h.in

@@ -0,0 +1,3 @@
+#pragma once
+
+#include <@OSS_HEADER_NAME@>