1 /*
2 Copyright (C) 2015. Guillermo A. Amaral B. <g@maral.me>
3 
4 Based on Pulse Input plugin by Leonhard Oelke.
5 
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 2 of the License, or
9 (at your option) any later version.
10 
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include <util/bmem.h>
21 #include <util/platform.h>
22 #include <util/threading.h>
23 #include <util/util_uint64.h>
24 #include <obs-module.h>
25 
26 #include <alsa/asoundlib.h>
27 #include <alsa/pcm.h>
28 
29 #include <pthread.h>
30 
31 #define blog(level, msg, ...) blog(level, "alsa-input: " msg, ##__VA_ARGS__)
32 
33 #define NSEC_PER_SEC 1000000000LL
34 #define NSEC_PER_MSEC 1000000L
35 #define STARTUP_TIMEOUT_NS (500 * NSEC_PER_MSEC)
36 #define REOPEN_TIMEOUT 1000UL
37 #define SHUTDOWN_ON_DEACTIVATE false
38 
39 struct alsa_data {
40 	obs_source_t *source;
41 #if SHUTDOWN_ON_DEACTIVATE
42 	bool active;
43 #endif
44 
45 	/* user settings */
46 	char *device;
47 
48 	/* pthread */
49 	pthread_t listen_thread;
50 	pthread_t reopen_thread;
51 	os_event_t *abort_event;
52 	volatile bool listen;
53 	volatile bool reopen;
54 
55 	/* alsa */
56 	snd_pcm_t *handle;
57 	snd_pcm_format_t format;
58 	snd_pcm_uframes_t period_size;
59 
60 	unsigned int channels;
61 	unsigned int rate;
62 	unsigned int sample_size;
63 	uint8_t *buffer;
64 	uint64_t first_ts;
65 };
66 
67 static const char *alsa_get_name(void *);
68 static bool alsa_devices_changed(obs_properties_t *props, obs_property_t *p,
69 				 obs_data_t *settings);
70 static obs_properties_t *alsa_get_properties(void *);
71 static void *alsa_create(obs_data_t *, obs_source_t *);
72 static void alsa_destroy(void *);
73 static void alsa_activate(void *);
74 static void alsa_deactivate(void *);
75 static void alsa_get_defaults(obs_data_t *);
76 static void alsa_update(void *, obs_data_t *);
77 
78 struct obs_source_info alsa_input_capture = {
79 	.id = "alsa_input_capture",
80 	.type = OBS_SOURCE_TYPE_INPUT,
81 	.output_flags = OBS_SOURCE_AUDIO,
82 	.create = alsa_create,
83 	.destroy = alsa_destroy,
84 #if SHUTDOWN_ON_DEACTIVATE
85 	.activate = alsa_activate,
86 	.deactivate = alsa_deactivate,
87 #endif
88 	.update = alsa_update,
89 	.get_defaults = alsa_get_defaults,
90 	.get_name = alsa_get_name,
91 	.get_properties = alsa_get_properties,
92 	.icon_type = OBS_ICON_TYPE_AUDIO_INPUT,
93 };
94 
95 static bool _alsa_try_open(struct alsa_data *);
96 static bool _alsa_open(struct alsa_data *);
97 static void _alsa_close(struct alsa_data *);
98 static bool _alsa_configure(struct alsa_data *);
99 static void _alsa_start_reopen(struct alsa_data *);
100 static void _alsa_stop_reopen(struct alsa_data *);
101 static void *_alsa_listen(void *);
102 static void *_alsa_reopen(void *);
103 
104 static enum audio_format _alsa_to_obs_audio_format(snd_pcm_format_t);
105 static enum speaker_layout _alsa_channels_to_obs_speakers(unsigned int);
106 
107 /*****************************************************************************/
108 
alsa_create(obs_data_t * settings,obs_source_t * source)109 void *alsa_create(obs_data_t *settings, obs_source_t *source)
110 {
111 	struct alsa_data *data = bzalloc(sizeof(struct alsa_data));
112 
113 	data->source = source;
114 #if SHUTDOWN_ON_DEACTIVATE
115 	data->active = false;
116 #endif
117 	data->buffer = NULL;
118 	data->device = NULL;
119 	data->first_ts = 0;
120 	data->handle = NULL;
121 	data->listen = false;
122 	data->reopen = false;
123 	data->listen_thread = 0;
124 	data->reopen_thread = 0;
125 
126 	const char *device = obs_data_get_string(settings, "device_id");
127 
128 	if (strcmp(device, "__custom__") == 0)
129 		device = obs_data_get_string(settings, "custom_pcm");
130 
131 	data->device = bstrdup(device);
132 	data->rate = obs_data_get_int(settings, "rate");
133 
134 	if (os_event_init(&data->abort_event, OS_EVENT_TYPE_MANUAL) != 0) {
135 		blog(LOG_ERROR, "Abort event creation failed!");
136 		goto cleanup;
137 	}
138 
139 #if !SHUTDOWN_ON_DEACTIVATE
140 	_alsa_try_open(data);
141 #endif
142 	return data;
143 
144 cleanup:
145 	if (data->device)
146 		bfree(data->device);
147 
148 	bfree(data);
149 	return NULL;
150 }
151 
alsa_destroy(void * vptr)152 void alsa_destroy(void *vptr)
153 {
154 	struct alsa_data *data = vptr;
155 
156 	if (data->handle)
157 		_alsa_close(data);
158 
159 	os_event_destroy(data->abort_event);
160 	bfree(data->device);
161 	bfree(data);
162 }
163 
164 #if SHUTDOWN_ON_DEACTIVATE
alsa_activate(void * vptr)165 void alsa_activate(void *vptr)
166 {
167 	struct alsa_data *data = vptr;
168 
169 	data->active = true;
170 	_alsa_try_open(data);
171 }
172 
alsa_deactivate(void * vptr)173 void alsa_deactivate(void *vptr)
174 {
175 	struct alsa_data *data = vptr;
176 
177 	_alsa_stop_reopen(data);
178 	_alsa_close(data);
179 	data->active = false;
180 }
181 #endif
182 
alsa_update(void * vptr,obs_data_t * settings)183 void alsa_update(void *vptr, obs_data_t *settings)
184 {
185 	struct alsa_data *data = vptr;
186 	const char *device;
187 	unsigned int rate;
188 	bool reset = false;
189 
190 	device = obs_data_get_string(settings, "device_id");
191 
192 	if (strcmp(device, "__custom__") == 0)
193 		device = obs_data_get_string(settings, "custom_pcm");
194 
195 	if (strcmp(data->device, device) != 0) {
196 		bfree(data->device);
197 		data->device = bstrdup(device);
198 		reset = true;
199 	}
200 
201 	rate = obs_data_get_int(settings, "rate");
202 	if (data->rate != rate) {
203 		data->rate = rate;
204 		reset = true;
205 	}
206 
207 #if SHUTDOWN_ON_DEACTIVATE
208 	if (reset && data->handle)
209 		_alsa_close(data);
210 
211 	if (data->active && !data->handle)
212 		_alsa_try_open(data);
213 #else
214 	if (reset) {
215 		if (data->handle)
216 			_alsa_close(data);
217 		_alsa_try_open(data);
218 	}
219 #endif
220 }
221 
alsa_get_name(void * unused)222 const char *alsa_get_name(void *unused)
223 {
224 	UNUSED_PARAMETER(unused);
225 	return obs_module_text("AlsaInput");
226 }
227 
alsa_get_defaults(obs_data_t * settings)228 void alsa_get_defaults(obs_data_t *settings)
229 {
230 	obs_data_set_default_string(settings, "device_id", "default");
231 	obs_data_set_default_string(settings, "custom_pcm", "default");
232 	obs_data_set_default_int(settings, "rate", 44100);
233 }
234 
alsa_devices_changed(obs_properties_t * props,obs_property_t * p,obs_data_t * settings)235 static bool alsa_devices_changed(obs_properties_t *props, obs_property_t *p,
236 				 obs_data_t *settings)
237 {
238 	UNUSED_PARAMETER(p);
239 	bool visible = false;
240 	const char *device_id = obs_data_get_string(settings, "device_id");
241 
242 	if (strcmp(device_id, "__custom__") == 0)
243 		visible = true;
244 
245 	obs_property_t *custom_pcm = obs_properties_get(props, "custom_pcm");
246 
247 	obs_property_set_visible(custom_pcm, visible);
248 	obs_property_modified(custom_pcm, settings);
249 
250 	return true;
251 }
252 
alsa_get_properties(void * unused)253 obs_properties_t *alsa_get_properties(void *unused)
254 {
255 	void **hints;
256 	void **hint;
257 	char *name = NULL;
258 	char *descr = NULL;
259 	char *io = NULL;
260 	char *descr_i;
261 	obs_properties_t *props;
262 	obs_property_t *devices;
263 	obs_property_t *rate;
264 
265 	UNUSED_PARAMETER(unused);
266 
267 	props = obs_properties_create();
268 
269 	devices = obs_properties_add_list(props, "device_id",
270 					  obs_module_text("Device"),
271 					  OBS_COMBO_TYPE_LIST,
272 					  OBS_COMBO_FORMAT_STRING);
273 
274 	obs_property_list_add_string(devices, "Default", "default");
275 
276 	obs_properties_add_text(props, "custom_pcm", obs_module_text("PCM"),
277 				OBS_TEXT_DEFAULT);
278 
279 	rate = obs_properties_add_list(props, "rate", obs_module_text("Rate"),
280 				       OBS_COMBO_TYPE_LIST,
281 				       OBS_COMBO_FORMAT_INT);
282 
283 	obs_property_set_modified_callback(devices, alsa_devices_changed);
284 
285 	obs_property_list_add_int(rate, "32000 Hz", 32000);
286 	obs_property_list_add_int(rate, "44100 Hz", 44100);
287 	obs_property_list_add_int(rate, "48000 Hz", 48000);
288 
289 	if (snd_device_name_hint(-1, "pcm", &hints) < 0)
290 		return props;
291 
292 	hint = hints;
293 	while (*hint != NULL) {
294 		/* check if we're dealing with an Input */
295 		io = snd_device_name_get_hint(*hint, "IOID");
296 		if (io != NULL && strcmp(io, "Input") != 0)
297 			goto next;
298 
299 		name = snd_device_name_get_hint(*hint, "NAME");
300 		if (name == NULL || strstr(name, "front:") == NULL)
301 			goto next;
302 
303 		descr = snd_device_name_get_hint(*hint, "DESC");
304 		if (!descr)
305 			goto next;
306 
307 		descr_i = descr;
308 		while (*descr_i) {
309 			if (*descr_i == '\n') {
310 				*descr_i = '\0';
311 				break;
312 			} else
313 				++descr_i;
314 		}
315 
316 		obs_property_list_add_string(devices, descr, name);
317 
318 	next:
319 		if (name != NULL)
320 			free(name), name = NULL;
321 
322 		if (descr != NULL)
323 			free(descr), descr = NULL;
324 
325 		if (io != NULL)
326 			free(io), io = NULL;
327 
328 		++hint;
329 	}
330 	obs_property_list_add_string(devices, "Custom", "__custom__");
331 
332 	snd_device_name_free_hint(hints);
333 
334 	return props;
335 }
336 
337 /*****************************************************************************/
338 
_alsa_try_open(struct alsa_data * data)339 bool _alsa_try_open(struct alsa_data *data)
340 {
341 	_alsa_stop_reopen(data);
342 
343 	if (_alsa_open(data))
344 		return true;
345 
346 	_alsa_start_reopen(data);
347 
348 	return false;
349 }
350 
_alsa_open(struct alsa_data * data)351 bool _alsa_open(struct alsa_data *data)
352 {
353 	pthread_attr_t attr;
354 	int err;
355 
356 	err = snd_pcm_open(&data->handle, data->device, SND_PCM_STREAM_CAPTURE,
357 			   0);
358 	if (err < 0) {
359 		blog(LOG_ERROR, "Failed to open '%s': %s", data->device,
360 		     snd_strerror(err));
361 		return false;
362 	}
363 
364 	if (!_alsa_configure(data))
365 		goto cleanup;
366 
367 	if (snd_pcm_state(data->handle) != SND_PCM_STATE_PREPARED) {
368 		blog(LOG_ERROR, "Device not prepared: '%s'", data->device);
369 		goto cleanup;
370 	}
371 
372 	/* start listening */
373 
374 	err = snd_pcm_start(data->handle);
375 	if (err < 0) {
376 		blog(LOG_ERROR, "Failed to start '%s': %s", data->device,
377 		     snd_strerror(err));
378 		goto cleanup;
379 	}
380 
381 	/* create capture thread */
382 
383 	pthread_attr_init(&attr);
384 	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
385 
386 	err = pthread_create(&data->listen_thread, &attr, _alsa_listen, data);
387 	if (err) {
388 		pthread_attr_destroy(&attr);
389 		blog(LOG_ERROR,
390 		     "Failed to create capture thread for device '%s'.",
391 		     data->device);
392 		goto cleanup;
393 	}
394 
395 	pthread_attr_destroy(&attr);
396 	return true;
397 
398 cleanup:
399 	_alsa_close(data);
400 	return false;
401 }
402 
_alsa_close(struct alsa_data * data)403 void _alsa_close(struct alsa_data *data)
404 {
405 	if (data->listen_thread) {
406 		os_atomic_set_bool(&data->listen, false);
407 		pthread_join(data->listen_thread, NULL);
408 		data->listen_thread = 0;
409 	}
410 
411 	if (data->handle) {
412 		snd_pcm_drop(data->handle);
413 		snd_pcm_close(data->handle), data->handle = NULL;
414 	}
415 
416 	if (data->buffer)
417 		bfree(data->buffer), data->buffer = NULL;
418 }
419 
_alsa_configure(struct alsa_data * data)420 bool _alsa_configure(struct alsa_data *data)
421 {
422 	snd_pcm_hw_params_t *hwparams;
423 	int err;
424 	int dir;
425 
426 	snd_pcm_hw_params_alloca(&hwparams);
427 
428 	err = snd_pcm_hw_params_any(data->handle, hwparams);
429 	if (err < 0) {
430 		blog(LOG_ERROR, "snd_pcm_hw_params_any failed: %s",
431 		     snd_strerror(err));
432 		return false;
433 	}
434 
435 	err = snd_pcm_hw_params_set_access(data->handle, hwparams,
436 					   SND_PCM_ACCESS_RW_INTERLEAVED);
437 	if (err < 0) {
438 		blog(LOG_ERROR, "snd_pcm_hw_params_set_access failed: %s",
439 		     snd_strerror(err));
440 		return false;
441 	}
442 
443 #define FORMAT_SIZE 4
444 	snd_pcm_format_t formats[FORMAT_SIZE] = {SND_PCM_FORMAT_S16_LE,
445 						 SND_PCM_FORMAT_S32_LE,
446 						 SND_PCM_FORMAT_FLOAT_LE,
447 						 SND_PCM_FORMAT_U8};
448 	bool format_found = false;
449 	for (int i = 0; i < FORMAT_SIZE; ++i) {
450 		data->format = formats[i];
451 		err = snd_pcm_hw_params_test_format(data->handle, hwparams,
452 						    data->format);
453 		if (err == 0) {
454 			format_found = true;
455 			break;
456 		}
457 	}
458 #undef FORMAT_SIZE
459 	if (!format_found) {
460 		blog(LOG_ERROR, "device doesnt support any OBS formats");
461 		return false;
462 	}
463 	snd_pcm_hw_params_set_format(data->handle, hwparams, data->format);
464 	if (err < 0) {
465 		blog(LOG_ERROR, "snd_pcm_hw_params_set_format failed: %s",
466 		     snd_strerror(err));
467 		return false;
468 	}
469 
470 	err = snd_pcm_hw_params_set_rate_near(data->handle, hwparams,
471 					      &data->rate, 0);
472 	if (err < 0) {
473 		blog(LOG_ERROR, "snd_pcm_hw_params_set_rate_near failed: %s",
474 		     snd_strerror(err));
475 		return false;
476 	}
477 	blog(LOG_INFO, "PCM '%s' rate set to %d", data->device, data->rate);
478 
479 	err = snd_pcm_hw_params_get_channels(hwparams, &data->channels);
480 	if (err < 0)
481 		data->channels = 2;
482 
483 	err = snd_pcm_hw_params_set_channels_near(data->handle, hwparams,
484 						  &data->channels);
485 	if (err < 0) {
486 		blog(LOG_ERROR,
487 		     "snd_pcm_hw_params_set_channels_near failed: %s",
488 		     snd_strerror(err));
489 		return false;
490 	}
491 	blog(LOG_INFO, "PCM '%s' channels set to %d", data->device,
492 	     data->channels);
493 
494 	err = snd_pcm_hw_params(data->handle, hwparams);
495 	if (err < 0) {
496 		blog(LOG_ERROR, "snd_pcm_hw_params failed: %s",
497 		     snd_strerror(err));
498 		return false;
499 	}
500 
501 	err = snd_pcm_hw_params_get_period_size(hwparams, &data->period_size,
502 						&dir);
503 	if (err < 0) {
504 		blog(LOG_ERROR, "snd_pcm_hw_params_get_period_size failed: %s",
505 		     snd_strerror(err));
506 		return false;
507 	}
508 
509 	data->sample_size =
510 		(data->channels * snd_pcm_format_physical_width(data->format)) /
511 		8;
512 
513 	if (data->buffer)
514 		bfree(data->buffer);
515 	data->buffer = bzalloc(data->period_size * data->sample_size);
516 
517 	return true;
518 }
519 
_alsa_start_reopen(struct alsa_data * data)520 void _alsa_start_reopen(struct alsa_data *data)
521 {
522 	pthread_attr_t attr;
523 	int err;
524 
525 	if (os_atomic_load_bool(&data->reopen))
526 		return;
527 
528 	pthread_attr_init(&attr);
529 	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
530 
531 	err = pthread_create(&data->reopen_thread, &attr, _alsa_reopen, data);
532 	if (err) {
533 		blog(LOG_ERROR,
534 		     "Failed to create reopen thread for device '%s'.",
535 		     data->device);
536 	}
537 
538 	pthread_attr_destroy(&attr);
539 }
540 
_alsa_stop_reopen(struct alsa_data * data)541 void _alsa_stop_reopen(struct alsa_data *data)
542 {
543 	if (os_atomic_load_bool(&data->reopen))
544 		os_event_signal(data->abort_event);
545 
546 	if (data->reopen_thread) {
547 		pthread_join(data->reopen_thread, NULL);
548 		data->reopen_thread = 0;
549 	}
550 
551 	os_event_reset(data->abort_event);
552 }
553 
_alsa_listen(void * attr)554 void *_alsa_listen(void *attr)
555 {
556 	struct alsa_data *data = attr;
557 	struct obs_source_audio out;
558 
559 	blog(LOG_DEBUG, "Capture thread started.");
560 
561 	out.data[0] = data->buffer;
562 	out.format = _alsa_to_obs_audio_format(data->format);
563 	out.speakers = _alsa_channels_to_obs_speakers(data->channels);
564 	out.samples_per_sec = data->rate;
565 
566 	os_atomic_set_bool(&data->listen, true);
567 
568 	do {
569 		snd_pcm_sframes_t frames = snd_pcm_readi(
570 			data->handle, data->buffer, data->period_size);
571 
572 		if (!os_atomic_load_bool(&data->listen))
573 			break;
574 
575 		if (frames <= 0) {
576 			frames = snd_pcm_recover(data->handle, frames, 0);
577 			if (frames <= 0) {
578 				snd_pcm_wait(data->handle, 100);
579 				continue;
580 			}
581 		}
582 
583 		out.frames = frames;
584 		out.timestamp =
585 			os_gettime_ns() -
586 			util_mul_div64(frames, NSEC_PER_SEC, data->rate);
587 
588 		if (!data->first_ts)
589 			data->first_ts = out.timestamp + STARTUP_TIMEOUT_NS;
590 
591 		if (out.timestamp > data->first_ts)
592 			obs_source_output_audio(data->source, &out);
593 	} while (os_atomic_load_bool(&data->listen));
594 
595 	blog(LOG_DEBUG, "Capture thread is about to exit.");
596 
597 	pthread_exit(NULL);
598 	return NULL;
599 }
600 
_alsa_reopen(void * attr)601 void *_alsa_reopen(void *attr)
602 {
603 	struct alsa_data *data = attr;
604 	unsigned long timeout = REOPEN_TIMEOUT;
605 
606 	blog(LOG_DEBUG, "Reopen thread started.");
607 
608 	os_atomic_set_bool(&data->reopen, true);
609 
610 	while (os_event_timedwait(data->abort_event, timeout) == ETIMEDOUT) {
611 		if (_alsa_open(data))
612 			break;
613 
614 		if (timeout < (REOPEN_TIMEOUT * 5))
615 			timeout += REOPEN_TIMEOUT;
616 	}
617 
618 	os_atomic_set_bool(&data->reopen, false);
619 
620 	blog(LOG_DEBUG, "Reopen thread is about to exit.");
621 
622 	pthread_exit(NULL);
623 	return NULL;
624 }
625 
_alsa_to_obs_audio_format(snd_pcm_format_t format)626 enum audio_format _alsa_to_obs_audio_format(snd_pcm_format_t format)
627 {
628 	switch (format) {
629 	case SND_PCM_FORMAT_U8:
630 		return AUDIO_FORMAT_U8BIT;
631 	case SND_PCM_FORMAT_S16_LE:
632 		return AUDIO_FORMAT_16BIT;
633 	case SND_PCM_FORMAT_S32_LE:
634 		return AUDIO_FORMAT_32BIT;
635 	case SND_PCM_FORMAT_FLOAT_LE:
636 		return AUDIO_FORMAT_FLOAT;
637 	default:
638 		break;
639 	}
640 
641 	return AUDIO_FORMAT_UNKNOWN;
642 }
643 
_alsa_channels_to_obs_speakers(unsigned int channels)644 enum speaker_layout _alsa_channels_to_obs_speakers(unsigned int channels)
645 {
646 	switch (channels) {
647 	case 1:
648 		return SPEAKERS_MONO;
649 	case 2:
650 		return SPEAKERS_STEREO;
651 	case 3:
652 		return SPEAKERS_2POINT1;
653 	case 4:
654 		return SPEAKERS_4POINT0;
655 	case 5:
656 		return SPEAKERS_4POINT1;
657 	case 6:
658 		return SPEAKERS_5POINT1;
659 	case 8:
660 		return SPEAKERS_7POINT1;
661 	}
662 
663 	return SPEAKERS_UNKNOWN;
664 }
665