mac-audio.c 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067
  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. /* If the device is also used for monitoring, a cleanup is needed. */
  621. if (!ca->input)
  622. obs_source_audio_output_capture_device_changed(ca->source, NULL);
  623. os_event_destroy(ca->exit_event);
  624. if (ca->channel_map) {
  625. bfree(ca->channel_map);
  626. ca->channel_map = NULL;
  627. }
  628. bfree(ca->device_name);
  629. bfree(ca->device_uid);
  630. bfree(ca);
  631. }
  632. }
  633. static void coreaudio_set_channels(struct coreaudio_data *ca, obs_data_t *settings)
  634. {
  635. ca->channel_map = bzalloc(sizeof(SInt32) * MAX_AUDIO_CHANNELS);
  636. char *device_config_name = sanitize_device_name(ca->device_uid);
  637. for (uint8_t i = 0; i < MAX_AUDIO_CHANNELS; i++) {
  638. char setting_name[128];
  639. snprintf(setting_name, 128, "output-%s-%i", device_config_name, i + 1);
  640. int64_t found = obs_data_has_user_value(settings, setting_name)
  641. ? obs_data_get_int(settings, setting_name)
  642. : -1L;
  643. int64_t adjusted = found > 0 ? found - 1 : -1;
  644. ca->channel_map[i] = (int32_t)adjusted;
  645. }
  646. bfree(device_config_name);
  647. }
  648. static void coreaudio_update(void *data, obs_data_t *settings)
  649. {
  650. struct coreaudio_data *ca = data;
  651. const char *new_id = obs_data_get_string(settings, "device_id");
  652. if (!ca->input && strcmp(new_id, ca->device_uid) != 0)
  653. obs_source_audio_output_capture_device_changed(ca->source, new_id);
  654. coreaudio_shutdown(ca);
  655. bfree(ca->device_uid);
  656. ca->device_uid = bstrdup(new_id);
  657. ca->enable_downmix = obs_data_get_bool(settings, "enable_downmix");
  658. if (!ca->enable_downmix) {
  659. coreaudio_set_channels(ca, settings);
  660. }
  661. coreaudio_try_init(ca);
  662. }
  663. static void coreaudio_defaults(obs_data_t *settings)
  664. {
  665. obs_data_set_default_string(settings, "device_id", "default");
  666. obs_data_set_default_bool(settings, "enable_downmix", true);
  667. }
  668. static void *coreaudio_create(obs_data_t *settings, obs_source_t *source, bool input)
  669. {
  670. struct coreaudio_data *ca = bzalloc(sizeof(struct coreaudio_data));
  671. if (os_event_init(&ca->exit_event, OS_EVENT_TYPE_MANUAL) != 0) {
  672. blog(LOG_ERROR,
  673. "[coreaudio_create] failed to create "
  674. "semephore: %d",
  675. errno);
  676. bfree(ca);
  677. return NULL;
  678. }
  679. ca->device_uid = bstrdup(obs_data_get_string(settings, "device_id"));
  680. ca->source = source;
  681. ca->input = input;
  682. ca->enable_downmix = obs_data_get_bool(settings, "enable_downmix");
  683. if (!ca->enable_downmix) {
  684. coreaudio_set_channels(ca, settings);
  685. }
  686. if (!ca->device_uid)
  687. ca->device_uid = bstrdup("default");
  688. coreaudio_try_init(ca);
  689. if (!ca->input)
  690. obs_source_audio_output_capture_device_changed(source, ca->device_uid);
  691. return ca;
  692. }
  693. static void *coreaudio_create_input_capture(obs_data_t *settings, obs_source_t *source)
  694. {
  695. return coreaudio_create(settings, source, true);
  696. }
  697. static void *coreaudio_create_output_capture(obs_data_t *settings, obs_source_t *source)
  698. {
  699. return coreaudio_create(settings, source, false);
  700. }
  701. static void coreaudio_fill_combo_with_inputs(const struct coreaudio_data *ca, obs_property_t *input_combo,
  702. uint32_t output_channel)
  703. {
  704. bool hasMutedChannel = false;
  705. obs_property_list_clear(input_combo);
  706. if (output_channel < ca->available_channels) {
  707. obs_property_list_add_int(input_combo, ca->channel_names[output_channel], output_channel + 1);
  708. } else {
  709. obs_property_list_add_int(input_combo, obs_module_text("CoreAudio.None"), -1);
  710. hasMutedChannel = true;
  711. }
  712. for (uint32_t input_chan = 0; input_chan < ca->available_channels; input_chan++) {
  713. if (input_chan != output_channel) {
  714. obs_property_list_add_int(input_combo, ca->channel_names[input_chan], input_chan + 1);
  715. }
  716. }
  717. if (!hasMutedChannel) {
  718. obs_property_list_add_int(input_combo, obs_module_text("CoreAudio.None"), -1);
  719. }
  720. }
  721. static void ensure_output_channel_prop(const struct coreaudio_data *ca, obs_properties_t *props,
  722. const char *device_config_name, uint32_t out_chan)
  723. {
  724. struct dstr name;
  725. dstr_init(&name);
  726. dstr_printf(&name, "output-%s-%d", device_config_name, out_chan + 1);
  727. obs_property_t *prop = obs_properties_get(props, name.array);
  728. if (prop) {
  729. obs_property_set_visible(prop, true);
  730. } else {
  731. struct dstr label;
  732. dstr_init(&label);
  733. dstr_printf(&label, "%s %i", obs_module_text("CoreAudio.Channel"), out_chan + 1);
  734. obs_property_t *input_combo = obs_properties_add_list(props, name.array, label.array,
  735. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  736. dstr_free(&label);
  737. coreaudio_fill_combo_with_inputs(ca, input_combo, out_chan);
  738. }
  739. dstr_free(&name);
  740. }
  741. static void ensure_output_channels_visible(obs_properties_t *props, const struct coreaudio_data *ca, uint32_t channels)
  742. {
  743. char *device_config_name = sanitize_device_name(ca->device_uid);
  744. for (uint32_t out_chan = 0; out_chan < channels; out_chan++) {
  745. ensure_output_channel_prop(ca, props, device_config_name, out_chan);
  746. }
  747. bfree(device_config_name);
  748. }
  749. static void hide_all_output_channels(obs_properties_t *props)
  750. {
  751. for (obs_property_t *prop = obs_properties_first(props); prop != NULL; obs_property_next(&prop)) {
  752. const char *prop_name = obs_property_name(prop);
  753. if (strncmp("output-", prop_name, 7) == 0) {
  754. obs_property_set_visible(prop, false);
  755. }
  756. }
  757. }
  758. static bool coreaudio_device_changed(void *data, obs_properties_t *props, obs_property_t *p, obs_data_t *settings)
  759. {
  760. struct coreaudio_data *ca = data;
  761. if (ca != NULL) {
  762. hide_all_output_channels(props);
  763. if (!ca->enable_downmix) {
  764. uint32_t channels = get_audio_channels(ca->speakers);
  765. ensure_output_channels_visible(props, ca, channels);
  766. }
  767. }
  768. UNUSED_PARAMETER(p);
  769. UNUSED_PARAMETER(settings);
  770. return true;
  771. }
  772. static bool coreaudio_downmix_changed(void *data, obs_properties_t *props, obs_property_t *p __unused,
  773. obs_data_t *settings)
  774. {
  775. struct coreaudio_data *ca = data;
  776. if (ca != NULL) {
  777. bool enable_downmix = obs_data_get_bool(settings, "enable_downmix");
  778. ca->enable_downmix = enable_downmix;
  779. hide_all_output_channels(props);
  780. if (!ca->enable_downmix) {
  781. uint32_t channels = get_audio_channels(ca->speakers);
  782. ensure_output_channels_visible(props, ca, channels);
  783. }
  784. }
  785. return true;
  786. }
  787. static obs_properties_t *coreaudio_properties(bool input, void *data)
  788. {
  789. struct coreaudio_data *ca = data;
  790. obs_properties_t *props = obs_properties_create();
  791. obs_property_t *property;
  792. struct device_list devices;
  793. memset(&devices, 0, sizeof(struct device_list));
  794. property =
  795. obs_properties_add_list(props, "device_id", TEXT_DEVICE, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  796. coreaudio_enum_devices(&devices, input);
  797. if (devices.items.num)
  798. obs_property_list_add_string(property, TEXT_DEVICE_DEFAULT, "default");
  799. for (size_t i = 0; i < devices.items.num; i++) {
  800. struct device_item *item = devices.items.array + i;
  801. obs_property_list_add_string(property, item->name.array, item->value.array);
  802. }
  803. obs_property_set_modified_callback2(property, coreaudio_device_changed, ca);
  804. property = obs_properties_add_bool(props, "enable_downmix", obs_module_text("CoreAudio.Downmix"));
  805. obs_property_set_modified_callback2(property, coreaudio_downmix_changed, ca);
  806. if (ca != NULL && ca->au_initialized) {
  807. uint32_t channels = get_audio_channels(ca->speakers);
  808. ensure_output_channels_visible(props, ca, channels);
  809. if (ca->enable_downmix) {
  810. hide_all_output_channels(props);
  811. }
  812. }
  813. device_list_free(&devices);
  814. return props;
  815. }
  816. static obs_properties_t *coreaudio_input_properties(void *data)
  817. {
  818. return coreaudio_properties(true, data);
  819. }
  820. static obs_properties_t *coreaudio_output_properties(void *data)
  821. {
  822. return coreaudio_properties(false, data);
  823. }
  824. struct obs_source_info coreaudio_input_capture_info = {
  825. .id = "coreaudio_input_capture",
  826. .type = OBS_SOURCE_TYPE_INPUT,
  827. .output_flags = OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE,
  828. .get_name = coreaudio_input_getname,
  829. .create = coreaudio_create_input_capture,
  830. .destroy = coreaudio_destroy,
  831. .update = coreaudio_update,
  832. .get_defaults = coreaudio_defaults,
  833. .get_properties = coreaudio_input_properties,
  834. .icon_type = OBS_ICON_TYPE_AUDIO_INPUT,
  835. };
  836. struct obs_source_info coreaudio_output_capture_info = {
  837. .id = "coreaudio_output_capture",
  838. .type = OBS_SOURCE_TYPE_INPUT,
  839. .output_flags = OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE | OBS_SOURCE_DO_NOT_SELF_MONITOR,
  840. .get_name = coreaudio_output_getname,
  841. .create = coreaudio_create_output_capture,
  842. .destroy = coreaudio_destroy,
  843. .update = coreaudio_update,
  844. .get_defaults = coreaudio_defaults,
  845. .get_properties = coreaudio_output_properties,
  846. .icon_type = OBS_ICON_TYPE_AUDIO_OUTPUT,
  847. };