#if !TARGET_OS_IPHONE /* Testing empirically, some headsets report a minimal latency that is very * low, but this does not work in practice. Lie and say the minimum is 256
* frames. */ const uint32_t SAFE_MIN_LATENCY_FRAMES = 128; const uint32_t SAFE_MAX_LATENCY_FRAMES = 512;
enum device_flags {
DEV_UNKNOWN = 0x00, /* Unknown */
DEV_INPUT = 0x01, /* Record device like mic */
DEV_OUTPUT = 0x02, /* Playback device like speakers */
DEV_SYSTEM_DEFAULT = 0x04, /* System default device */
DEV_SELECTED_DEFAULT =
0x08, /* User selected to use the system default device */
};
cubeb_channel
channel_label_to_cubeb_channel(UInt32 label)
{ switch (label) { case kAudioChannelLabel_Left: return CHANNEL_FRONT_LEFT; case kAudioChannelLabel_Right: return CHANNEL_FRONT_RIGHT; case kAudioChannelLabel_Center: return CHANNEL_FRONT_CENTER; case kAudioChannelLabel_LFEScreen: return CHANNEL_LOW_FREQUENCY; case kAudioChannelLabel_LeftSurround: return CHANNEL_BACK_LEFT; case kAudioChannelLabel_RightSurround: return CHANNEL_BACK_RIGHT; case kAudioChannelLabel_LeftCenter: return CHANNEL_FRONT_LEFT_OF_CENTER; case kAudioChannelLabel_RightCenter: return CHANNEL_FRONT_RIGHT_OF_CENTER; case kAudioChannelLabel_CenterSurround: return CHANNEL_BACK_CENTER; case kAudioChannelLabel_LeftSurroundDirect: return CHANNEL_SIDE_LEFT; case kAudioChannelLabel_RightSurroundDirect: return CHANNEL_SIDE_RIGHT; case kAudioChannelLabel_TopCenterSurround: return CHANNEL_TOP_CENTER; case kAudioChannelLabel_VerticalHeightLeft: return CHANNEL_TOP_FRONT_LEFT; case kAudioChannelLabel_VerticalHeightCenter: return CHANNEL_TOP_FRONT_CENTER; case kAudioChannelLabel_VerticalHeightRight: return CHANNEL_TOP_FRONT_RIGHT; case kAudioChannelLabel_TopBackLeft: return CHANNEL_TOP_BACK_LEFT; case kAudioChannelLabel_TopBackCenter: return CHANNEL_TOP_BACK_CENTER; case kAudioChannelLabel_TopBackRight: return CHANNEL_TOP_BACK_RIGHT; default: return CHANNEL_UNKNOWN;
}
}
AudioChannelLabel
cubeb_channel_to_channel_label(cubeb_channel channel)
{ switch (channel) { case CHANNEL_FRONT_LEFT: return kAudioChannelLabel_Left; case CHANNEL_FRONT_RIGHT: return kAudioChannelLabel_Right; case CHANNEL_FRONT_CENTER: return kAudioChannelLabel_Center; case CHANNEL_LOW_FREQUENCY: return kAudioChannelLabel_LFEScreen; case CHANNEL_BACK_LEFT: return kAudioChannelLabel_LeftSurround; case CHANNEL_BACK_RIGHT: return kAudioChannelLabel_RightSurround; case CHANNEL_FRONT_LEFT_OF_CENTER: return kAudioChannelLabel_LeftCenter; case CHANNEL_FRONT_RIGHT_OF_CENTER: return kAudioChannelLabel_RightCenter; case CHANNEL_BACK_CENTER: return kAudioChannelLabel_CenterSurround; case CHANNEL_SIDE_LEFT: return kAudioChannelLabel_LeftSurroundDirect; case CHANNEL_SIDE_RIGHT: return kAudioChannelLabel_RightSurroundDirect; case CHANNEL_TOP_CENTER: return kAudioChannelLabel_TopCenterSurround; case CHANNEL_TOP_FRONT_LEFT: return kAudioChannelLabel_VerticalHeightLeft; case CHANNEL_TOP_FRONT_CENTER: return kAudioChannelLabel_VerticalHeightCenter; case CHANNEL_TOP_FRONT_RIGHT: return kAudioChannelLabel_VerticalHeightRight; case CHANNEL_TOP_BACK_LEFT: return kAudioChannelLabel_TopBackLeft; case CHANNEL_TOP_BACK_CENTER: return kAudioChannelLabel_TopBackCenter; case CHANNEL_TOP_BACK_RIGHT: return kAudioChannelLabel_TopBackRight; default: return kAudioChannelLabel_Unknown;
}
}
bool
is_common_sample_rate(Float64 sample_rate)
{ /* Some commonly used sample rates and their multiples and divisors. */ return sample_rate == 8000 || sample_rate == 16000 || sample_rate == 22050 ||
sample_rate == 32000 || sample_rate == 44100 || sample_rate == 48000 ||
sample_rate == 88200 || sample_rate == 96000;
}
if (r != noErr) {
LOG("AudioUnitRender rv=%d", r); if (r != kAudioUnitErr_CannotDoInCurrentContext) { return r;
} if (stm->output_unit) { // kAudioUnitErr_CannotDoInCurrentContext is returned when using a BT // headset and the profile is changed from A2DP to HFP/HSP. The previous // output device is no longer valid and must be reset.
audiounit_reinit_stream_async(stm, DEV_INPUT | DEV_OUTPUT);
} // For now state that no error occurred and feed silence, stream will be // resumed once reinit has completed.
ALOGV("(%p) input: reinit pending feeding silence instead", stm);
stm->input_linear_buffer->push_silence(input_frames *
stm->input_desc.mChannelsPerFrame);
} else { /* Copy input data in linear buffer. */
stm->input_linear_buffer->push(input_buffer_list.mBuffers[0].mData,
input_frames *
stm->input_desc.mChannelsPerFrame);
}
if (stm->shutdown) {
ALOG("(%p) input shutdown", stm); return noErr;
}
if (stm->draining) {
OSStatus r = AudioOutputUnitStop(stm->input_unit);
assert(r == 0); // Only fire state callback in input-only stream. For duplex stream, // the state callback will be fired in output callback. if (stm->output_unit == NULL) {
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
} return noErr;
}
OSStatus r = audiounit_render_input(stm, flags, tstamp, bus, input_frames); if (r != noErr) { return r;
}
// Full Duplex. We'll call data_callback in the AudioUnit output callback. if (stm->output_unit != NULL) { return noErr;
}
/* Input only. Call the user callback through resampler.
Resampler will deliver input buffer in the correct rate. */
assert(input_frames <= stm->input_linear_buffer->length() /
stm->input_desc.mChannelsPerFrame); long total_input_frames =
stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame; long outframes = cubeb_resampler_fill(stm->resampler.get(),
stm->input_linear_buffer->data(),
&total_input_frames, NULL, 0); if (outframes < 0) {
stm->shutdown = true;
OSStatus r = AudioOutputUnitStop(stm->input_unit);
assert(r == 0);
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); return noErr;
}
stm->draining = outframes < total_input_frames;
int r = cubeb_mixer_mix(stm->mixer.get(), output_frames, input_buffer,
input_buffer_size, output_buffer, output_buffer_size); if (r != 0) {
LOG("Remix error = %d", r);
}
}
// Return how many input frames (sampled at input_hw_rate) are needed to provide // output_frames (sampled at output_stream_params.rate) static int64_t
minimum_resampling_input_frames(cubeb_stream * stm, uint32_t output_frames)
{ if (stm->input_hw_rate == stm->output_stream_params.rate) { // Fast path. return output_frames;
} return ceil(stm->input_hw_rate * output_frames /
stm->output_stream_params.rate);
}
if (stm->draining) {
OSStatus r = AudioOutputUnitStop(stm->output_unit);
assert(r == 0);
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
audiounit_make_silent(&outBufferList->mBuffers[0]); return noErr;
}
/* Get output buffer. */ if (stm->mixer) { // If remixing needs to occur, we can't directly work in our final // destination buffer as data may be overwritten or too small to start with.
size_t size_needed = output_frames * stm->output_stream_params.channels *
cubeb_sample_size(stm->output_stream_params.format); if (stm->temp_buffer_size < size_needed) {
stm->temp_buffer.reset(new uint8_t[size_needed]);
stm->temp_buffer_size = size_needed;
}
output_buffer = stm->temp_buffer.get();
} else {
output_buffer = outBufferList->mBuffers[0].mData;
}
stm->frames_written += output_frames;
/* If Full duplex get also input buffer */ if (stm->input_unit != NULL) { /* If the output callback came first and this is a duplex stream, we need to * fill in some additional silence in the resampler. * Otherwise, if we had more than expected callbacks in a row, or we're * currently switching, we add some silence as well to compensate for the
* fact that we're lacking some input data. */
uint32_t input_frames_needed =
minimum_resampling_input_frames(stm, stm->frames_written); long missing_frames = input_frames_needed - stm->frames_read; if (missing_frames > 0) {
stm->input_linear_buffer->push_silence(missing_frames *
stm->input_desc.mChannelsPerFrame);
stm->frames_read = input_frames_needed;
ALOG("(%p) %s pushed %ld frames of input silence.", stm,
stm->frames_read == 0 ? "Input hasn't started,"
: stm->switching_device ? "Device switching,"
: "Drop out,",
missing_frames);
}
input_buffer = stm->input_linear_buffer->data(); // Number of input frames in the buffer. It will change to actually used // frames inside fill
input_frames =
stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame;
}
/* Call user callback through resampler. */ long outframes = cubeb_resampler_fill(stm->resampler.get(), input_buffer,
input_buffer ? &input_frames : NULL,
output_buffer, output_frames);
if (input_buffer) { // Pop from the buffer the frames used by the the resampler.
stm->input_linear_buffer->pop(input_frames *
stm->input_desc.mChannelsPerFrame);
}
if (outframes < 0 || outframes > output_frames) {
stm->shutdown = true;
OSStatus r = AudioOutputUnitStop(stm->output_unit);
assert(r == 0); if (stm->input_unit) {
r = AudioOutputUnitStop(stm->input_unit);
assert(r == 0);
}
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
audiounit_make_silent(&outBufferList->mBuffers[0]); return noErr;
}
int r; #if !TARGET_OS_IPHONE
r = audiounit_uninstall_device_changed_callback(stm); if (r != CUBEB_OK) {
LOG("(%p) Could not uninstall all device change listeners.", stm);
} #endif
{
auto_lock lock(stm->mutex); float volume = 0.0; int vol_rv = CUBEB_ERROR; if (stm->output_unit) {
vol_rv = audiounit_stream_get_volume(stm, &volume);
}
audiounit_close_stream(stm);
#if !TARGET_OS_IPHONE /* Reinit occurs in one of the following case: * - When the device is not alive any more * - When the default system device change. * - The bluetooth device changed from A2DP to/from HFP/HSP profile * We first attempt to re-use the same device id, should that fail we will
* default to the (potentially new) default device. */
AudioDeviceID input_device =
flags & DEV_INPUT ? stm->input_device.id : kAudioObjectUnknown; if (flags & DEV_INPUT) {
r = audiounit_set_device_info(stm, input_device, io_side::INPUT); if (r != CUBEB_OK) {
LOG("(%p) Set input device info failed. This can happen when last " "media device is unplugged",
stm); return CUBEB_ERROR;
}
}
/* Always use the default output on reinit. This is not correct in every * case but it is sufficient for Firefox and prevent reinit from reporting
* failures. It will change soon when reinit mechanism will be updated. */
r = audiounit_set_device_info(stm, kAudioObjectUnknown, io_side::OUTPUT); if (r != CUBEB_OK) {
LOG("(%p) Set output device info failed. This can happen when last media " "device is unplugged",
stm); return CUBEB_ERROR;
}
#endif
if (audiounit_setup_stream(stm) != CUBEB_OK) {
LOG("(%p) Stream reinit failed.", stm); #if !TARGET_OS_IPHONE if (flags & DEV_INPUT && input_device != kAudioObjectUnknown) { // Attempt to re-use the same device-id failed, so attempt again with // default input device.
audiounit_close_stream(stm); if (audiounit_set_device_info(stm, kAudioObjectUnknown,
io_side::INPUT) != CUBEB_OK ||
audiounit_setup_stream(stm) != CUBEB_OK) {
LOG("(%p) Second stream reinit failed.", stm); return CUBEB_ERROR;
}
} #endif
}
if (vol_rv == CUBEB_OK) {
audiounit_stream_set_volume(stm, volume);
}
// If the stream was running, start it again. if (!stm->shutdown) {
r = audiounit_stream_start_internal(stm); if (r != CUBEB_OK) { return CUBEB_ERROR;
}
}
} return CUBEB_OK;
}
staticvoid
audiounit_reinit_stream_async(cubeb_stream * stm, device_flags_value flags)
{ if (std::atomic_exchange(&stm->reinit_pending, true)) { // A reinit task is already pending, nothing more to do.
ALOG("(%p) re-init stream task already pending, cancelling request", stm); return;
}
// Use a new thread, through the queue, to avoid deadlock when calling // Get/SetProperties method from inside notify callback
dispatch_async(stm->context->serial_queue, ^() { if (stm->destroy_pending) {
ALOG("(%p) stream pending destroy, cancelling reinit task", stm); return;
}
if (audiounit_reinit_stream(stm, flags) != CUBEB_OK) { #if !TARGET_OS_IPHONE if (audiounit_uninstall_system_changed_callback(stm) != CUBEB_OK) {
LOG("(%p) Could not uninstall system changed callback", stm);
} #endif
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
LOG("(%p) Could not reopen the stream after switching.", stm);
}
stm->switching_device = false;
stm->reinit_pending = false;
});
}
#if !TARGET_OS_IPHONE staticcharconst *
event_addr_to_string(AudioObjectPropertySelector selector)
{ switch (selector) { case kAudioHardwarePropertyDefaultOutputDevice: return"kAudioHardwarePropertyDefaultOutputDevice"; case kAudioHardwarePropertyDefaultInputDevice: return"kAudioHardwarePropertyDefaultInputDevice"; case kAudioDevicePropertyDeviceIsAlive: return"kAudioDevicePropertyDeviceIsAlive"; case kAudioDevicePropertyDataSource: return"kAudioDevicePropertyDataSource"; default: return"Unknown";
}
}
LOG("(%p) Audio device changed, %u events.", stm,
(unsignedint)address_count); for (UInt32 i = 0; i < address_count; i++) { switch (addresses[i].mSelector) { case kAudioHardwarePropertyDefaultOutputDevice: {
LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultOutputDevice " "for id=%d",
(unsignedint)i, id);
} break; case kAudioHardwarePropertyDefaultInputDevice: {
LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultInputDevice " "for id=%d",
(unsignedint)i, id);
} break; case kAudioDevicePropertyDeviceIsAlive: {
LOG("Event[%u] - mSelector == kAudioDevicePropertyDeviceIsAlive for " "id=%d",
(unsignedint)i, id); // If this is the default input device ignore the event, // kAudioHardwarePropertyDefaultInputDevice will take care of the switch if (stm->input_device.flags & DEV_SYSTEM_DEFAULT) {
LOG("It's the default input device, ignore the event");
stm->switching_device = false; return noErr;
}
} break; case kAudioDevicePropertyDataSource: {
LOG("Event[%u] - mSelector == kAudioDevicePropertyDataSource for id=%d",
(unsignedint)i, id);
} break; default:
LOG("Event[%u] - mSelector == Unexpected Event id %d, return",
(unsignedint)i, addresses[i].mSelector);
stm->switching_device = false; return noErr;
}
}
// Allow restart to choose the new default
device_flags_value switch_side = DEV_UNKNOWN; if (has_input(stm)) {
switch_side |= DEV_INPUT;
} if (has_output(stm)) {
switch_side |= DEV_OUTPUT;
}
for (UInt32 i = 0; i < address_count; i++) { switch (addresses[i].mSelector) { case kAudioHardwarePropertyDefaultOutputDevice: case kAudioHardwarePropertyDefaultInputDevice: case kAudioDevicePropertyDeviceIsAlive: /* fall through */ case kAudioDevicePropertyDataSource: {
auto_lock dev_cb_lock(stm->device_changed_callback_lock); if (stm->device_changed_callback) {
stm->device_changed_callback(stm->user_ptr);
} break;
}
}
}
staticint
audiounit_install_device_changed_callback(cubeb_stream * stm)
{
OSStatus rv; int r = CUBEB_OK;
if (stm->output_unit) { /* This event will notify us when the data source on the same device * changes, for example when the user plugs in a normal (non-usb) headset in
* the headphone jack. */
stm->output_source_listener.reset(new property_listener(
stm->output_device.id, &OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS,
&audiounit_property_listener_callback, stm));
rv = audiounit_add_listener(stm->output_source_listener.get()); if (rv != noErr) {
stm->output_source_listener.reset();
LOG("AudioObjectAddPropertyListener/output/" "kAudioDevicePropertyDataSource rv=%d, device id=%d",
rv, stm->output_device.id);
r = CUBEB_ERROR;
}
}
if (stm->input_unit) { /* This event will notify us when the data source on the input device
* changes. */
stm->input_source_listener.reset(new property_listener(
stm->input_device.id, &INPUT_DATA_SOURCE_PROPERTY_ADDRESS,
&audiounit_property_listener_callback, stm));
rv = audiounit_add_listener(stm->input_source_listener.get()); if (rv != noErr) {
stm->input_source_listener.reset();
LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource " "rv=%d, device id=%d",
rv, stm->input_device.id);
r = CUBEB_ERROR;
}
/* Event to notify when the input is going away. */
stm->input_alive_listener.reset(new property_listener(
stm->input_device.id, &DEVICE_IS_ALIVE_PROPERTY_ADDRESS,
&audiounit_property_listener_callback, stm));
rv = audiounit_add_listener(stm->input_alive_listener.get()); if (rv != noErr) {
stm->input_alive_listener.reset();
LOG("AudioObjectAddPropertyListener/input/" "kAudioDevicePropertyDeviceIsAlive rv=%d, device id =%d",
rv, stm->input_device.id);
r = CUBEB_ERROR;
}
}
if (stm->output_unit) { /* This event will notify us when the default audio device changes, * for example when the user plugs in a USB headset and the system chooses * it automatically as the default, or when another device is chosen in the
* dropdown list. */
stm->default_output_listener.reset(new property_listener(
kAudioObjectSystemObject, &DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS,
&audiounit_property_listener_callback, stm));
r = audiounit_add_listener(stm->default_output_listener.get()); if (r != noErr) {
stm->default_output_listener.reset();
LOG("AudioObjectAddPropertyListener/output/" "kAudioHardwarePropertyDefaultOutputDevice rv=%d",
r); return CUBEB_ERROR;
}
}
if (stm->input_unit) { /* This event will notify us when the default input device changes. */
stm->default_input_listener.reset(new property_listener(
kAudioObjectSystemObject, &DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS,
&audiounit_property_listener_callback, stm));
r = audiounit_add_listener(stm->default_input_listener.get()); if (r != noErr) {
stm->default_input_listener.reset();
LOG("AudioObjectAddPropertyListener/input/" "kAudioHardwarePropertyDefaultInputDevice rv=%d",
r); return CUBEB_ERROR;
}
}
return CUBEB_OK;
} #endif
staticint
audiounit_uninstall_device_changed_callback(cubeb_stream * stm)
{
OSStatus rv; // Failing to uninstall listeners is not a fatal error. int r = CUBEB_OK;
if (stm->output_source_listener) {
rv = audiounit_remove_listener(stm->output_source_listener.get()); if (rv != noErr) {
LOG("AudioObjectRemovePropertyListener/output/" "kAudioDevicePropertyDataSource rv=%d, device id=%d",
rv, stm->output_device.id);
r = CUBEB_ERROR;
}
stm->output_source_listener.reset();
}
if (stm->input_source_listener) {
rv = audiounit_remove_listener(stm->input_source_listener.get()); if (rv != noErr) {
LOG("AudioObjectRemovePropertyListener/input/" "kAudioDevicePropertyDataSource rv=%d, device id=%d",
rv, stm->input_device.id);
r = CUBEB_ERROR;
}
stm->input_source_listener.reset();
}
if (stm->input_alive_listener) {
rv = audiounit_remove_listener(stm->input_alive_listener.get()); if (rv != noErr) {
LOG("AudioObjectRemovePropertyListener/input/" "kAudioDevicePropertyDeviceIsAlive rv=%d, device id=%d",
rv, stm->input_device.id);
r = CUBEB_ERROR;
}
stm->input_alive_listener.reset();
}
if (stm->default_output_listener) {
r = audiounit_remove_listener(stm->default_output_listener.get()); if (r != noErr) { return CUBEB_ERROR;
}
stm->default_output_listener.reset();
}
if (stm->default_input_listener) {
r = audiounit_remove_listener(stm->default_input_listener.get()); if (r != noErr) { return CUBEB_ERROR;
}
stm->default_input_listener.reset();
} return CUBEB_OK;
}
/* Get the acceptable buffer size (in frames) that this device can work with. */ staticint
audiounit_get_acceptable_latency_range(AudioValueRange * latency_range)
{
UInt32 size;
OSStatus r;
AudioDeviceID output_device_id;
AudioObjectPropertyAddress output_device_buffer_size_range = {
kAudioDevicePropertyBufferFrameSizeRange, kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMain};
output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); if (output_device_id == kAudioObjectUnknown) {
LOG("Could not get default output device id."); return CUBEB_ERROR;
}
/* Get the buffer size range this device supports */
size = sizeof(*latency_range);
r = AudioObjectGetPropertyData(output_device_id,
&output_device_buffer_size_range, 0, NULL,
&size, latency_range); if (r != noErr) {
LOG("AudioObjectGetPropertyData/buffer size range rv=%d", r); return CUBEB_ERROR;
}
static cubeb_channel_layout
audiounit_convert_channel_layout(AudioChannelLayout * layout)
{ // When having one or two channel, force mono or stereo. Some devices (namely, // Bose QC35, mark 1 and 2), expose a single channel mapped to the right for // some reason. if (layout->mNumberChannelDescriptions == 1) { return CUBEB_LAYOUT_MONO;
} elseif (layout->mNumberChannelDescriptions == 2) { return CUBEB_LAYOUT_STEREO;
}
// Disabling this assert for bug 1083664 -- we seem to leak a stream // assert(ctx->active_streams == 0); if (audiounit_active_streams(ctx) > 0) {
LOG("(%p) API misuse, %d streams active when context destroyed!", ctx,
audiounit_active_streams(ctx));
}
// Destroying a cubeb context with device collection callbacks registered // is misuse of the API, assert then attempt to clean up.
assert(!ctx->input_collection_changed_callback &&
!ctx->input_collection_changed_user_ptr &&
!ctx->output_collection_changed_callback &&
!ctx->output_collection_changed_user_ptr);
#if !TARGET_OS_IPHONE /* Unregister the callback if necessary. */ if (ctx->input_collection_changed_callback) {
audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_INPUT);
} if (ctx->output_collection_changed_callback) {
audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_OUTPUT);
} #endif
}
void
audiounit_init_mixer(cubeb_stream * stm)
{ // We can't rely on macOS' AudioUnit to properly downmix (or upmix) the audio // data, it silently drop the channels so we need to remix the // audio data by ourselves to keep all the information.
stm->mixer.reset(cubeb_mixer_create(
stm->output_stream_params.format, stm->output_stream_params.channels,
stm->output_stream_params.layout, stm->context->channels,
stm->context->layout));
assert(stm->mixer);
}
staticint
audiounit_set_channel_layout(AudioUnit unit, io_side side,
cubeb_channel_layout layout)
{ if (side != io_side::OUTPUT) { return CUBEB_ERROR;
}
if (layout == CUBEB_LAYOUT_UNDEFINED) { // We leave everything as-is... return CUBEB_OK;
}
// We do not use CoreAudio standard layout for lack of documentation on what // the actual channel orders are. So we set a custom layout.
size_t size = offsetof(AudioChannelLayout, mChannelDescriptions[nb_channels]); auto au_layout = make_sized_audio_channel_layout(size);
au_layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
au_layout->mNumberChannelDescriptions = nb_channels;
r = AudioUnitSetProperty(unit, kAudioUnitProperty_AudioChannelLayout,
kAudioUnitScope_Input, AU_OUT_BUS, au_layout.get(),
size); if (r != noErr) {
LOG("AudioUnitSetProperty/%s/kAudioUnitProperty_AudioChannelLayout rv=%d",
to_string(side), r); return CUBEB_ERROR;
}
return CUBEB_OK;
}
void
audiounit_layout_init(cubeb_stream * stm, io_side side)
{ // We currently don't support the input layout setting. if (side == io_side::INPUT) { return;
}
// The returned CFStringRef object needs to be released (via CFRelease) // if it's not NULL, since the reference count of the returned CFStringRef // object is increased. static CFStringRef
get_device_name(AudioDeviceID id)
{
UInt32 size = sizeof(CFStringRef);
CFStringRef UIname = nullptr;
AudioObjectPropertyAddress address_uuid = {kAudioDevicePropertyDeviceUID,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMain};
OSStatus err =
AudioObjectGetPropertyData(id, &address_uuid, 0, nullptr, &size, &UIname); return (err == noErr) ? UIname : NULL;
}
CFMutableArrayRef aggregate_sub_devices_array =
CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); /* The order of the items in the array is significant and is used to determine
the order of the streams of the AudioAggregateDevice. */ for (UInt32 i = 0; i < output_sub_devices.size(); i++) {
CFStringRef ref = get_device_name(output_sub_devices[i]); if (ref == NULL) {
CFRelease(aggregate_sub_devices_array); return CUBEB_ERROR;
}
CFArrayAppendValue(aggregate_sub_devices_array, ref);
CFRelease(ref);
} for (UInt32 i = 0; i < input_sub_devices.size(); i++) {
CFStringRef ref = get_device_name(input_sub_devices[i]); if (ref == NULL) {
CFRelease(aggregate_sub_devices_array); return CUBEB_ERROR;
}
CFArrayAppendValue(aggregate_sub_devices_array, ref);
CFRelease(ref);
}
// Start from the second device since the first is the master clock for (UInt32 i = 1; i < subdevices_num; ++i) {
UInt32 drift_compensation_value = 1;
rv = AudioObjectSetPropertyData(sub_devices[i], &address_drift, 0, nullptr, sizeof(UInt32), &drift_compensation_value); if (rv != noErr) {
LOG("AudioObjectSetPropertyData/" "kAudioSubDevicePropertyDriftCompensation, rv=%d",
rv); return CUBEB_OK;
}
} return CUBEB_OK;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.