pulseaudio-wrapper.c 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  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, void *userdata)
  26. {
  27. UNUSED_PARAMETER(c);
  28. struct pulseaudio_default_output *d = (struct pulseaudio_default_output *)userdata;
  29. d->default_sink_name = bstrdup(i->default_sink_name);
  30. pulseaudio_signal(0);
  31. }
  32. void get_default_id(char **id)
  33. {
  34. pulseaudio_init();
  35. struct pulseaudio_default_output *pdo = bzalloc(sizeof(struct pulseaudio_default_output));
  36. pulseaudio_get_server_info((pa_server_info_cb_t)pulseaudio_default_devices, (void *)pdo);
  37. if (!pdo->default_sink_name || !*pdo->default_sink_name) {
  38. *id = bzalloc(1);
  39. } else {
  40. *id = bzalloc(strlen(pdo->default_sink_name) + 9);
  41. strcat(*id, pdo->default_sink_name);
  42. bfree(pdo->default_sink_name);
  43. }
  44. bfree(pdo);
  45. pulseaudio_unref();
  46. }
  47. /**
  48. * Checks whether a sound source (id1) is the .monitor device for the
  49. * selected monitoring output (id2).
  50. */
  51. bool devices_match(const char *id1, const char *id2)
  52. {
  53. bool match;
  54. char *name_default = NULL;
  55. char *name1 = NULL;
  56. char *name2 = NULL;
  57. if (!id1 || !id2)
  58. return false;
  59. if (strcmp(id1, "default") == 0) {
  60. get_default_id(&name_default);
  61. name1 = bzalloc(strlen(name_default) + 9);
  62. strcat(name1, name_default);
  63. strcat(name1, ".monitor");
  64. } else {
  65. name1 = bstrdup(id1);
  66. }
  67. if (strcmp(id2, "default") == 0) {
  68. if (!name_default)
  69. get_default_id(&name_default);
  70. name2 = bzalloc(strlen(name_default) + 9);
  71. strcat(name2, name_default);
  72. strcat(name2, ".monitor");
  73. } else {
  74. name2 = bzalloc(strlen(id2) + 9);
  75. strcat(name2, id2);
  76. strcat(name2, ".monitor");
  77. }
  78. match = strcmp(name1, name2) == 0;
  79. bfree(name_default);
  80. bfree(name1);
  81. bfree(name2);
  82. return match;
  83. }
  84. /**
  85. * context status change callback
  86. *
  87. * @todo this is currently a noop, we want to reconnect here if the connection
  88. * is lost ...
  89. */
  90. static void pulseaudio_context_state_changed(pa_context *c, void *userdata)
  91. {
  92. UNUSED_PARAMETER(userdata);
  93. UNUSED_PARAMETER(c);
  94. pulseaudio_signal(0);
  95. }
  96. /**
  97. * get the default properties
  98. */
  99. static pa_proplist *pulseaudio_properties()
  100. {
  101. pa_proplist *p = pa_proplist_new();
  102. pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, "OBS");
  103. pa_proplist_sets(p, PA_PROP_APPLICATION_ICON_NAME, "obs");
  104. pa_proplist_sets(p, PA_PROP_MEDIA_ROLE, "production");
  105. return p;
  106. }
  107. /**
  108. * Initialize the pulse audio context with properties and callback
  109. */
  110. static void pulseaudio_init_context()
  111. {
  112. pulseaudio_lock();
  113. pa_proplist *p = pulseaudio_properties();
  114. pulseaudio_context =
  115. pa_context_new_with_proplist(pa_threaded_mainloop_get_api(pulseaudio_mainloop), "OBS-Monitor", p);
  116. pa_context_set_state_callback(pulseaudio_context, pulseaudio_context_state_changed, NULL);
  117. pa_context_connect(pulseaudio_context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL);
  118. pa_proplist_free(p);
  119. pulseaudio_unlock();
  120. }
  121. /**
  122. * wait for context to be ready
  123. */
  124. static int_fast32_t pulseaudio_context_ready()
  125. {
  126. pulseaudio_lock();
  127. if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(pulseaudio_context))) {
  128. pulseaudio_unlock();
  129. return -1;
  130. }
  131. while (pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY)
  132. pulseaudio_wait();
  133. pulseaudio_unlock();
  134. return 0;
  135. }
  136. int_fast32_t pulseaudio_init()
  137. {
  138. pthread_mutex_lock(&pulseaudio_mutex);
  139. if (pulseaudio_refs == 0) {
  140. pulseaudio_mainloop = pa_threaded_mainloop_new();
  141. pa_threaded_mainloop_start(pulseaudio_mainloop);
  142. pulseaudio_init_context();
  143. }
  144. pulseaudio_refs++;
  145. pthread_mutex_unlock(&pulseaudio_mutex);
  146. return 0;
  147. }
  148. void pulseaudio_unref()
  149. {
  150. pthread_mutex_lock(&pulseaudio_mutex);
  151. if (--pulseaudio_refs == 0) {
  152. pulseaudio_lock();
  153. if (pulseaudio_context != NULL) {
  154. pa_context_disconnect(pulseaudio_context);
  155. pa_context_unref(pulseaudio_context);
  156. pulseaudio_context = NULL;
  157. }
  158. pulseaudio_unlock();
  159. if (pulseaudio_mainloop != NULL) {
  160. pa_threaded_mainloop_stop(pulseaudio_mainloop);
  161. pa_threaded_mainloop_free(pulseaudio_mainloop);
  162. pulseaudio_mainloop = NULL;
  163. }
  164. }
  165. pthread_mutex_unlock(&pulseaudio_mutex);
  166. }
  167. void pulseaudio_lock()
  168. {
  169. pa_threaded_mainloop_lock(pulseaudio_mainloop);
  170. }
  171. void pulseaudio_unlock()
  172. {
  173. pa_threaded_mainloop_unlock(pulseaudio_mainloop);
  174. }
  175. void pulseaudio_wait()
  176. {
  177. pa_threaded_mainloop_wait(pulseaudio_mainloop);
  178. }
  179. void pulseaudio_signal(int wait_for_accept)
  180. {
  181. pa_threaded_mainloop_signal(pulseaudio_mainloop, wait_for_accept);
  182. }
  183. void pulseaudio_accept()
  184. {
  185. pa_threaded_mainloop_accept(pulseaudio_mainloop);
  186. }
  187. int_fast32_t pulseaudio_get_source_info_list(pa_source_info_cb_t cb, void *userdata)
  188. {
  189. if (pulseaudio_context_ready() < 0)
  190. return -1;
  191. pulseaudio_lock();
  192. pa_operation *op = pa_context_get_source_info_list(pulseaudio_context, cb, userdata);
  193. if (!op) {
  194. pulseaudio_unlock();
  195. return -1;
  196. }
  197. while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
  198. pulseaudio_wait();
  199. pa_operation_unref(op);
  200. pulseaudio_unlock();
  201. return 0;
  202. }
  203. int_fast32_t pulseaudio_get_source_info(pa_source_info_cb_t cb, const char *name, void *userdata)
  204. {
  205. if (pulseaudio_context_ready() < 0)
  206. return -1;
  207. pulseaudio_lock();
  208. pa_operation *op = pa_context_get_source_info_by_name(pulseaudio_context, name, cb, userdata);
  209. if (!op) {
  210. pulseaudio_unlock();
  211. return -1;
  212. }
  213. while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
  214. pulseaudio_wait();
  215. pa_operation_unref(op);
  216. pulseaudio_unlock();
  217. return 0;
  218. }
  219. int_fast32_t pulseaudio_get_sink_info_list(pa_sink_info_cb_t cb, void *userdata)
  220. {
  221. if (pulseaudio_context_ready() < 0)
  222. return -1;
  223. pulseaudio_lock();
  224. pa_operation *op = pa_context_get_sink_info_list(pulseaudio_context, cb, userdata);
  225. if (!op) {
  226. pulseaudio_unlock();
  227. return -1;
  228. }
  229. while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
  230. pulseaudio_wait();
  231. pa_operation_unref(op);
  232. pulseaudio_unlock();
  233. return 0;
  234. }
  235. int_fast32_t pulseaudio_get_sink_info(pa_sink_info_cb_t cb, const char *name, void *userdata)
  236. {
  237. if (pulseaudio_context_ready() < 0)
  238. return -1;
  239. pulseaudio_lock();
  240. pa_operation *op = pa_context_get_sink_info_by_name(pulseaudio_context, name, cb, userdata);
  241. if (!op) {
  242. pulseaudio_unlock();
  243. return -1;
  244. }
  245. while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
  246. pulseaudio_wait();
  247. pa_operation_unref(op);
  248. pulseaudio_unlock();
  249. return 0;
  250. }
  251. int_fast32_t pulseaudio_get_server_info(pa_server_info_cb_t cb, void *userdata)
  252. {
  253. if (pulseaudio_context_ready() < 0)
  254. return -1;
  255. pulseaudio_lock();
  256. pa_operation *op = pa_context_get_server_info(pulseaudio_context, cb, userdata);
  257. if (!op) {
  258. pulseaudio_unlock();
  259. return -1;
  260. }
  261. while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
  262. pulseaudio_wait();
  263. pa_operation_unref(op);
  264. pulseaudio_unlock();
  265. return 0;
  266. }
  267. pa_stream *pulseaudio_stream_new(const char *name, const pa_sample_spec *ss, const pa_channel_map *map)
  268. {
  269. if (pulseaudio_context_ready() < 0)
  270. return NULL;
  271. pulseaudio_lock();
  272. pa_proplist *p = pulseaudio_properties();
  273. pa_stream *s = pa_stream_new_with_proplist(pulseaudio_context, name, ss, map, p);
  274. pa_proplist_free(p);
  275. pulseaudio_unlock();
  276. return s;
  277. }
  278. int_fast32_t pulseaudio_connect_playback(pa_stream *s, const char *name, const pa_buffer_attr *attr,
  279. pa_stream_flags_t flags)
  280. {
  281. if (pulseaudio_context_ready() < 0)
  282. return -1;
  283. size_t dev_len = strlen(name);
  284. char *device = bzalloc(dev_len + 1);
  285. memcpy(device, name, dev_len);
  286. pulseaudio_lock();
  287. int_fast32_t ret = pa_stream_connect_playback(s, device, attr, flags, NULL, NULL);
  288. pulseaudio_unlock();
  289. bfree(device);
  290. return ret;
  291. }
  292. void pulseaudio_write_callback(pa_stream *p, pa_stream_request_cb_t cb, void *userdata)
  293. {
  294. if (pulseaudio_context_ready() < 0)
  295. return;
  296. pulseaudio_lock();
  297. pa_stream_set_write_callback(p, cb, userdata);
  298. pulseaudio_unlock();
  299. }
  300. void pulseaudio_set_underflow_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata)
  301. {
  302. if (pulseaudio_context_ready() < 0)
  303. return;
  304. pulseaudio_lock();
  305. pa_stream_set_underflow_callback(p, cb, userdata);
  306. pulseaudio_unlock();
  307. }