mac-audio.c 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057
  1. #include <AudioUnit/AudioUnit.h>
  2. #include <CoreFoundation/CFString.h>
  3. #include <CoreAudio/CoreAudio.h>
  4. #include <unistd.h>
  5. #include <errno.h>
  6. #include <obs-module.h>
  7. #include <mach/mach_time.h>
  8. #include <util/threading.h>
  9. #include <util/c99defs.h>
  10. #include <util/apple/cfstring-utils.h>
  11. #include "audio-device-enum.h"
  12. #define PROPERTY_DEFAULT_DEVICE kAudioHardwarePropertyDefaultInputDevice
  13. #define PROPERTY_FORMATS kAudioStreamPropertyAvailablePhysicalFormats
  14. #define SCOPE_OUTPUT kAudioUnitScope_Output
  15. #define SCOPE_INPUT kAudioUnitScope_Input
  16. #define SCOPE_GLOBAL kAudioUnitScope_Global
  17. #define BUS_OUTPUT 0
  18. #define BUS_INPUT 1
  19. #define set_property AudioUnitSetProperty
  20. #define get_property AudioUnitGetProperty
  21. #define TEXT_AUDIO_INPUT obs_module_text("CoreAudio.InputCapture");
  22. #define TEXT_AUDIO_OUTPUT obs_module_text("CoreAudio.OutputCapture");
  23. #define TEXT_DEVICE obs_module_text("CoreAudio.Device")
  24. #define TEXT_DEVICE_DEFAULT obs_module_text("CoreAudio.Device.Default")
  25. struct coreaudio_data {
  26. char *device_name;
  27. char *device_uid;
  28. AudioUnit unit;
  29. AudioDeviceID device_id;
  30. AudioBufferList *buf_list;
  31. bool au_initialized;
  32. bool active;
  33. bool default_device;
  34. bool input;
  35. bool no_devices;
  36. uint32_t available_channels;
  37. char **channel_names;
  38. int32_t *channel_map;
  39. uint32_t sample_rate;
  40. enum audio_format format;
  41. enum speaker_layout speakers;
  42. bool enable_downmix;
  43. pthread_t reconnect_thread;
  44. os_event_t *exit_event;
  45. volatile bool reconnecting;
  46. unsigned long retry_time;
  47. obs_source_t *source;
  48. };
  49. static bool get_default_output_device(struct coreaudio_data *ca)
  50. {
  51. struct device_list list;
  52. memset(&list, 0, sizeof(struct device_list));
  53. coreaudio_enum_devices(&list, false);
  54. if (!list.items.num)
  55. return false;
  56. bfree(ca->device_uid);
  57. ca->device_uid = bstrdup(list.items.array[0].value.array);
  58. device_list_free(&list);
  59. return true;
  60. }
  61. static bool find_device_id_by_uid(struct coreaudio_data *ca)
  62. {
  63. UInt32 size = sizeof(AudioDeviceID);
  64. CFStringRef cf_uid = NULL;
  65. CFStringRef qual = NULL;
  66. UInt32 qual_size = 0;
  67. OSStatus stat;
  68. bool success;
  69. AudioObjectPropertyAddress addr = {.mScope = kAudioObjectPropertyScopeGlobal,
  70. .mElement = kAudioObjectPropertyElementMain};
  71. if (!ca->device_uid)
  72. ca->device_uid = bstrdup("default");
  73. ca->default_device = false;
  74. ca->no_devices = false;
  75. /* have to do this because mac output devices don't actually exist */
  76. if (astrcmpi(ca->device_uid, "default") == 0) {
  77. if (ca->input) {
  78. ca->default_device = true;
  79. } else {
  80. if (!get_default_output_device(ca)) {
  81. ca->no_devices = true;
  82. return false;
  83. }
  84. }
  85. }
  86. cf_uid = CFStringCreateWithCString(NULL, ca->device_uid, kCFStringEncodingUTF8);
  87. if (ca->default_device) {
  88. addr.mSelector = kAudioHardwarePropertyDefaultInputDevice;
  89. stat = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, qual_size, &qual, &size,
  90. &ca->device_id);
  91. success = (stat == noErr);
  92. } else {
  93. success = coreaudio_get_device_id(cf_uid, &ca->device_id);
  94. }
  95. if (cf_uid)
  96. CFRelease(cf_uid);
  97. return success;
  98. }
  99. static inline void ca_warn(struct coreaudio_data *ca, const char *func, const char *format, ...)
  100. {
  101. va_list args;
  102. struct dstr str = {0};
  103. va_start(args, format);
  104. dstr_printf(&str, "[%s]:[device '%s'] ", func, ca->device_name);
  105. dstr_vcatf(&str, format, args);
  106. blog(LOG_WARNING, "%s", str.array);
  107. dstr_free(&str);
  108. va_end(args);
  109. }
  110. static inline bool ca_success(OSStatus stat, struct coreaudio_data *ca, const char *func, const char *action)
  111. {
  112. if (stat != noErr) {
  113. blog(LOG_WARNING, "[%s]:[device '%s'] %s failed: %d", func, ca->device_name, action, (int)stat);
  114. return false;
  115. }
  116. return true;
  117. }
  118. enum coreaudio_io_type {
  119. IO_TYPE_INPUT,
  120. IO_TYPE_OUTPUT,
  121. };
  122. static inline bool enable_io(struct coreaudio_data *ca, enum coreaudio_io_type type, bool enable)
  123. {
  124. UInt32 enable_int = enable;
  125. return set_property(ca->unit, kAudioOutputUnitProperty_EnableIO,
  126. (type == IO_TYPE_INPUT) ? SCOPE_INPUT : SCOPE_OUTPUT,
  127. (type == IO_TYPE_INPUT) ? BUS_INPUT : BUS_OUTPUT, &enable_int, sizeof(enable_int));
  128. }
  129. static inline enum speaker_layout convert_ca_speaker_layout(UInt32 channels)
  130. {
  131. switch (channels) {
  132. case 1:
  133. return SPEAKERS_MONO;
  134. case 2:
  135. return SPEAKERS_STEREO;
  136. case 3:
  137. return SPEAKERS_2POINT1;
  138. case 4:
  139. return SPEAKERS_4POINT0;
  140. case 5:
  141. return SPEAKERS_4POINT1;
  142. case 6:
  143. return SPEAKERS_5POINT1;
  144. case 8:
  145. return SPEAKERS_7POINT1;
  146. }
  147. return SPEAKERS_UNKNOWN;
  148. }
  149. static inline enum audio_format convert_ca_format(UInt32 format_flags, UInt32 bits)
  150. {
  151. bool planar = (format_flags & kAudioFormatFlagIsNonInterleaved) != 0;
  152. if (format_flags & kAudioFormatFlagIsFloat)
  153. return planar ? AUDIO_FORMAT_FLOAT_PLANAR : AUDIO_FORMAT_FLOAT;
  154. if (!(format_flags & kAudioFormatFlagIsSignedInteger) && bits == 8)
  155. return planar ? AUDIO_FORMAT_U8BIT_PLANAR : AUDIO_FORMAT_U8BIT;
  156. /* not float? not signed int? no clue, fail */
  157. if ((format_flags & kAudioFormatFlagIsSignedInteger) == 0)
  158. return AUDIO_FORMAT_UNKNOWN;
  159. if (bits == 16)
  160. return planar ? AUDIO_FORMAT_16BIT_PLANAR : AUDIO_FORMAT_16BIT;
  161. else if (bits == 32)
  162. return planar ? AUDIO_FORMAT_32BIT_PLANAR : AUDIO_FORMAT_32BIT;
  163. return AUDIO_FORMAT_UNKNOWN;
  164. }
  165. static char *sanitize_device_name(char *name)
  166. {
  167. const size_t max_len = 64;
  168. size_t len = strlen(name);
  169. char buf[64];
  170. size_t out_idx = 0;
  171. for (size_t i = len > max_len ? len - max_len : 0; i < len; i++) {
  172. char c = name[i];
  173. if (isalnum(c)) {
  174. buf[out_idx++] = name[i];
  175. }
  176. if (c == '-' || c == ' ' || c == '_' || c == ':') {
  177. buf[out_idx++] = '_';
  178. }
  179. }
  180. return bstrdup_n(buf, out_idx);
  181. }
  182. static char **coreaudio_get_channel_names(struct coreaudio_data *ca)
  183. {
  184. char **channel_names = bzalloc(sizeof(char *) * ca->available_channels);
  185. for (uint32_t i = 0; i < ca->available_channels; i++) {
  186. CFStringRef cf_chan_name = NULL;
  187. UInt32 dataSize = sizeof(cf_chan_name);
  188. AudioObjectPropertyAddress pa;
  189. pa.mSelector = kAudioObjectPropertyElementName;
  190. pa.mScope = kAudioDevicePropertyScopeInput;
  191. pa.mElement = i + 1;
  192. OSStatus stat = AudioObjectGetPropertyData(ca->device_id, &pa, 0, NULL, &dataSize, &cf_chan_name);
  193. struct dstr name;
  194. dstr_init(&name);
  195. if (ca_success(stat, ca, "coreaudio_init_format", "get channel names") &&
  196. CFStringGetLength(cf_chan_name)) {
  197. char *channelName = cfstr_copy_cstr(cf_chan_name, kCFStringEncodingUTF8);
  198. dstr_printf(&name, "%s", channelName);
  199. if (channelName) {
  200. bfree(channelName);
  201. }
  202. } else {
  203. dstr_printf(&name, "%s %d", obs_module_text("CoreAudio.Channel.Device"), i + 1);
  204. }
  205. channel_names[i] = bstrdup_n(name.array, name.len);
  206. dstr_free(&name);
  207. if (cf_chan_name) {
  208. CFRelease(cf_chan_name);
  209. }
  210. }
  211. return channel_names;
  212. }
  213. static bool coreaudio_init_format(struct coreaudio_data *ca)
  214. {
  215. AudioStreamBasicDescription desc;
  216. AudioStreamBasicDescription inputDescription;
  217. OSStatus stat;
  218. UInt32 size;
  219. struct obs_audio_info aoi;
  220. if (!obs_get_audio_info(&aoi)) {
  221. blog(LOG_WARNING, "No active audio");
  222. return false;
  223. }
  224. ca->speakers = aoi.speakers;
  225. uint32_t channels = get_audio_channels(ca->speakers);
  226. size = sizeof(inputDescription);
  227. stat = get_property(ca->unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 1, &inputDescription,
  228. &size);
  229. if (!ca_success(stat, ca, "coreaudio_init_format", "get input device format"))
  230. return false;
  231. stat = get_property(ca->unit, kAudioUnitProperty_StreamFormat, SCOPE_OUTPUT, BUS_INPUT, &desc, &size);
  232. if (!ca_success(stat, ca, "coreaudio_init_format", "get input format"))
  233. return false;
  234. ca->available_channels = inputDescription.mChannelsPerFrame;
  235. if (ca->available_channels > MAX_DEVICE_INPUT_CHANNELS) {
  236. ca->available_channels = MAX_DEVICE_INPUT_CHANNELS;
  237. }
  238. ca->channel_names = coreaudio_get_channel_names(ca);
  239. if (ca->enable_downmix) {
  240. blog(LOG_INFO, "Downmix enabled: %d to %d channels.", ca->available_channels, channels);
  241. desc.mChannelsPerFrame = ca->available_channels;
  242. } else {
  243. // Mute any channels mapped in config that we don't really have
  244. char *sep = "";
  245. struct dstr cm_str;
  246. dstr_init(&cm_str);
  247. for (size_t i = 0; i < channels; i++) {
  248. dstr_cat(&cm_str, sep);
  249. if (ca->channel_map[i] >= (int32_t)ca->available_channels) {
  250. ca->channel_map[i] = -1;
  251. }
  252. dstr_catf(&cm_str, "%d", ca->channel_map[i]);
  253. sep = ",";
  254. }
  255. blog(LOG_INFO, "Channel map enabled: [%s] (%d channels available)", cm_str.array,
  256. ca->available_channels);
  257. dstr_free(&cm_str);
  258. stat = set_property(ca->unit, kAudioOutputUnitProperty_ChannelMap, SCOPE_OUTPUT, BUS_INPUT,
  259. ca->channel_map, sizeof(SInt32) * channels);
  260. if (!ca_success(stat, ca, "coreaudio_init_format", "set channel map")) {
  261. return false;
  262. }
  263. desc.mChannelsPerFrame = channels;
  264. }
  265. desc.mSampleRate = inputDescription.mSampleRate;
  266. stat = set_property(ca->unit, kAudioUnitProperty_StreamFormat, SCOPE_OUTPUT, BUS_INPUT, &desc, size);
  267. if (!ca_success(stat, ca, "coreaudio_init_format", "set output format"))
  268. return false;
  269. if (desc.mFormatID != kAudioFormatLinearPCM) {
  270. ca_warn(ca, "coreaudio_init_format", "format is not PCM");
  271. return false;
  272. }
  273. ca->format = convert_ca_format(desc.mFormatFlags, desc.mBitsPerChannel);
  274. if (ca->format == AUDIO_FORMAT_UNKNOWN) {
  275. ca_warn(ca, "coreaudio_init_format",
  276. "unknown format flags: "
  277. "%u, bits: %u",
  278. (unsigned int)desc.mFormatFlags, (unsigned int)desc.mBitsPerChannel);
  279. return false;
  280. }
  281. ca->sample_rate = (uint32_t)desc.mSampleRate;
  282. return true;
  283. }
  284. static bool coreaudio_init_buffer(struct coreaudio_data *ca)
  285. {
  286. UInt32 bufferSizeFrames;
  287. UInt32 bufferSizeBytes;
  288. UInt32 propertySize;
  289. OSStatus err = noErr;
  290. propertySize = sizeof(bufferSizeFrames);
  291. err = AudioUnitGetProperty(ca->unit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, 0,
  292. &bufferSizeFrames, &propertySize);
  293. if (!ca_success(err, ca, "coreaudio_init_buffer", "get buffer frame size")) {
  294. return false;
  295. }
  296. bufferSizeBytes = bufferSizeFrames * sizeof(Float32);
  297. AudioStreamBasicDescription streamDescription;
  298. propertySize = sizeof(streamDescription);
  299. err = AudioUnitGetProperty(ca->unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1,
  300. &streamDescription, &propertySize);
  301. if (!ca_success(err, ca, "coreaudio_init_buffer", "get stream format")) {
  302. return false;
  303. }
  304. if (!ca->enable_downmix) {
  305. streamDescription.mChannelsPerFrame = get_audio_channels(ca->speakers);
  306. }
  307. Float64 rate = 0.0;
  308. propertySize = sizeof(Float64);
  309. AudioObjectPropertyAddress propertyAddress = {kAudioDevicePropertyNominalSampleRate,
  310. kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain};
  311. err = AudioObjectGetPropertyData(ca->device_id, &propertyAddress, 0, NULL, &propertySize, &rate);
  312. if (!ca_success(err, ca, "coreaudio_init_buffer", "get input sample rate")) {
  313. return false;
  314. }
  315. streamDescription.mSampleRate = rate;
  316. int bufferPropertySize =
  317. offsetof(AudioBufferList, mBuffers[0]) + (sizeof(AudioBuffer) * streamDescription.mChannelsPerFrame);
  318. AudioBufferList *inputBuffer = (AudioBufferList *)bmalloc(bufferPropertySize);
  319. inputBuffer->mNumberBuffers = streamDescription.mChannelsPerFrame;
  320. for (UInt32 i = 0; i < inputBuffer->mNumberBuffers; i++) {
  321. inputBuffer->mBuffers[i].mNumberChannels = 1;
  322. inputBuffer->mBuffers[i].mDataByteSize = bufferSizeBytes;
  323. inputBuffer->mBuffers[i].mData = bmalloc(bufferSizeBytes);
  324. }
  325. ca->buf_list = inputBuffer;
  326. return true;
  327. }
  328. static void buf_list_free(AudioBufferList *buf_list)
  329. {
  330. if (buf_list) {
  331. for (UInt32 i = 0; i < buf_list->mNumberBuffers; i++)
  332. bfree(buf_list->mBuffers[i].mData);
  333. bfree(buf_list);
  334. }
  335. }
  336. static OSStatus input_callback(void *data, AudioUnitRenderActionFlags *action_flags, const AudioTimeStamp *ts_data,
  337. UInt32 bus_num, UInt32 frames, AudioBufferList *ignored_buffers)
  338. {
  339. struct coreaudio_data *ca = data;
  340. OSStatus stat;
  341. struct obs_source_audio audio;
  342. stat = AudioUnitRender(ca->unit, action_flags, ts_data, bus_num, frames, ca->buf_list);
  343. if (!ca_success(stat, ca, "input_callback", "audio retrieval"))
  344. return noErr;
  345. for (UInt32 i = 0; i < ca->buf_list->mNumberBuffers; i++) {
  346. if (i < MAX_AUDIO_CHANNELS) {
  347. audio.data[i] = ca->buf_list->mBuffers[i].mData;
  348. }
  349. }
  350. audio.frames = frames;
  351. audio.speakers = (ca->buf_list->mNumberBuffers > MAX_AUDIO_CHANNELS) ? MAX_AUDIO_CHANNELS
  352. : ca->buf_list->mNumberBuffers;
  353. audio.format = ca->format;
  354. audio.samples_per_sec = ca->sample_rate;
  355. static double factor = 0.;
  356. static mach_timebase_info_data_t info = {0, 0};
  357. if (info.numer == 0 && info.denom == 0) {
  358. mach_timebase_info(&info);
  359. factor = ((double)info.numer) / info.denom;
  360. }
  361. if (info.numer != info.denom)
  362. audio.timestamp = (uint64_t)(factor * ts_data->mHostTime);
  363. else
  364. audio.timestamp = ts_data->mHostTime;
  365. obs_source_output_audio(ca->source, &audio);
  366. UNUSED_PARAMETER(ignored_buffers);
  367. return noErr;
  368. }
  369. static void coreaudio_stop(struct coreaudio_data *ca);
  370. static bool coreaudio_init(struct coreaudio_data *ca);
  371. static void coreaudio_uninit(struct coreaudio_data *ca);
  372. static void *reconnect_thread(void *param)
  373. {
  374. struct coreaudio_data *ca = param;
  375. ca->reconnecting = true;
  376. while (os_event_timedwait(ca->exit_event, ca->retry_time) == ETIMEDOUT) {
  377. if (coreaudio_init(ca))
  378. break;
  379. }
  380. blog(LOG_DEBUG, "coreaudio: exit the reconnect thread");
  381. ca->reconnecting = false;
  382. return NULL;
  383. }
  384. static void coreaudio_begin_reconnect(struct coreaudio_data *ca)
  385. {
  386. int ret;
  387. if (ca->reconnecting)
  388. return;
  389. ret = pthread_create(&ca->reconnect_thread, NULL, reconnect_thread, ca);
  390. if (ret != 0)
  391. blog(LOG_WARNING,
  392. "[coreaudio_begin_reconnect] failed to "
  393. "create thread, error code: %d",
  394. ret);
  395. }
  396. static OSStatus notification_callback(AudioObjectID id, UInt32 num_addresses,
  397. const AudioObjectPropertyAddress addresses[], void *data)
  398. {
  399. struct coreaudio_data *ca = data;
  400. coreaudio_stop(ca);
  401. coreaudio_uninit(ca);
  402. if (addresses[0].mSelector == PROPERTY_DEFAULT_DEVICE)
  403. ca->retry_time = 300;
  404. else
  405. ca->retry_time = 2000;
  406. blog(LOG_INFO,
  407. "coreaudio: device '%s' disconnected or changed. "
  408. "attempting to reconnect",
  409. ca->device_name);
  410. coreaudio_begin_reconnect(ca);
  411. UNUSED_PARAMETER(id);
  412. UNUSED_PARAMETER(num_addresses);
  413. return noErr;
  414. }
  415. static OSStatus add_listener(struct coreaudio_data *ca, UInt32 property)
  416. {
  417. AudioObjectPropertyAddress addr = {property, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain};
  418. return AudioObjectAddPropertyListener(ca->device_id, &addr, notification_callback, ca);
  419. }
  420. static bool coreaudio_init_hooks(struct coreaudio_data *ca)
  421. {
  422. OSStatus stat;
  423. AURenderCallbackStruct callback_info = {.inputProc = input_callback, .inputProcRefCon = ca};
  424. stat = add_listener(ca, kAudioDevicePropertyDeviceIsAlive);
  425. if (!ca_success(stat, ca, "coreaudio_init_hooks", "set disconnect callback"))
  426. return false;
  427. stat = add_listener(ca, PROPERTY_FORMATS);
  428. if (!ca_success(stat, ca, "coreaudio_init_hooks", "set format change callback"))
  429. return false;
  430. if (ca->default_device) {
  431. AudioObjectPropertyAddress addr = {PROPERTY_DEFAULT_DEVICE, kAudioObjectPropertyScopeGlobal,
  432. kAudioObjectPropertyElementMain};
  433. stat = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &addr, notification_callback, ca);
  434. if (!ca_success(stat, ca, "coreaudio_init_hooks", "set device change callback"))
  435. return false;
  436. }
  437. stat = set_property(ca->unit, kAudioOutputUnitProperty_SetInputCallback, SCOPE_GLOBAL, 0, &callback_info,
  438. sizeof(callback_info));
  439. if (!ca_success(stat, ca, "coreaudio_init_hooks", "set input callback"))
  440. return false;
  441. return true;
  442. }
  443. static void coreaudio_remove_hooks(struct coreaudio_data *ca)
  444. {
  445. AURenderCallbackStruct callback_info = {.inputProc = NULL, .inputProcRefCon = NULL};
  446. AudioObjectPropertyAddress addr = {kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal,
  447. kAudioObjectPropertyElementMain};
  448. AudioObjectRemovePropertyListener(ca->device_id, &addr, notification_callback, ca);
  449. addr.mSelector = PROPERTY_FORMATS;
  450. AudioObjectRemovePropertyListener(ca->device_id, &addr, notification_callback, ca);
  451. if (ca->default_device) {
  452. addr.mSelector = PROPERTY_DEFAULT_DEVICE;
  453. AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &addr, notification_callback, ca);
  454. }
  455. set_property(ca->unit, kAudioOutputUnitProperty_SetInputCallback, SCOPE_GLOBAL, 0, &callback_info,
  456. sizeof(callback_info));
  457. }
  458. static bool coreaudio_get_device_name(struct coreaudio_data *ca)
  459. {
  460. CFStringRef cf_name = NULL;
  461. UInt32 size = sizeof(CFStringRef);
  462. char *name = NULL;
  463. const AudioObjectPropertyAddress addr = {kAudioDevicePropertyDeviceNameCFString, kAudioObjectPropertyScopeInput,
  464. kAudioObjectPropertyElementMain};
  465. OSStatus stat = AudioObjectGetPropertyData(ca->device_id, &addr, 0, NULL, &size, &cf_name);
  466. if (stat != noErr) {
  467. blog(LOG_WARNING,
  468. "[coreaudio_get_device_name] failed to "
  469. "get name: %d",
  470. (int)stat);
  471. return false;
  472. }
  473. name = cfstr_copy_cstr(cf_name, kCFStringEncodingUTF8);
  474. if (!name) {
  475. blog(LOG_WARNING, "[coreaudio_get_device_name] failed to "
  476. "convert name to cstr for some reason");
  477. return false;
  478. }
  479. bfree(ca->device_name);
  480. ca->device_name = name;
  481. if (cf_name)
  482. CFRelease(cf_name);
  483. return true;
  484. }
  485. static bool coreaudio_start(struct coreaudio_data *ca)
  486. {
  487. OSStatus stat;
  488. if (ca->active)
  489. return true;
  490. stat = AudioOutputUnitStart(ca->unit);
  491. return ca_success(stat, ca, "coreaudio_start", "start audio");
  492. }
  493. static void coreaudio_stop(struct coreaudio_data *ca)
  494. {
  495. OSStatus stat;
  496. if (!ca->active)
  497. return;
  498. ca->active = false;
  499. stat = AudioOutputUnitStop(ca->unit);
  500. ca_success(stat, ca, "coreaudio_stop", "stop audio");
  501. }
  502. static bool coreaudio_init_unit(struct coreaudio_data *ca)
  503. {
  504. AudioComponentDescription desc = {.componentType = kAudioUnitType_Output,
  505. .componentSubType = kAudioUnitSubType_HALOutput};
  506. AudioComponent component = AudioComponentFindNext(NULL, &desc);
  507. if (!component) {
  508. ca_warn(ca, "coreaudio_init_unit", "find component failed");
  509. return false;
  510. }
  511. OSStatus stat = AudioComponentInstanceNew(component, &ca->unit);
  512. if (!ca_success(stat, ca, "coreaudio_init_unit", "instance unit"))
  513. return false;
  514. ca->au_initialized = true;
  515. return true;
  516. }
  517. static bool coreaudio_init(struct coreaudio_data *ca)
  518. {
  519. OSStatus stat;
  520. if (ca->au_initialized)
  521. return true;
  522. if (!find_device_id_by_uid(ca))
  523. return false;
  524. if (!coreaudio_get_device_name(ca))
  525. return false;
  526. if (!coreaudio_init_unit(ca))
  527. return false;
  528. stat = enable_io(ca, IO_TYPE_INPUT, true);
  529. if (!ca_success(stat, ca, "coreaudio_init", "enable input io"))
  530. goto fail;
  531. stat = enable_io(ca, IO_TYPE_OUTPUT, false);
  532. if (!ca_success(stat, ca, "coreaudio_init", "disable output io"))
  533. goto fail;
  534. stat = set_property(ca->unit, kAudioOutputUnitProperty_CurrentDevice, SCOPE_GLOBAL, 0, &ca->device_id,
  535. sizeof(ca->device_id));
  536. if (!ca_success(stat, ca, "coreaudio_init", "set current device"))
  537. goto fail;
  538. if (!coreaudio_init_format(ca))
  539. goto fail;
  540. if (!coreaudio_init_buffer(ca))
  541. goto fail;
  542. if (!coreaudio_init_hooks(ca))
  543. goto fail;
  544. stat = AudioUnitInitialize(ca->unit);
  545. if (!ca_success(stat, ca, "coreaudio_initialize", "initialize"))
  546. goto fail;
  547. if (!coreaudio_start(ca))
  548. goto fail;
  549. blog(LOG_INFO, "coreaudio: Device '%s' [%" PRIu32 " Hz] initialized", ca->device_name, ca->sample_rate);
  550. return ca->au_initialized;
  551. fail:
  552. coreaudio_uninit(ca);
  553. return false;
  554. }
  555. static void coreaudio_try_init(struct coreaudio_data *ca)
  556. {
  557. if (!coreaudio_init(ca)) {
  558. blog(LOG_INFO,
  559. "coreaudio: failed to find device "
  560. "uid: %s, waiting for connection",
  561. ca->device_uid);
  562. ca->retry_time = 2000;
  563. if (ca->no_devices)
  564. blog(LOG_INFO, "coreaudio: no device found");
  565. else
  566. coreaudio_begin_reconnect(ca);
  567. }
  568. }
  569. static void coreaudio_uninit(struct coreaudio_data *ca)
  570. {
  571. if (!ca->au_initialized)
  572. return;
  573. if (ca->unit) {
  574. coreaudio_stop(ca);
  575. OSStatus stat = AudioUnitUninitialize(ca->unit);
  576. ca_success(stat, ca, "coreaudio_uninit", "uninitialize");
  577. coreaudio_remove_hooks(ca);
  578. stat = AudioComponentInstanceDispose(ca->unit);
  579. ca_success(stat, ca, "coreaudio_uninit", "dispose");
  580. ca->unit = NULL;
  581. }
  582. ca->au_initialized = false;
  583. buf_list_free(ca->buf_list);
  584. ca->buf_list = NULL;
  585. if (ca->channel_names) {
  586. for (uint32_t i = 0; i < ca->available_channels; i++) {
  587. bfree(ca->channel_names[i]);
  588. }
  589. bfree(ca->channel_names);
  590. ca->channel_names = NULL;
  591. }
  592. }
  593. /* ------------------------------------------------------------------------- */
  594. static const char *coreaudio_input_getname(void *unused)
  595. {
  596. UNUSED_PARAMETER(unused);
  597. return TEXT_AUDIO_INPUT;
  598. }
  599. static const char *coreaudio_output_getname(void *unused)
  600. {
  601. UNUSED_PARAMETER(unused);
  602. return TEXT_AUDIO_OUTPUT;
  603. }
  604. static void coreaudio_shutdown(struct coreaudio_data *ca)
  605. {
  606. if (ca->reconnecting) {
  607. os_event_signal(ca->exit_event);
  608. pthread_join(ca->reconnect_thread, NULL);
  609. os_event_reset(ca->exit_event);
  610. }
  611. coreaudio_uninit(ca);
  612. if (ca->unit)
  613. AudioComponentInstanceDispose(ca->unit);
  614. }
  615. static void coreaudio_destroy(void *data)
  616. {
  617. struct coreaudio_data *ca = data;
  618. if (ca) {
  619. coreaudio_shutdown(ca);
  620. os_event_destroy(ca->exit_event);
  621. if (ca->channel_map) {
  622. bfree(ca->channel_map);
  623. ca->channel_map = NULL;
  624. }
  625. bfree(ca->device_name);
  626. bfree(ca->device_uid);
  627. bfree(ca);
  628. }
  629. }
  630. static void coreaudio_set_channels(struct coreaudio_data *ca, obs_data_t *settings)
  631. {
  632. ca->channel_map = bzalloc(sizeof(SInt32) * MAX_AUDIO_CHANNELS);
  633. char *device_config_name = sanitize_device_name(ca->device_uid);
  634. for (uint8_t i = 0; i < MAX_AUDIO_CHANNELS; i++) {
  635. char setting_name[128];
  636. snprintf(setting_name, 128, "output-%s-%i", device_config_name, i + 1);
  637. int64_t found = obs_data_has_user_value(settings, setting_name)
  638. ? obs_data_get_int(settings, setting_name)
  639. : -1L;
  640. int64_t adjusted = found > 0 ? found - 1 : -1;
  641. ca->channel_map[i] = (int32_t)adjusted;
  642. }
  643. bfree(device_config_name);
  644. }
  645. static void coreaudio_update(void *data, obs_data_t *settings)
  646. {
  647. struct coreaudio_data *ca = data;
  648. coreaudio_shutdown(ca);
  649. bfree(ca->device_uid);
  650. ca->device_uid = bstrdup(obs_data_get_string(settings, "device_id"));
  651. ca->enable_downmix = obs_data_get_bool(settings, "enable_downmix");
  652. if (!ca->enable_downmix) {
  653. coreaudio_set_channels(ca, settings);
  654. }
  655. coreaudio_try_init(ca);
  656. }
  657. static void coreaudio_defaults(obs_data_t *settings)
  658. {
  659. obs_data_set_default_string(settings, "device_id", "default");
  660. obs_data_set_default_bool(settings, "enable_downmix", true);
  661. }
  662. static void *coreaudio_create(obs_data_t *settings, obs_source_t *source, bool input)
  663. {
  664. struct coreaudio_data *ca = bzalloc(sizeof(struct coreaudio_data));
  665. if (os_event_init(&ca->exit_event, OS_EVENT_TYPE_MANUAL) != 0) {
  666. blog(LOG_ERROR,
  667. "[coreaudio_create] failed to create "
  668. "semephore: %d",
  669. errno);
  670. bfree(ca);
  671. return NULL;
  672. }
  673. ca->device_uid = bstrdup(obs_data_get_string(settings, "device_id"));
  674. ca->source = source;
  675. ca->input = input;
  676. ca->enable_downmix = obs_data_get_bool(settings, "enable_downmix");
  677. if (!ca->enable_downmix) {
  678. coreaudio_set_channels(ca, settings);
  679. }
  680. if (!ca->device_uid)
  681. ca->device_uid = bstrdup("default");
  682. coreaudio_try_init(ca);
  683. return ca;
  684. }
  685. static void *coreaudio_create_input_capture(obs_data_t *settings, obs_source_t *source)
  686. {
  687. return coreaudio_create(settings, source, true);
  688. }
  689. static void *coreaudio_create_output_capture(obs_data_t *settings, obs_source_t *source)
  690. {
  691. return coreaudio_create(settings, source, false);
  692. }
  693. static void coreaudio_fill_combo_with_inputs(const struct coreaudio_data *ca, obs_property_t *input_combo,
  694. uint32_t output_channel)
  695. {
  696. bool hasMutedChannel = false;
  697. obs_property_list_clear(input_combo);
  698. if (output_channel < ca->available_channels) {
  699. obs_property_list_add_int(input_combo, ca->channel_names[output_channel], output_channel + 1);
  700. } else {
  701. obs_property_list_add_int(input_combo, obs_module_text("CoreAudio.None"), -1);
  702. hasMutedChannel = true;
  703. }
  704. for (uint32_t input_chan = 0; input_chan < ca->available_channels; input_chan++) {
  705. if (input_chan != output_channel) {
  706. obs_property_list_add_int(input_combo, ca->channel_names[input_chan], input_chan + 1);
  707. }
  708. }
  709. if (!hasMutedChannel) {
  710. obs_property_list_add_int(input_combo, obs_module_text("CoreAudio.None"), -1);
  711. }
  712. }
  713. static void ensure_output_channel_prop(const struct coreaudio_data *ca, obs_properties_t *props,
  714. const char *device_config_name, uint32_t out_chan)
  715. {
  716. struct dstr name;
  717. dstr_init(&name);
  718. dstr_printf(&name, "output-%s-%d", device_config_name, out_chan + 1);
  719. obs_property_t *prop = obs_properties_get(props, name.array);
  720. if (prop) {
  721. obs_property_set_visible(prop, true);
  722. } else {
  723. struct dstr label;
  724. dstr_init(&label);
  725. dstr_printf(&label, "%s %i", obs_module_text("CoreAudio.Channel"), out_chan + 1);
  726. obs_property_t *input_combo = obs_properties_add_list(props, name.array, label.array,
  727. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  728. dstr_free(&label);
  729. coreaudio_fill_combo_with_inputs(ca, input_combo, out_chan);
  730. }
  731. dstr_free(&name);
  732. }
  733. static void ensure_output_channels_visible(obs_properties_t *props, const struct coreaudio_data *ca, uint32_t channels)
  734. {
  735. char *device_config_name = sanitize_device_name(ca->device_uid);
  736. for (uint32_t out_chan = 0; out_chan < channels; out_chan++) {
  737. ensure_output_channel_prop(ca, props, device_config_name, out_chan);
  738. }
  739. bfree(device_config_name);
  740. }
  741. static void hide_all_output_channels(obs_properties_t *props)
  742. {
  743. for (obs_property_t *prop = obs_properties_first(props); prop != NULL; obs_property_next(&prop)) {
  744. const char *prop_name = obs_property_name(prop);
  745. if (strncmp("output-", prop_name, 7) == 0) {
  746. obs_property_set_visible(prop, false);
  747. }
  748. }
  749. }
  750. static bool coreaudio_device_changed(void *data, obs_properties_t *props, obs_property_t *p, obs_data_t *settings)
  751. {
  752. struct coreaudio_data *ca = data;
  753. if (ca != NULL) {
  754. hide_all_output_channels(props);
  755. if (!ca->enable_downmix) {
  756. uint32_t channels = get_audio_channels(ca->speakers);
  757. ensure_output_channels_visible(props, ca, channels);
  758. }
  759. }
  760. UNUSED_PARAMETER(p);
  761. UNUSED_PARAMETER(settings);
  762. return true;
  763. }
  764. static bool coreaudio_downmix_changed(void *data, obs_properties_t *props, obs_property_t *p __unused,
  765. obs_data_t *settings)
  766. {
  767. struct coreaudio_data *ca = data;
  768. if (ca != NULL) {
  769. bool enable_downmix = obs_data_get_bool(settings, "enable_downmix");
  770. ca->enable_downmix = enable_downmix;
  771. hide_all_output_channels(props);
  772. if (!ca->enable_downmix) {
  773. uint32_t channels = get_audio_channels(ca->speakers);
  774. ensure_output_channels_visible(props, ca, channels);
  775. }
  776. }
  777. return true;
  778. }
  779. static obs_properties_t *coreaudio_properties(bool input, void *data)
  780. {
  781. struct coreaudio_data *ca = data;
  782. obs_properties_t *props = obs_properties_create();
  783. obs_property_t *property;
  784. struct device_list devices;
  785. memset(&devices, 0, sizeof(struct device_list));
  786. property =
  787. obs_properties_add_list(props, "device_id", TEXT_DEVICE, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  788. coreaudio_enum_devices(&devices, input);
  789. if (devices.items.num)
  790. obs_property_list_add_string(property, TEXT_DEVICE_DEFAULT, "default");
  791. for (size_t i = 0; i < devices.items.num; i++) {
  792. struct device_item *item = devices.items.array + i;
  793. obs_property_list_add_string(property, item->name.array, item->value.array);
  794. }
  795. obs_property_set_modified_callback2(property, coreaudio_device_changed, ca);
  796. property = obs_properties_add_bool(props, "enable_downmix", obs_module_text("CoreAudio.Downmix"));
  797. obs_property_set_modified_callback2(property, coreaudio_downmix_changed, ca);
  798. if (ca != NULL && ca->au_initialized) {
  799. uint32_t channels = get_audio_channels(ca->speakers);
  800. ensure_output_channels_visible(props, ca, channels);
  801. if (ca->enable_downmix) {
  802. hide_all_output_channels(props);
  803. }
  804. }
  805. device_list_free(&devices);
  806. return props;
  807. }
  808. static obs_properties_t *coreaudio_input_properties(void *data)
  809. {
  810. return coreaudio_properties(true, data);
  811. }
  812. static obs_properties_t *coreaudio_output_properties(void *data)
  813. {
  814. return coreaudio_properties(false, data);
  815. }
  816. struct obs_source_info coreaudio_input_capture_info = {
  817. .id = "coreaudio_input_capture",
  818. .type = OBS_SOURCE_TYPE_INPUT,
  819. .output_flags = OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE,
  820. .get_name = coreaudio_input_getname,
  821. .create = coreaudio_create_input_capture,
  822. .destroy = coreaudio_destroy,
  823. .update = coreaudio_update,
  824. .get_defaults = coreaudio_defaults,
  825. .get_properties = coreaudio_input_properties,
  826. .icon_type = OBS_ICON_TYPE_AUDIO_INPUT,
  827. };
  828. struct obs_source_info coreaudio_output_capture_info = {
  829. .id = "coreaudio_output_capture",
  830. .type = OBS_SOURCE_TYPE_INPUT,
  831. .output_flags = OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE | OBS_SOURCE_DO_NOT_SELF_MONITOR,
  832. .get_name = coreaudio_output_getname,
  833. .create = coreaudio_create_output_capture,
  834. .destroy = coreaudio_destroy,
  835. .update = coreaudio_update,
  836. .get_defaults = coreaudio_defaults,
  837. .get_properties = coreaudio_output_properties,
  838. .icon_type = OBS_ICON_TYPE_AUDIO_OUTPUT,
  839. };