| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 | /*Copyright (C) 2014 by Leonhard Oelke <[email protected]>Copyright (C) 2017 by Fabio Madia <[email protected]>This program is free software: you can redistribute it and/or modifyit under the terms of the GNU General Public License as published bythe 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 ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See theGNU General Public License for more details.You should have received a copy of the GNU General Public Licensealong with this program.  If not, see <http://www.gnu.org/licenses/>.*/#include <pthread.h>#include <pulse/thread-mainloop.h>#include <util/base.h>#include <obs.h>#include "pulseaudio-wrapper.h"/* global data */static uint_fast32_t pulseaudio_refs = 0;static pthread_mutex_t pulseaudio_mutex = PTHREAD_MUTEX_INITIALIZER;static pa_threaded_mainloop *pulseaudio_mainloop = NULL;static pa_context *pulseaudio_context = NULL;static void pulseaudio_default_devices(pa_context *c, const pa_server_info *i, void *userdata){	UNUSED_PARAMETER(c);	struct pulseaudio_default_output *d = (struct pulseaudio_default_output *)userdata;	d->default_sink_name = bstrdup(i->default_sink_name);	pulseaudio_signal(0);}void get_default_id(char **id){	pulseaudio_init();	struct pulseaudio_default_output *pdo = bzalloc(sizeof(struct pulseaudio_default_output));	pulseaudio_get_server_info((pa_server_info_cb_t)pulseaudio_default_devices, (void *)pdo);	if (!pdo->default_sink_name || !*pdo->default_sink_name) {		*id = bzalloc(1);	} else {		*id = bzalloc(strlen(pdo->default_sink_name) + 9);		strcat(*id, pdo->default_sink_name);		bfree(pdo->default_sink_name);	}	bfree(pdo);	pulseaudio_unref();}/** * Checks whether a sound source (id1) is the .monitor device for the * selected monitoring output (id2). */bool devices_match(const char *id1, const char *id2){	bool match;	char *name_default = NULL;	char *name1 = NULL;	char *name2 = NULL;	if (!id1 || !id2)		return false;	if (strcmp(id1, "default") == 0) {		get_default_id(&name_default);		name1 = bzalloc(strlen(name_default) + 9);		strcat(name1, name_default);		strcat(name1, ".monitor");	} else {		name1 = bstrdup(id1);	}	if (strcmp(id2, "default") == 0) {		if (!name_default)			get_default_id(&name_default);		name2 = bzalloc(strlen(name_default) + 9);		strcat(name2, name_default);		strcat(name2, ".monitor");	} else {		name2 = bzalloc(strlen(id2) + 9);		strcat(name2, id2);		strcat(name2, ".monitor");	}	match = strcmp(name1, name2) == 0;	bfree(name_default);	bfree(name1);	bfree(name2);	return match;}/** * context status change callback * * @todo this is currently a noop, we want to reconnect here if the connection *       is lost ... */static void pulseaudio_context_state_changed(pa_context *c, void *userdata){	UNUSED_PARAMETER(userdata);	UNUSED_PARAMETER(c);	pulseaudio_signal(0);}/** * get the default properties */static pa_proplist *pulseaudio_properties(){	pa_proplist *p = pa_proplist_new();	pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, "OBS");	pa_proplist_sets(p, PA_PROP_APPLICATION_ICON_NAME, "obs");	pa_proplist_sets(p, PA_PROP_MEDIA_ROLE, "production");	return p;}/** * Initialize the pulse audio context with properties and callback */static void pulseaudio_init_context(){	pulseaudio_lock();	pa_proplist *p = pulseaudio_properties();	pulseaudio_context =		pa_context_new_with_proplist(pa_threaded_mainloop_get_api(pulseaudio_mainloop), "OBS-Monitor", p);	pa_context_set_state_callback(pulseaudio_context, pulseaudio_context_state_changed, NULL);	pa_context_connect(pulseaudio_context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL);	pa_proplist_free(p);	pulseaudio_unlock();}/** * wait for context to be ready */static int_fast32_t pulseaudio_context_ready(){	pulseaudio_lock();	if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(pulseaudio_context))) {		pulseaudio_unlock();		return -1;	}	while (pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY)		pulseaudio_wait();	pulseaudio_unlock();	return 0;}int_fast32_t pulseaudio_init(){	pthread_mutex_lock(&pulseaudio_mutex);	if (pulseaudio_refs == 0) {		pulseaudio_mainloop = pa_threaded_mainloop_new();		pa_threaded_mainloop_start(pulseaudio_mainloop);		pulseaudio_init_context();	}	pulseaudio_refs++;	pthread_mutex_unlock(&pulseaudio_mutex);	return 0;}void pulseaudio_unref(){	pthread_mutex_lock(&pulseaudio_mutex);	if (--pulseaudio_refs == 0) {		pulseaudio_lock();		if (pulseaudio_context != NULL) {			pa_context_disconnect(pulseaudio_context);			pa_context_unref(pulseaudio_context);			pulseaudio_context = NULL;		}		pulseaudio_unlock();		if (pulseaudio_mainloop != NULL) {			pa_threaded_mainloop_stop(pulseaudio_mainloop);			pa_threaded_mainloop_free(pulseaudio_mainloop);			pulseaudio_mainloop = NULL;		}	}	pthread_mutex_unlock(&pulseaudio_mutex);}void pulseaudio_lock(){	pa_threaded_mainloop_lock(pulseaudio_mainloop);}void pulseaudio_unlock(){	pa_threaded_mainloop_unlock(pulseaudio_mainloop);}void pulseaudio_wait(){	pa_threaded_mainloop_wait(pulseaudio_mainloop);}void pulseaudio_signal(int wait_for_accept){	pa_threaded_mainloop_signal(pulseaudio_mainloop, wait_for_accept);}void pulseaudio_accept(){	pa_threaded_mainloop_accept(pulseaudio_mainloop);}int_fast32_t pulseaudio_get_source_info_list(pa_source_info_cb_t cb, void *userdata){	if (pulseaudio_context_ready() < 0)		return -1;	pulseaudio_lock();	pa_operation *op = pa_context_get_source_info_list(pulseaudio_context, cb, userdata);	if (!op) {		pulseaudio_unlock();		return -1;	}	while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)		pulseaudio_wait();	pa_operation_unref(op);	pulseaudio_unlock();	return 0;}int_fast32_t pulseaudio_get_source_info(pa_source_info_cb_t cb, const char *name, void *userdata){	if (pulseaudio_context_ready() < 0)		return -1;	pulseaudio_lock();	pa_operation *op = pa_context_get_source_info_by_name(pulseaudio_context, name, cb, userdata);	if (!op) {		pulseaudio_unlock();		return -1;	}	while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)		pulseaudio_wait();	pa_operation_unref(op);	pulseaudio_unlock();	return 0;}int_fast32_t pulseaudio_get_sink_info_list(pa_sink_info_cb_t cb, void *userdata){	if (pulseaudio_context_ready() < 0)		return -1;	pulseaudio_lock();	pa_operation *op = pa_context_get_sink_info_list(pulseaudio_context, cb, userdata);	if (!op) {		pulseaudio_unlock();		return -1;	}	while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)		pulseaudio_wait();	pa_operation_unref(op);	pulseaudio_unlock();	return 0;}int_fast32_t pulseaudio_get_sink_info(pa_sink_info_cb_t cb, const char *name, void *userdata){	if (pulseaudio_context_ready() < 0)		return -1;	pulseaudio_lock();	pa_operation *op = pa_context_get_sink_info_by_name(pulseaudio_context, name, cb, userdata);	if (!op) {		pulseaudio_unlock();		return -1;	}	while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)		pulseaudio_wait();	pa_operation_unref(op);	pulseaudio_unlock();	return 0;}int_fast32_t pulseaudio_get_server_info(pa_server_info_cb_t cb, void *userdata){	if (pulseaudio_context_ready() < 0)		return -1;	pulseaudio_lock();	pa_operation *op = pa_context_get_server_info(pulseaudio_context, cb, userdata);	if (!op) {		pulseaudio_unlock();		return -1;	}	while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)		pulseaudio_wait();	pa_operation_unref(op);	pulseaudio_unlock();	return 0;}pa_stream *pulseaudio_stream_new(const char *name, const pa_sample_spec *ss, const pa_channel_map *map){	if (pulseaudio_context_ready() < 0)		return NULL;	pulseaudio_lock();	pa_proplist *p = pulseaudio_properties();	pa_stream *s = pa_stream_new_with_proplist(pulseaudio_context, name, ss, map, p);	pa_proplist_free(p);	pulseaudio_unlock();	return s;}int_fast32_t pulseaudio_connect_playback(pa_stream *s, const char *name, const pa_buffer_attr *attr,					 pa_stream_flags_t flags){	if (pulseaudio_context_ready() < 0)		return -1;	size_t dev_len = strlen(name);	char *device = bzalloc(dev_len + 1);	memcpy(device, name, dev_len);	pulseaudio_lock();	int_fast32_t ret = pa_stream_connect_playback(s, device, attr, flags, NULL, NULL);	pulseaudio_unlock();	bfree(device);	return ret;}void pulseaudio_write_callback(pa_stream *p, pa_stream_request_cb_t cb, void *userdata){	if (pulseaudio_context_ready() < 0)		return;	pulseaudio_lock();	pa_stream_set_write_callback(p, cb, userdata);	pulseaudio_unlock();}void pulseaudio_set_underflow_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata){	if (pulseaudio_context_ready() < 0)		return;	pulseaudio_lock();	pa_stream_set_underflow_callback(p, cb, userdata);	pulseaudio_unlock();}
 |