audio: api for mixeng code free backends

This will make it possible to skip mixeng with audio playback and
recording, allowing us to free ourselves from the limitations of the
current mixeng (stereo, int64 samples only).  In this case, HW and SW
voices will be essentially the same, for every SW voice we will create
a HW voice, since we can no longer mix multiple voices together.

Some backends expect us to call a function when we have data ready
write()/read() style, while others provide a buffer and expects us to
directly write/read it, so for optimal performance audio_pcm_ops provide
methods for both cases.  Previously backends asked mixeng for more data
in run_out/run_it, now instead mixeng or the frontends will call the
backends, so that's why two sets of functions required.  audio.c
contains glue code between the two styles, so backends only ever have to
implement one style and frontends are free to call whichever is more
convenient for them.

Signed-off-by: Kővágó, Zoltán <DirtY.iCE.hu@gmail.com>
Message-id: 15a33c03a62228922d851f7324c52f73cb8d2414.1568927990.git.DirtY.iCE.hu@gmail.com
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
This commit is contained in:
Kővágó, Zoltán 2019-09-19 23:24:09 +02:00 committed by Gerd Hoffmann
parent dfc5434384
commit ff095e5231
3 changed files with 250 additions and 8 deletions

View file

@ -573,6 +573,25 @@ size_t audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf,
return clipped;
}
static void audio_pcm_hw_clip_out2(HWVoiceOut *hw, void *pcm_buf, size_t len)
{
size_t clipped = 0;
size_t pos = hw->rpos;
while (len) {
st_sample *src = hw->mix_buf + pos;
uint8_t *dst = advance(pcm_buf, clipped << hw->info.shift);
size_t samples_till_end_of_buf = hw->samples - pos;
size_t samples_to_clip = MIN(len, samples_till_end_of_buf);
hw->clip(dst, src, samples_to_clip);
pos = (pos + samples_to_clip) % hw->samples;
len -= samples_to_clip;
clipped += samples_to_clip;
}
}
/*
* Soft voice (capture)
*/
@ -1050,6 +1069,36 @@ static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos,
mixeng_clear(hw->mix_buf, samples - n);
}
static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live)
{
size_t clipped = 0;
while (live) {
size_t size, decr, proc;
void *buf = hw->pcm_ops->get_buffer_out(hw, &size);
if (!buf) {
/* retrying will likely won't help, drop everything. */
hw->rpos = (hw->rpos + live) % hw->samples;
return clipped + live;
}
decr = MIN(size >> hw->info.shift, live);
audio_pcm_hw_clip_out2(hw, buf, decr);
proc = hw->pcm_ops->put_buffer_out(hw, buf, decr << hw->info.shift) >>
hw->info.shift;
live -= proc;
clipped += proc;
hw->rpos = (hw->rpos + proc) % hw->samples;
if (proc == 0 || proc < decr) {
break;
}
}
return clipped;
}
static void audio_run_out (AudioState *s)
{
HWVoiceOut *hw = NULL;
@ -1097,7 +1146,11 @@ static void audio_run_out (AudioState *s)
}
prev_rpos = hw->rpos;
played = hw->pcm_ops->run_out (hw, live);
if (hw->pcm_ops->run_out) {
played = hw->pcm_ops->run_out(hw, live);
} else {
played = audio_pcm_hw_run_out(hw, live);
}
replay_audio_out(&played);
if (audio_bug(__func__, hw->rpos >= hw->samples)) {
dolog("hw->rpos=%zu hw->samples=%zu played=%zu\n",
@ -1156,6 +1209,35 @@ static void audio_run_out (AudioState *s)
}
}
static size_t audio_pcm_hw_run_in(HWVoiceIn *hw, size_t samples)
{
size_t conv = 0;
while (samples) {
size_t proc;
size_t size = samples << hw->info.shift;
void *buf = hw->pcm_ops->get_buffer_in(hw, &size);
assert((size & hw->info.align) == 0);
if (size == 0) {
hw->pcm_ops->put_buffer_in(hw, buf, size);
break;
}
proc = MIN(size >> hw->info.shift,
hw->samples - hw->wpos);
hw->conv(hw->conv_buf + hw->wpos, buf, proc);
hw->wpos = (hw->wpos + proc) % hw->samples;
samples -= proc;
conv += proc;
hw->pcm_ops->put_buffer_in(hw, buf, proc << hw->info.shift);
}
return conv;
}
static void audio_run_in (AudioState *s)
{
HWVoiceIn *hw = NULL;
@ -1165,7 +1247,12 @@ static void audio_run_in (AudioState *s)
size_t captured = 0, min;
if (replay_mode != REPLAY_MODE_PLAY) {
captured = hw->pcm_ops->run_in(hw);
if (hw->pcm_ops->run_in) {
captured = hw->pcm_ops->run_in(hw);
} else {
captured = audio_pcm_hw_run_in(
hw, hw->samples - audio_pcm_hw_get_live_in(hw));
}
}
replay_audio_in(&captured, hw->conv_buf, &hw->wpos, hw->samples);
@ -1259,12 +1346,137 @@ void audio_run(AudioState *s, const char *msg)
#endif
}
void *audio_generic_get_buffer_in(HWVoiceIn *hw, size_t *size)
{
ssize_t start;
if (unlikely(!hw->buf_emul)) {
size_t calc_size = hw->samples << hw->info.shift;
hw->buf_emul = g_malloc(calc_size);
hw->size_emul = calc_size;
hw->pos_emul = hw->pending_emul = 0;
}
while (hw->pending_emul < hw->size_emul) {
size_t read_len = MIN(hw->size_emul - hw->pos_emul,
hw->size_emul - hw->pending_emul);
size_t read = hw->pcm_ops->read(hw, hw->buf_emul + hw->pos_emul,
read_len);
hw->pending_emul += read;
if (read < read_len) {
break;
}
}
start = ((ssize_t) hw->pos_emul) - hw->pending_emul;
if (start < 0) {
start += hw->size_emul;
}
assert(start >= 0 && start < hw->size_emul);
*size = MIN(hw->pending_emul, hw->size_emul - start);
return hw->buf_emul + start;
}
void audio_generic_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size)
{
assert(size <= hw->pending_emul);
hw->pending_emul -= size;
}
void *audio_generic_get_buffer_out(HWVoiceOut *hw, size_t *size)
{
if (unlikely(!hw->buf_emul)) {
size_t calc_size = hw->samples << hw->info.shift;
hw->buf_emul = g_malloc(calc_size);
hw->size_emul = calc_size;
hw->pos_emul = hw->pending_emul = 0;
}
*size = MIN(hw->size_emul - hw->pending_emul,
hw->size_emul - hw->pos_emul);
return hw->buf_emul + hw->pos_emul;
}
size_t audio_generic_put_buffer_out_nowrite(HWVoiceOut *hw, void *buf,
size_t size)
{
assert(buf == hw->buf_emul + hw->pos_emul &&
size + hw->pending_emul <= hw->size_emul);
hw->pending_emul += size;
hw->pos_emul = (hw->pos_emul + size) % hw->size_emul;
return size;
}
size_t audio_generic_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size)
{
audio_generic_put_buffer_out_nowrite(hw, buf, size);
while (hw->pending_emul) {
size_t write_len, written;
ssize_t start = ((ssize_t) hw->pos_emul) - hw->pending_emul;
if (start < 0) {
start += hw->size_emul;
}
assert(start >= 0 && start < hw->size_emul);
write_len = MIN(hw->pending_emul, hw->size_emul - start);
written = hw->pcm_ops->write(hw, hw->buf_emul + start, write_len);
hw->pending_emul -= written;
if (written < write_len) {
break;
}
}
/*
* fake we have written everything. non-written data remain in pending_emul,
* so we do not have to clip them multiple times
*/
return size;
}
size_t audio_generic_write(HWVoiceOut *hw, void *buf, size_t size)
{
size_t dst_size, copy_size;
void *dst = hw->pcm_ops->get_buffer_out(hw, &dst_size);
copy_size = MIN(size, dst_size);
memcpy(dst, buf, copy_size);
return hw->pcm_ops->put_buffer_out(hw, buf, copy_size);
}
size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size)
{
size_t dst_size, copy_size;
void *dst = hw->pcm_ops->get_buffer_in(hw, &dst_size);
copy_size = MIN(size, dst_size);
memcpy(dst, buf, copy_size);
hw->pcm_ops->put_buffer_in(hw, buf, copy_size);
return copy_size;
}
static int audio_driver_init(AudioState *s, struct audio_driver *drv,
bool msg, Audiodev *dev)
{
s->drv_opaque = drv->init(dev);
if (s->drv_opaque) {
if (!drv->pcm_ops->get_buffer_in) {
drv->pcm_ops->get_buffer_in = audio_generic_get_buffer_in;
drv->pcm_ops->put_buffer_in = audio_generic_put_buffer_in;
}
if (!drv->pcm_ops->get_buffer_out) {
drv->pcm_ops->get_buffer_out = audio_generic_get_buffer_out;
drv->pcm_ops->put_buffer_out = audio_generic_put_buffer_out;
}
audio_init_nb_voices_out(s, drv);
audio_init_nb_voices_in(s, drv);
s->drv = drv;

View file

@ -65,6 +65,8 @@ typedef struct HWVoiceOut {
uint64_t ts_helper;
struct st_sample *mix_buf;
void *buf_emul;
size_t pos_emul, pending_emul, size_emul;
size_t samples;
QLIST_HEAD (sw_out_listhead, SWVoiceOut) sw_head;
@ -87,6 +89,8 @@ typedef struct HWVoiceIn {
uint64_t ts_helper;
struct st_sample *conv_buf;
void *buf_emul;
size_t pos_emul, pending_emul, size_emul;
size_t samples;
QLIST_HEAD (sw_in_listhead, SWVoiceIn) sw_head;
@ -147,17 +151,42 @@ struct audio_driver {
};
struct audio_pcm_ops {
int (*init_out)(HWVoiceOut *hw, struct audsettings *as, void *drv_opaque);
void (*fini_out)(HWVoiceOut *hw);
int (*init_out)(HWVoiceOut *hw, audsettings *as, void *drv_opaque);
void (*fini_out)(HWVoiceOut *hw);
size_t (*run_out)(HWVoiceOut *hw, size_t live);
int (*ctl_out) (HWVoiceOut *hw, int cmd, ...);
size_t (*write) (HWVoiceOut *hw, void *buf, size_t size);
/*
* get a buffer that after later can be passed to put_buffer_out; optional
* returns the buffer, and writes it's size to size (in bytes)
* this is unrelated to the above buffer_size_out function
*/
void *(*get_buffer_out)(HWVoiceOut *hw, size_t *size);
/*
* put back the buffer returned by get_buffer_out; optional
* buf must be equal the pointer returned by get_buffer_out,
* size may be smaller
*/
size_t (*put_buffer_out)(HWVoiceOut *hw, void *buf, size_t size);
int (*ctl_out) (HWVoiceOut *hw, int cmd, ...);
int (*init_in) (HWVoiceIn *hw, struct audsettings *as, void *drv_opaque);
void (*fini_in) (HWVoiceIn *hw);
int (*init_in) (HWVoiceIn *hw, audsettings *as, void *drv_opaque);
void (*fini_in) (HWVoiceIn *hw);
size_t (*run_in)(HWVoiceIn *hw);
int (*ctl_in) (HWVoiceIn *hw, int cmd, ...);
size_t (*read) (HWVoiceIn *hw, void *buf, size_t size);
void *(*get_buffer_in)(HWVoiceIn *hw, size_t *size);
void (*put_buffer_in)(HWVoiceIn *hw, void *buf, size_t size);
int (*ctl_in) (HWVoiceIn *hw, int cmd, ...);
};
void *audio_generic_get_buffer_in(HWVoiceIn *hw, size_t *size);
void audio_generic_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size);
void *audio_generic_get_buffer_out(HWVoiceOut *hw, size_t *size);
size_t audio_generic_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size);
size_t audio_generic_put_buffer_out_nowrite(HWVoiceOut *hw, void *buf,
size_t size);
size_t audio_generic_write(HWVoiceOut *hw, void *buf, size_t size);
size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size);
struct capture_callback {
struct audio_capture_ops ops;
void *opaque;

View file

@ -71,6 +71,7 @@ static void glue(audio_init_nb_voices_, TYPE)(AudioState *s,
static void glue (audio_pcm_hw_free_resources_, TYPE) (HW *hw)
{
g_free(hw->buf_emul);
g_free (HWBUF);
HWBUF = NULL;
}