mirror of
https://github.com/obsproject/obs-studio.git
synced 2024-07-20 10:09:15 +00:00
mac-capture: Switch AudioUnits to use canonical format on output bus
This commit is contained in:
parent
73e9561633
commit
5697b085da
|
@ -2,6 +2,12 @@ CoreAudio.InputCapture="Audio Input Capture"
|
|||
CoreAudio.OutputCapture="Audio Output Capture"
|
||||
CoreAudio.Device="Device"
|
||||
CoreAudio.Device.Default="Default"
|
||||
CoreAudio.Channel="Channel"
|
||||
CoreAudio.Channel.Default="Default"
|
||||
CoreAudio.Channel.Unnamed="Unnamed"
|
||||
CoreAudio.Channel.Device="Device Channel"
|
||||
CoreAudio.None="None"
|
||||
CoreAudio.Downmix="Enable Downmixing"
|
||||
ApplicationCapture="Application Capture"
|
||||
ApplicationAudioCapture="Application Audio Capture"
|
||||
DesktopAudioCapture="Desktop Audio Capture"
|
||||
|
|
|
@ -49,6 +49,7 @@ struct coreaudio_data {
|
|||
uint32_t sample_rate;
|
||||
enum audio_format format;
|
||||
enum speaker_layout speakers;
|
||||
bool enable_downmix;
|
||||
|
||||
pthread_t reconnect_thread;
|
||||
os_event_t *exit_event;
|
||||
|
@ -110,7 +111,7 @@ static bool find_device_id_by_uid(struct coreaudio_data *ca)
|
|||
kCFStringEncodingUTF8);
|
||||
|
||||
if (ca->default_device) {
|
||||
addr.mSelector = PROPERTY_DEFAULT_DEVICE;
|
||||
addr.mSelector = kAudioHardwarePropertyDefaultInputDevice;
|
||||
stat = AudioObjectGetPropertyData(kAudioObjectSystemObject,
|
||||
&addr, qual_size, &qual,
|
||||
&size, &ca->device_id);
|
||||
|
@ -169,6 +170,27 @@ static inline bool enable_io(struct coreaudio_data *ca,
|
|||
&enable_int, sizeof(enable_int));
|
||||
}
|
||||
|
||||
static inline enum speaker_layout convert_ca_speaker_layout(UInt32 channels)
|
||||
{
|
||||
switch (channels) {
|
||||
case 1:
|
||||
return SPEAKERS_MONO;
|
||||
case 2:
|
||||
return SPEAKERS_STEREO;
|
||||
case 3:
|
||||
return SPEAKERS_2POINT1;
|
||||
case 4:
|
||||
return SPEAKERS_4POINT0;
|
||||
case 5:
|
||||
return SPEAKERS_4POINT1;
|
||||
case 6:
|
||||
return SPEAKERS_5POINT1;
|
||||
case 8:
|
||||
return SPEAKERS_7POINT1;
|
||||
}
|
||||
return SPEAKERS_UNKNOWN;
|
||||
}
|
||||
|
||||
static inline enum audio_format convert_ca_format(UInt32 format_flags,
|
||||
UInt32 bits)
|
||||
{
|
||||
|
@ -192,11 +214,11 @@ static inline enum audio_format convert_ca_format(UInt32 format_flags,
|
|||
return AUDIO_FORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
static char *sanitise_device_name(char *name)
|
||||
static char *sanitize_device_name(char *name)
|
||||
{
|
||||
const size_t max_len = 64;
|
||||
size_t len = strlen(name);
|
||||
char buf[max_len];
|
||||
char buf[64];
|
||||
size_t out_idx = 0;
|
||||
|
||||
for (size_t i = len > max_len ? len - max_len : 0; i < len; i++) {
|
||||
|
@ -230,12 +252,18 @@ static char **coreaudio_get_channel_names(struct coreaudio_data *ca)
|
|||
if (ca_success(stat, ca, "coreaudio_init_format",
|
||||
"get channel names") &&
|
||||
CFStringGetLength(cf_chan_name)) {
|
||||
dstr_printf(
|
||||
&name, "%d - %s", i + 1,
|
||||
CFStringGetCStringPtr(cf_chan_name,
|
||||
kCFStringEncodingUTF8));
|
||||
|
||||
char *channelName = cfstr_copy_cstr(
|
||||
cf_chan_name, kCFStringEncodingUTF8);
|
||||
|
||||
dstr_printf(&name, "%s", channelName);
|
||||
|
||||
if (channelName) {
|
||||
bfree(channelName);
|
||||
}
|
||||
} else {
|
||||
dstr_printf(&name, "%d - Channel %d [unnamed]", i + 1,
|
||||
dstr_printf(&name, "%s %d",
|
||||
obs_module_text("CoreAudio.Channel.Device"),
|
||||
i + 1);
|
||||
}
|
||||
channel_names[i] = bstrdup_n(name.array, name.len + 1);
|
||||
|
@ -251,6 +279,7 @@ static char **coreaudio_get_channel_names(struct coreaudio_data *ca)
|
|||
static bool coreaudio_init_format(struct coreaudio_data *ca)
|
||||
{
|
||||
AudioStreamBasicDescription desc;
|
||||
AudioStreamBasicDescription inputDescription;
|
||||
OSStatus stat;
|
||||
UInt32 size;
|
||||
struct obs_audio_info aoi;
|
||||
|
@ -261,45 +290,62 @@ static bool coreaudio_init_format(struct coreaudio_data *ca)
|
|||
ca->speakers = aoi.speakers;
|
||||
uint32_t channels = get_audio_channels(ca->speakers);
|
||||
|
||||
size = sizeof(desc);
|
||||
size = sizeof(inputDescription);
|
||||
stat = get_property(ca->unit, kAudioUnitProperty_StreamFormat,
|
||||
SCOPE_INPUT, BUS_INPUT, &desc, &size);
|
||||
kAudioUnitScope_Input, 1, &inputDescription, &size);
|
||||
|
||||
if (!ca_success(stat, ca, "coreaudio_init_format",
|
||||
"get input device format"))
|
||||
return false;
|
||||
|
||||
stat = get_property(ca->unit, kAudioUnitProperty_StreamFormat,
|
||||
SCOPE_OUTPUT, BUS_INPUT, &desc, &size);
|
||||
if (!ca_success(stat, ca, "coreaudio_init_format", "get input format"))
|
||||
return false;
|
||||
|
||||
ca->available_channels = desc.mChannelsPerFrame;
|
||||
ca->available_channels = inputDescription.mChannelsPerFrame;
|
||||
if (ca->available_channels > MAX_DEVICE_INPUT_CHANNELS) {
|
||||
ca->available_channels = MAX_DEVICE_INPUT_CHANNELS;
|
||||
}
|
||||
|
||||
ca->channel_names = coreaudio_get_channel_names(ca);
|
||||
|
||||
// Always work out the format from the number of channels in the speaker layout
|
||||
desc.mChannelsPerFrame = channels;
|
||||
desc.mBytesPerFrame = desc.mChannelsPerFrame * desc.mBitsPerChannel / 8;
|
||||
desc.mBytesPerPacket = desc.mFramesPerPacket * desc.mBytesPerFrame;
|
||||
|
||||
// Mute any channels mapped in config that we don't really have
|
||||
char *sep = "";
|
||||
struct dstr cm_str;
|
||||
dstr_init(&cm_str);
|
||||
for (size_t i = 0; i < channels; i++) {
|
||||
dstr_cat(&cm_str, sep);
|
||||
if (ca->channel_map[i] >= (int32_t)ca->available_channels) {
|
||||
ca->channel_map[i] = -1;
|
||||
if (ca->enable_downmix) {
|
||||
blog(LOG_INFO, "Downmix enabled: %d to %d channels.",
|
||||
ca->available_channels, channels);
|
||||
desc.mChannelsPerFrame = ca->available_channels;
|
||||
} else {
|
||||
// Mute any channels mapped in config that we don't really have
|
||||
char *sep = "";
|
||||
struct dstr cm_str;
|
||||
dstr_init(&cm_str);
|
||||
for (size_t i = 0; i < channels; i++) {
|
||||
dstr_cat(&cm_str, sep);
|
||||
if (ca->channel_map[i] >=
|
||||
(int32_t)ca->available_channels) {
|
||||
ca->channel_map[i] = -1;
|
||||
}
|
||||
dstr_catf(&cm_str, "%d", ca->channel_map[i]);
|
||||
sep = ",";
|
||||
}
|
||||
dstr_catf(&cm_str, "%d", ca->channel_map[i]);
|
||||
sep = ",";
|
||||
}
|
||||
blog(LOG_INFO, "Channel map [%s]. Inputs available: %d", cm_str.array,
|
||||
ca->available_channels);
|
||||
dstr_free(&cm_str);
|
||||
blog(LOG_INFO,
|
||||
"Channel map enabled: [%s] (%d channels available)",
|
||||
cm_str.array, ca->available_channels);
|
||||
dstr_free(&cm_str);
|
||||
|
||||
stat = set_property(ca->unit, kAudioOutputUnitProperty_ChannelMap,
|
||||
SCOPE_INPUT, BUS_INPUT, ca->channel_map,
|
||||
sizeof(SInt32) * channels);
|
||||
if (!ca_success(stat, ca, "coreaudio_init_format", "set channel map"))
|
||||
return false;
|
||||
stat = set_property(ca->unit,
|
||||
kAudioOutputUnitProperty_ChannelMap,
|
||||
SCOPE_OUTPUT, BUS_INPUT, ca->channel_map,
|
||||
sizeof(SInt32) * channels);
|
||||
if (!ca_success(stat, ca, "coreaudio_init_format",
|
||||
"set channel map")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
desc.mChannelsPerFrame = channels;
|
||||
}
|
||||
|
||||
desc.mSampleRate = inputDescription.mSampleRate;
|
||||
|
||||
stat = set_property(ca->unit, kAudioUnitProperty_StreamFormat,
|
||||
SCOPE_OUTPUT, BUS_INPUT, &desc, size);
|
||||
|
@ -328,55 +374,72 @@ static bool coreaudio_init_format(struct coreaudio_data *ca)
|
|||
|
||||
static bool coreaudio_init_buffer(struct coreaudio_data *ca)
|
||||
{
|
||||
UInt32 buf_size = 0;
|
||||
UInt32 size = 0;
|
||||
UInt32 frames = 0;
|
||||
OSStatus stat;
|
||||
UInt32 bufferSizeFrames;
|
||||
UInt32 bufferSizeBytes;
|
||||
UInt32 propertySize;
|
||||
OSStatus err = noErr;
|
||||
|
||||
AudioObjectPropertyAddress addr = {
|
||||
kAudioDevicePropertyStreamConfiguration,
|
||||
kAudioDevicePropertyScopeInput,
|
||||
propertySize = sizeof(bufferSizeFrames);
|
||||
err = AudioUnitGetProperty(ca->unit,
|
||||
kAudioDevicePropertyBufferFrameSize,
|
||||
kAudioUnitScope_Global, 0, &bufferSizeFrames,
|
||||
&propertySize);
|
||||
|
||||
if (!ca_success(err, ca, "coreaudio_init_buffer",
|
||||
"get buffer frame size")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bufferSizeBytes = bufferSizeFrames * sizeof(Float32);
|
||||
|
||||
AudioStreamBasicDescription streamDescription;
|
||||
propertySize = sizeof(streamDescription);
|
||||
err = AudioUnitGetProperty(ca->unit, kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Output, 1,
|
||||
&streamDescription, &propertySize);
|
||||
|
||||
if (!ca_success(err, ca, "coreaudio_init_buffer",
|
||||
"get stream format")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ca->enable_downmix) {
|
||||
streamDescription.mChannelsPerFrame =
|
||||
get_audio_channels(ca->speakers);
|
||||
}
|
||||
|
||||
Float64 rate = 0.0;
|
||||
propertySize = sizeof(Float64);
|
||||
AudioObjectPropertyAddress propertyAddress = {
|
||||
kAudioDevicePropertyNominalSampleRate,
|
||||
kAudioObjectPropertyScopeGlobal,
|
||||
kAudioObjectPropertyElementMain};
|
||||
|
||||
stat = AudioObjectGetPropertyDataSize(ca->device_id, &addr, 0, NULL,
|
||||
&buf_size);
|
||||
if (!ca_success(stat, ca, "coreaudio_init_buffer", "get list size"))
|
||||
return false;
|
||||
err = AudioObjectGetPropertyData(ca->device_id, &propertyAddress, 0,
|
||||
NULL, &propertySize, &rate);
|
||||
|
||||
size = sizeof(frames);
|
||||
stat = get_property(ca->unit, kAudioDevicePropertyBufferFrameSize,
|
||||
SCOPE_GLOBAL, 0, &frames, &size);
|
||||
if (!ca_success(stat, ca, "coreaudio_init_buffer", "get frame size"))
|
||||
return false;
|
||||
|
||||
/* ---------------------- */
|
||||
|
||||
AudioBufferList *buf_list = bmalloc(buf_size);
|
||||
|
||||
stat = AudioObjectGetPropertyData(ca->device_id, &addr, 0, NULL,
|
||||
&buf_size, buf_list);
|
||||
if (!ca_success(stat, ca, "coreaudio_init_buffer", "allocate")) {
|
||||
bfree(buf_list);
|
||||
buf_list = NULL;
|
||||
if (!ca_success(err, ca, "coreaudio_init_buffer",
|
||||
"get input sample rate")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int channels = get_audio_channels(ca->speakers);
|
||||
for (UInt32 i = 0; i < buf_list->mNumberBuffers; i++) {
|
||||
// The returned buffer seems to ignore the channel mapping.
|
||||
// This corrects it to take that into account -
|
||||
// but this probably wouldn't work for planar formats.
|
||||
// We are probably better off explicitly setting the format we want,
|
||||
// and just taking sample rate from the device
|
||||
uint chan_size = buf_list->mBuffers[i].mDataByteSize /
|
||||
buf_list->mBuffers[i].mNumberChannels;
|
||||
buf_list->mBuffers[i].mDataByteSize = chan_size * channels;
|
||||
buf_list->mBuffers[i].mNumberChannels = channels;
|
||||
size = buf_list->mBuffers[i].mDataByteSize;
|
||||
buf_list->mBuffers[i].mData = bmalloc(size);
|
||||
streamDescription.mSampleRate = rate;
|
||||
|
||||
int bufferPropertySize =
|
||||
offsetof(AudioBufferList, mBuffers[0]) +
|
||||
(sizeof(AudioBuffer) * streamDescription.mChannelsPerFrame);
|
||||
|
||||
AudioBufferList *inputBuffer =
|
||||
(AudioBufferList *)bmalloc(bufferPropertySize);
|
||||
inputBuffer->mNumberBuffers = streamDescription.mChannelsPerFrame;
|
||||
|
||||
for (UInt32 i = 0; i < inputBuffer->mNumberBuffers; i++) {
|
||||
inputBuffer->mBuffers[i].mNumberChannels = 1;
|
||||
inputBuffer->mBuffers[i].mDataByteSize = bufferSizeBytes;
|
||||
inputBuffer->mBuffers[i].mData = bmalloc(bufferSizeBytes);
|
||||
}
|
||||
|
||||
ca->buf_list = buf_list;
|
||||
ca->buf_list = inputBuffer;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -408,7 +471,7 @@ static OSStatus input_callback(void *data,
|
|||
audio.data[i] = ca->buf_list->mBuffers[i].mData;
|
||||
|
||||
audio.frames = frames;
|
||||
audio.speakers = ca->speakers;
|
||||
audio.speakers = ca->buf_list->mNumberBuffers;
|
||||
audio.format = ca->format;
|
||||
audio.samples_per_sec = ca->sample_rate;
|
||||
static double factor = 0.;
|
||||
|
@ -802,7 +865,7 @@ static void coreaudio_set_channels(struct coreaudio_data *ca,
|
|||
{
|
||||
ca->channel_map = bzalloc(sizeof(SInt32) * MAX_AV_PLANES);
|
||||
|
||||
char *device_config_name = sanitise_device_name(ca->device_uid);
|
||||
char *device_config_name = sanitize_device_name(ca->device_uid);
|
||||
for (uint8_t i = 0; i < MAX_AV_PLANES; i++) {
|
||||
char setting_name[128];
|
||||
snprintf(setting_name, 128, "output-%s-%i", device_config_name,
|
||||
|
@ -825,7 +888,12 @@ static void coreaudio_update(void *data, obs_data_t *settings)
|
|||
|
||||
bfree(ca->device_uid);
|
||||
ca->device_uid = bstrdup(obs_data_get_string(settings, "device_id"));
|
||||
coreaudio_set_channels(ca, settings);
|
||||
|
||||
ca->enable_downmix = obs_data_get_bool(settings, "enable_downmix");
|
||||
|
||||
if (!ca->enable_downmix) {
|
||||
coreaudio_set_channels(ca, settings);
|
||||
}
|
||||
|
||||
coreaudio_try_init(ca);
|
||||
}
|
||||
|
@ -833,6 +901,7 @@ static void coreaudio_update(void *data, obs_data_t *settings)
|
|||
static void coreaudio_defaults(obs_data_t *settings)
|
||||
{
|
||||
obs_data_set_default_string(settings, "device_id", "default");
|
||||
obs_data_set_default_bool(settings, "enable_downmix", true);
|
||||
}
|
||||
|
||||
static void *coreaudio_create(obs_data_t *settings, obs_source_t *source,
|
||||
|
@ -852,7 +921,11 @@ static void *coreaudio_create(obs_data_t *settings, obs_source_t *source,
|
|||
ca->device_uid = bstrdup(obs_data_get_string(settings, "device_id"));
|
||||
ca->source = source;
|
||||
ca->input = input;
|
||||
coreaudio_set_channels(ca, settings);
|
||||
ca->enable_downmix = obs_data_get_bool(settings, "enable_downmix");
|
||||
|
||||
if (!ca->enable_downmix) {
|
||||
coreaudio_set_channels(ca, settings);
|
||||
}
|
||||
|
||||
if (!ca->device_uid)
|
||||
ca->device_uid = bstrdup("default");
|
||||
|
@ -874,34 +947,35 @@ static void *coreaudio_create_output_capture(obs_data_t *settings,
|
|||
}
|
||||
|
||||
static void coreaudio_fill_combo_with_inputs(const struct coreaudio_data *ca,
|
||||
uint32_t out_chan,
|
||||
obs_property_t *input_combo)
|
||||
obs_property_t *input_combo,
|
||||
uint32_t output_channel)
|
||||
{
|
||||
bool hasMutedChannel = false;
|
||||
obs_property_list_clear(input_combo);
|
||||
bool defaulted = out_chan < ca->available_channels;
|
||||
if (defaulted) {
|
||||
struct dstr default_name;
|
||||
dstr_init(&default_name);
|
||||
dstr_printf(&default_name, "%s [Default]",
|
||||
ca->channel_names[out_chan]);
|
||||
obs_property_list_add_int(input_combo, default_name.array,
|
||||
out_chan + 1);
|
||||
dstr_free(&default_name);
|
||||
|
||||
if (output_channel < ca->available_channels) {
|
||||
obs_property_list_add_int(input_combo,
|
||||
ca->channel_names[output_channel],
|
||||
output_channel + 1);
|
||||
} else {
|
||||
obs_property_list_add_int(input_combo, "Mute [Default]", -1);
|
||||
obs_property_list_add_int(
|
||||
input_combo, obs_module_text("CoreAudio.None"), -1);
|
||||
hasMutedChannel = true;
|
||||
}
|
||||
|
||||
for (uint32_t input_chan = 0; input_chan < ca->available_channels;
|
||||
input_chan++) {
|
||||
if (!defaulted || input_chan != out_chan) {
|
||||
|
||||
if (input_chan != output_channel) {
|
||||
obs_property_list_add_int(input_combo,
|
||||
ca->channel_names[input_chan],
|
||||
input_chan + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (defaulted) {
|
||||
obs_property_list_add_int(input_combo, "Mute", -1);
|
||||
if (!hasMutedChannel) {
|
||||
obs_property_list_add_int(
|
||||
input_combo, obs_module_text("CoreAudio.None"), -1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -921,12 +995,13 @@ static void ensure_output_channel_prop(const struct coreaudio_data *ca,
|
|||
} else {
|
||||
struct dstr label;
|
||||
dstr_init(&label);
|
||||
dstr_printf(&label, "Output %i", out_chan + 1);
|
||||
dstr_printf(&label, "%s %i",
|
||||
obs_module_text("CoreAudio.Channel"), out_chan + 1);
|
||||
obs_property_t *input_combo = obs_properties_add_list(
|
||||
props, name.array, label.array, OBS_COMBO_TYPE_LIST,
|
||||
OBS_COMBO_FORMAT_INT);
|
||||
dstr_free(&label);
|
||||
coreaudio_fill_combo_with_inputs(ca, out_chan, input_combo);
|
||||
coreaudio_fill_combo_with_inputs(ca, input_combo, out_chan);
|
||||
}
|
||||
dstr_free(&name);
|
||||
}
|
||||
|
@ -935,7 +1010,7 @@ static void ensure_output_channels_visible(obs_properties_t *props,
|
|||
const struct coreaudio_data *ca,
|
||||
uint32_t channels)
|
||||
{
|
||||
char *device_config_name = sanitise_device_name(ca->device_uid);
|
||||
char *device_config_name = sanitize_device_name(ca->device_uid);
|
||||
for (uint32_t out_chan = 0; out_chan < channels; out_chan++) {
|
||||
ensure_output_channel_prop(ca, props, device_config_name,
|
||||
out_chan);
|
||||
|
@ -959,15 +1034,39 @@ static bool coreaudio_device_changed(void *data, obs_properties_t *props,
|
|||
{
|
||||
struct coreaudio_data *ca = data;
|
||||
if (ca != NULL) {
|
||||
uint32_t channels = get_audio_channels(ca->speakers);
|
||||
hide_all_output_channels(props);
|
||||
ensure_output_channels_visible(props, ca, channels);
|
||||
|
||||
if (!ca->enable_downmix) {
|
||||
uint32_t channels = get_audio_channels(ca->speakers);
|
||||
ensure_output_channels_visible(props, ca, channels);
|
||||
}
|
||||
}
|
||||
UNUSED_PARAMETER(p);
|
||||
UNUSED_PARAMETER(settings);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool coreaudio_downmix_changed(void *data, obs_properties_t *props,
|
||||
obs_property_t *p __unused,
|
||||
obs_data_t *settings)
|
||||
{
|
||||
struct coreaudio_data *ca = data;
|
||||
if (ca != NULL) {
|
||||
bool enable_downmix =
|
||||
obs_data_get_bool(settings, "enable_downmix");
|
||||
ca->enable_downmix = enable_downmix;
|
||||
|
||||
hide_all_output_channels(props);
|
||||
|
||||
if (!ca->enable_downmix) {
|
||||
uint32_t channels = get_audio_channels(ca->speakers);
|
||||
ensure_output_channels_visible(props, ca, channels);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static obs_properties_t *coreaudio_properties(bool input, void *data)
|
||||
{
|
||||
struct coreaudio_data *ca = data;
|
||||
|
@ -996,9 +1095,18 @@ static obs_properties_t *coreaudio_properties(bool input, void *data)
|
|||
obs_property_set_modified_callback2(property, coreaudio_device_changed,
|
||||
ca);
|
||||
|
||||
property = obs_properties_add_bool(
|
||||
props, "enable_downmix", obs_module_text("CoreAudio.Downmix"));
|
||||
obs_property_set_modified_callback2(property, coreaudio_downmix_changed,
|
||||
ca);
|
||||
|
||||
if (ca != NULL) {
|
||||
uint32_t channels = get_audio_channels(ca->speakers);
|
||||
ensure_output_channels_visible(props, ca, channels);
|
||||
|
||||
if (ca->enable_downmix) {
|
||||
hide_all_output_channels(props);
|
||||
}
|
||||
}
|
||||
|
||||
device_list_free(&devices);
|
||||
|
|
Loading…
Reference in a new issue