jp9000 f53df7da64 clang-format: Apply formatting
Code submissions have continually suffered from formatting
inconsistencies that constantly have to be addressed.  Using
clang-format simplifies this by making code formatting more consistent,
and allows automation of the code formatting so that maintainers can
focus more on the code itself instead of code formatting.
2019-06-23 23:49:10 -07:00

644 lines
14 KiB
C

/*
Copyright (C) 2015. Guillermo A. Amaral B. <g@maral.me>
Based on Pulse Input plugin by Leonhard Oelke.
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 <alsa/asoundlib.h>
#include <alsa/pcm.h>
#include <pthread.h>
#define blog(level, msg, ...) blog(level, "alsa-input: " msg, ##__VA_ARGS__)
#define NSEC_PER_SEC 1000000000LL
#define NSEC_PER_MSEC 1000000L
#define STARTUP_TIMEOUT_NS (500 * NSEC_PER_MSEC)
#define REOPEN_TIMEOUT 1000UL
#define SHUTDOWN_ON_DEACTIVATE false
struct alsa_data {
obs_source_t *source;
#if SHUTDOWN_ON_DEACTIVATE
bool active;
#endif
/* user settings */
char *device;
/* pthread */
pthread_t listen_thread;
pthread_t reopen_thread;
os_event_t *abort_event;
volatile bool listen;
volatile bool reopen;
/* alsa */
snd_pcm_t *handle;
snd_pcm_format_t format;
snd_pcm_uframes_t period_size;
unsigned int channels;
unsigned int rate;
unsigned int sample_size;
uint8_t *buffer;
uint64_t first_ts;
};
static const char *alsa_get_name(void *);
static bool alsa_devices_changed(obs_properties_t *props, obs_property_t *p,
obs_data_t *settings);
static obs_properties_t *alsa_get_properties(void *);
static void *alsa_create(obs_data_t *, obs_source_t *);
static void alsa_destroy(void *);
static void alsa_activate(void *);
static void alsa_deactivate(void *);
static void alsa_get_defaults(obs_data_t *);
static void alsa_update(void *, obs_data_t *);
struct obs_source_info alsa_input_capture = {
.id = "alsa_input_capture",
.type = OBS_SOURCE_TYPE_INPUT,
.output_flags = OBS_SOURCE_AUDIO,
.create = alsa_create,
.destroy = alsa_destroy,
#if SHUTDOWN_ON_DEACTIVATE
.activate = alsa_activate,
.deactivate = alsa_deactivate,
#endif
.update = alsa_update,
.get_defaults = alsa_get_defaults,
.get_name = alsa_get_name,
.get_properties = alsa_get_properties,
};
static bool _alsa_try_open(struct alsa_data *);
static bool _alsa_open(struct alsa_data *);
static void _alsa_close(struct alsa_data *);
static bool _alsa_configure(struct alsa_data *);
static void _alsa_start_reopen(struct alsa_data *);
static void _alsa_stop_reopen(struct alsa_data *);
static void *_alsa_listen(void *);
static void *_alsa_reopen(void *);
static enum audio_format _alsa_to_obs_audio_format(snd_pcm_format_t);
static enum speaker_layout _alsa_channels_to_obs_speakers(unsigned int);
/*****************************************************************************/
void *alsa_create(obs_data_t *settings, obs_source_t *source)
{
struct alsa_data *data = bzalloc(sizeof(struct alsa_data));
data->source = source;
#if SHUTDOWN_ON_DEACTIVATE
data->active = false;
#endif
data->buffer = NULL;
data->device = NULL;
data->first_ts = 0;
data->handle = NULL;
data->listen = false;
data->reopen = false;
data->listen_thread = 0;
data->reopen_thread = 0;
const char *device = obs_data_get_string(settings, "device_id");
if (strcmp(device, "__custom__") == 0)
device = obs_data_get_string(settings, "custom_pcm");
data->device = bstrdup(device);
data->rate = obs_data_get_int(settings, "rate");
if (os_event_init(&data->abort_event, OS_EVENT_TYPE_MANUAL) != 0) {
blog(LOG_ERROR, "Abort event creation failed!");
goto cleanup;
}
#if !SHUTDOWN_ON_DEACTIVATE
_alsa_try_open(data);
#endif
return data;
cleanup:
if (data->device)
bfree(data->device);
bfree(data);
return NULL;
}
void alsa_destroy(void *vptr)
{
struct alsa_data *data = vptr;
if (data->handle)
_alsa_close(data);
os_event_destroy(data->abort_event);
bfree(data->device);
bfree(data);
}
#if SHUTDOWN_ON_DEACTIVATE
void alsa_activate(void *vptr)
{
struct alsa_data *data = vptr;
data->active = true;
_alsa_try_open(data);
}
void alsa_deactivate(void *vptr)
{
struct alsa_data *data = vptr;
_alsa_stop_reopen(data);
_alsa_close(data);
data->active = false;
}
#endif
void alsa_update(void *vptr, obs_data_t *settings)
{
struct alsa_data *data = vptr;
const char *device;
unsigned int rate;
bool reset = false;
device = obs_data_get_string(settings, "device_id");
if (strcmp(device, "__custom__") == 0)
device = obs_data_get_string(settings, "custom_pcm");
if (strcmp(data->device, device) != 0) {
bfree(data->device);
data->device = bstrdup(device);
reset = true;
}
rate = obs_data_get_int(settings, "rate");
if (data->rate != rate) {
data->rate = rate;
reset = true;
}
#if SHUTDOWN_ON_DEACTIVATE
if (reset && data->handle)
_alsa_close(data);
if (data->active && !data->handle)
_alsa_try_open(data);
#else
if (reset) {
if (data->handle)
_alsa_close(data);
_alsa_try_open(data);
}
#endif
}
const char *alsa_get_name(void *unused)
{
UNUSED_PARAMETER(unused);
return obs_module_text("AlsaInput");
}
void alsa_get_defaults(obs_data_t *settings)
{
obs_data_set_default_string(settings, "device_id", "default");
obs_data_set_default_string(settings, "custom_pcm", "default");
obs_data_set_default_int(settings, "rate", 44100);
}
static bool alsa_devices_changed(obs_properties_t *props, obs_property_t *p,
obs_data_t *settings)
{
UNUSED_PARAMETER(p);
bool visible = false;
const char *device_id = obs_data_get_string(settings, "device_id");
if (strcmp(device_id, "__custom__") == 0)
visible = true;
obs_property_t *custom_pcm = obs_properties_get(props, "custom_pcm");
obs_property_set_visible(custom_pcm, visible);
obs_property_modified(custom_pcm, settings);
return true;
}
obs_properties_t *alsa_get_properties(void *unused)
{
void **hints;
void **hint;
char *name = NULL;
char *descr = NULL;
char *io = NULL;
char *descr_i;
obs_properties_t *props;
obs_property_t *devices;
obs_property_t *rate;
UNUSED_PARAMETER(unused);
props = obs_properties_create();
devices = obs_properties_add_list(props, "device_id",
obs_module_text("Device"),
OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_STRING);
obs_property_list_add_string(devices, "Default", "default");
obs_properties_add_text(props, "custom_pcm", obs_module_text("PCM"),
OBS_TEXT_DEFAULT);
rate = obs_properties_add_list(props, "rate", obs_module_text("Rate"),
OBS_COMBO_TYPE_LIST,
OBS_COMBO_FORMAT_INT);
obs_property_set_modified_callback(devices, alsa_devices_changed);
obs_property_list_add_int(rate, "32000 Hz", 32000);
obs_property_list_add_int(rate, "44100 Hz", 44100);
obs_property_list_add_int(rate, "48000 Hz", 48000);
if (snd_device_name_hint(-1, "pcm", &hints) < 0)
return props;
hint = hints;
while (*hint != NULL) {
/* check if we're dealing with an Input */
io = snd_device_name_get_hint(*hint, "IOID");
if (io != NULL && strcmp(io, "Input") != 0)
goto next;
name = snd_device_name_get_hint(*hint, "NAME");
if (name == NULL || strstr(name, "front:") == NULL)
goto next;
descr = snd_device_name_get_hint(*hint, "DESC");
if (!descr)
goto next;
descr_i = descr;
while (*descr_i) {
if (*descr_i == '\n') {
*descr_i = '\0';
break;
} else
++descr_i;
}
obs_property_list_add_string(devices, descr, name);
next:
if (name != NULL)
free(name), name = NULL;
if (descr != NULL)
free(descr), descr = NULL;
if (io != NULL)
free(io), io = NULL;
++hint;
}
obs_property_list_add_string(devices, "Custom", "__custom__");
snd_device_name_free_hint(hints);
return props;
}
/*****************************************************************************/
bool _alsa_try_open(struct alsa_data *data)
{
_alsa_stop_reopen(data);
if (_alsa_open(data))
return true;
_alsa_start_reopen(data);
return false;
}
bool _alsa_open(struct alsa_data *data)
{
pthread_attr_t attr;
int err;
err = snd_pcm_open(&data->handle, data->device, SND_PCM_STREAM_CAPTURE,
0);
if (err < 0) {
blog(LOG_ERROR, "Failed to open '%s': %s", data->device,
snd_strerror(err));
return false;
}
if (!_alsa_configure(data))
goto cleanup;
if (snd_pcm_state(data->handle) != SND_PCM_STATE_PREPARED) {
blog(LOG_ERROR, "Device not prepared: '%s'", data->device);
goto cleanup;
}
/* start listening */
err = snd_pcm_start(data->handle);
if (err < 0) {
blog(LOG_ERROR, "Failed to start '%s': %s", data->device,
snd_strerror(err));
goto cleanup;
}
/* create capture thread */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
err = pthread_create(&data->listen_thread, &attr, _alsa_listen, data);
if (err) {
pthread_attr_destroy(&attr);
blog(LOG_ERROR,
"Failed to create capture thread for device '%s'.",
data->device);
goto cleanup;
}
pthread_attr_destroy(&attr);
return true;
cleanup:
_alsa_close(data);
return false;
}
void _alsa_close(struct alsa_data *data)
{
if (data->listen_thread) {
os_atomic_set_bool(&data->listen, false);
pthread_join(data->listen_thread, NULL);
data->listen_thread = 0;
}
if (data->handle) {
snd_pcm_drop(data->handle);
snd_pcm_close(data->handle), data->handle = NULL;
}
if (data->buffer)
bfree(data->buffer), data->buffer = NULL;
}
bool _alsa_configure(struct alsa_data *data)
{
snd_pcm_hw_params_t *hwparams;
int err;
int dir;
snd_pcm_hw_params_alloca(&hwparams);
err = snd_pcm_hw_params_any(data->handle, hwparams);
if (err < 0) {
blog(LOG_ERROR, "snd_pcm_hw_params_any failed: %s",
snd_strerror(err));
return false;
}
err = snd_pcm_hw_params_set_access(data->handle, hwparams,
SND_PCM_ACCESS_RW_INTERLEAVED);
if (err < 0) {
blog(LOG_ERROR, "snd_pcm_hw_params_set_access failed: %s",
snd_strerror(err));
return false;
}
data->format = SND_PCM_FORMAT_S16;
err = snd_pcm_hw_params_set_format(data->handle, hwparams,
data->format);
if (err < 0) {
blog(LOG_ERROR, "snd_pcm_hw_params_set_format failed: %s",
snd_strerror(err));
return false;
}
err = snd_pcm_hw_params_set_rate_near(data->handle, hwparams,
&data->rate, 0);
if (err < 0) {
blog(LOG_ERROR, "snd_pcm_hw_params_set_rate_near failed: %s",
snd_strerror(err));
return false;
}
blog(LOG_INFO, "PCM '%s' rate set to %d", data->device, data->rate);
err = snd_pcm_hw_params_get_channels(hwparams, &data->channels);
if (err < 0)
data->channels = 2;
err = snd_pcm_hw_params_set_channels_near(data->handle, hwparams,
&data->channels);
if (err < 0) {
blog(LOG_ERROR,
"snd_pcm_hw_params_set_channels_near failed: %s",
snd_strerror(err));
return false;
}
blog(LOG_INFO, "PCM '%s' channels set to %d", data->device,
data->channels);
err = snd_pcm_hw_params(data->handle, hwparams);
if (err < 0) {
blog(LOG_ERROR, "snd_pcm_hw_params failed: %s",
snd_strerror(err));
return false;
}
err = snd_pcm_hw_params_get_period_size(hwparams, &data->period_size,
&dir);
if (err < 0) {
blog(LOG_ERROR, "snd_pcm_hw_params_get_period_size failed: %s",
snd_strerror(err));
return false;
}
data->sample_size =
(data->channels * snd_pcm_format_physical_width(data->format)) /
8;
if (data->buffer)
bfree(data->buffer);
data->buffer = bzalloc(data->period_size * data->sample_size);
return true;
}
void _alsa_start_reopen(struct alsa_data *data)
{
pthread_attr_t attr;
int err;
if (os_atomic_load_bool(&data->reopen))
return;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
err = pthread_create(&data->reopen_thread, &attr, _alsa_reopen, data);
if (err) {
blog(LOG_ERROR,
"Failed to create reopen thread for device '%s'.",
data->device);
}
pthread_attr_destroy(&attr);
}
void _alsa_stop_reopen(struct alsa_data *data)
{
if (os_atomic_load_bool(&data->reopen))
os_event_signal(data->abort_event);
if (data->reopen_thread) {
pthread_join(data->reopen_thread, NULL);
data->reopen_thread = 0;
}
os_event_reset(data->abort_event);
}
void *_alsa_listen(void *attr)
{
struct alsa_data *data = attr;
struct obs_source_audio out;
blog(LOG_DEBUG, "Capture thread started.");
out.data[0] = data->buffer;
out.format = _alsa_to_obs_audio_format(data->format);
out.speakers = _alsa_channels_to_obs_speakers(data->channels);
out.samples_per_sec = data->rate;
os_atomic_set_bool(&data->listen, true);
do {
snd_pcm_sframes_t frames = snd_pcm_readi(
data->handle, data->buffer, data->period_size);
if (!os_atomic_load_bool(&data->listen))
break;
if (frames <= 0) {
frames = snd_pcm_recover(data->handle, frames, 0);
if (frames <= 0) {
snd_pcm_wait(data->handle, 100);
continue;
}
}
out.frames = frames;
out.timestamp = os_gettime_ns() -
((frames * NSEC_PER_SEC) / data->rate);
if (!data->first_ts)
data->first_ts = out.timestamp + STARTUP_TIMEOUT_NS;
if (out.timestamp > data->first_ts)
obs_source_output_audio(data->source, &out);
} while (os_atomic_load_bool(&data->listen));
blog(LOG_DEBUG, "Capture thread is about to exit.");
pthread_exit(NULL);
return NULL;
}
void *_alsa_reopen(void *attr)
{
struct alsa_data *data = attr;
unsigned long timeout = REOPEN_TIMEOUT;
blog(LOG_DEBUG, "Reopen thread started.");
os_atomic_set_bool(&data->reopen, true);
while (os_event_timedwait(data->abort_event, timeout) == ETIMEDOUT) {
if (_alsa_open(data))
break;
if (timeout < (REOPEN_TIMEOUT * 5))
timeout += REOPEN_TIMEOUT;
}
os_atomic_set_bool(&data->reopen, false);
blog(LOG_DEBUG, "Reopen thread is about to exit.");
pthread_exit(NULL);
return NULL;
}
enum audio_format _alsa_to_obs_audio_format(snd_pcm_format_t format)
{
switch (format) {
case SND_PCM_FORMAT_U8:
return AUDIO_FORMAT_U8BIT;
case SND_PCM_FORMAT_S16_LE:
return AUDIO_FORMAT_16BIT;
case SND_PCM_FORMAT_S32_LE:
return AUDIO_FORMAT_32BIT;
case SND_PCM_FORMAT_FLOAT_LE:
return AUDIO_FORMAT_FLOAT;
default:
break;
}
return AUDIO_FORMAT_UNKNOWN;
}
enum speaker_layout _alsa_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;
}