Rework crossfade to properly follow pcmbuf chunks instaed of blindly inserting into the ring buffer

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@9780 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
Brandon Low 2006-04-23 22:54:34 +00:00
parent 6a6c322a19
commit 6c0908b416
3 changed files with 223 additions and 241 deletions

View file

@ -208,7 +208,6 @@ bool dbg_audio_thread(void)
return false;
}
#else /* CONFIG_CODEC == SWCODEC */
extern size_t audiobuffer_free;
extern size_t filebuflen;
/* This is a size_t, but call it a long so it puts a - when it's bad. */
extern long filebufused;
@ -260,12 +259,12 @@ bool dbg_audio_thread(void)
lcd_clear_display();
snprintf(buf, sizeof(buf), "pcm: %7ld/%7ld",
bufsize-audiobuffer_free, bufsize);
bufsize-pcmbuf_free(), bufsize);
lcd_puts(0, line++, buf);
/* Playable space left */
scrollbar(0, line*8, LCD_WIDTH, 6, bufsize, 0,
bufsize-audiobuffer_free, HORIZONTAL);
bufsize-pcmbuf_free(), HORIZONTAL);
line++;
snprintf(buf, sizeof(buf), "codec: %8ld/%8ld", filebufused, filebuflen);

View file

@ -38,44 +38,6 @@
#define PCMBUF_WATERMARK (NATIVE_FREQUENCY * 4 * 1)
/* Size of the PCM buffer. */
static size_t pcmbuf_size IDATA_ATTR = 0;
static char *audiobuffer IDATA_ATTR;
/* Current audio buffer write index. */
static size_t audiobuffer_pos IDATA_ATTR;
/* Amount of bytes left in the buffer. */
size_t audiobuffer_free IDATA_ATTR;
/* Amount audiobuffer_pos will be increased.*/
static size_t audiobuffer_fillpos IDATA_ATTR;
static char *fadebuf IDATA_ATTR;
static char *voicebuf IDATA_ATTR;
static void (*pcmbuf_event_handler)(void) IDATA_ATTR;
static void (*position_callback)(size_t size) IDATA_ATTR;
/* Crossfade related. */
static int crossfade_mode IDATA_ATTR;
static bool crossfade_enabled IDATA_ATTR;
static bool crossfade_active IDATA_ATTR;
static bool crossfade_init IDATA_ATTR;
static size_t crossfade_pos IDATA_ATTR;
static size_t crossfade_rem IDATA_ATTR;
/* Crossfade modes. If CFM_CROSSFADE is selected, normal
* crossfader will activate. Selecting CFM_FLUSH is a special
* operation that only overwrites the pcm buffer without crossfading.
*/
enum {
CFM_CROSSFADE,
CFM_MIX,
CFM_FLUSH
};
static size_t crossfade_fade_in_amount IDATA_ATTR;
static size_t crossfade_fade_in_rem IDATA_ATTR;
/* Structure we can use to queue pcm chunks in memory to be played
* by the driver code. */
struct pcmbufdesc
@ -87,17 +49,61 @@ struct pcmbufdesc
void (*callback)(void);
};
/* Size of the PCM buffer. */
static size_t pcmbuf_size IDATA_ATTR = 0;
static char *audiobuffer IDATA_ATTR;
/* Current audio buffer write index. */
static size_t audiobuffer_pos IDATA_ATTR;
/* Amount audiobuffer_pos will be increased.*/
static size_t audiobuffer_fillpos IDATA_ATTR;
static char *fadebuf IDATA_ATTR;
static char *voicebuf IDATA_ATTR;
static void (*pcmbuf_event_handler)(void) IDATA_ATTR;
static void (*position_callback)(size_t size) IDATA_ATTR;
/* Crossfade related state */
static bool crossfade_enabled;
static bool crossfade_mix;
static bool crossfade_active IDATA_ATTR;
static bool crossfade_init IDATA_ATTR;
/* Track the current location for processing crossfade */
static struct pcmbufdesc *crossfade_chunk IDATA_ATTR;
static size_t crossfade_sample IDATA_ATTR;
/* Counters for fading in new data */
static size_t crossfade_fade_in_total IDATA_ATTR;
static size_t crossfade_fade_in_rem IDATA_ATTR;
static size_t pcmbuf_descsize;
static struct pcmbufdesc *pcmbuf_read IDATA_ATTR;
static struct pcmbufdesc *pcmbuf_read_end IDATA_ATTR;
static struct pcmbufdesc *pcmbuf_write IDATA_ATTR;
static struct pcmbufdesc *pcmbuf_write_end IDATA_ATTR;
static size_t last_chunksize IDATA_ATTR;
/*
static inline size_t pcmbuf_unplayed_bytes(void)
{
size_t bytes = 0;
if (pcmbuf_read)
{
struct pcmbufdesc *pcmbuf_chunk = pcmbuf_read;
do
{
bytes += pcmbuf_chunk->size;
pcmbuf_chunk = pcmbuf_chunk->link;
} while (pcmbuf_chunk);
}
return bytes;
} */
static size_t pcmbuf_unplayed_bytes IDATA_ATTR;
static size_t pcmbuf_watermark IDATA_ATTR;
static struct pcmbufdesc *pcmbuf_mix_chunk IDATA_ATTR;
static size_t pcmbuf_mix_sample IDATA_ATTR;
static bool low_latency_mode = false;
static bool pcmbuf_flush;
/* Helpful macros for use in conditionals this assumes some of the above
* static variable names */
@ -106,7 +112,6 @@ static bool low_latency_mode = false;
#define LOW_DATA(quarter_secs) \
(pcmbuf_unplayed_bytes < NATIVE_FREQUENCY * quarter_secs)
static void pcmbuf_flush_audio(void);
static void pcmbuf_under_watermark(void);
static bool pcmbuf_flush_fillpos(void);
@ -140,13 +145,8 @@ static void pcmbuf_callback(unsigned char** start, size_t* size)
/* Take the finished buffer out of circulation */
pcmbuf_read = pcmbuf_current->link;
{
size_t finished_size = last_chunksize;
audiobuffer_free += finished_size;
/* The buffer is finished, call the callback functions */
CALL_IF_EXISTS(position_callback, finished_size);
}
/* The buffer is finished, call the callback functions */
CALL_IF_EXISTS(position_callback, last_chunksize);
CALL_IF_EXISTS(pcmbuf_current->callback);
/* Put the finished buffer back into circulation */
@ -214,8 +214,20 @@ static inline void pcmbuf_add_chunk(void)
/* This is single use only */
pcmbuf_event_handler = NULL;
if (pcmbuf_read) {
if (pcmbuf_flush)
{
pcmbuf_write_end->link = pcmbuf_read->link;
pcmbuf_read->link = pcmbuf_current;
while (pcmbuf_write_end->link)
{
pcmbuf_write_end = pcmbuf_write_end->link;
pcmbuf_unplayed_bytes -= pcmbuf_write_end->size;
}
pcmbuf_flush = false;
}
/* If there is already a read buffer setup, add to it */
pcmbuf_read_end->link = pcmbuf_current;
else
pcmbuf_read_end->link = pcmbuf_current;
} else {
/* Otherwise create the buffer */
pcmbuf_read = pcmbuf_current;
@ -238,7 +250,7 @@ static void pcmbuf_under_watermark(void)
/* Fill audio buffer by boosting cpu */
pcmbuf_boost(true);
/* Disable crossfade if < .5s of audio */
if (LOW_DATA(2) && crossfade_mode != CFM_FLUSH)
if (LOW_DATA(2))
crossfade_active = false;
}
@ -269,23 +281,43 @@ bool pcmbuf_is_lowdata(void)
return LOW_DATA(2);
}
/* Amount of bytes left in the buffer. */
inline size_t pcmbuf_free(void)
{
if (pcmbuf_read)
{
size_t read = (size_t)pcmbuf_read->addr;
size_t write =
(size_t)&audiobuffer[audiobuffer_pos + audiobuffer_fillpos];
if (read < write)
read += pcmbuf_size;
return read - write;
}
return pcmbuf_size;
}
bool pcmbuf_crossfade_init(bool manual_skip)
{
if (pcmbuf_unplayed_bytes < PCMBUF_TARGET_CHUNK * 8
|| !pcmbuf_is_crossfade_enabled()
|| crossfade_active || crossfade_init || low_latency_mode) {
pcmbuf_flush_audio();
/* Can't do two crossfades at once and, no fade if pcm is off now */
if (crossfade_init || crossfade_active || !pcm_is_playing())
{
pcmbuf_play_stop();
return false;
}
/* Not enough data, or crossfade disabled, flush the old data instead */
if (LOW_DATA(6) || !pcmbuf_is_crossfade_enabled() || low_latency_mode)
{
pcmbuf_boost(true);
pcmbuf_flush = true;
return false;
}
logf("pcmbuf_crossfade_init");
pcmbuf_boost(true);
/* Don't enable mix mode when skipping tracks manually. */
if (manual_skip)
crossfade_mode = CFM_CROSSFADE;
else
crossfade_mode = global_settings.crossfade_fade_out_mixmode
? CFM_MIX : CFM_CROSSFADE;
crossfade_mix = manual_skip && global_settings.crossfade_fade_out_mixmode;
crossfade_init = true;
return true;
@ -309,9 +341,9 @@ void pcmbuf_play_stop(void)
}
audiobuffer_pos = 0;
audiobuffer_fillpos = 0;
audiobuffer_free = pcmbuf_size;
crossfade_init = false;
crossfade_active = false;
pcmbuf_flush = false;
pcmbuf_boost(false);
@ -328,7 +360,7 @@ int pcmbuf_used_descs(void) {
}
int pcmbuf_descs(void) {
return pcmbuf_size / PCMBUF_MINAVG_CHUNK;
return pcmbuf_size / PCMBUF_TARGET_CHUNK;
}
size_t get_pcmbuf_descsize(void) {
@ -368,21 +400,6 @@ size_t pcmbuf_get_bufsize(void)
return pcmbuf_size;
}
/** Initialize a track switch so that audio playback will not stop but
* the switch to next track would happen as soon as possible.
*/
static void pcmbuf_flush_audio(void)
{
if (crossfade_init || crossfade_active || !pcm_is_playing()) {
pcmbuf_play_stop();
return ;
}
pcmbuf_boost(true);
crossfade_mode = CFM_FLUSH;
crossfade_init = true;
}
void pcmbuf_pause(bool pause) {
if (pause)
pcm_mute(true);
@ -442,60 +459,67 @@ static bool pcmbuf_flush_fillpos(void)
static void crossfade_process_buffer(size_t fade_in_delay,
size_t fade_out_delay, size_t fade_out_rem)
{
if (crossfade_mode == CFM_CROSSFADE)
if (!crossfade_mix)
{
/* Fade out the specified amount of the already processed audio */
size_t total_fade_out = fade_out_rem;
short *buf = (short *)&audiobuffer[crossfade_pos + fade_out_delay * 2];
short *buf_end = (short *)fadebuf;
/* Wrap the starting position if needed */
if (buf >= buf_end) buf -= pcmbuf_size / 2;
size_t fade_out_sample;
struct pcmbufdesc *fade_out_chunk = crossfade_chunk;
/* Find the right chunk to start fading out */
while (fade_out_delay >= fade_out_chunk->size)
{
fade_out_delay -= fade_out_chunk->size;
fade_out_chunk = fade_out_chunk->link;
}
/* The start sample within the chunk */
fade_out_sample = fade_out_delay / 2;
while (fade_out_rem > 0)
{
/* Each 1/10 second of audio will have the same fade applied */
size_t block_rem = MIN(NATIVE_FREQUENCY * 2 / 10, fade_out_rem);
unsigned int factor = (fade_out_rem << 8) / total_fade_out;
short *block_end = buf + block_rem;
int factor = (fade_out_rem << 8) / total_fade_out;
fade_out_rem -= block_rem;
/* Fade this block */
while (buf < block_end)
while (block_rem > 0)
{
/* Fade one sample */
*buf = (*buf * factor) >> 8;
buf++;
short *buf = (short *)(fade_out_chunk->addr);
int sample = buf[fade_out_sample];
buf[fade_out_sample++] = (sample * factor) >> 8;
if (buf >= buf_end)
block_rem--;
/* Move to the next chunk as needed */
if (fade_out_sample * 2 >= fade_out_chunk->size)
{
/* Wrap the pcmbuffer */
buf -= pcmbuf_size / 2;
/* Wrap the end pointer to ensure proper termination */
block_end -= pcmbuf_size / 2;
fade_out_chunk = fade_out_chunk->link;
fade_out_sample = 0;
}
}
}
}
/* And finally set the mixing position where we should start fading in. */
crossfade_rem -= fade_in_delay;
crossfade_pos += fade_in_delay*2;
if (crossfade_pos >= pcmbuf_size)
crossfade_pos -= pcmbuf_size;
/* Find the right chunk and sample to start fading in */
while (fade_in_delay >= crossfade_chunk->size)
{
fade_in_delay -= crossfade_chunk->size;
crossfade_chunk = crossfade_chunk->link;
}
crossfade_sample = fade_in_delay / 2;
logf("process done!");
}
/**
* Initializes crossfader, calculates all necessary parameters and
* performs fade-out with the pcm buffer.
*/
/* Initializes crossfader, calculates all necessary parameters and
* performs fade-out with the pcm buffer. */
static void crossfade_start(void)
{
size_t fade_out_rem = 0;
unsigned int fade_out_delay = 0;
unsigned fade_in_delay = 0;
size_t crossfade_rem;
size_t fade_out_rem;
size_t fade_out_delay;
size_t fade_in_delay;
crossfade_init = false;
/* Reject crossfade if less than .5s of data */
@ -506,88 +530,81 @@ static void crossfade_start(void)
}
logf("crossfade_start");
pcmbuf_boost(true);
pcmbuf_flush_fillpos();
crossfade_active = true;
crossfade_pos = audiobuffer_pos;
/* Initialize the crossfade buffer size to all of the buffered data that
* has not yet been sent to the DMA */
crossfade_rem = pcmbuf_unplayed_bytes / 2;
crossfade_rem = pcmbuf_unplayed_bytes;
crossfade_chunk = pcmbuf_read->link;
switch (crossfade_mode) {
case CFM_MIX:
case CFM_CROSSFADE:
/* Get fade out delay from settings. */
fade_out_delay = NATIVE_FREQUENCY
* global_settings.crossfade_fade_out_delay * 2;
/* Get fade out delay from settings. */
fade_out_delay =
NATIVE_FREQUENCY * global_settings.crossfade_fade_out_delay * 4;
/* Get fade out duration from settings. */
fade_out_rem = NATIVE_FREQUENCY
* global_settings.crossfade_fade_out_duration * 2;
/* Get fade out duration from settings. */
fade_out_rem =
NATIVE_FREQUENCY * global_settings.crossfade_fade_out_duration * 4;
/* We want only to modify the last part of the buffer. */
if (crossfade_rem > fade_out_rem + fade_out_delay)
crossfade_rem = fade_out_rem + fade_out_delay;
/* Truncate fade out duration if necessary. */
if (crossfade_rem < fade_out_rem + fade_out_delay)
fade_out_rem -= (fade_out_rem + fade_out_delay) - crossfade_rem;
/* Get also fade in duration and delays from settings. */
crossfade_fade_in_rem = NATIVE_FREQUENCY
* global_settings.crossfade_fade_in_duration * 2;
crossfade_fade_in_amount = crossfade_fade_in_rem;
/* We should avoid to divide by zero. */
if (crossfade_fade_in_amount == 0)
crossfade_fade_in_amount = 1;
fade_in_delay = NATIVE_FREQUENCY
* global_settings.crossfade_fade_in_delay * 2;
/* Decrease the fade out delay if necessary. */
if (crossfade_rem < fade_out_rem + fade_out_delay)
fade_out_delay -=
(fade_out_rem + fade_out_delay) - crossfade_rem;
break ;
case CFM_FLUSH:
crossfade_fade_in_rem = 0;
crossfade_fade_in_amount = 0;
break ;
/* We want only to modify the last part of the buffer. */
if (crossfade_rem > fade_out_rem + fade_out_delay)
{
size_t crossfade_extra = crossfade_rem - fade_out_rem + fade_out_delay;
while (crossfade_extra > crossfade_chunk->size)
{
crossfade_extra -= crossfade_chunk->size;
crossfade_chunk = crossfade_chunk->link;
}
crossfade_sample = crossfade_extra / 2;
}
/* Truncate fade out duration if necessary. */
else if (crossfade_rem < fade_out_rem + fade_out_delay)
{
size_t crossfade_short = fade_out_rem + fade_out_delay - crossfade_rem;
if (fade_out_rem > crossfade_short)
fade_out_rem -= crossfade_short;
else
{
fade_out_delay -= crossfade_short - fade_out_rem;
fade_out_rem = 0;
}
}
if (crossfade_pos < crossfade_rem * 2)
crossfade_pos += pcmbuf_size;
crossfade_pos -= crossfade_rem*2;
/* Get also fade in duration and delays from settings. */
crossfade_fade_in_total =
NATIVE_FREQUENCY * global_settings.crossfade_fade_in_duration * 4;
crossfade_fade_in_rem = crossfade_fade_in_total;
if (crossfade_mode != CFM_FLUSH) {
/* Process the fade out part of the crossfade. */
crossfade_process_buffer(fade_in_delay, fade_out_delay, fade_out_rem);
}
/* We should avoid to divide by zero. */
if (crossfade_fade_in_total == 0)
crossfade_fade_in_total = 1;
fade_in_delay =
NATIVE_FREQUENCY * global_settings.crossfade_fade_in_delay * 4;
crossfade_process_buffer(fade_in_delay, fade_out_delay, fade_out_rem);
}
/**
* Fades in samples passed to the function and inserts them
* to the pcm buffer.
*/
static void fade_insert(const short *inbuf, size_t length)
static void fade_insert(const char *buf, size_t length)
{
size_t copy_n;
int factor;
unsigned int i, samples;
short *buf;
unsigned int i;
short *output_buf;
const short *input_buf = (const short *)buf;
factor = ((crossfade_fade_in_amount-crossfade_fade_in_rem)<<8)
/crossfade_fade_in_amount;
factor = ((crossfade_fade_in_total-crossfade_fade_in_rem)<<8)
/crossfade_fade_in_total;
while (audiobuffer_free < length)
while (pcmbuf_free() < length)
{
pcmbuf_boost(false);
sleep(1);
}
audiobuffer_free -= length;
while (length > 0) {
unsigned int audiobuffer_index = audiobuffer_pos + audiobuffer_fillpos;
@ -597,79 +614,24 @@ static void fade_insert(const short *inbuf, size_t length)
pcmbuf_flush_fillpos();
audiobuffer_index = audiobuffer_pos + audiobuffer_fillpos;
}
copy_n = MIN(length, pcmbuf_size - audiobuffer_index);
buf = (short *)&audiobuffer[audiobuffer_index];
samples = copy_n / 2;
for (i = 0; i < samples; i++)
buf[i] = (inbuf[i] * factor) >> 8;
inbuf += samples;
audiobuffer_fillpos += copy_n;
length -= copy_n;
}
}
output_buf = (short *)&audiobuffer[audiobuffer_index];
/**
* Fades in buf2 and mixes it with buf.
*/
static int crossfade(short *buf, const short *buf2, unsigned int length)
{
size_t size;
unsigned int i;
size_t size_insert = 0;
int factor;
size = MIN(length, crossfade_rem);
switch (crossfade_mode) {
/* Fade in the current stream and mix it. */
case CFM_MIX:
case CFM_CROSSFADE:
factor = ((crossfade_fade_in_amount-crossfade_fade_in_rem)<<8) /
crossfade_fade_in_amount;
for (i = 0; i < size; i++) {
buf[i] = MIN(32767, MAX(-32768,
buf[i] + ((buf2[i] * factor) >> 8)));
}
break ;
/* Join two streams. */
case CFM_FLUSH:
for (i = 0; i < size; i++) {
buf[i] = buf2[i];
}
//memcpy((char *)buf, (char *)buf2, size*2);
break ;
}
if (crossfade_fade_in_rem > size)
crossfade_fade_in_rem = crossfade_fade_in_rem - size;
else
crossfade_fade_in_rem = 0;
crossfade_rem -= size;
if (crossfade_rem == 0)
{
if (crossfade_fade_in_rem > 0 && crossfade_fade_in_amount > 0)
for (copy_n /=2, i = 0; i < copy_n; i++)
{
size_insert = MIN(crossfade_fade_in_rem, length - size);
fade_insert(&buf2[size], size_insert*2);
crossfade_fade_in_rem -= size_insert;
int sample = input_buf[i];
output_buf[i] = (sample * factor) >> 8;
}
if (crossfade_fade_in_rem == 0)
crossfade_active = false;
input_buf += copy_n;
}
return size + size_insert;
}
static void pcmbuf_flush_buffer(const char *buf, size_t length)
{
size_t copy_n;
audiobuffer_free -= length;
while (length > 0) {
size_t audiobuffer_index = audiobuffer_pos + audiobuffer_fillpos;
if (NEED_FLUSH(audiobuffer_index))
@ -685,21 +647,47 @@ static void pcmbuf_flush_buffer(const char *buf, size_t length)
}
}
static void flush_crossfade(const char *buf, size_t length) {
size_t copy_n;
static void flush_crossfade(const char *buf, size_t length)
{
const short *input_buf = (const short *)buf;
int factor = ((crossfade_fade_in_total-crossfade_fade_in_rem)<<8) /
crossfade_fade_in_total;
while (length > 0 && crossfade_active) {
copy_n = MIN(length, pcmbuf_size - crossfade_pos);
copy_n = 2 * crossfade((short *)&audiobuffer[crossfade_pos],
(const short *)buf, copy_n/2);
buf += copy_n;
length -= copy_n;
crossfade_pos += copy_n;
if (crossfade_pos >= pcmbuf_size)
crossfade_pos = 0;
while (length && crossfade_fade_in_rem && crossfade_chunk)
{
short *output_buf = (short *)(crossfade_chunk->addr);
int sample = *input_buf++;
sample = ((sample * factor) >> 8) + output_buf[crossfade_sample];
output_buf[crossfade_sample++] = MIN(32767, MAX(-32768, sample));
length -= 2;
crossfade_fade_in_rem -= 2;
if (crossfade_sample * 2 >= crossfade_chunk->size)
{
crossfade_chunk = crossfade_chunk->link;
crossfade_sample = 0;
}
}
pcmbuf_flush_buffer(buf, length);
buf = (const char *)input_buf;
if (!crossfade_chunk)
{
if (crossfade_fade_in_rem > 0 && crossfade_fade_in_total > 0)
{
size_t size_insert = MIN(crossfade_fade_in_rem, length);
fade_insert(buf, size_insert);
crossfade_fade_in_rem -= size_insert;
length -= size_insert;
buf += size_insert;
}
}
if (crossfade_fade_in_rem == 0)
crossfade_active = false;
if (length > 0)
pcmbuf_flush_buffer(buf, length);
}
static bool prepare_insert(size_t length)
@ -713,7 +701,7 @@ static bool prepare_insert(size_t length)
}
/* Need to save PCMBUF_MIN_CHUNK to prevent wrapping overwriting */
if (audiobuffer_free < length + PCMBUF_MIN_CHUNK && !crossfade_active)
if (pcmbuf_free() < length + PCMBUF_MIN_CHUNK && !crossfade_active)
{
pcmbuf_boost(false);
return false;
@ -801,7 +789,6 @@ void pcmbuf_write_complete(size_t length)
flush_crossfade(fadebuf, length);
else
{
audiobuffer_free -= length;
audiobuffer_fillpos += length;
if (NEED_FLUSH(audiobuffer_pos + audiobuffer_fillpos))
@ -937,7 +924,7 @@ void pcmbuf_mix_voice(size_t length)
length /= 2;
while (length-- > 0) {
long sample = *ibuf++;
int sample = *ibuf++;
if (pcmbuf_mix_sample >= chunk_samples)
{
pcmbuf_mix_chunk = pcmbuf_mix_chunk->link;

View file

@ -21,10 +21,6 @@
#define PCMBUF_TARGET_CHUNK 32768 /* This is the target fill size of chunks
on the pcm buffer */
#define PCMBUF_MINAVG_CHUNK 24576 /* This is the minimum average size of
chunks on the pcm buffer (or we run out
of buffer descriptors, which is
non-fatal) */
#define PCMBUF_MIN_CHUNK 4096 /* We try to never feed a chunk smaller than
this to the DMA */
#define PCMBUF_MIX_CHUNK 8192 /* This is the maximum size of one packet
@ -54,9 +50,9 @@ void pcmbuf_play_start(void);
bool pcmbuf_crossfade_init(bool manual_skip);
void pcmbuf_set_event_handler(void (*callback)(void));
void pcmbuf_set_position_callback(void (*callback)(size_t size));
size_t pcmbuf_free(void);
unsigned int pcmbuf_get_latency(void);
void pcmbuf_set_low_latency(bool state);
bool pcmbuf_insert_buffer(const char *buf, size_t length);
void pcmbuf_write_complete(size_t length);
void* pcmbuf_request_buffer(size_t length, size_t *realsize);
void* pcmbuf_request_voice_buffer(size_t length, size_t *realsize, bool mix);