Skip to content

Commit

Permalink
coreaudio: rework the render callback
Browse files Browse the repository at this point in the history
Code written by me few years ago but quite hard to understand (even by
the author), rework it by taking inspiration from the AAudio plugin.

Don't use both mach host time and vlc_tick_t but transform immediatly
the host_time to a delay (in ticks) and always use ticks.
  • Loading branch information
tguillem authored and jbkempf committed Dec 2, 2022
1 parent 2700902 commit 370f785
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 130 deletions.
225 changes: 99 additions & 126 deletions modules/audio_output/coreaudio_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
#include "coreaudio_common.h"
#include <CoreAudio/CoreAudioTypes.h>

#define TIMING_REPORT_DELAY_TICKS VLC_TICK_FROM_MS(1000)

static inline uint64_t
BytesToFrames(struct aout_sys_common *p_sys, size_t i_bytes)
{
Expand All @@ -37,6 +39,12 @@ FramesToTicks(struct aout_sys_common *p_sys, int64_t i_nb_frames)
return vlc_tick_from_samples(i_nb_frames, p_sys->i_rate);
}

static inline vlc_tick_t
BytesToTicks(struct aout_sys_common *p_sys, size_t i_bytes)
{
return FramesToTicks(p_sys, BytesToFrames(p_sys, i_bytes));
}

static inline size_t
FramesToBytes(struct aout_sys_common *p_sys, uint64_t i_frames)
{
Expand All @@ -49,6 +57,12 @@ TicksToFrames(struct aout_sys_common *p_sys, vlc_tick_t i_ticks)
return samples_from_vlc_tick(i_ticks, p_sys->i_rate);
}

static inline size_t
TicksToBytes(struct aout_sys_common *p_sys, vlc_tick_t i_ticks)
{
return FramesToBytes(p_sys, TicksToFrames(p_sys, i_ticks));
}

/**
* Convert a relative audio host time to vlc_ticks
*
Expand All @@ -62,19 +76,6 @@ HostTimeToTick(struct aout_sys_common *p_sys, int64_t i_host_time)
return VLC_TICK_FROM_NS(i_host_time * p_sys->tinfo.numer / p_sys->tinfo.denom);
}

/**
* Convert relative vlc_ticks to an audio host time
*
* \warning This function may only be used to convert relative
* vlc_ticks, as vlc_ticks do not have the same origin
* as the audio host clock!
*/
static inline int64_t
TickToHostTime(struct aout_sys_common *p_sys, vlc_tick_t i_ticks)
{
return NS_FROM_VLC_TICK(i_ticks * p_sys->tinfo.denom / p_sys->tinfo.numer);
}

static void
ca_ClearOutBuffers(audio_output_t *p_aout)
{
Expand All @@ -83,8 +84,6 @@ ca_ClearOutBuffers(audio_output_t *p_aout)
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 inline void
Expand Down Expand Up @@ -140,12 +139,19 @@ ca_Open(audio_output_t *p_aout)

/* Called from render callbacks. No lock, wait, and IO here */
void
ca_Render(audio_output_t *p_aout, uint64_t i_host_time,
uint8_t *p_output, size_t i_requested, bool *is_silence)
ca_Render(audio_output_t *p_aout, uint64_t host_time,
uint8_t *data, size_t bytes, bool *is_silence)
{
struct aout_sys_common *p_sys = (struct aout_sys_common *) p_aout->sys;

vlc_tick_t i_now_ticks = vlc_tick_now();
const vlc_tick_t host_delay_ticks = host_time == 0 ? 0
: HostTimeToTick(p_sys, host_time - mach_absolute_time());
const vlc_tick_t bytes_ticks = BytesToTicks(p_sys, bytes);

const vlc_tick_t now_ticks = vlc_tick_now();
const vlc_tick_t end_ticks = now_ticks + bytes_ticks
+ host_delay_ticks
+ p_sys->i_dev_latency_ticks;

lock_lock(p_sys);

Expand All @@ -157,130 +163,94 @@ ca_Render(audio_output_t *p_aout, uint64_t i_host_time,
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 ))
if (!p_sys->started)
{
/* 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 vlc_tick_t i_requested_ticks =
FramesToTicks(p_sys, BytesToFrames(p_sys, i_requested));
const int64_t i_requested_host_time =
TickToHostTime(p_sys, i_requested_ticks);
if (p_sys->i_first_render_host_time >= i_host_time + i_requested_host_time)
size_t tocopy;

if (p_sys->first_play_date == VLC_TICK_INVALID)
tocopy = bytes;
else
{
/* Fill the buffer with silence */
goto drop;
/* Write silence to reach the first play date */
vlc_tick_t silence_ticks = p_sys->first_play_date - end_ticks
+ bytes_ticks;
if (silence_ticks > 0)
{
tocopy = TicksToBytes(p_sys, silence_ticks);
if (tocopy > bytes)
tocopy = bytes;
}
else
tocopy = 0;
}

/* Write silence to reach the first_render host time */
const vlc_tick_t i_silence_ticks =
HostTimeToTick(p_sys, p_sys->i_first_render_host_time - i_host_time);

const size_t i_silence_bytes =
FramesToBytes(p_sys, TicksToFrames(p_sys, i_silence_ticks));
assert(i_silence_bytes <= i_requested);
memset(p_output, 0, i_silence_bytes);
if (tocopy > 0)
{
memset(data, 0, tocopy);

i_requested -= i_silence_bytes;
p_output += i_silence_bytes;
data += tocopy;
bytes -= tocopy;

/* Start the first rendering */
if (bytes == 0 && is_silence != NULL)
*is_silence = true;
}
}
p_sys->i_render_host_time = i_host_time;

size_t i_copied = 0;
block_t *p_block = p_sys->p_out_chain;
while (p_block != NULL && i_requested != 0)
while (bytes > 0)
{
size_t i_tocopy = __MIN(i_requested, p_block->i_buffer);
if (unlikely(p_sys->b_muted))
memset(p_output, 0, i_tocopy);
else
memcpy(p_output, p_block->p_buffer, i_tocopy);
i_requested -= i_tocopy;
i_copied += i_tocopy;
p_output += i_tocopy;
vlc_frame_t *f = p_sys->p_out_chain;
if (f == NULL)
goto drop;

size_t tocopy = f->i_buffer > bytes ? bytes : f->i_buffer;

p_sys->i_out_size -= tocopy;
p_sys->i_total_bytes += tocopy;

if (i_tocopy == p_block->i_buffer)
if (!p_sys->started
|| (p_sys->timing_report_last_written_bytes >=
p_sys->timing_report_delay_bytes))
{
block_t *p_release = p_block;
p_block = p_block->p_next;
block_Release(p_release);
p_sys->timing_report_last_written_bytes = 0;
vlc_tick_t pos_ticks = BytesToTicks(p_sys, p_sys->i_total_bytes);
aout_TimingReport(p_aout, end_ticks, pos_ticks);
}
else
{
assert(i_requested == 0);
p_sys->timing_report_last_written_bytes += tocopy;

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;
p_sys->started = true;

p_sys->i_total_bytes += i_copied;
memcpy(data, f->p_buffer, tocopy);

/* 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);
}

if (is_silence != NULL)
*is_silence = p_sys->b_muted;
data += tocopy;
bytes -= tocopy;
f->i_buffer -= tocopy;
f->p_buffer += tocopy;

/* Convert host time to ticks */
vlc_tick_t i_host_ticks = HostTimeToTick(p_sys, i_host_time - mach_absolute_time())
+ i_now_ticks;

/* Report a timing every seconds */
if (p_sys->i_last_latency_ticks == VLC_TICK_INVALID
|| i_host_ticks - p_sys->i_last_latency_ticks >= VLC_TICK_FROM_SEC(1))
{
vlc_tick_t frames_tick = FramesToTicks(p_sys,
BytesToFrames(p_sys, p_sys->i_total_bytes))
+ p_sys->i_dev_latency_ticks;
if (f->i_buffer == 0)
{
p_sys->p_out_chain = f->p_next;
if (p_sys->p_out_chain == NULL)
p_sys->pp_out_last = &p_sys->p_out_chain;

aout_TimingReport(p_aout, i_host_ticks, frames_tick);
p_sys->i_last_latency_ticks = i_host_ticks;
block_Release(f);
}
}

lock_unlock(p_sys);

return;

drop:
memset(p_output, 0, i_requested);
memset(data, 0, bytes);
if (is_silence != NULL)
*is_silence = true;
lock_unlock(p_sys);
}

static vlc_tick_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 FramesToTicks(p_sys, i_out_frames)
+ p_sys->i_dev_latency_ticks;
}

void
ca_Flush(audio_output_t *p_aout)
{
Expand All @@ -299,9 +269,11 @@ ca_Flush(audio_output_t *p_aout)
lock_lock(p_sys);
}

p_sys->i_render_host_time = p_sys->i_first_render_host_time = 0;
p_sys->i_last_latency_ticks = VLC_TICK_INVALID;
p_sys->started = false;
p_sys->i_out_size = 0;
p_sys->i_total_bytes = 0;
p_sys->first_play_date = VLC_TICK_INVALID;
p_sys->timing_report_last_written_bytes = 0;
lock_unlock(p_sys);

p_sys->b_played = false;
Expand All @@ -315,6 +287,7 @@ ca_Pause(audio_output_t * p_aout, bool pause, vlc_tick_t date)

lock_lock(p_sys);
p_sys->b_paused = pause;
p_sys->started = false;
lock_unlock(p_sys);
}

Expand All @@ -341,20 +314,17 @@ ca_Play(audio_output_t * p_aout, block_t * p_block, vlc_tick_t date)

lock_lock(p_sys);

if (p_sys->i_render_host_time == 0)
if (!p_sys->started)
{
/* 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. */

/* We can't convert date to host time directly since the clock source
* may be different (MONOTONIC vs continue during sleep). The solution
* is to convert it to a relative time and then add it to
* mach_absolute_time() */
const vlc_tick_t first_render_delay = date - vlc_tick_now()
- ca_GetLatencyLocked(p_aout);
p_sys->i_first_render_host_time
= mach_absolute_time() + TickToHostTime(p_sys, first_render_delay);
vlc_tick_t now = vlc_tick_now();
p_sys->first_play_date = date - BytesToTicks(p_sys, p_sys->i_out_size);

if (p_sys->first_play_date > now)
msg_Dbg(p_aout, "deferring start (%"PRId64" us)",
p_sys->first_play_date - now);
else
msg_Dbg(p_aout, "starting late (%"PRId64" us)",
p_sys->first_play_date - now);
}

p_sys->i_out_size += p_block->i_buffer;
Expand All @@ -381,16 +351,19 @@ ca_Initialize(audio_output_t *p_aout, const audio_sample_format_t *fmt,

p_sys->i_underrun_size = 0;
p_sys->b_paused = false;
p_sys->started = false;
p_sys->b_muted = false;
p_sys->i_render_host_time = p_sys->i_first_render_host_time = 0;
p_sys->i_last_latency_ticks = VLC_TICK_INVALID;
p_sys->i_out_size = 0;
p_sys->i_total_bytes = 0;
p_sys->first_play_date = VLC_TICK_INVALID;
p_sys->timing_report_last_written_bytes = 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;

p_sys->i_dev_latency_ticks = i_dev_latency_ticks;
p_sys->timing_report_delay_bytes = TicksToBytes(p_sys, TIMING_REPORT_DELAY_TICKS);

ca_ClearOutBuffers(p_aout);
p_sys->b_played = false;
Expand Down
14 changes: 10 additions & 4 deletions modules/audio_output/coreaudio_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,24 @@ struct aout_sys_common
mach_timebase_info_data_t tinfo;

size_t i_underrun_size;
bool started;
bool b_paused;
bool b_muted;
bool b_do_flush;

size_t i_out_size;
bool b_played;
block_t *p_out_chain;
block_t **pp_out_last;
uint64_t i_render_host_time;
uint64_t i_first_render_host_time;
vlc_tick_t i_last_latency_ticks;
/* Size of the frame FIFO */
size_t i_out_size;
/* Size written via the render callback */
uint64_t i_total_bytes;
/* Date when the data callback should start to process audio */
vlc_tick_t first_play_date;
/* Bytes written since the last timing report */
size_t timing_report_last_written_bytes;
/* Number of bytes to write before sending a timing report */
size_t timing_report_delay_bytes;

vlc_sem_t flush_sem;

Expand Down

0 comments on commit 370f785

Please sign in to comment.