pulseaudio-wrapper.c 7.6 KB

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