| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 | #include <AudioUnit/AudioUnit.h>#include <AudioToolbox/AudioQueue.h>#include <CoreFoundation/CFString.h>#include <CoreAudio/CoreAudio.h>#include "../../media-io/audio-resampler.h"#include "../../util/circlebuf.h"#include "../../util/threading.h"#include "../../util/platform.h"#include "../../obs-internal.h"#include "../../util/darray.h"#include "mac-helpers.h"struct audio_monitor {	obs_source_t *source;	AudioQueueRef queue;	AudioQueueBufferRef buffers[3];	pthread_mutex_t mutex;	struct circlebuf empty_buffers;	struct circlebuf new_data;	audio_resampler_t *resampler;	size_t buffer_size;	size_t wait_size;	uint32_t channels;	volatile bool active;	bool paused;	bool ignore;};static inline bool fill_buffer(struct audio_monitor *monitor){	AudioQueueBufferRef buf;	OSStatus stat;	if (monitor->new_data.size < monitor->buffer_size) {		return false;	}	circlebuf_pop_front(&monitor->empty_buffers, &buf, sizeof(buf));	circlebuf_pop_front(&monitor->new_data, buf->mAudioData,			    monitor->buffer_size);	buf->mAudioDataByteSize = monitor->buffer_size;	stat = AudioQueueEnqueueBuffer(monitor->queue, buf, 0, NULL);	if (!success(stat, "AudioQueueEnqueueBuffer")) {		blog(LOG_WARNING, "%s: %s", __FUNCTION__,		     "Failed to enqueue buffer");		AudioQueueStop(monitor->queue, false);	}	return true;}static void on_audio_playback(void *param, obs_source_t *source,			      const struct audio_data *audio_data, bool muted){	struct audio_monitor *monitor = param;	float vol = source->user_volume;	uint32_t bytes;	UNUSED_PARAMETER(source);	if (!os_atomic_load_bool(&monitor->active)) {		return;	}	uint8_t *resample_data[MAX_AV_PLANES];	uint32_t resample_frames;	uint64_t ts_offset;	bool success;	success = audio_resampler_resample(		monitor->resampler, resample_data, &resample_frames, &ts_offset,		(const uint8_t *const *)audio_data->data,		(uint32_t)audio_data->frames);	if (!success) {		return;	}	bytes = sizeof(float) * monitor->channels * resample_frames;	if (muted) {		memset(resample_data[0], 0, bytes);	} else {		/* apply volume */		if (!close_float(vol, 1.0f, EPSILON)) {			register float *cur = (float *)resample_data[0];			register float *end =				cur + resample_frames * monitor->channels;			while (cur < end)				*(cur++) *= vol;		}	}	pthread_mutex_lock(&monitor->mutex);	circlebuf_push_back(&monitor->new_data, resample_data[0], bytes);	if (monitor->new_data.size >= monitor->wait_size) {		monitor->wait_size = 0;		while (monitor->empty_buffers.size > 0) {			if (!fill_buffer(monitor)) {				break;			}		}		if (monitor->paused) {			AudioQueueStart(monitor->queue, NULL);			monitor->paused = false;		}	}	pthread_mutex_unlock(&monitor->mutex);}static void buffer_audio(void *data, AudioQueueRef aq, AudioQueueBufferRef buf){	struct audio_monitor *monitor = data;	pthread_mutex_lock(&monitor->mutex);	circlebuf_push_back(&monitor->empty_buffers, &buf, sizeof(buf));	while (monitor->empty_buffers.size > 0) {		if (!fill_buffer(monitor)) {			break;		}	}	if (monitor->empty_buffers.size == sizeof(buf) * 3) {		monitor->paused = true;		monitor->wait_size = monitor->buffer_size * 3;		AudioQueuePause(monitor->queue);	}	pthread_mutex_unlock(&monitor->mutex);	UNUSED_PARAMETER(aq);}extern bool devices_match(const char *id1, const char *id2);static bool audio_monitor_init(struct audio_monitor *monitor,			       obs_source_t *source){	const struct audio_output_info *info =		audio_output_get_info(obs->audio.audio);	uint32_t channels = get_audio_channels(info->speakers);	OSStatus stat;	AudioStreamBasicDescription desc = {		.mSampleRate = (Float64)info->samples_per_sec,		.mFormatID = kAudioFormatLinearPCM,		.mFormatFlags = kAudioFormatFlagIsFloat |				kAudioFormatFlagIsPacked,		.mBytesPerPacket = sizeof(float) * channels,		.mFramesPerPacket = 1,		.mBytesPerFrame = sizeof(float) * channels,		.mChannelsPerFrame = channels,		.mBitsPerChannel = sizeof(float) * 8};	monitor->source = source;	monitor->channels = channels;	monitor->buffer_size =		channels * sizeof(float) * info->samples_per_sec / 100 * 3;	monitor->wait_size = monitor->buffer_size * 3;	pthread_mutex_init_value(&monitor->mutex);	const char *uid = obs->audio.monitoring_device_id;	if (!uid || !*uid) {		return false;	}	if (source->info.output_flags & OBS_SOURCE_DO_NOT_SELF_MONITOR) {		obs_data_t *s = obs_source_get_settings(source);		const char *s_dev_id = obs_data_get_string(s, "device_id");		bool match = devices_match(s_dev_id, uid);		obs_data_release(s);		if (match) {			monitor->ignore = true;			return true;		}	}	stat = AudioQueueNewOutput(&desc, buffer_audio, monitor, NULL, NULL, 0,				   &monitor->queue);	if (!success(stat, "AudioStreamBasicDescription")) {		return false;	}	if (strcmp(uid, "default") != 0) {		CFStringRef cf_uid = CFStringCreateWithBytes(			NULL, (const UInt8 *)uid, strlen(uid),			kCFStringEncodingUTF8, false);		stat = AudioQueueSetProperty(monitor->queue,					     kAudioQueueProperty_CurrentDevice,					     &cf_uid, sizeof(cf_uid));		CFRelease(cf_uid);		if (!success(stat, "set current device")) {			return false;		}	}	stat = AudioQueueSetParameter(monitor->queue, kAudioQueueParam_Volume,				      1.0);	if (!success(stat, "set volume")) {		return false;	}	for (size_t i = 0; i < 3; i++) {		stat = AudioQueueAllocateBuffer(monitor->queue,						monitor->buffer_size,						&monitor->buffers[i]);		if (!success(stat, "allocation of buffer")) {			return false;		}		circlebuf_push_back(&monitor->empty_buffers,				    &monitor->buffers[i],				    sizeof(monitor->buffers[i]));	}	if (pthread_mutex_init(&monitor->mutex, NULL) != 0) {		blog(LOG_WARNING, "%s: %s", __FUNCTION__,		     "Failed to init mutex");		return false;	}	struct resample_info from = {.samples_per_sec = info->samples_per_sec,				     .speakers = info->speakers,				     .format = AUDIO_FORMAT_FLOAT_PLANAR};	struct resample_info to = {.samples_per_sec = info->samples_per_sec,				   .speakers = info->speakers,				   .format = AUDIO_FORMAT_FLOAT};	monitor->resampler = audio_resampler_create(&to, &from);	if (!monitor->resampler) {		blog(LOG_WARNING, "%s: %s", __FUNCTION__,		     "Failed to create resampler");		return false;	}	stat = AudioQueueStart(monitor->queue, NULL);	if (!success(stat, "start")) {		return false;	}	monitor->active = true;	return true;}static void audio_monitor_free(struct audio_monitor *monitor){	if (monitor->source) {		obs_source_remove_audio_capture_callback(			monitor->source, on_audio_playback, monitor);	}	if (monitor->active) {		AudioQueueStop(monitor->queue, true);	}	for (size_t i = 0; i < 3; i++) {		if (monitor->buffers[i]) {			AudioQueueFreeBuffer(monitor->queue,					     monitor->buffers[i]);		}	}	if (monitor->queue) {		AudioQueueDispose(monitor->queue, true);	}	audio_resampler_destroy(monitor->resampler);	circlebuf_free(&monitor->empty_buffers);	circlebuf_free(&monitor->new_data);	pthread_mutex_destroy(&monitor->mutex);}static void audio_monitor_init_final(struct audio_monitor *monitor){	if (monitor->ignore)		return;	obs_source_add_audio_capture_callback(monitor->source,					      on_audio_playback, monitor);}struct audio_monitor *audio_monitor_create(obs_source_t *source){	struct audio_monitor *monitor = bzalloc(sizeof(*monitor));	if (!audio_monitor_init(monitor, source)) {		goto fail;	}	pthread_mutex_lock(&obs->audio.monitoring_mutex);	da_push_back(obs->audio.monitors, &monitor);	pthread_mutex_unlock(&obs->audio.monitoring_mutex);	audio_monitor_init_final(monitor);	return monitor;fail:	audio_monitor_free(monitor);	bfree(monitor);	return NULL;}void audio_monitor_reset(struct audio_monitor *monitor){	bool success;	obs_source_t *source = monitor->source;	audio_monitor_free(monitor);	memset(monitor, 0, sizeof(*monitor));	success = audio_monitor_init(monitor, source);	if (success)		audio_monitor_init_final(monitor);}void audio_monitor_destroy(struct audio_monitor *monitor){	if (monitor) {		audio_monitor_free(monitor);		pthread_mutex_lock(&obs->audio.monitoring_mutex);		da_erase_item(obs->audio.monitors, &monitor);		pthread_mutex_unlock(&obs->audio.monitoring_mutex);		bfree(monitor);	}}
 |