Advertisement
jpenguin

obs-pule-monitor

Jul 17th, 2017
684
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 36.08 KB | None | 0 0
  1. diff --git a/UI/adv-audio-control.cpp b/UI/adv-audio-control.cpp
  2. index b7bfd4d08..531b00173 100644
  3. --- a/UI/adv-audio-control.cpp
  4. +++ b/UI/adv-audio-control.cpp
  5. @@ -32,7 +32,7 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *layout, obs_source_t *source_)
  6. volume = new QSpinBox();
  7. forceMono = new QCheckBox();
  8. panning = new QSlider(Qt::Horizontal);
  9. -#if defined(_WIN32) || defined(__APPLE__)
  10. +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO
  11. monitoringType = new QComboBox();
  12. #endif
  13. syncOffset = new QSpinBox();
  14. @@ -93,7 +93,7 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *layout, obs_source_t *source_)
  15. syncOffset->setValue(int(cur_sync / NSEC_PER_MSEC));
  16.  
  17. int idx;
  18. -#if defined(_WIN32) || defined(__APPLE__)
  19. +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO
  20. monitoringType->addItem(QTStr("Basic.AdvAudio.Monitoring.None"),
  21. (int)OBS_MONITORING_TYPE_NONE);
  22. monitoringType->addItem(QTStr("Basic.AdvAudio.Monitoring.MonitorOnly"),
  23. @@ -138,7 +138,7 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *layout, obs_source_t *source_)
  24. this, SLOT(panningChanged(int)));
  25. QWidget::connect(syncOffset, SIGNAL(valueChanged(int)),
  26. this, SLOT(syncOffsetChanged(int)));
  27. -#if defined(_WIN32) || defined(__APPLE__)
  28. +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO
  29. QWidget::connect(monitoringType, SIGNAL(currentIndexChanged(int)),
  30. this, SLOT(monitoringTypeChanged(int)));
  31. #endif
  32. @@ -163,7 +163,7 @@ OBSAdvAudioCtrl::OBSAdvAudioCtrl(QGridLayout *layout, obs_source_t *source_)
  33. layout->addWidget(forceMonoContainer, lastRow, idx++);
  34. layout->addWidget(panningContainer, lastRow, idx++);
  35. layout->addWidget(syncOffset, lastRow, idx++);
  36. -#if defined(_WIN32) || defined(__APPLE__)
  37. +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO
  38. layout->addWidget(monitoringType, lastRow, idx++);
  39. #endif
  40. layout->addWidget(mixerContainer, lastRow, idx++);
  41. @@ -178,7 +178,7 @@ OBSAdvAudioCtrl::~OBSAdvAudioCtrl()
  42. forceMonoContainer->deleteLater();
  43. panningContainer->deleteLater();
  44. syncOffset->deleteLater();
  45. -#if defined(_WIN32) || defined(__APPLE__)
  46. +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO
  47. monitoringType->deleteLater();
  48. #endif
  49. mixerContainer->deleteLater();
  50. diff --git a/UI/window-basic-adv-audio.cpp b/UI/window-basic-adv-audio.cpp
  51. index 33fe0cb5a..2894f6bf2 100644
  52. --- a/UI/window-basic-adv-audio.cpp
  53. +++ b/UI/window-basic-adv-audio.cpp
  54. @@ -42,7 +42,7 @@ OBSBasicAdvAudio::OBSBasicAdvAudio(QWidget *parent)
  55. label = new QLabel(QTStr("Basic.AdvAudio.SyncOffset"));
  56. label->setAlignment(Qt::AlignHCenter);
  57. mainLayout->addWidget(label, 0, idx++);
  58. -#if defined(_WIN32) || defined(__APPLE__)
  59. +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO
  60. label = new QLabel(QTStr("Basic.AdvAudio.Monitoring"));
  61. label->setAlignment(Qt::AlignHCenter);
  62. mainLayout->addWidget(label, 0, idx++);
  63. diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp
  64. index 52f10b9b8..5785b1543 100644
  65. --- a/UI/window-basic-settings.cpp
  66. +++ b/UI/window-basic-settings.cpp
  67. @@ -410,7 +410,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
  68. HookWidget(ui->colorRange, COMBO_CHANGED, ADV_CHANGED);
  69. HookWidget(ui->disableOSXVSync, CHECK_CHANGED, ADV_CHANGED);
  70. HookWidget(ui->resetOSXVSync, CHECK_CHANGED, ADV_CHANGED);
  71. -#if defined(_WIN32) || defined(__APPLE__)
  72. +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO
  73. HookWidget(ui->monitoringDevice, COMBO_CHANGED, ADV_CHANGED);
  74. #endif
  75. #ifdef _WIN32
  76. @@ -431,15 +431,11 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
  77. HookWidget(ui->enableNewSocketLoop, CHECK_CHANGED, ADV_CHANGED);
  78. HookWidget(ui->enableLowLatencyMode, CHECK_CHANGED, ADV_CHANGED);
  79.  
  80. -#if !defined(_WIN32) && !defined(__APPLE__)
  81. - delete ui->monitoringDevice;
  82. - delete ui->monitoringDeviceLabel;
  83. - delete ui->advAudioGroupBox;
  84. +#if !defined(_WIN32) && !defined(__APPLE__) && !HAVE_PULSEAUDIO
  85. delete ui->enableAutoUpdates;
  86. - ui->monitoringDevice = nullptr;
  87. - ui->monitoringDeviceLabel = nullptr;
  88. - ui->advAudioGroupBox = nullptr;
  89. + delete ui->advAudioGroupBox;
  90. ui->enableAutoUpdates = nullptr;
  91. + ui->advAudioGroupBox = nullptr;
  92. #endif
  93.  
  94. #ifdef _WIN32
  95. @@ -484,7 +480,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
  96. delete ui->advancedGeneralGroupBox;
  97. delete ui->enableNewSocketLoop;
  98. delete ui->enableLowLatencyMode;
  99. -#ifdef __APPLE__
  100. +#if defined(__APPLE__) || HAVE_PULSEAUDIO
  101. delete ui->disableAudioDucking;
  102. #endif
  103. ui->rendererLabel = nullptr;
  104. @@ -496,7 +492,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
  105. ui->advancedGeneralGroupBox = nullptr;
  106. ui->enableNewSocketLoop = nullptr;
  107. ui->enableLowLatencyMode = nullptr;
  108. -#ifdef __APPLE__
  109. +#if defined(__APPLE__) || HAVE_PULSEAUDIO
  110. ui->disableAudioDucking = nullptr;
  111. #endif
  112. #endif
  113. @@ -583,7 +579,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
  114.  
  115. FillSimpleRecordingValues();
  116. FillSimpleStreamingValues();
  117. -#if defined(_WIN32) || defined(__APPLE__)
  118. +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO
  119. FillAudioMonitoringDevices();
  120. #endif
  121.  
  122. @@ -2025,7 +2021,7 @@ void OBSBasicSettings::LoadAdvancedSettings()
  123. "Video", "ColorSpace");
  124. const char *videoColorRange = config_get_string(main->Config(),
  125. "Video", "ColorRange");
  126. -#if defined(_WIN32) || defined(__APPLE__)
  127. +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO
  128. const char *monDevName = config_get_string(main->Config(), "Audio",
  129. "MonitoringDeviceName");
  130. const char *monDevId = config_get_string(main->Config(), "Audio",
  131. @@ -2059,7 +2055,7 @@ void OBSBasicSettings::LoadAdvancedSettings()
  132.  
  133. LoadRendererList();
  134.  
  135. -#if defined(_WIN32) || defined(__APPLE__)
  136. +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO
  137. if (!SetComboByValue(ui->monitoringDevice, monDevId))
  138. SetInvalidValue(ui->monitoringDevice, monDevName, monDevId);
  139. #endif
  140. @@ -2622,7 +2618,7 @@ void OBSBasicSettings::SaveAdvancedSettings()
  141. SaveCombo(ui->colorFormat, "Video", "ColorFormat");
  142. SaveCombo(ui->colorSpace, "Video", "ColorSpace");
  143. SaveComboData(ui->colorRange, "Video", "ColorRange");
  144. -#if defined(_WIN32) || defined(__APPLE__)
  145. +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO
  146. SaveCombo(ui->monitoringDevice, "Audio", "MonitoringDeviceName");
  147. SaveComboData(ui->monitoringDevice, "Audio", "MonitoringDeviceId");
  148. #endif
  149. @@ -2648,7 +2644,7 @@ void OBSBasicSettings::SaveAdvancedSettings()
  150. SaveSpinBox(ui->reconnectMaxRetries, "Output", "MaxRetries");
  151. SaveComboData(ui->bindToIP, "Output", "BindIP");
  152.  
  153. -#if defined(_WIN32) || defined(__APPLE__)
  154. +#if defined(_WIN32) || defined(__APPLE__) || HAVE_PULSEAUDIO
  155. QString newDevice = ui->monitoringDevice->currentData().toString();
  156.  
  157. if (lastMonitoringDevice != newDevice) {
  158. diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt
  159. index cd2b80e1e..e86bb8f2b 100644
  160. --- a/libobs/CMakeLists.txt
  161. +++ b/libobs/CMakeLists.txt
  162. @@ -12,12 +12,20 @@ if (NOT "${FFMPEG_AVCODEC_LIBRARIES}" STREQUAL "")
  163. endif()
  164.  
  165. if(UNIX)
  166. + find_package(PulseAudio)
  167. + if (NOT "${PULSEAUDIO_LIBRARY}" STREQUAL "")
  168. + message(STATUS "Found PulseAudio - Audio Monitor enabled")
  169. + set(HAVE_PULSEAUDIO "1")
  170. + else()
  171. + set(HAVE_PULSEAUDIO "0")
  172. + endif()
  173. find_package(DBus QUIET)
  174. if (NOT APPLE)
  175. find_package(X11_XCB REQUIRED)
  176. endif()
  177. else()
  178. set(HAVE_DBUS "0")
  179. + set(HAVE_PULSEAUDIO "0")
  180. endif()
  181.  
  182. find_package(ImageMagick QUIET COMPONENTS MagickCore)
  183. @@ -145,12 +153,22 @@ elseif(UNIX)
  184. util/threading-posix.c
  185. util/pipe-posix.c
  186. util/platform-nix.c)
  187. +
  188. set(libobs_PLATFORM_HEADERS
  189. util/threading-posix.h)
  190. - set(libobs_audio_monitoring_SOURCES
  191. - audio-monitoring/null/null-audio-monitoring.c
  192. - )
  193.  
  194. + if(HAVE_PULSEAUDIO)
  195. + set(libobs_audio_monitoring_HEADERS
  196. + audio-monitoring/pulse/pulseaudio-wrapper.h)
  197. +
  198. + set(libobs_audio_monitoring_SOURCES
  199. + audio-monitoring/pulse/pulseaudio-wrapper.c
  200. + audio-monitoring/pulse/pulseaudio-enum-devices.c
  201. + audio-monitoring/pulse/pulseaudio-output.c)
  202. + else()
  203. + set(libobs_audio_monitoring_SOURCES
  204. + audio-monitoring/null/null-audio-monitoring.c)
  205. + endif()
  206. if(DBUS_FOUND)
  207. set(libobs_PLATFORM_SOURCES ${libobs_PLATFORM_SOURCES}
  208. util/platform-nix-dbus.c)
  209. @@ -168,6 +186,12 @@ elseif(UNIX)
  210. ${libobs_PLATFORM_DEPS}
  211. ${X11_XCB_LIBRARIES})
  212.  
  213. + if(HAVE_PULSEAUDIO)
  214. + set(libobs_PLATFORM_DEPS
  215. + ${libobs_PLATFORM_DEPS}
  216. + ${PULSEAUDIO_LIBRARY})
  217. + endif()
  218. +
  219. if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
  220. # use the sysinfo compatibility library on bsd
  221. find_package(Libsysinfo REQUIRED)
  222. @@ -358,7 +382,9 @@ set(libobs_SOURCES
  223. ${libobs_graphics_SOURCES}
  224. ${libobs_mediaio_SOURCES}
  225. ${libobs_util_SOURCES}
  226. - ${libobs_libobs_SOURCES})
  227. + ${libobs_libobs_SOURCES}
  228. + ${libobs_audio_monitoring_SOURCES}
  229. + )
  230.  
  231. set(libobs_HEADERS
  232. ${libobs_config_HEADERS}
  233. @@ -367,7 +393,6 @@ set(libobs_HEADERS
  234. ${libobs_mediaio_HEADERS}
  235. ${libobs_util_HEADERS}
  236. ${libobs_libobs_HEADERS}
  237. - ${libobs_audio_monitoring_SOURCES}
  238. ${libobs_audio_monitoring_HEADERS}
  239. )
  240.  
  241. diff --git a/libobs/audio-monitoring/null/null-audio-monitoring.c b/libobs/audio-monitoring/null/null-audio-monitoring.c
  242. index 9d7b2a4ed..7926e84f6 100644
  243. --- a/libobs/audio-monitoring/null/null-audio-monitoring.c
  244. +++ b/libobs/audio-monitoring/null/null-audio-monitoring.c
  245. @@ -1,4 +1,4 @@
  246. -#include "../../obs-internal.h"
  247. +#include <obs-internal.h>
  248.  
  249. void obs_enum_audio_monitoring_devices(obs_enum_audio_device_cb cb, void *data)
  250. {
  251. diff --git a/libobs/audio-monitoring/pulse/pulseaudio-enum-devices.c b/libobs/audio-monitoring/pulse/pulseaudio-enum-devices.c
  252. new file mode 100644
  253. index 000000000..f59882997
  254. --- /dev/null
  255. +++ b/libobs/audio-monitoring/pulse/pulseaudio-enum-devices.c
  256. @@ -0,0 +1,33 @@
  257. +#include <obs-internal.h>
  258. +#include "pulseaudio-wrapper.h"
  259. +
  260. +static void pulseaudio_output_info(pa_context *c, const pa_source_info *i,
  261. + int eol, void *userdata)
  262. +{
  263. + UNUSED_PARAMETER(c);
  264. + if (eol != 0 || i->monitor_of_sink == PA_INVALID_INDEX)
  265. + goto skip;
  266. +
  267. + struct enum_cb *ecb = (struct enum_cb *) userdata;
  268. + if (ecb->cont)
  269. + ecb->cont = ecb->cb(ecb->data, i->description, i->name);
  270. +
  271. +skip:
  272. + pulseaudio_signal(0);
  273. +}
  274. +
  275. +void obs_enum_audio_monitoring_devices(obs_enum_audio_device_cb cb,
  276. + void *data)
  277. +{
  278. + struct enum_cb *ecb = bzalloc(sizeof(struct enum_cb));
  279. + ecb->cb = cb;
  280. + ecb->data = data;
  281. + ecb->cont = 1;
  282. +
  283. + pulseaudio_init();
  284. + pa_source_info_cb_t pa_cb = pulseaudio_output_info;
  285. + pulseaudio_get_source_info_list(pa_cb, (void *) ecb);
  286. + pulseaudio_unref();
  287. +
  288. + bfree(ecb);
  289. +}
  290. diff --git a/libobs/audio-monitoring/pulse/pulseaudio-output.c b/libobs/audio-monitoring/pulse/pulseaudio-output.c
  291. new file mode 100644
  292. index 000000000..b182eb088
  293. --- /dev/null
  294. +++ b/libobs/audio-monitoring/pulse/pulseaudio-output.c
  295. @@ -0,0 +1,469 @@
  296. +#include "obs-internal.h"
  297. +#include "pulseaudio-wrapper.h"
  298. +
  299. +#define PULSE_DATA(voidptr) struct audio_monitor *data = voidptr;
  300. +#define blog(level, msg, ...) blog(level, "pulse-am: " msg, ##__VA_ARGS__)
  301. +
  302. +struct audio_monitor {
  303. + obs_source_t *source;
  304. + pa_stream *stream;
  305. + char *device;
  306. +
  307. + enum speaker_layout speakers;
  308. + pa_sample_format_t format;
  309. + uint_fast32_t samples_per_sec;
  310. + uint_fast32_t bytes_per_frame;
  311. + uint_fast8_t channels;
  312. +
  313. + uint_fast32_t packets;
  314. + uint_fast64_t frames;
  315. +
  316. + struct circlebuf new_data;
  317. + audio_resampler_t *resampler;
  318. + size_t buffer_size;
  319. + size_t bytesRemaining;
  320. +
  321. + size_t bytes_per_channel;
  322. + bool ignore : 1;
  323. + pthread_mutex_t playback_mutex;
  324. +};
  325. +
  326. +static enum speaker_layout pulseaudio_channels_to_obs_speakers(
  327. + uint_fast32_t channels)
  328. +{
  329. + if ((channels >= 1 && channels <= 6) || channels == 8)
  330. + return (enum speaker_layout) channels;
  331. + return SPEAKERS_UNKNOWN;
  332. +}
  333. +
  334. +static enum audio_format pulseaudio_to_obs_audio_format(
  335. + pa_sample_format_t format)
  336. +{
  337. + switch (format) {
  338. + case PA_SAMPLE_U8:
  339. + return AUDIO_FORMAT_U8BIT;
  340. + case PA_SAMPLE_S16LE:
  341. + return AUDIO_FORMAT_16BIT;
  342. + case PA_SAMPLE_S32LE:
  343. + return AUDIO_FORMAT_32BIT;
  344. + case PA_SAMPLE_FLOAT32LE:
  345. + return AUDIO_FORMAT_FLOAT;
  346. + default:
  347. + return AUDIO_FORMAT_UNKNOWN;
  348. + }
  349. +}
  350. +
  351. +static void process_byte(void *p, size_t frames, size_t channels, float vol)
  352. +{
  353. + register char *cur = (char *) p;
  354. + register char *end = cur + frames * channels;
  355. +
  356. + while (cur < end)
  357. + *(cur++) *= vol;
  358. +}
  359. +
  360. +static void process_short(void *p, size_t frames, size_t channels, float vol)
  361. +{
  362. + register short *cur = (short *) p;
  363. + register short *end = cur + frames * channels;
  364. +
  365. + while (cur < end)
  366. + *(cur++) *= vol;
  367. +}
  368. +
  369. +static void process_float(void *p, size_t frames, size_t channels, float vol)
  370. +{
  371. + register float *cur = (float *) p;
  372. + register float *end = cur + frames * channels;
  373. +
  374. + while (cur < end)
  375. + *(cur++) *= vol;
  376. +}
  377. +
  378. +void process_volume(const struct audio_monitor *monitor, float vol,
  379. + uint8_t *const *resample_data, uint32_t resample_frames)
  380. +{
  381. + switch (monitor->bytes_per_channel) {
  382. + case 1:
  383. + process_byte(resample_data[0], resample_frames,
  384. + monitor->channels, vol);
  385. + break;
  386. + case 2:
  387. + process_short(resample_data[0], resample_frames,
  388. + monitor->channels, vol);
  389. + break;
  390. + default:
  391. + process_float(resample_data[0], resample_frames,
  392. + monitor->channels, vol);
  393. + break;
  394. + }
  395. +}
  396. +
  397. +static void do_stream_write(void *param)
  398. +{
  399. + PULSE_DATA(param);
  400. + uint8_t *buffer = NULL;
  401. +
  402. + while (data->new_data.size >= data->buffer_size &&
  403. + data->bytesRemaining > 0) {
  404. + size_t bytesToFill = data->buffer_size;
  405. +
  406. + if (bytesToFill > data->bytesRemaining)
  407. + bytesToFill = data->bytesRemaining;
  408. +
  409. + pa_stream_begin_write(data->stream, (void **) &buffer,
  410. + &bytesToFill);
  411. +
  412. + circlebuf_pop_front(&data->new_data, buffer, bytesToFill);
  413. +
  414. + pulseaudio_lock();
  415. + pa_stream_write(data->stream, buffer, bytesToFill, NULL,
  416. + 0LL, PA_SEEK_RELATIVE);
  417. + pulseaudio_unlock();
  418. +
  419. + data->bytesRemaining -= bytesToFill;
  420. + }
  421. +}
  422. +
  423. +static void on_audio_playback(void *param, obs_source_t *source,
  424. + const struct audio_data *audio_data, bool muted)
  425. +{
  426. + struct audio_monitor *monitor = param;
  427. + float vol = source->user_volume;
  428. + size_t bytes;
  429. +
  430. + uint8_t *resample_data[MAX_AV_PLANES];
  431. + uint32_t resample_frames;
  432. + uint64_t ts_offset;
  433. + bool success;
  434. +
  435. + if (pthread_mutex_trylock(&monitor->playback_mutex) != 0)
  436. + return;
  437. +
  438. + if (os_atomic_load_long(&source->activate_refs) == 0)
  439. + goto unlock;
  440. +
  441. + success = audio_resampler_resample(monitor->resampler, resample_data,
  442. + &resample_frames, &ts_offset,
  443. + (const uint8_t *const *) audio_data->data,
  444. + (uint32_t) audio_data->frames);
  445. +
  446. + if (!success)
  447. + goto unlock;
  448. +
  449. + bytes = monitor->bytes_per_frame * resample_frames;
  450. +
  451. + if (muted) {
  452. + memset(resample_data[0], 0, bytes);
  453. + } else {
  454. + if (!close_float(vol, 1.0f, EPSILON)) {
  455. + process_volume(monitor, vol, resample_data,
  456. + resample_frames);
  457. + }
  458. + }
  459. +
  460. + circlebuf_push_back(&monitor->new_data, resample_data[0], bytes);
  461. + monitor->packets++;
  462. + monitor->frames += resample_frames;
  463. +
  464. +unlock:
  465. + pthread_mutex_unlock(&monitor->playback_mutex);
  466. + do_stream_write(param);
  467. +}
  468. +
  469. +static void pulseaudio_stream_write(pa_stream *p, size_t nbytes, void *userdata)
  470. +{
  471. + UNUSED_PARAMETER(p);
  472. + PULSE_DATA(userdata);
  473. +
  474. + pthread_mutex_lock(&data->playback_mutex);
  475. + data->bytesRemaining += nbytes;
  476. + pthread_mutex_unlock(&data->playback_mutex);
  477. +
  478. + pulseaudio_signal(0);
  479. +}
  480. +
  481. +static void pulseaudio_server_info(pa_context *c, const pa_server_info *i,
  482. + void *userdata)
  483. +{
  484. + UNUSED_PARAMETER(c);
  485. + UNUSED_PARAMETER(userdata);
  486. +
  487. + blog(LOG_INFO, "Server name: '%s %s'", i->server_name,
  488. + i->server_version);
  489. +
  490. + pulseaudio_signal(0);
  491. +}
  492. +
  493. +static void pulseaudio_source_info(pa_context *c, const pa_source_info *i,
  494. + int eol, void *userdata)
  495. +{
  496. + UNUSED_PARAMETER(c);
  497. + PULSE_DATA(userdata);
  498. + // An error occured
  499. + if (eol < 0) {
  500. + data->format = PA_SAMPLE_INVALID;
  501. + goto skip;
  502. + }
  503. + // Terminating call for multi instance callbacks
  504. + if (eol > 0)
  505. + goto skip;
  506. +
  507. + blog(LOG_INFO, "Audio format: %s, %"PRIu32" Hz, %"PRIu8" channels",
  508. + pa_sample_format_to_string(i->sample_spec.format),
  509. + i->sample_spec.rate, i->sample_spec.channels);
  510. +
  511. + pa_sample_format_t format = i->sample_spec.format;
  512. + if (pulseaudio_to_obs_audio_format(format) == AUDIO_FORMAT_UNKNOWN) {
  513. + format = PA_SAMPLE_S16LE;
  514. +
  515. + blog(LOG_INFO, "Sample format %s not supported by OBS,"
  516. + "using %s instead for recording",
  517. + pa_sample_format_to_string(
  518. + i->sample_spec.format),
  519. + pa_sample_format_to_string(format));
  520. + }
  521. +
  522. + uint8_t channels = i->sample_spec.channels;
  523. + if (pulseaudio_channels_to_obs_speakers(channels) == SPEAKERS_UNKNOWN) {
  524. + channels = 2;
  525. +
  526. + blog(LOG_INFO, "%c channels not supported by OBS,"
  527. + "using %c instead for recording",
  528. + i->sample_spec.channels,
  529. + channels);
  530. + }
  531. +
  532. + data->format = format;
  533. + data->samples_per_sec = i->sample_spec.rate;
  534. + data->channels = channels;
  535. +skip:
  536. + pulseaudio_signal(0);
  537. +}
  538. +
  539. +static void pulseaudio_stop_playback(struct audio_monitor *monitor)
  540. +{
  541. + if (monitor->stream) {
  542. + pa_stream_disconnect(monitor->stream);
  543. + pa_stream_unref(monitor->stream);
  544. + monitor->stream = NULL;
  545. + }
  546. +
  547. + blog(LOG_INFO, "Stopped Monitoring in '%s'", monitor->device);
  548. + blog(LOG_INFO, "Got %"PRIuFAST32" packets with %"PRIuFAST64" frames",
  549. + monitor->packets, monitor->frames);
  550. +
  551. + monitor->packets = 0;
  552. + monitor->frames = 0;
  553. +}
  554. +
  555. +static bool audio_monitor_init(struct audio_monitor *monitor,
  556. + obs_source_t *source)
  557. +{
  558. + pthread_mutex_init_value(&monitor->playback_mutex);
  559. +
  560. + monitor->source = source;
  561. +
  562. + const char *id = obs->audio.monitoring_device_id;
  563. + if (!id)
  564. + return false;
  565. +
  566. + if (source->info.output_flags & OBS_SOURCE_DO_NOT_SELF_MONITOR) {
  567. + obs_data_t *s = obs_source_get_settings(source);
  568. + const char *s_dev_id = obs_data_get_string(s, "device_id");
  569. + bool match = devices_match(s_dev_id, id);
  570. + obs_data_release(s);
  571. +
  572. + if (match) {
  573. + monitor->ignore = true;
  574. + blog(LOG_INFO, "Prevented feedback-loop in '%s'",
  575. + s_dev_id);
  576. + return true;
  577. + }
  578. + }
  579. +
  580. + pulseaudio_init();
  581. +
  582. + if (strcmp(id, "default") == 0)
  583. + get_default_id(&monitor->device);
  584. + else
  585. + monitor->device = bstrdup(id);
  586. +
  587. + if (!monitor->device)
  588. + return false;
  589. +
  590. + if (pulseaudio_get_server_info(pulseaudio_server_info,
  591. + (void *) monitor) < 0) {
  592. + blog(LOG_ERROR, "Unable to get server info !");
  593. + return false;
  594. + }
  595. +
  596. + if (pulseaudio_get_source_info(pulseaudio_source_info, monitor->device,
  597. + (void *) monitor) < 0) {
  598. + blog(LOG_ERROR, "Unable to get source info !");
  599. + return false;
  600. + }
  601. + if (monitor->format == PA_SAMPLE_INVALID) {
  602. + blog(LOG_ERROR,
  603. + "An error occurred while getting the source info!");
  604. + return false;
  605. + }
  606. +
  607. + pa_sample_spec spec;
  608. + spec.format = monitor->format;
  609. + spec.rate = (uint32_t) monitor->samples_per_sec;
  610. + spec.channels = monitor->channels;
  611. +
  612. + if (!pa_sample_spec_valid(&spec)) {
  613. + blog(LOG_ERROR, "Sample spec is not valid");
  614. + return false;
  615. + }
  616. +
  617. + const struct audio_output_info *info = audio_output_get_info(
  618. + obs->audio.audio);
  619. +
  620. + struct resample_info from = {
  621. + .samples_per_sec = info->samples_per_sec,
  622. + .speakers = info->speakers,
  623. + .format = AUDIO_FORMAT_FLOAT_PLANAR
  624. + };
  625. + struct resample_info to = {
  626. + .samples_per_sec = (uint32_t) monitor->samples_per_sec,
  627. + .speakers = pulseaudio_channels_to_obs_speakers(
  628. + monitor->channels),
  629. + .format = pulseaudio_to_obs_audio_format(
  630. + monitor->format)
  631. + };
  632. +
  633. + monitor->resampler = audio_resampler_create(&to, &from);
  634. + if (!monitor->resampler) {
  635. + blog(LOG_WARNING, "%s: %s", __FUNCTION__,
  636. + "Failed to create resampler");
  637. + return false;
  638. + }
  639. +
  640. + monitor->bytes_per_channel = get_audio_bytes_per_channel(
  641. + pulseaudio_to_obs_audio_format(monitor->format));
  642. + monitor->speakers = pulseaudio_channels_to_obs_speakers(spec.channels);
  643. + monitor->bytes_per_frame = pa_frame_size(&spec);
  644. +
  645. + monitor->stream = pulseaudio_stream_new(
  646. + obs_source_get_name(monitor->source), &spec, NULL);
  647. + if (!monitor->stream) {
  648. + blog(LOG_ERROR, "Unable to create stream");
  649. + return false;
  650. + }
  651. +
  652. + pa_buffer_attr attr;
  653. + attr.fragsize = (uint32_t) -1;
  654. + attr.maxlength = (uint32_t) -1;
  655. + attr.minreq = (uint32_t) -1;
  656. + attr.prebuf = (uint32_t) -1;
  657. + attr.tlength = pa_usec_to_bytes(25000, &spec);
  658. +
  659. + monitor->buffer_size =
  660. + monitor->bytes_per_frame * pa_usec_to_bytes(100, &spec);
  661. +
  662. + pa_stream_flags_t flags = PA_STREAM_ADJUST_LATENCY;
  663. +
  664. + if (pthread_mutex_init(&monitor->playback_mutex, NULL) != 0) {
  665. + blog(LOG_WARNING, "%s: %s", __FUNCTION__,
  666. + "Failed to init mutex");
  667. + return false;
  668. + }
  669. +
  670. + int_fast32_t ret = pulseaudio_connect_playback(monitor->stream,
  671. + monitor->device, &attr, flags);
  672. + if (ret < 0) {
  673. + pulseaudio_stop_playback(monitor);
  674. + blog(LOG_ERROR, "Unable to connect to stream");
  675. + return false;
  676. + }
  677. +
  678. + blog(LOG_INFO, "Started Monitoring in '%s'", monitor->device);
  679. + return true;
  680. +}
  681. +
  682. +static void audio_monitor_init_final(struct audio_monitor *monitor)
  683. +{
  684. + if (monitor->ignore)
  685. + return;
  686. +
  687. + obs_source_add_audio_capture_callback(monitor->source,
  688. + on_audio_playback, monitor);
  689. +
  690. + pulseaudio_write_callback(monitor->stream, pulseaudio_stream_write,
  691. + (void *) monitor);
  692. +}
  693. +
  694. +static inline void audio_monitor_free(struct audio_monitor *monitor)
  695. +{
  696. + if (monitor->ignore)
  697. + return;
  698. +
  699. + if (monitor->source)
  700. + obs_source_remove_audio_capture_callback(monitor->source,
  701. + on_audio_playback, monitor);
  702. +
  703. + audio_resampler_destroy(monitor->resampler);
  704. + circlebuf_free(&monitor->new_data);
  705. +
  706. + if (monitor->stream)
  707. + pulseaudio_stop_playback(monitor);
  708. + pulseaudio_unref();
  709. +
  710. + bfree(monitor->device);
  711. +}
  712. +
  713. +struct audio_monitor *audio_monitor_create(obs_source_t *source)
  714. +{
  715. + struct audio_monitor monitor = {0};
  716. + struct audio_monitor *out;
  717. +
  718. + if (!audio_monitor_init(&monitor, source))
  719. + goto fail;
  720. +
  721. + out = bmemdup(&monitor, sizeof(monitor));
  722. +
  723. + pthread_mutex_lock(&obs->audio.monitoring_mutex);
  724. + da_push_back(obs->audio.monitors, &out);
  725. + pthread_mutex_unlock(&obs->audio.monitoring_mutex);
  726. +
  727. + audio_monitor_init_final(out);
  728. + return out;
  729. +
  730. +fail:
  731. + audio_monitor_free(&monitor);
  732. + return NULL;
  733. +}
  734. +
  735. +void audio_monitor_reset(struct audio_monitor *monitor)
  736. +{
  737. + struct audio_monitor new_monitor = {0};
  738. + bool success;
  739. + audio_monitor_free(monitor);
  740. +
  741. + pthread_mutex_lock(&monitor->playback_mutex);
  742. + success = audio_monitor_init(&new_monitor, monitor->source);
  743. + pthread_mutex_unlock(&monitor->playback_mutex);
  744. +
  745. + if (success) {
  746. + *monitor = new_monitor;
  747. + audio_monitor_init_final(monitor);
  748. + } else {
  749. + audio_monitor_free(&new_monitor);
  750. + }
  751. +}
  752. +
  753. +void audio_monitor_destroy(struct audio_monitor *monitor)
  754. +{
  755. + if (monitor) {
  756. + audio_monitor_free(monitor);
  757. +
  758. + pthread_mutex_lock(&obs->audio.monitoring_mutex);
  759. + da_erase_item(obs->audio.monitors, &monitor);
  760. + pthread_mutex_unlock(&obs->audio.monitoring_mutex);
  761. +
  762. + bfree(monitor);
  763. + }
  764. +}
  765. diff --git a/libobs/audio-monitoring/pulse/pulseaudio-wrapper.c b/libobs/audio-monitoring/pulse/pulseaudio-wrapper.c
  766. new file mode 100644
  767. index 000000000..91939ae1b
  768. --- /dev/null
  769. +++ b/libobs/audio-monitoring/pulse/pulseaudio-wrapper.c
  770. @@ -0,0 +1,330 @@
  771. +/*
  772. +Copyright (C) 2014 by Leonhard Oelke <leonhard@in-verted.de>
  773. +Copyright (C) 2017 by Fabio Madia <admshao@gmail.com>
  774. +
  775. +This program is free software: you can redistribute it and/or modify
  776. +it under the terms of the GNU General Public License as published by
  777. +the Free Software Foundation, either version 2 of the License, or
  778. +(at your option) any later version.
  779. +
  780. +This program is distributed in the hope that it will be useful,
  781. +but WITHOUT ANY WARRANTY; without even the implied warranty of
  782. +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  783. +GNU General Public License for more details.
  784. +
  785. +You should have received a copy of the GNU General Public License
  786. +along with this program. If not, see <http://www.gnu.org/licenses/>.
  787. +*/
  788. +
  789. +#include <pthread.h>
  790. +
  791. +#include <pulse/thread-mainloop.h>
  792. +
  793. +#include <util/base.h>
  794. +#include <obs.h>
  795. +
  796. +#include "pulseaudio-wrapper.h"
  797. +
  798. +/* global data */
  799. +static uint_fast32_t pulseaudio_refs = 0;
  800. +static pthread_mutex_t pulseaudio_mutex = PTHREAD_MUTEX_INITIALIZER;
  801. +static pa_threaded_mainloop *pulseaudio_mainloop = NULL;
  802. +static pa_context *pulseaudio_context = NULL;
  803. +
  804. +static void pulseaudio_default_devices(pa_context *c, const pa_server_info *i,
  805. + void *userdata)
  806. +{
  807. + UNUSED_PARAMETER(c);
  808. + struct pulseaudio_default_output *d =
  809. + (struct pulseaudio_default_output *) userdata;
  810. + d->default_sink_name = bstrdup(i->default_sink_name);
  811. + pulseaudio_signal(0);
  812. +}
  813. +
  814. +void get_default_id(char **id)
  815. +{
  816. + pulseaudio_init();
  817. + struct pulseaudio_default_output *pdo = bzalloc(
  818. + sizeof(struct pulseaudio_default_output));
  819. + pulseaudio_get_server_info(
  820. + (pa_server_info_cb_t) pulseaudio_default_devices,
  821. + (void *) pdo);
  822. + *id = bzalloc(strlen(pdo->default_sink_name) + 9);
  823. + strcat(*id, pdo->default_sink_name);
  824. + strcat(*id, ".monitor");
  825. + bfree(pdo->default_sink_name);
  826. + bfree(pdo);
  827. + pulseaudio_unref();
  828. +}
  829. +
  830. +bool devices_match(const char *id1, const char *id2)
  831. +{
  832. + bool match;
  833. + char *name1 = NULL;
  834. + char *name2 = NULL;
  835. +
  836. + if (!id1 || !id2)
  837. + return false;
  838. +
  839. + if (strcmp(id1, "default") == 0) {
  840. + get_default_id(&name1);
  841. + id1 = name1;
  842. + }
  843. + if (strcmp(id2, "default") == 0) {
  844. + get_default_id(&name2);
  845. + id2 = name2;
  846. + }
  847. +
  848. + match = strcmp(id1, id2) == 0;
  849. + bfree(name1);
  850. + bfree(name2);
  851. + return match;
  852. +}
  853. +
  854. +/**
  855. + * context status change callback
  856. + *
  857. + * @todo this is currently a noop, we want to reconnect here if the connection
  858. + * is lost ...
  859. + */
  860. +static void pulseaudio_context_state_changed(pa_context *c, void *userdata)
  861. +{
  862. + UNUSED_PARAMETER(userdata);
  863. + UNUSED_PARAMETER(c);
  864. +
  865. + pulseaudio_signal(0);
  866. +}
  867. +
  868. +/**
  869. + * get the default properties
  870. + */
  871. +static pa_proplist *pulseaudio_properties()
  872. +{
  873. + pa_proplist *p = pa_proplist_new();
  874. +
  875. + pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, "OBS");
  876. + pa_proplist_sets(p, PA_PROP_APPLICATION_ICON_NAME, "obs");
  877. + pa_proplist_sets(p, PA_PROP_MEDIA_ROLE, "production");
  878. +
  879. + return p;
  880. +}
  881. +
  882. +/**
  883. + * Initialize the pulse audio context with properties and callback
  884. + */
  885. +static void pulseaudio_init_context()
  886. +{
  887. + pulseaudio_lock();
  888. +
  889. + pa_proplist *p = pulseaudio_properties();
  890. + pulseaudio_context = pa_context_new_with_proplist(
  891. + pa_threaded_mainloop_get_api(pulseaudio_mainloop),
  892. + "OBS-Monitor", p);
  893. +
  894. + pa_context_set_state_callback(pulseaudio_context,
  895. + pulseaudio_context_state_changed, NULL);
  896. +
  897. + pa_context_connect(pulseaudio_context, NULL, PA_CONTEXT_NOAUTOSPAWN,
  898. + NULL);
  899. + pa_proplist_free(p);
  900. +
  901. + pulseaudio_unlock();
  902. +}
  903. +
  904. +/**
  905. + * wait for context to be ready
  906. + */
  907. +static int_fast32_t pulseaudio_context_ready()
  908. +{
  909. + pulseaudio_lock();
  910. +
  911. + if (!PA_CONTEXT_IS_GOOD(pa_context_get_state(pulseaudio_context))) {
  912. + pulseaudio_unlock();
  913. + return -1;
  914. + }
  915. +
  916. + while (pa_context_get_state(pulseaudio_context) != PA_CONTEXT_READY)
  917. + pulseaudio_wait();
  918. +
  919. + pulseaudio_unlock();
  920. + return 0;
  921. +}
  922. +
  923. +int_fast32_t pulseaudio_init()
  924. +{
  925. + pthread_mutex_lock(&pulseaudio_mutex);
  926. +
  927. + if (pulseaudio_refs == 0) {
  928. + pulseaudio_mainloop = pa_threaded_mainloop_new();
  929. + pa_threaded_mainloop_start(pulseaudio_mainloop);
  930. +
  931. + pulseaudio_init_context();
  932. + }
  933. +
  934. + pulseaudio_refs++;
  935. +
  936. + pthread_mutex_unlock(&pulseaudio_mutex);
  937. +
  938. + return 0;
  939. +}
  940. +
  941. +void pulseaudio_unref()
  942. +{
  943. + pthread_mutex_lock(&pulseaudio_mutex);
  944. +
  945. + if (--pulseaudio_refs == 0) {
  946. + pulseaudio_lock();
  947. + if (pulseaudio_context != NULL) {
  948. + pa_context_disconnect(pulseaudio_context);
  949. + pa_context_unref(pulseaudio_context);
  950. + pulseaudio_context = NULL;
  951. + }
  952. + pulseaudio_unlock();
  953. +
  954. + if (pulseaudio_mainloop != NULL) {
  955. + pa_threaded_mainloop_stop(pulseaudio_mainloop);
  956. + pa_threaded_mainloop_free(pulseaudio_mainloop);
  957. + pulseaudio_mainloop = NULL;
  958. + }
  959. + }
  960. +
  961. + pthread_mutex_unlock(&pulseaudio_mutex);
  962. +}
  963. +
  964. +void pulseaudio_lock()
  965. +{
  966. + pa_threaded_mainloop_lock(pulseaudio_mainloop);
  967. +}
  968. +
  969. +void pulseaudio_unlock()
  970. +{
  971. + pa_threaded_mainloop_unlock(pulseaudio_mainloop);
  972. +}
  973. +
  974. +void pulseaudio_wait()
  975. +{
  976. + pa_threaded_mainloop_wait(pulseaudio_mainloop);
  977. +}
  978. +
  979. +void pulseaudio_signal(int wait_for_accept)
  980. +{
  981. + pa_threaded_mainloop_signal(pulseaudio_mainloop, wait_for_accept);
  982. +}
  983. +
  984. +void pulseaudio_accept()
  985. +{
  986. + pa_threaded_mainloop_accept(pulseaudio_mainloop);
  987. +}
  988. +
  989. +int_fast32_t pulseaudio_get_source_info_list(pa_source_info_cb_t cb,
  990. + void *userdata)
  991. +{
  992. + if (pulseaudio_context_ready() < 0)
  993. + return -1;
  994. +
  995. + pulseaudio_lock();
  996. +
  997. + pa_operation *op = pa_context_get_source_info_list(
  998. + pulseaudio_context, cb, userdata);
  999. + if (!op) {
  1000. + pulseaudio_unlock();
  1001. + return -1;
  1002. + }
  1003. + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
  1004. + pulseaudio_wait();
  1005. + pa_operation_unref(op);
  1006. +
  1007. + pulseaudio_unlock();
  1008. +
  1009. + return 0;
  1010. +}
  1011. +
  1012. +int_fast32_t pulseaudio_get_source_info(pa_source_info_cb_t cb,
  1013. + const char *name, void *userdata)
  1014. +{
  1015. + if (pulseaudio_context_ready() < 0)
  1016. + return -1;
  1017. +
  1018. + pulseaudio_lock();
  1019. +
  1020. + pa_operation *op = pa_context_get_source_info_by_name(
  1021. + pulseaudio_context, name, cb, userdata);
  1022. + if (!op) {
  1023. + pulseaudio_unlock();
  1024. + return -1;
  1025. + }
  1026. + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
  1027. + pulseaudio_wait();
  1028. + pa_operation_unref(op);
  1029. +
  1030. + pulseaudio_unlock();
  1031. +
  1032. + return 0;
  1033. +}
  1034. +
  1035. +int_fast32_t pulseaudio_get_server_info(pa_server_info_cb_t cb, void *userdata)
  1036. +{
  1037. + if (pulseaudio_context_ready() < 0)
  1038. + return -1;
  1039. +
  1040. + pulseaudio_lock();
  1041. +
  1042. + pa_operation *op = pa_context_get_server_info(
  1043. + pulseaudio_context, cb, userdata);
  1044. + if (!op) {
  1045. + pulseaudio_unlock();
  1046. + return -1;
  1047. + }
  1048. + while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
  1049. + pulseaudio_wait();
  1050. + pa_operation_unref(op);
  1051. +
  1052. + pulseaudio_unlock();
  1053. + return 0;
  1054. +}
  1055. +
  1056. +pa_stream *pulseaudio_stream_new(const char *name, const pa_sample_spec *ss,
  1057. + const pa_channel_map *map)
  1058. +{
  1059. + if (pulseaudio_context_ready() < 0)
  1060. + return NULL;
  1061. +
  1062. + pulseaudio_lock();
  1063. +
  1064. + pa_proplist *p = pulseaudio_properties();
  1065. + pa_stream *s = pa_stream_new_with_proplist(
  1066. + pulseaudio_context, name, ss, map, p);
  1067. + pa_proplist_free(p);
  1068. +
  1069. + pulseaudio_unlock();
  1070. + return s;
  1071. +}
  1072. +
  1073. +int_fast32_t pulseaudio_connect_playback(pa_stream *s, const char *name,
  1074. + const pa_buffer_attr *attr, pa_stream_flags_t flags)
  1075. +{
  1076. + if (pulseaudio_context_ready() < 0)
  1077. + return -1;
  1078. +
  1079. + size_t dev_len = strlen(name) - 8;
  1080. + char device[dev_len];
  1081. + memcpy(device, name, dev_len);
  1082. + device[dev_len] = '\0';
  1083. +
  1084. + pulseaudio_lock();
  1085. + int_fast32_t ret = pa_stream_connect_playback(s, device, attr, flags,
  1086. + NULL, NULL);
  1087. + pulseaudio_unlock();
  1088. + return ret;
  1089. +}
  1090. +
  1091. +void pulseaudio_write_callback(pa_stream *p, pa_stream_request_cb_t cb,
  1092. + void *userdata)
  1093. +{
  1094. + if (pulseaudio_context_ready() < 0)
  1095. + return;
  1096. +
  1097. + pulseaudio_lock();
  1098. + pa_stream_set_write_callback(p, cb, userdata);
  1099. + pulseaudio_unlock();
  1100. +}
  1101. diff --git a/libobs/audio-monitoring/pulse/pulseaudio-wrapper.h b/libobs/audio-monitoring/pulse/pulseaudio-wrapper.h
  1102. new file mode 100644
  1103. index 000000000..3630ab0f0
  1104. --- /dev/null
  1105. +++ b/libobs/audio-monitoring/pulse/pulseaudio-wrapper.h
  1106. @@ -0,0 +1,175 @@
  1107. +/*
  1108. +Copyright (C) 2014 by Leonhard Oelke <leonhard@in-verted.de>
  1109. +Copyright (C) 2017 by Fabio Madia <admshao@gmail.com>
  1110. +
  1111. +This program is free software: you can redistribute it and/or modify
  1112. +it under the terms of the GNU General Public License as published by
  1113. +the Free Software Foundation, either version 2 of the License, or
  1114. +(at your option) any later version.
  1115. +
  1116. +This program is distributed in the hope that it will be useful,
  1117. +but WITHOUT ANY WARRANTY; without even the implied warranty of
  1118. +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  1119. +GNU General Public License for more details.
  1120. +
  1121. +You should have received a copy of the GNU General Public License
  1122. +along with this program. If not, see <http://www.gnu.org/licenses/>.
  1123. +*/
  1124. +
  1125. +#include <inttypes.h>
  1126. +#include <pulse/stream.h>
  1127. +#include <pulse/context.h>
  1128. +#include <pulse/introspect.h>
  1129. +
  1130. +#pragma once
  1131. +
  1132. +struct pulseaudio_default_output {
  1133. + char *default_sink_name;
  1134. +};
  1135. +
  1136. +struct enum_cb {
  1137. + obs_enum_audio_device_cb cb;
  1138. + void *data;
  1139. + int cont;
  1140. +};
  1141. +
  1142. +void get_default_id(char **id);
  1143. +
  1144. +bool devices_match(const char *id1, const char *id2);
  1145. +
  1146. +/**
  1147. + * Initialize the pulseaudio mainloop and increase the reference count
  1148. + */
  1149. +int_fast32_t pulseaudio_init();
  1150. +
  1151. +/**
  1152. + * Unreference the pulseaudio mainloop, when the reference count reaches
  1153. + * zero the mainloop will automatically be destroyed
  1154. + */
  1155. +void pulseaudio_unref();
  1156. +
  1157. +/**
  1158. + * Lock the mainloop
  1159. + *
  1160. + * In order to allow for multiple threads to use the same mainloop pulseaudio
  1161. + * provides it's own locking mechanism. This function should be called before
  1162. + * using any pulseaudio function that is in any way related to the mainloop or
  1163. + * context.
  1164. + *
  1165. + * @note use of this function may cause deadlocks
  1166. + *
  1167. + * @warning do not use with pulseaudio_ wrapper functions
  1168. + */
  1169. +void pulseaudio_lock();
  1170. +
  1171. +/**
  1172. + * Unlock the mainloop
  1173. + *
  1174. + * @see pulseaudio_lock()
  1175. + */
  1176. +void pulseaudio_unlock();
  1177. +
  1178. +/**
  1179. + * Wait for events to happen
  1180. + *
  1181. + * This function should be called when waiting for an event to happen.
  1182. + */
  1183. +void pulseaudio_wait();
  1184. +
  1185. +/**
  1186. + * Wait for accept signal from calling thread
  1187. + *
  1188. + * This function tells the pulseaudio mainloop wheter the data provided to
  1189. + * the callback should be retained until the calling thread executes
  1190. + * pulseaudio_accept()
  1191. + *
  1192. + * If wait_for_accept is 0 the function returns and the data is freed.
  1193. + */
  1194. +void pulseaudio_signal(int wait_for_accept);
  1195. +
  1196. +/**
  1197. + * Signal the waiting callback to return
  1198. + *
  1199. + * This function is used in conjunction with pulseaudio_signal()
  1200. + */
  1201. +void pulseaudio_accept();
  1202. +
  1203. +/**
  1204. + * Request source information
  1205. + *
  1206. + * The function will block until the operation was executed and the mainloop
  1207. + * called the provided callback function.
  1208. + *
  1209. + * @return negative on error
  1210. + *
  1211. + * @note The function will block until the server context is ready.
  1212. + *
  1213. + * @warning call without active locks
  1214. + */
  1215. +int_fast32_t pulseaudio_get_source_info_list(pa_source_info_cb_t cb,
  1216. + void *userdata);
  1217. +
  1218. +/**
  1219. + * Request source information from a specific source
  1220. + *
  1221. + * The function will block until the operation was executed and the mainloop
  1222. + * called the provided callback function.
  1223. + *
  1224. + * @param cb pointer to the callback function
  1225. + * @param name the source name to get information for
  1226. + * @param userdata pointer to userdata the callback will be called with
  1227. + *
  1228. + * @return negative on error
  1229. + *
  1230. + * @note The function will block until the server context is ready.
  1231. + *
  1232. + * @warning call without active locks
  1233. + */
  1234. +int_fast32_t pulseaudio_get_source_info(pa_source_info_cb_t cb,
  1235. + const char *name, void *userdata);
  1236. +
  1237. +/**
  1238. + * Request server information
  1239. + *
  1240. + * The function will block until the operation was executed and the mainloop
  1241. + * called the provided callback function.
  1242. + *
  1243. + * @return negative on error
  1244. + *
  1245. + * @note The function will block until the server context is ready.
  1246. + *
  1247. + * @warning call without active locks
  1248. + */
  1249. +int_fast32_t pulseaudio_get_server_info(pa_server_info_cb_t cb, void *userdata);
  1250. +
  1251. +/**
  1252. + * Create a new stream with the default properties
  1253. + *
  1254. + * @note The function will block until the server context is ready.
  1255. + *
  1256. + * @warning call without active locks
  1257. + */
  1258. +pa_stream *pulseaudio_stream_new(const char *name, const pa_sample_spec *ss,
  1259. + const pa_channel_map *map);
  1260. +
  1261. +/**
  1262. + * Connect to a pulseaudio playback stream
  1263. + *
  1264. + * @param s pa_stream to connect to. NULL for default
  1265. + * @param attr pa_buffer_attr
  1266. + * @param name Device name. NULL for default device
  1267. + * @param flags pa_stream_flags_t
  1268. + * @return negative on error
  1269. + */
  1270. +int_fast32_t pulseaudio_connect_playback(pa_stream *s, const char *name,
  1271. + const pa_buffer_attr *attr, pa_stream_flags_t flags);
  1272. +
  1273. +/**
  1274. + * Sets a callback function for when data can be written to the stream
  1275. + *
  1276. + * @param p pa_stream to connect to. NULL for default
  1277. + * @param cb pa_stream_request_cb_t
  1278. + * @param userdata pointer to userdata the callback will be called with
  1279. + */
  1280. +void pulseaudio_write_callback(pa_stream *p, pa_stream_request_cb_t cb,
  1281. + void *userdata);
  1282. diff --git a/libobs/obs.c b/libobs/obs.c
  1283. index b41c12a8d..ab2fb3d1c 100644
  1284. --- a/libobs/obs.c
  1285. +++ b/libobs/obs.c
  1286. @@ -1898,7 +1898,7 @@ bool obs_set_audio_monitoring_device(const char *name, const char *id)
  1287. if (!obs || !name || !id || !*name || !*id)
  1288. return false;
  1289.  
  1290. -#ifdef _WIN32
  1291. +#if defined(_WIN32) || HAVE_PULSEAUDIO
  1292. pthread_mutex_lock(&obs->audio.monitoring_mutex);
  1293.  
  1294. if (strcmp(id, obs->audio.monitoring_device_id) == 0) {
  1295. diff --git a/libobs/obsconfig.h.in b/libobs/obsconfig.h.in
  1296. index f86962d59..130a8b203 100644
  1297. --- a/libobs/obsconfig.h.in
  1298. +++ b/libobs/obsconfig.h.in
  1299. @@ -17,3 +17,4 @@
  1300. #define OBS_UNIX_STRUCTURE @OBS_UNIX_STRUCTURE@
  1301. #define BUILD_CAPTIONS @BUILD_CAPTIONS@
  1302. #define HAVE_DBUS @HAVE_DBUS@
  1303. +#define HAVE_PULSEAUDIO @HAVE_PULSEAUDIO@
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement