You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
974 lines
30 KiB
974 lines
30 KiB
/*****************************************************************************
|
|
* coreaudio_common.c: Common AudioUnit code for iOS and macOS
|
|
*****************************************************************************
|
|
* Copyright (C) 2005 - 2017 VLC authors and VideoLAN
|
|
*
|
|
* Authors: Derk-Jan Hartman <hartman at videolan dot org>
|
|
* Felix Paul Kühne <fkuehne at videolan dot org>
|
|
* David Fuhrmann <david dot fuhrmann at googlemail dot com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation; either version 2.1 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
#import "coreaudio_common.h"
|
|
#import <CoreAudio/CoreAudioTypes.h>
|
|
|
|
static inline uint64_t
|
|
BytesToFrames(struct aout_sys_common *p_sys, size_t i_bytes)
|
|
{
|
|
return i_bytes * p_sys->i_frame_length / p_sys->i_bytes_per_frame;
|
|
}
|
|
|
|
static inline mtime_t
|
|
FramesToUs(struct aout_sys_common *p_sys, uint64_t i_nb_frames)
|
|
{
|
|
return i_nb_frames * CLOCK_FREQ / p_sys->i_rate;
|
|
}
|
|
|
|
static inline size_t
|
|
FramesToBytes(struct aout_sys_common *p_sys, uint64_t i_frames)
|
|
{
|
|
return i_frames * p_sys->i_bytes_per_frame / p_sys->i_frame_length;
|
|
}
|
|
|
|
static inline uint64_t
|
|
UsToFrames(struct aout_sys_common *p_sys, vlc_tick_t i_us)
|
|
{
|
|
return i_us * p_sys->i_rate / CLOCK_FREQ;
|
|
}
|
|
|
|
static inline mtime_t
|
|
HostTimeToTick(struct aout_sys_common *p_sys, uint64_t i_host_time)
|
|
{
|
|
return i_host_time * p_sys->tinfo.numer / p_sys->tinfo.denom / 1000;
|
|
}
|
|
|
|
static inline uint64_t
|
|
TickToHostTime(struct aout_sys_common *p_sys, vlc_tick_t i_us)
|
|
{
|
|
return i_us * 1000 * p_sys->tinfo.denom / p_sys->tinfo.numer;
|
|
}
|
|
|
|
static void
|
|
ca_ClearOutBuffers(audio_output_t *p_aout)
|
|
{
|
|
struct aout_sys_common *p_sys = (struct aout_sys_common *) p_aout->sys;
|
|
|
|
block_ChainRelease(p_sys->p_out_chain);
|
|
p_sys->p_out_chain = NULL;
|
|
p_sys->pp_out_last = &p_sys->p_out_chain;
|
|
|
|
p_sys->i_out_size = 0;
|
|
}
|
|
|
|
static void
|
|
lock_init(struct aout_sys_common *p_sys)
|
|
{
|
|
if (likely(os_unfair_lock_lock))
|
|
p_sys->lock.unfair = OS_UNFAIR_LOCK_INIT;
|
|
else
|
|
vlc_mutex_init(&p_sys->lock.mutex);
|
|
}
|
|
|
|
static inline void
|
|
lock_destroy(struct aout_sys_common *p_sys)
|
|
{
|
|
if (unlikely(!os_unfair_lock_lock))
|
|
vlc_mutex_destroy(&p_sys->lock.mutex);
|
|
}
|
|
|
|
static inline void
|
|
lock_lock(struct aout_sys_common *p_sys)
|
|
{
|
|
if (likely(os_unfair_lock_lock))
|
|
os_unfair_lock_lock(&p_sys->lock.unfair);
|
|
else
|
|
vlc_mutex_lock(&p_sys->lock.mutex);
|
|
}
|
|
|
|
static inline void
|
|
lock_unlock(struct aout_sys_common *p_sys)
|
|
{
|
|
if (likely(os_unfair_lock_lock))
|
|
os_unfair_lock_unlock(&p_sys->lock.unfair);
|
|
else
|
|
vlc_mutex_unlock(&p_sys->lock.mutex);
|
|
}
|
|
|
|
int
|
|
ca_Open(audio_output_t *p_aout)
|
|
{
|
|
struct aout_sys_common *p_sys = (struct aout_sys_common *) p_aout->sys;
|
|
|
|
if (mach_timebase_info(&p_sys->tinfo) != KERN_SUCCESS)
|
|
return VLC_EGENERIC;
|
|
|
|
assert(p_sys->tinfo.denom != 0 && p_sys->tinfo.numer != 0);
|
|
|
|
vlc_sem_init(&p_sys->flush_sem, 0);
|
|
lock_init(p_sys);
|
|
p_sys->p_out_chain = NULL;
|
|
p_sys->pp_out_last = &p_sys->p_out_chain;
|
|
p_sys->chans_to_reorder = 0;
|
|
|
|
p_aout->play = ca_Play;
|
|
p_aout->pause = ca_Pause;
|
|
p_aout->flush = ca_Flush;
|
|
p_aout->time_get = ca_TimeGet;
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
void
|
|
ca_Close(audio_output_t *p_aout)
|
|
{
|
|
struct aout_sys_common *p_sys = (struct aout_sys_common *) p_aout->sys;
|
|
|
|
vlc_sem_destroy(&p_sys->flush_sem);
|
|
lock_destroy(p_sys);
|
|
}
|
|
|
|
/* Called from render callbacks. No lock, wait, and IO here */
|
|
void
|
|
ca_Render(audio_output_t *p_aout, uint32_t i_frames, uint64_t i_host_time,
|
|
uint8_t *p_output, size_t i_requested)
|
|
{
|
|
struct aout_sys_common *p_sys = (struct aout_sys_common *) p_aout->sys;
|
|
|
|
lock_lock(p_sys);
|
|
|
|
if (p_sys->b_do_flush)
|
|
{
|
|
ca_ClearOutBuffers(p_aout);
|
|
/* Signal that the renderer is flushed */
|
|
p_sys->b_do_flush = false;
|
|
vlc_sem_post(&p_sys->flush_sem);
|
|
}
|
|
|
|
if (unlikely(p_sys->i_first_render_host_time == 0))
|
|
goto drop;
|
|
|
|
if (p_sys->b_paused)
|
|
{
|
|
p_sys->i_render_host_time = i_host_time;
|
|
goto drop;
|
|
}
|
|
|
|
/* Start deferred: write silence (zeros) until we reach the first render
|
|
* host time. */
|
|
if (unlikely(p_sys->i_first_render_host_time > i_host_time ))
|
|
{
|
|
/* Convert the requested bytes into host time and check that it does
|
|
* not overlap between the first_render host time and the current one.
|
|
* */
|
|
const size_t i_requested_us =
|
|
FramesToUs(p_sys, BytesToFrames(p_sys, i_requested));
|
|
const uint64_t i_requested_host_time =
|
|
TickToHostTime(p_sys, i_requested_us);
|
|
if (p_sys->i_first_render_host_time >= i_host_time + i_requested_host_time)
|
|
{
|
|
/* Fill the buffer with silence */
|
|
goto drop;
|
|
}
|
|
|
|
/* Write silence to reach the first_render host time */
|
|
const vlc_tick_t i_silence_us =
|
|
HostTimeToTick(p_sys, p_sys->i_first_render_host_time - i_host_time);
|
|
|
|
const uint64_t i_silence_bytes =
|
|
FramesToBytes(p_sys, UsToFrames(p_sys, i_silence_us));
|
|
assert(i_silence_bytes <= i_requested);
|
|
memset(p_output, 0, i_silence_bytes);
|
|
|
|
i_requested -= i_silence_bytes;
|
|
p_output += i_silence_bytes;
|
|
|
|
/* Start the first rendering */
|
|
}
|
|
|
|
p_sys->i_render_host_time = i_host_time;
|
|
p_sys->i_render_frames = i_frames;
|
|
|
|
size_t i_copied = 0;
|
|
block_t *p_block = p_sys->p_out_chain;
|
|
while (p_block != NULL && i_requested != 0)
|
|
{
|
|
size_t i_tocopy = __MIN(i_requested, p_block->i_buffer);
|
|
memcpy(p_output, p_block->p_buffer, i_tocopy);
|
|
i_requested -= i_tocopy;
|
|
i_copied += i_tocopy;
|
|
p_output += i_tocopy;
|
|
|
|
if (i_tocopy == p_block->i_buffer)
|
|
{
|
|
block_t *p_release = p_block;
|
|
p_block = p_block->p_next;
|
|
block_Release(p_release);
|
|
}
|
|
else
|
|
{
|
|
assert(i_requested == 0);
|
|
|
|
p_block->p_buffer += i_tocopy;
|
|
p_block->i_buffer -= i_tocopy;
|
|
}
|
|
}
|
|
p_sys->p_out_chain = p_block;
|
|
if (!p_sys->p_out_chain)
|
|
p_sys->pp_out_last = &p_sys->p_out_chain;
|
|
p_sys->i_out_size -= i_copied;
|
|
|
|
/* Pad with 0 */
|
|
if (i_requested > 0)
|
|
{
|
|
assert(p_sys->i_out_size == 0);
|
|
p_sys->i_underrun_size += i_requested;
|
|
memset(p_output, 0, i_requested);
|
|
}
|
|
|
|
lock_unlock(p_sys);
|
|
return;
|
|
|
|
drop:
|
|
memset(p_output, 0, i_requested);
|
|
lock_unlock(p_sys);
|
|
}
|
|
|
|
static mtime_t
|
|
ca_GetLatencyLocked(audio_output_t *p_aout)
|
|
{
|
|
struct aout_sys_common *p_sys = (struct aout_sys_common *) p_aout->sys;
|
|
|
|
const int64_t i_out_frames = BytesToFrames(p_sys, p_sys->i_out_size);
|
|
return FramesToUs(p_sys, i_out_frames + p_sys->i_render_frames)
|
|
+ p_sys->i_dev_latency_us;
|
|
}
|
|
|
|
int
|
|
ca_TimeGet(audio_output_t *p_aout, vlc_tick_t *delay)
|
|
{
|
|
struct aout_sys_common *p_sys = (struct aout_sys_common *) p_aout->sys;
|
|
|
|
lock_lock(p_sys);
|
|
|
|
if (p_sys->i_render_host_time == 0 || p_sys->i_first_render_host_time == 0)
|
|
{
|
|
/* Not yet started (or reached the first_render host time) */
|
|
lock_unlock(p_sys);
|
|
return -1;
|
|
}
|
|
|
|
const vlc_tick_t i_render_time_us =
|
|
HostTimeToTick(p_sys, p_sys->i_render_host_time);
|
|
const vlc_tick_t i_render_delay = i_render_time_us - mdate();
|
|
|
|
*delay = ca_GetLatencyLocked(p_aout) + i_render_delay;
|
|
lock_unlock(p_sys);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
ca_Flush(audio_output_t *p_aout, bool wait)
|
|
{
|
|
struct aout_sys_common *p_sys = (struct aout_sys_common *) p_aout->sys;
|
|
|
|
lock_lock(p_sys);
|
|
if (wait)
|
|
{
|
|
while (p_sys->i_out_size > 0)
|
|
{
|
|
if (p_sys->b_paused)
|
|
{
|
|
ca_ClearOutBuffers(p_aout);
|
|
break;
|
|
}
|
|
|
|
/* Calculate the duration of the circular buffer, in order to wait
|
|
* for the render thread to play it all */
|
|
const vlc_tick_t i_frame_us =
|
|
FramesToUs(p_sys, BytesToFrames(p_sys, p_sys->i_out_size)) + 10000;
|
|
lock_unlock(p_sys);
|
|
msleep(i_frame_us);
|
|
lock_lock(p_sys);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
assert(!p_sys->b_do_flush);
|
|
if (p_sys->b_paused)
|
|
ca_ClearOutBuffers(p_aout);
|
|
else
|
|
{
|
|
p_sys->b_do_flush = true;
|
|
lock_unlock(p_sys);
|
|
vlc_sem_wait(&p_sys->flush_sem);
|
|
lock_lock(p_sys);
|
|
}
|
|
}
|
|
|
|
p_sys->i_render_host_time = p_sys->i_first_render_host_time = 0;
|
|
p_sys->i_render_frames = 0;
|
|
lock_unlock(p_sys);
|
|
|
|
p_sys->b_played = false;
|
|
}
|
|
|
|
void
|
|
ca_Pause(audio_output_t * p_aout, bool pause, vlc_tick_t date)
|
|
{
|
|
struct aout_sys_common *p_sys = (struct aout_sys_common *) p_aout->sys;
|
|
VLC_UNUSED(date);
|
|
|
|
lock_lock(p_sys);
|
|
p_sys->b_paused = pause;
|
|
lock_unlock(p_sys);
|
|
}
|
|
|
|
void
|
|
ca_Play(audio_output_t * p_aout, block_t * p_block)
|
|
{
|
|
struct aout_sys_common *p_sys = (struct aout_sys_common *) p_aout->sys;
|
|
|
|
/* Do the channel reordering */
|
|
if (p_sys->chans_to_reorder)
|
|
aout_ChannelReorder(p_block->p_buffer, p_block->i_buffer,
|
|
p_sys->chans_to_reorder, p_sys->chan_table,
|
|
VLC_CODEC_FL32);
|
|
|
|
lock_lock(p_sys);
|
|
|
|
if (p_sys->i_render_host_time == 0)
|
|
{
|
|
/* Setup the first render time, this date must be updated until the
|
|
* first (non-silence/zero) frame is rendered by the render callback.
|
|
* Once the rendering is truly started, the date can be ignored. */
|
|
|
|
const vlc_tick_t first_render_time = p_block->i_pts - ca_GetLatencyLocked(p_aout);
|
|
p_sys->i_first_render_host_time =
|
|
TickToHostTime(p_sys, first_render_time);
|
|
}
|
|
|
|
do
|
|
{
|
|
const size_t i_avalaible_bytes =
|
|
__MIN(p_block->i_buffer, p_sys->i_out_max_size - p_sys->i_out_size);
|
|
|
|
if (unlikely(i_avalaible_bytes != p_block->i_buffer))
|
|
{
|
|
/* Not optimal but unlikely code path. */
|
|
|
|
lock_unlock(p_sys);
|
|
|
|
block_t *p_new = block_Alloc(i_avalaible_bytes);
|
|
if (!p_new)
|
|
{
|
|
block_Release(p_block);
|
|
return;
|
|
}
|
|
|
|
memcpy(p_new->p_buffer, p_block->p_buffer, i_avalaible_bytes);
|
|
|
|
p_block->p_buffer += i_avalaible_bytes;
|
|
p_block->i_buffer -= i_avalaible_bytes;
|
|
|
|
lock_lock(p_sys);
|
|
|
|
block_ChainLastAppend(&p_sys->pp_out_last, p_new);
|
|
p_sys->i_out_size += i_avalaible_bytes;
|
|
|
|
if (p_sys->b_paused)
|
|
{
|
|
lock_unlock(p_sys);
|
|
block_Release(p_block);
|
|
return;
|
|
}
|
|
|
|
const vlc_tick_t i_frame_us =
|
|
FramesToUs(p_sys, BytesToFrames(p_sys, p_block->i_buffer));
|
|
|
|
/* Wait for the render buffer to play the remaining data */
|
|
lock_unlock(p_sys);
|
|
msleep(i_frame_us);
|
|
lock_lock(p_sys);
|
|
}
|
|
else
|
|
{
|
|
block_ChainLastAppend(&p_sys->pp_out_last, p_block);
|
|
p_sys->i_out_size += i_avalaible_bytes;
|
|
p_block = NULL;
|
|
}
|
|
} while (p_block != NULL);
|
|
|
|
size_t i_underrun_size = p_sys->i_underrun_size;
|
|
p_sys->i_underrun_size = 0;
|
|
|
|
lock_unlock(p_sys);
|
|
|
|
if (!p_sys->b_played)
|
|
p_sys->b_played = true;
|
|
else if (i_underrun_size > 0)
|
|
msg_Warn(p_aout, "underrun of %zu bytes", i_underrun_size);
|
|
}
|
|
|
|
int
|
|
ca_Initialize(audio_output_t *p_aout, const audio_sample_format_t *fmt,
|
|
vlc_tick_t i_dev_latency_us)
|
|
{
|
|
struct aout_sys_common *p_sys = (struct aout_sys_common *) p_aout->sys;
|
|
|
|
p_sys->i_underrun_size = 0;
|
|
p_sys->b_paused = false;
|
|
p_sys->i_render_host_time = p_sys->i_first_render_host_time = 0;
|
|
p_sys->i_render_frames = 0;
|
|
|
|
p_sys->i_rate = fmt->i_rate;
|
|
p_sys->i_bytes_per_frame = fmt->i_bytes_per_frame;
|
|
p_sys->i_frame_length = fmt->i_frame_length;
|
|
|
|
/* TODO VLC can't handle latency higher than 1 seconds */
|
|
if (i_dev_latency_us > 1000000)
|
|
{
|
|
i_dev_latency_us = 1000000;
|
|
msg_Warn(p_aout, "VLC can't handle this device latency, lowering it to "
|
|
"%lld", i_dev_latency_us);
|
|
}
|
|
p_sys->i_dev_latency_us = i_dev_latency_us;
|
|
|
|
/* setup circular buffer */
|
|
size_t i_audiobuffer_size = fmt->i_rate * fmt->i_bytes_per_frame
|
|
/ p_sys->i_frame_length;
|
|
if (fmt->channel_type == AUDIO_CHANNEL_TYPE_AMBISONICS)
|
|
{
|
|
/* lower latency: 200 ms of buffering. XXX: Decrease when VLC's core
|
|
* can handle lower audio latency */
|
|
p_sys->i_out_max_size = i_audiobuffer_size / 5;
|
|
}
|
|
else
|
|
{
|
|
/* 2 seconds of buffering */
|
|
p_sys->i_out_max_size = i_audiobuffer_size * 2;
|
|
}
|
|
|
|
ca_ClearOutBuffers(p_aout);
|
|
p_sys->b_played = false;
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
void
|
|
ca_Uninitialize(audio_output_t *p_aout)
|
|
{
|
|
struct aout_sys_common *p_sys = (struct aout_sys_common *) p_aout->sys;
|
|
ca_ClearOutBuffers(p_aout);
|
|
p_sys->i_out_max_size = 0;
|
|
p_sys->chans_to_reorder = 0;
|
|
}
|
|
|
|
void
|
|
ca_SetAliveState(audio_output_t *p_aout, bool alive)
|
|
{
|
|
struct aout_sys_common *p_sys = (struct aout_sys_common *) p_aout->sys;
|
|
|
|
lock_lock(p_sys);
|
|
|
|
bool b_sem_post = false;
|
|
p_sys->b_paused = !alive;
|
|
if (!alive && p_sys->b_do_flush)
|
|
{
|
|
ca_ClearOutBuffers(p_aout);
|
|
p_sys->b_played = false;
|
|
p_sys->b_do_flush = false;
|
|
b_sem_post = true;
|
|
}
|
|
|
|
lock_unlock(p_sys);
|
|
|
|
if (b_sem_post)
|
|
vlc_sem_post(&p_sys->flush_sem);
|
|
}
|
|
|
|
void ca_SetDeviceLatency(audio_output_t *p_aout, vlc_tick_t i_dev_latency_us)
|
|
{
|
|
struct aout_sys_common *p_sys = (struct aout_sys_common *) p_aout->sys;
|
|
|
|
lock_lock(p_sys);
|
|
/* cf. TODO in ca_Initialize */
|
|
p_sys->i_dev_latency_us = i_dev_latency_us > 1000000 ? 1000000 : i_dev_latency_us;
|
|
lock_unlock(p_sys);
|
|
}
|
|
|
|
AudioUnit
|
|
au_NewOutputInstance(audio_output_t *p_aout, OSType comp_sub_type)
|
|
{
|
|
AudioComponentDescription desc = {
|
|
.componentType = kAudioUnitType_Output,
|
|
.componentSubType = comp_sub_type,
|
|
.componentManufacturer = kAudioUnitManufacturer_Apple,
|
|
.componentFlags = 0,
|
|
.componentFlagsMask = 0,
|
|
};
|
|
|
|
AudioComponent au_component;
|
|
au_component = AudioComponentFindNext(NULL, &desc);
|
|
if (au_component == NULL)
|
|
{
|
|
msg_Err(p_aout, "cannot find any AudioComponent, PCM output failed");
|
|
return NULL;
|
|
}
|
|
|
|
AudioUnit au;
|
|
OSStatus err = AudioComponentInstanceNew(au_component, &au);
|
|
if (err != noErr)
|
|
{
|
|
ca_LogErr("cannot open AudioComponent, PCM output failed");
|
|
return NULL;
|
|
}
|
|
return au;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* RenderCallback: This function is called every time the AudioUnit wants
|
|
* us to provide some more audio data.
|
|
* Don't print anything during normal playback, calling blocking function from
|
|
* this callback is not allowed.
|
|
*****************************************************************************/
|
|
static OSStatus
|
|
RenderCallback(void *p_data, AudioUnitRenderActionFlags *ioActionFlags,
|
|
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
|
|
UInt32 inNumberFrames, AudioBufferList *ioData)
|
|
{
|
|
VLC_UNUSED(ioActionFlags);
|
|
VLC_UNUSED(inTimeStamp);
|
|
VLC_UNUSED(inBusNumber);
|
|
|
|
uint64_t i_host_time = (inTimeStamp->mFlags & kAudioTimeStampHostTimeValid)
|
|
? inTimeStamp->mHostTime : 0;
|
|
|
|
ca_Render(p_data, inNumberFrames, i_host_time, ioData->mBuffers[0].mData,
|
|
ioData->mBuffers[0].mDataByteSize);
|
|
|
|
return noErr;
|
|
}
|
|
|
|
static AudioChannelLayout *
|
|
GetLayoutDescription(audio_output_t *p_aout,
|
|
const AudioChannelLayout *outlayout)
|
|
{
|
|
AudioFormatPropertyID id;
|
|
UInt32 size;
|
|
const void *data;
|
|
/* We need to "fill out" the ChannelLayout, because there are multiple
|
|
* ways that it can be set */
|
|
if (outlayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap)
|
|
{
|
|
id = kAudioFormatProperty_ChannelLayoutForBitmap;
|
|
size = sizeof(UInt32);
|
|
data = &outlayout->mChannelBitmap;
|
|
}
|
|
else
|
|
{
|
|
id = kAudioFormatProperty_ChannelLayoutForTag;
|
|
size = sizeof(AudioChannelLayoutTag);
|
|
data = &outlayout->mChannelLayoutTag;
|
|
}
|
|
|
|
UInt32 param_size;
|
|
OSStatus err = AudioFormatGetPropertyInfo(id, size, data, ¶m_size);
|
|
if (err != noErr)
|
|
return NULL;
|
|
|
|
AudioChannelLayout *reslayout = malloc(param_size);
|
|
if (reslayout == NULL)
|
|
return NULL;
|
|
|
|
err = AudioFormatGetProperty(id, size, data, ¶m_size, reslayout);
|
|
if (err != noErr || reslayout->mNumberChannelDescriptions == 0)
|
|
{
|
|
msg_Err(p_aout, "insufficient number of output channels");
|
|
free(reslayout);
|
|
return NULL;
|
|
}
|
|
|
|
return reslayout;
|
|
}
|
|
|
|
static unsigned
|
|
AudioChannelLabelToVlcChan(AudioChannelLabel chan, bool swap_rear_surround)
|
|
{
|
|
/* maps auhal channels to vlc ones */
|
|
switch (chan)
|
|
{
|
|
case kAudioChannelLabel_Left:
|
|
return AOUT_CHAN_LEFT;
|
|
case kAudioChannelLabel_Right:
|
|
return AOUT_CHAN_RIGHT;
|
|
case kAudioChannelLabel_Center:
|
|
return AOUT_CHAN_CENTER;
|
|
case kAudioChannelLabel_LFEScreen:
|
|
return AOUT_CHAN_LFE;
|
|
case kAudioChannelLabel_LeftSurround:
|
|
return swap_rear_surround ? AOUT_CHAN_MIDDLELEFT
|
|
: AOUT_CHAN_REARLEFT;
|
|
case kAudioChannelLabel_RightSurround:
|
|
return swap_rear_surround ? AOUT_CHAN_MIDDLERIGHT
|
|
: AOUT_CHAN_REARRIGHT;
|
|
case kAudioChannelLabel_RearSurroundLeft:
|
|
return swap_rear_surround ? AOUT_CHAN_REARLEFT
|
|
: AOUT_CHAN_MIDDLELEFT;
|
|
case kAudioChannelLabel_RearSurroundRight:
|
|
return swap_rear_surround ? AOUT_CHAN_REARRIGHT
|
|
: AOUT_CHAN_MIDDLERIGHT;
|
|
case kAudioChannelLabel_CenterSurround:
|
|
return AOUT_CHAN_REARCENTER;
|
|
case kAudioChannelLabel_LeftSurroundDirect:
|
|
return AOUT_CHAN_MIDDLELEFT;
|
|
case kAudioChannelLabel_RightSurroundDirect:
|
|
return AOUT_CHAN_MIDDLERIGHT;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static AudioChannelLabel
|
|
VlcChanToAudioChannelLabel(unsigned chan, bool swap_rear_surround)
|
|
{
|
|
/* maps auhal channels to vlc ones */
|
|
switch (chan)
|
|
{
|
|
case AOUT_CHAN_LEFT:
|
|
return kAudioChannelLabel_Left;
|
|
case AOUT_CHAN_RIGHT:
|
|
return kAudioChannelLabel_Right;
|
|
case AOUT_CHAN_CENTER:
|
|
return kAudioChannelLabel_Center;
|
|
case AOUT_CHAN_LFE:
|
|
return kAudioChannelLabel_LFEScreen;
|
|
case AOUT_CHAN_REARLEFT:
|
|
return swap_rear_surround ? kAudioChannelLabel_RearSurroundLeft
|
|
: kAudioChannelLabel_LeftSurround;
|
|
case AOUT_CHAN_REARRIGHT:
|
|
return swap_rear_surround ? kAudioChannelLabel_RearSurroundRight
|
|
: kAudioChannelLabel_RightSurround;
|
|
case AOUT_CHAN_MIDDLELEFT:
|
|
return swap_rear_surround ? kAudioChannelLabel_LeftSurround
|
|
: kAudioChannelLabel_RearSurroundLeft;
|
|
case AOUT_CHAN_MIDDLERIGHT:
|
|
return swap_rear_surround ? kAudioChannelLabel_RightSurround
|
|
: kAudioChannelLabel_RearSurroundRight;
|
|
case AOUT_CHAN_REARCENTER:
|
|
return kAudioChannelLabel_CenterSurround;
|
|
default:
|
|
vlc_assert_unreachable();
|
|
}
|
|
}
|
|
|
|
static int
|
|
MapOutputLayout(audio_output_t *p_aout, audio_sample_format_t *fmt,
|
|
const AudioChannelLayout *outlayout, bool *warn_configuration)
|
|
{
|
|
struct aout_sys_common *p_sys = (struct aout_sys_common *) p_aout->sys;
|
|
/* Fill VLC physical_channels from output layout */
|
|
uint32_t i_original = fmt->i_physical_channels;
|
|
fmt->i_physical_channels = 0;
|
|
AudioChannelLayout *reslayout = NULL;
|
|
assert(outlayout != NULL);
|
|
|
|
if (outlayout->mChannelLayoutTag !=
|
|
kAudioChannelLayoutTag_UseChannelDescriptions)
|
|
{
|
|
reslayout = GetLayoutDescription(p_aout, outlayout);
|
|
if (reslayout == NULL)
|
|
return VLC_EGENERIC;
|
|
outlayout = reslayout;
|
|
}
|
|
|
|
if (i_original == AOUT_CHAN_CENTER
|
|
|| outlayout->mNumberChannelDescriptions < 2)
|
|
{
|
|
/* We only need Mono or cannot output more than 1 channel */
|
|
fmt->i_physical_channels = AOUT_CHAN_CENTER;
|
|
msg_Dbg(p_aout, "output layout of AUHAL has 1 channel");
|
|
}
|
|
else if (i_original == (AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT)
|
|
|| outlayout->mNumberChannelDescriptions < 3)
|
|
{
|
|
/* We only need Stereo or cannot output more than 2 channels */
|
|
fmt->i_physical_channels = AOUT_CHANS_STEREO;
|
|
msg_Dbg(p_aout, "output layout of AUHAL is Stereo");
|
|
}
|
|
else
|
|
{
|
|
assert(outlayout->mNumberChannelDescriptions > 0);
|
|
|
|
msg_Dbg(p_aout, "output layout of AUHAL has %i channels",
|
|
outlayout->mNumberChannelDescriptions);
|
|
uint32_t chans_out[AOUT_CHAN_MAX];
|
|
|
|
/* For 7.1, AOUT_CHAN_MIDDLELEFT/RIGHT needs to be swapped with
|
|
* AOUT_CHAN_REARLEFT/RIGHT. Auhal
|
|
* kAudioChannelLabel_Left/RightSurround are used as surround for 5.1,
|
|
* but as middle speakers for rear 7.1. */
|
|
unsigned swap_rear_surround = 0;
|
|
if (outlayout->mNumberChannelDescriptions == 8)
|
|
{
|
|
for (unsigned i = 0; i < outlayout->mNumberChannelDescriptions; i++)
|
|
{
|
|
AudioChannelLabel chan =
|
|
outlayout->mChannelDescriptions[i].mChannelLabel;
|
|
if (chan == kAudioChannelLabel_RearSurroundLeft
|
|
|| chan == kAudioChannelLabel_RearSurroundRight)
|
|
swap_rear_surround++;
|
|
}
|
|
if (swap_rear_surround == 2)
|
|
msg_Dbg(p_aout, "swapping Surround and RearSurround channels "
|
|
"for 7.1 Rear Surround");
|
|
}
|
|
|
|
size_t chans_out_idx = 0;
|
|
for (unsigned i = 0; i < outlayout->mNumberChannelDescriptions; i++)
|
|
{
|
|
AudioChannelLabel chan =
|
|
outlayout->mChannelDescriptions[i].mChannelLabel;
|
|
#ifndef NDEBUG
|
|
msg_Dbg(p_aout, "this is channel: %d", (int) chan);
|
|
#endif
|
|
unsigned mapped_chan =
|
|
AudioChannelLabelToVlcChan(chan, swap_rear_surround == 2);
|
|
if (mapped_chan != 0)
|
|
{
|
|
chans_out[chans_out_idx++] = mapped_chan;
|
|
fmt->i_physical_channels |= mapped_chan;
|
|
}
|
|
else
|
|
msg_Dbg(p_aout, "found nonrecognized channel %d at index "
|
|
"%d", chan, i);
|
|
}
|
|
if (fmt->i_physical_channels == 0)
|
|
{
|
|
fmt->i_physical_channels = AOUT_CHANS_STEREO;
|
|
if (warn_configuration)
|
|
*warn_configuration = true;
|
|
}
|
|
else
|
|
{
|
|
p_sys->chans_to_reorder =
|
|
aout_CheckChannelReorder(NULL, chans_out,
|
|
fmt->i_physical_channels,
|
|
p_sys->chan_table);
|
|
if (p_sys->chans_to_reorder)
|
|
msg_Dbg(p_aout, "channel reordering needed");
|
|
}
|
|
}
|
|
|
|
free(reslayout);
|
|
aout_FormatPrepare(fmt);
|
|
|
|
msg_Dbg(p_aout, "VLC will output: %s", aout_FormatPrintChannels(fmt));
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
static int
|
|
MapInputLayout(audio_output_t *p_aout, const audio_sample_format_t *fmt,
|
|
AudioChannelLayout **inlayoutp, size_t *inlayout_size)
|
|
{
|
|
struct aout_sys_common *p_sys = (struct aout_sys_common *) p_aout->sys;
|
|
uint32_t chans_out[AOUT_CHAN_MAX] = { 0, };
|
|
|
|
unsigned channels = aout_FormatNbChannels(fmt);
|
|
|
|
size_t size;
|
|
if (mul_overflow(channels, sizeof(AudioChannelDescription), &size))
|
|
return VLC_ENOMEM;
|
|
if (add_overflow(size, sizeof(AudioChannelLayout), &size))
|
|
return VLC_ENOMEM;
|
|
AudioChannelLayout *inlayout = malloc(size);
|
|
if (inlayout == NULL)
|
|
return VLC_ENOMEM;
|
|
|
|
*inlayoutp = inlayout;
|
|
*inlayout_size = size;
|
|
inlayout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
|
|
inlayout->mNumberChannelDescriptions = aout_FormatNbChannels(fmt);
|
|
|
|
bool swap_rear_surround = (fmt->i_physical_channels & AOUT_CHANS_7_0) == AOUT_CHANS_7_0;
|
|
if (swap_rear_surround)
|
|
msg_Dbg(p_aout, "swapping Surround and RearSurround channels "
|
|
"for 7.1 Rear Surround");
|
|
unsigned chan_idx = 0;
|
|
for (unsigned i = 0; i < AOUT_CHAN_MAX; ++i)
|
|
{
|
|
unsigned vlcchan = pi_vlc_chan_order_wg4[i];
|
|
if ((vlcchan & fmt->i_physical_channels) == 0)
|
|
continue;
|
|
|
|
inlayout->mChannelDescriptions[chan_idx].mChannelLabel =
|
|
VlcChanToAudioChannelLabel(vlcchan, swap_rear_surround);
|
|
inlayout->mChannelDescriptions[chan_idx].mChannelFlags =
|
|
kAudioChannelFlags_AllOff;
|
|
chan_idx++;
|
|
}
|
|
|
|
msg_Dbg(p_aout, "VLC keeping the same input layout");
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
int
|
|
au_Initialize(audio_output_t *p_aout, AudioUnit au, audio_sample_format_t *fmt,
|
|
const AudioChannelLayout *outlayout, vlc_tick_t i_dev_latency_us,
|
|
bool *warn_configuration)
|
|
{
|
|
int ret;
|
|
AudioChannelLayout *inlayout_buf = NULL;
|
|
const AudioChannelLayout *inlayout = NULL;
|
|
size_t inlayout_size = 0;
|
|
|
|
if (warn_configuration)
|
|
*warn_configuration = false;
|
|
|
|
/* Set the desired format */
|
|
AudioStreamBasicDescription desc;
|
|
if (aout_BitsPerSample(fmt->i_format) != 0)
|
|
{
|
|
/* PCM */
|
|
fmt->i_format = VLC_CODEC_FL32;
|
|
if (outlayout != NULL)
|
|
{
|
|
ret = MapOutputLayout(p_aout, fmt, outlayout, warn_configuration);
|
|
if (ret != VLC_SUCCESS)
|
|
return ret;
|
|
}
|
|
else
|
|
{
|
|
aout_FormatPrepare(fmt);
|
|
ret = MapInputLayout(p_aout, fmt, &inlayout_buf, &inlayout_size);
|
|
if (ret != VLC_SUCCESS)
|
|
return ret;
|
|
inlayout = inlayout_buf;
|
|
}
|
|
|
|
desc.mFormatFlags = kAudioFormatFlagsNativeFloatPacked;
|
|
desc.mChannelsPerFrame = aout_FormatNbChannels(fmt);
|
|
desc.mBitsPerChannel = 32;
|
|
}
|
|
else if (AOUT_FMT_SPDIF(fmt))
|
|
{
|
|
/* Passthrough */
|
|
fmt->i_format = VLC_CODEC_SPDIFL;
|
|
fmt->i_bytes_per_frame = 4;
|
|
fmt->i_frame_length = 1;
|
|
|
|
static const AudioChannelLayout inlayout_spdif = {
|
|
.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo,
|
|
};
|
|
inlayout = &inlayout_spdif;
|
|
inlayout_size = sizeof(inlayout_spdif);
|
|
|
|
desc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger |
|
|
kLinearPCMFormatFlagIsPacked; /* S16LE */
|
|
desc.mChannelsPerFrame = 2;
|
|
desc.mBitsPerChannel = 16;
|
|
}
|
|
else
|
|
return VLC_EGENERIC;
|
|
|
|
desc.mSampleRate = fmt->i_rate;
|
|
desc.mFormatID = kAudioFormatLinearPCM;
|
|
desc.mFramesPerPacket = 1;
|
|
desc.mBytesPerFrame = desc.mBitsPerChannel * desc.mChannelsPerFrame / 8;
|
|
desc.mBytesPerPacket = desc.mBytesPerFrame * desc.mFramesPerPacket;
|
|
|
|
OSStatus err = AudioUnitSetProperty(au, kAudioUnitProperty_StreamFormat,
|
|
kAudioUnitScope_Input, 0, &desc,
|
|
sizeof(desc));
|
|
if (err != noErr)
|
|
{
|
|
ca_LogErr("failed to set stream format");
|
|
free(inlayout_buf);
|
|
return VLC_EGENERIC;
|
|
}
|
|
msg_Dbg(p_aout, STREAM_FORMAT_MSG("Current AU format: " , desc));
|
|
|
|
/* Retrieve actual format */
|
|
err = AudioUnitGetProperty(au, kAudioUnitProperty_StreamFormat,
|
|
kAudioUnitScope_Input, 0, &desc,
|
|
&(UInt32) { sizeof(desc) });
|
|
if (err != noErr)
|
|
{
|
|
ca_LogErr("failed to set stream format");
|
|
free(inlayout_buf);
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
/* Set the IOproc callback */
|
|
const AURenderCallbackStruct callback = {
|
|
.inputProc = RenderCallback,
|
|
.inputProcRefCon = p_aout,
|
|
};
|
|
|
|
err = AudioUnitSetProperty(au, kAudioUnitProperty_SetRenderCallback,
|
|
kAudioUnitScope_Input, 0, &callback,
|
|
sizeof(callback));
|
|
if (err != noErr)
|
|
{
|
|
ca_LogErr("failed to setup render callback");
|
|
free(inlayout_buf);
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
if (inlayout != NULL)
|
|
{
|
|
/* Set the input_layout as the layout VLC will use to feed the AU unit.
|
|
* Yes, it must be the INPUT scope */
|
|
err = AudioUnitSetProperty(au, kAudioUnitProperty_AudioChannelLayout,
|
|
kAudioUnitScope_Input, 0, inlayout,
|
|
inlayout_size);
|
|
free(inlayout_buf);
|
|
if (err != noErr)
|
|
{
|
|
ca_LogErr("failed to setup input layout");
|
|
return VLC_EGENERIC;
|
|
}
|
|
}
|
|
|
|
/* AU init */
|
|
err = AudioUnitInitialize(au);
|
|
|
|
if (err != noErr)
|
|
{
|
|
ca_LogErr("AudioUnitInitialize failed");
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
ret = ca_Initialize(p_aout, fmt, i_dev_latency_us);
|
|
if (ret != VLC_SUCCESS)
|
|
{
|
|
AudioUnitUninitialize(au);
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
void
|
|
au_Uninitialize(audio_output_t *p_aout, AudioUnit au)
|
|
{
|
|
OSStatus err = AudioUnitUninitialize(au);
|
|
if (err != noErr)
|
|
ca_LogWarn("AudioUnitUninitialize failed");
|
|
|
|
ca_Uninitialize(p_aout);
|
|
}
|
|
|