From a5a8cc261c69f47472d875d6b42a60488c9c57ad Mon Sep 17 00:00:00 2001 From: Ka Ho Ng Date: Sun, 12 Apr 2020 18:28:32 +0800 Subject: [PATCH] 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. --- cmake/Modules/FindOSS.cmake | 33 ++ plugins/CMakeLists.txt | 1 + plugins/oss-audio/CMakeLists.txt | 36 ++ plugins/oss-audio/oss-audio.c | 33 ++ plugins/oss-audio/oss-input.c | 676 ++++++++++++++++++++++++++++ plugins/oss-audio/oss-platform.h.in | 3 + 6 files changed, 782 insertions(+) create mode 100644 cmake/Modules/FindOSS.cmake create mode 100644 plugins/oss-audio/CMakeLists.txt create mode 100644 plugins/oss-audio/oss-audio.c create mode 100644 plugins/oss-audio/oss-input.c create mode 100644 plugins/oss-audio/oss-platform.h.in diff --git a/cmake/Modules/FindOSS.cmake b/cmake/Modules/FindOSS.cmake new file mode 100644 index 000000000..9717ce2e7 --- /dev/null +++ b/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) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 3ca41ec12..151f50652 100644 --- a/plugins/CMakeLists.txt +++ b/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) diff --git a/plugins/oss-audio/CMakeLists.txt b/plugins/oss-audio/CMakeLists.txt new file mode 100644 index 000000000..1348fcde7 --- /dev/null +++ b/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) diff --git a/plugins/oss-audio/oss-audio.c b/plugins/oss-audio/oss-audio.c new file mode 100644 index 000000000..9db61ca2e --- /dev/null +++ b/plugins/oss-audio/oss-audio.c @@ -0,0 +1,33 @@ +/* +Copyright (C) 2020. Ka Ho Ng + +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("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; +} diff --git a/plugins/oss-audio/oss-input.c b/plugins/oss-audio/oss-input.c new file mode 100644 index 000000000..95371a34e --- /dev/null +++ b/plugins/oss-audio/oss-input.c @@ -0,0 +1,676 @@ +/* +Copyright (C) 2020. Ka Ho Ng +Copyright (C) 2020. Ed Maste + +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 +#include +#include +#include + +#include +#include +#include +#include +#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, +}; diff --git a/plugins/oss-audio/oss-platform.h.in b/plugins/oss-audio/oss-platform.h.in new file mode 100644 index 000000000..4329e8149 --- /dev/null +++ b/plugins/oss-audio/oss-platform.h.in @@ -0,0 +1,3 @@ +#pragma once + +#include <@OSS_HEADER_NAME@>