1
0

pulseaudio-wrapper.c 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. /*
  2. Copyright (C) 2014 by Leonhard Oelke <[email protected]>
  3. Copyright (C) 2017 by Fabio Madia <[email protected]>
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. #include <pthread.h>
  16. #include <pulse/thread-mainloop.h>
  17. #include <util/base.h>
  18. #include <obs.h>
  19. #include "pulseaudio-wrapper.h"
  20. /* global data */
  21. static uint_fast32_t pulseaudio_refs = 0;
  22. static pthread_mutex_t pulseaudio_mutex = PTHREAD_MUTEX_INITIALIZER;
  23. static pa_threaded_mainloop *pulseaudio_mainloop = NULL;
  24. static pa_context *pulseaudio_context = NULL;
  25. static void pulseaudio_default_devices(pa_context *c, const pa_server_info *i,
  26. void *userdata)
  27. {
  28. UNUSED_PARAMETER(c);
  29. struct pulseaudio_default_output *d =
  30. (struct pulseaudio_default_output *)userdata;
  31. d->default_sink_name = bstrdup(i->default_sink_name);
  32. pulseaudio_signal(0);
  33. }
  34. void get_default_id(char **id)
  35. {
  36. pulseaudio_init();
  37. struct pulseaudio_default_output *pdo =
  38. bzalloc(sizeof(struct pulseaudio_default_output));
  39. pulseaudio_get_server_info(
  40. (pa_server_info_cb_t)pulseaudio_default_devices, (void *)pdo);
  41. if (!pdo->default_sink_name || !*pdo->default_sink_name) {
  42. *id = bzalloc(1);
  43. } else {
  44. *id = bzalloc(strlen(pdo->default_sink_name) + 9);
  45. strcat(*id, pdo->default_sink_name);
  46. bfree(pdo->default_sink_name);
  47. }
  48. bfree(pdo);
  49. pulseaudio_unref();
  50. }
  51. /**
  52. * Checks whether a sound source (id1) is the .monitor device for the
  53. * selected monitoring output (id2).
  54. */
  55. bool devices_match(const char *id1, const char *id2)
  56. {
  57. bool match;
  58. char *name_default = NULL;
  59. char *name1 = NULL;
  60. char *name2 = NULL;
  61. if (!id1 || !id2)
  62. return false;
  63. if (strcmp(id1, "default") == 0) {
  64. get_default_id(&name_default);
  65. name1 = bzalloc(strlen(name_default) + 9);
  66. strcat(name1, name_default);
  67. strcat(name1, ".monitor");
  68. } else {
  69. name1 = bstrdup(id1);
  70. }
  71. if (strcmp(id2, "default") == 0) {
  72. if (!name_default)
  73. get_default_id(&name_default);
  74. name2 = bzalloc(strlen(name_default) + 9);
  75. strcat(name2, name_default);
  76. strcat(name2, ".monitor");
  77. } else {
  78. name2 = bzalloc(strlen(id2) + 9);
  79. strcat(name2, id2);
  80. strcat(name2, ".monitor");
  81. }
  82. match = strcmp(name1, name2) == 0;
  83. bfree(name_default);
  84. bfree(name1);
  85. bfree(name2);
  86. return match;
  87. }
  88. /**
  89. * context status change callback
  90. *
  91. * @todo this is currently a noop, we want to reconnect here if the connection
  92. * is lost ...
  93. */
  94. static void pulseaudio_context_state_changed(pa_context *c, void *userdata)
  95. {
  96. UNUSED_PARAMETER(userdata);
  97. UNUSED_PARAMETER(c);
  98. pulseaudio_signal(0);
  99. }
  100. /**
  101. * get the default properties
  102. */
  103. static pa_proplist *pulseaudio_properties()
  104. {
  105. pa_proplist *p = pa_proplist_new();
  106. pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, "OBS");
  107. pa_proplist_sets(p, PA_PROP_APPLICATION_ICON_NAME, "obs");
  108. pa_proplist_sets(p, PA_PROP_MEDIA_ROLE, "production");
  109. return p;
  110. }
  111. /**
  112. * Initialize the pulse audio context with properties and callback
  113. */
  114. static void pulseaudio_init_context()
  115. {
  116. pulseaudio_lock();
  117. pa_proplist *p = pulseaudio_properties();
  118. pulseaudio_context = pa_context_new_with_proplist(
  119. pa_threaded_mainloop_get_api(pulseaudio_mainloop),
  120. "OBS-Monitor", p);
  121. pa_context_set_state_callback(pulseaudio_context,
  122. pulseaudio_context_state_changed, NULL);
  123. pa_context_connect(pulseaudio_context, NULL, PA_CONTEXT_NOAUTOSPAWN,
  124. NULL);
  125. pa_proplist_free(p);
  126. pulseaudio_unlock();
  127. }
  128. /**
  129. * wait for context to be ready
  130. */
  131. static int_fast32_t pulseaudio_context_ready()
  132. {
  133. pulseaudio_lock();
  134. if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(pulseaudio_context))) {
  135. pulseaudio_unlock();
  136. return -1;
  137. }
  138. while (pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY)
  139. pulseaudio_wait();
  140. pulseaudio_unlock();
  141. return 0;
  142. }
  143. int_fast32_t pulseaudio_init()
  144. {
  145. pthread_mutex_lock(&pulseaudio_mutex);
  146. if (pulseaudio_refs == 0) {
  147. pulseaudio_mainloop = pa_threaded_mainloop_new();
  148. pa_threaded_mainloop_start(pulseaudio_mainloop);
  149. pulseaudio_init_context();
  150. }
  151. pulseaudio_refs++;
  152. pthread_mutex_unlock(&pulseaudio_mutex);
  153. return 0;
  154. }
  155. void pulseaudio_unref()
  156. {
  157. pthread_mutex_lock(&pulseaudio_mutex);
  158. if (--pulseaudio_refs == 0) {
  159. pulseaudio_lock();
  160. if (pulseaudio_context != NULL) {
  161. pa_context_disconnect(pulseaudio_context);
  162. pa_context_unref(pulseaudio_context);
  163. pulseaudio_context = NULL;
  164. }
  165. pulseaudio_unlock();
  166. if (pulseaudio_mainloop != NULL) {
  167. pa_threaded_mainloop_stop(pulseaudio_mainloop);
  168. pa_threaded_mainloop_free(pulseaudio_mainloop);
  169. pulseaudio_mainloop = NULL;
  170. }
  171. }
  172. pthread_mutex_unlock(&pulseaudio_mutex);
  173. }
  174. void pulseaudio_lock()
  175. {
  176. pa_threaded_mainloop_lock(pulseaudio_mainloop);
  177. }
  178. void pulseaudio_unlock()
  179. {
  180. pa_threaded_mainloop_unlock(pulseaudio_mainloop);
  181. }
  182. void pulseaudio_wait()
  183. {
  184. pa_threaded_mainloop_wait(pulseaudio_mainloop);
  185. }
  186. void pulseaudio_signal(int wait_for_accept)
  187. {
  188. pa_threaded_mainloop_signal(pulseaudio_mainloop, wait_for_accept);
  189. }
  190. void pulseaudio_accept()
  191. {
  192. pa_threaded_mainloop_accept(pulseaudio_mainloop);
  193. }
  194. int_fast32_t pulseaudio_get_source_info_list(pa_source_info_cb_t cb,
  195. void *userdata)
  196. {
  197. if (pulseaudio_context_ready() < 0)
  198. return -1;
  199. pulseaudio_lock();
  200. pa_operation *op = pa_context_get_source_info_list(pulseaudio_context,
  201. cb, userdata);
  202. if (!op) {
  203. pulseaudio_unlock();
  204. return -1;
  205. }
  206. while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
  207. pulseaudio_wait();
  208. pa_operation_unref(op);
  209. pulseaudio_unlock();
  210. return 0;
  211. }
  212. int_fast32_t pulseaudio_get_source_info(pa_source_info_cb_t cb,
  213. const char *name, void *userdata)
  214. {
  215. if (pulseaudio_context_ready() < 0)
  216. return -1;
  217. pulseaudio_lock();
  218. pa_operation *op = pa_context_get_source_info_by_name(
  219. pulseaudio_context, name, cb, userdata);
  220. if (!op) {
  221. pulseaudio_unlock();
  222. return -1;
  223. }
  224. while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
  225. pulseaudio_wait();
  226. pa_operation_unref(op);
  227. pulseaudio_unlock();
  228. return 0;
  229. }
  230. int_fast32_t pulseaudio_get_sink_info_list(pa_sink_info_cb_t cb, void *userdata)
  231. {
  232. if (pulseaudio_context_ready() < 0)
  233. return -1;
  234. pulseaudio_lock();
  235. pa_operation *op =
  236. pa_context_get_sink_info_list(pulseaudio_context, cb, userdata);
  237. if (!op) {
  238. pulseaudio_unlock();
  239. return -1;
  240. }
  241. while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
  242. pulseaudio_wait();
  243. pa_operation_unref(op);
  244. pulseaudio_unlock();
  245. return 0;
  246. }
  247. int_fast32_t pulseaudio_get_sink_info(pa_sink_info_cb_t cb, const char *name,
  248. void *userdata)
  249. {
  250. if (pulseaudio_context_ready() < 0)
  251. return -1;
  252. pulseaudio_lock();
  253. pa_operation *op = pa_context_get_sink_info_by_name(pulseaudio_context,
  254. name, cb, userdata);
  255. if (!op) {
  256. pulseaudio_unlock();
  257. return -1;
  258. }
  259. while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
  260. pulseaudio_wait();
  261. pa_operation_unref(op);
  262. pulseaudio_unlock();
  263. return 0;
  264. }
  265. int_fast32_t pulseaudio_get_server_info(pa_server_info_cb_t cb, void *userdata)
  266. {
  267. if (pulseaudio_context_ready() < 0)
  268. return -1;
  269. pulseaudio_lock();
  270. pa_operation *op =
  271. pa_context_get_server_info(pulseaudio_context, cb, userdata);
  272. if (!op) {
  273. pulseaudio_unlock();
  274. return -1;
  275. }
  276. while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
  277. pulseaudio_wait();
  278. pa_operation_unref(op);
  279. pulseaudio_unlock();
  280. return 0;
  281. }
  282. pa_stream *pulseaudio_stream_new(const char *name, const pa_sample_spec *ss,
  283. const pa_channel_map *map)
  284. {
  285. if (pulseaudio_context_ready() < 0)
  286. return NULL;
  287. pulseaudio_lock();
  288. pa_proplist *p = pulseaudio_properties();
  289. pa_stream *s = pa_stream_new_with_proplist(pulseaudio_context, name, ss,
  290. map, p);
  291. pa_proplist_free(p);
  292. pulseaudio_unlock();
  293. return s;
  294. }
  295. int_fast32_t pulseaudio_connect_playback(pa_stream *s, const char *name,
  296. const pa_buffer_attr *attr,
  297. pa_stream_flags_t flags)
  298. {
  299. if (pulseaudio_context_ready() < 0)
  300. return -1;
  301. size_t dev_len = strlen(name);
  302. char *device = bzalloc(dev_len + 1);
  303. memcpy(device, name, dev_len);
  304. pulseaudio_lock();
  305. int_fast32_t ret =
  306. pa_stream_connect_playback(s, device, attr, flags, NULL, NULL);
  307. pulseaudio_unlock();
  308. bfree(device);
  309. return ret;
  310. }
  311. void pulseaudio_write_callback(pa_stream *p, pa_stream_request_cb_t cb,
  312. void *userdata)
  313. {
  314. if (pulseaudio_context_ready() < 0)
  315. return;
  316. pulseaudio_lock();
  317. pa_stream_set_write_callback(p, cb, userdata);
  318. pulseaudio_unlock();
  319. }
  320. void pulseaudio_set_underflow_callback(pa_stream *p, pa_stream_notify_cb_t cb,
  321. void *userdata)
  322. {
  323. if (pulseaudio_context_ready() < 0)
  324. return;
  325. pulseaudio_lock();
  326. pa_stream_set_underflow_callback(p, cb, userdata);
  327. pulseaudio_unlock();
  328. }