vbox: use ring buffer in audio backend

Instead of hardcoding the device model configuration use a ring buffer
to gather samples.

Fixes #1767.
This commit is contained in:
Josef Söntgen 2015-09-02 17:15:17 +02:00 committed by Christian Helmuth
parent 83e5a64864
commit be8f16ac3e
1 changed files with 249 additions and 211 deletions

View File

@ -16,7 +16,6 @@
#include <audio_out_session/connection.h>
#include <terminal_session/connection.h>
#include <timer_session/connection.h>
#include <trace/timestamp.h>
/* VirtualBox includes */
#include "VBoxDD.h"
@ -36,115 +35,210 @@ static bool const verbose = false;
#define PLOGV(...) if (verbose) PLOG(__VA_ARGS__);
enum {
AUDIO_CHANNELS = 2,
AUDIO_SAMPLE_SIZE = sizeof(int16_t),
AUDIO_FREQ = 44100,
SIZE_PER_FRAME = AUDIO_CHANNELS * AUDIO_SAMPLE_SIZE,
template <size_t CAPACITY>
struct A_ring_buffer_to_bind_them
{
size_t wpos { 0 };
size_t rpos { 0 };
RECORD_BUFFER_SAMPLES = 2 * Audio_in::PERIOD,
char _data[CAPACITY];
A_ring_buffer_to_bind_them() { }
size_t read_avail() const
{
if (wpos > rpos) return wpos - rpos;
else return (wpos - rpos + CAPACITY) % CAPACITY;
}
size_t write_avail() const
{
if (wpos > rpos) return ((rpos - wpos + CAPACITY) % CAPACITY) - 2;
else if (wpos < rpos) return rpos - wpos;
else return CAPACITY - 2;
}
size_t write(void const *src, size_t len)
{
size_t const avail = write_avail();
if (avail == 0) return 0;
size_t const limit_len = len > avail ? avail : len;
size_t const total = wpos + len;
size_t first, rest;
if (total > CAPACITY) {
first = CAPACITY - wpos;
rest = total % CAPACITY;
} else {
first = limit_len;
rest = 0;
}
Genode::memcpy(&_data[wpos], src, first);
wpos = (wpos + first) % CAPACITY;
if (rest) {
Genode::memcpy(&_data[wpos], ((char const*)src) + first, rest);
wpos = (wpos + rest) % CAPACITY;
}
return limit_len;
}
size_t read(void *dst, size_t len, bool peek = false)
{
size_t const avail = read_avail();
if (avail == 0) return 0;
size_t new_rpos = rpos;
size_t const limit_len = len > avail ? avail : len;
size_t const total = new_rpos + len;
size_t first, rest;
if (total > CAPACITY) {
first = CAPACITY - new_rpos;
rest = total % CAPACITY;
} else {
first = limit_len;
rest = 0;
}
Genode::memcpy(dst, &_data[new_rpos], first);
new_rpos = (new_rpos + first) % CAPACITY;
if (rest) {
Genode::memcpy(((char*)dst) + first, &_data[new_rpos], rest);
new_rpos = (new_rpos + rest) % CAPACITY;
}
if (!peek) rpos = new_rpos;
return limit_len;
}
void read_advance(size_t len) { rpos = (rpos + len) % CAPACITY; }
};
static const char *channel_names[] = { "front left", "front right" };
enum {
VBOX_CHANNELS = 2,
VBOX_SAMPLE_SIZE = sizeof(int16_t),
OUT_PACKET_NUM = 16, /* number of buffered packets */
IN_PACKET_NUM = 2, /* number of buffered in packets */
OUT_PCM_SIZE = Audio_out::PERIOD * VBOX_SAMPLE_SIZE * VBOX_CHANNELS,
IN_PCM_SIZE = Audio_in::PERIOD * VBOX_SAMPLE_SIZE * VBOX_CHANNELS,
OUT_PCM_BUFFER_SIZE = OUT_PCM_SIZE * OUT_PACKET_NUM,
IN_PCM_BUFFER_SIZE = IN_PCM_SIZE * IN_PACKET_NUM,
OUT_PACKET_SIZE = Audio_out::PERIOD * VBOX_SAMPLE_SIZE * VBOX_CHANNELS,
IN_PACKET_SIZE = Audio_in::PERIOD * VBOX_SAMPLE_SIZE * VBOX_CHANNELS,
OUT_PACKET_BUFFER_SIZE = OUT_PACKET_SIZE * 2,
IN_PACKET_BUFFER_SIZE = IN_PACKET_SIZE * 2,
};
static char const * const channel_names[] = { "front left", "front right" };
typedef A_ring_buffer_to_bind_them<OUT_PCM_BUFFER_SIZE> Pcm_out_buffer;
typedef A_ring_buffer_to_bind_them<OUT_PACKET_BUFFER_SIZE> Out_packet_buffer;
typedef A_ring_buffer_to_bind_them<IN_PCM_BUFFER_SIZE> Pcm_in_buffer;
typedef A_ring_buffer_to_bind_them<IN_PACKET_BUFFER_SIZE> In_packet_buffer;
struct GenodeVoiceOut
{
HWVoiceOut hw;
Audio_out::Connection *audio[AUDIO_CHANNELS];
Audio_out::Packet *packet[AUDIO_CHANNELS];
uint8_t *packet_buf;
int wpos; /* in samples */
Audio_out::Connection *audio[VBOX_CHANNELS];
Out_packet_buffer packet_buf;
Pcm_out_buffer pcm_buf;
unsigned packets;
unsigned last_pos;
};
struct GenodeVoiceIn {
HWVoiceIn hw;
Audio_in::Connection *audio;
uint8_t *pcm_buf;
int pcm_rpos; /* in bytes */
uint8_t *packet_buf;
int avail;
int wpos; /* in samples */
int rpos; /* in samples */
In_packet_buffer packet_buf;
Pcm_in_buffer pcm_buf;
unsigned packets;
};
static inline int16_t *advance_buffer(void *buffer, int samples)
{
enum { CHANNELS = 2 };
return ((int16_t*)buffer) + (samples * CHANNELS);
}
static inline void copy_samples(void *dst, void const *src, int samples) {
Genode::memcpy(dst, src, samples * SIZE_PER_FRAME); }
static int write_samples(GenodeVoiceOut *out, int16_t *src, int samples)
{
int16_t *buf = (int16_t*)out->packet_buf;
Out_packet_buffer &packet_buf = out->packet_buf;
/* fill and submit packet */
if (out->wpos >= Audio_out::PERIOD) {
/* alloc new packet */
while (true) {
Audio_out::Connection *c = out->audio[0];
try {
out->packet[0] = c->stream()->alloc();
break;
} catch (Audio_out::Stream::Alloc_failed) {
c->wait_for_alloc();
}
}
/* try to fill and submit packet */
if (packet_buf.read_avail() >= OUT_PACKET_SIZE) {
Audio_out::Connection *c = out->audio[0];
unsigned const pos = out->audio[0]->stream()->packet_position(out->packet[0]);
out->packet[1] = out->audio[1]->stream()->get(pos);
/* check how many submitted packets are still in the queue */
if (c->stream()->queued() > OUT_PACKET_NUM) return 0;
/* alloc new packets */
Audio_out::Packet *p[2] { nullptr, nullptr };
try { p[0] = c->stream()->alloc(); }
catch (Audio_out::Stream::Alloc_failed) { return 0; }
unsigned const ppos = out->audio[0]->stream()->packet_position(p[0]);
p[1] = out->audio[1]->stream()->get(ppos);
/* copy */
float *left_content = out->packet[0]->content();
float *right_content = out->packet[1]->content();
float *left_content = p[0]->content();
float *right_content = p[1]->content();
int16_t buf[Audio_out::PERIOD*2];
size_t const n = packet_buf.read(buf, sizeof(buf));
if (n != sizeof(buf))
PERR("%s: n: %zu buf: %zu", __func__, n, buf);
for (int i = 0; i < Audio_out::PERIOD; i++) {
left_content[i] = (float)(buf[i * AUDIO_CHANNELS + 0]) / 32768.0f;
right_content[i] = (float)(buf[i * AUDIO_CHANNELS + 1]) / 32768.0f;
left_content[i] = (float)(buf[i * VBOX_CHANNELS + 0]) / 32768.0f;
right_content[i] = (float)(buf[i * VBOX_CHANNELS + 1]) / 32768.0f;
}
/* submit */
for (int i = 0; i < AUDIO_CHANNELS; i++) {
out->audio[i]->submit(out->packet[i]);
out->packet[i] = nullptr;
}
out->last_pos = pos;
for (int i = 0; i < VBOX_CHANNELS; i++)
out->audio[i]->submit(p[i]);
/* move remaining samples if any to front of buffer */
int const remaining_samples = out->wpos - Audio_out::PERIOD;
if (remaining_samples) {
copy_samples(buf, advance_buffer(buf, Audio_out::PERIOD), remaining_samples);
}
out->wpos = remaining_samples;
out->packets++;
}
/* copy new samples */
copy_samples(advance_buffer(buf, out->wpos), src, samples);
out->wpos += samples;
return samples;
int const bytes = samples * VBOX_SAMPLE_SIZE * VBOX_SAMPLE_SIZE;
size_t const n = packet_buf.write(src, bytes);
return n / (VBOX_SAMPLE_SIZE * VBOX_SAMPLE_SIZE);
}
static int genode_run_out(HWVoiceOut *hw)
{
GenodeVoiceOut *out = (GenodeVoiceOut *)hw;
GenodeVoiceOut * const out = (GenodeVoiceOut *)hw;
Pcm_out_buffer & pcm_buf = out->pcm_buf;
int const live = audio_pcm_hw_get_live_out(&out->hw);
if (!live)
return 0;
int const samples = audio_MIN(live, out->hw.samples);
int const decr = audio_MIN(live, out->hw.samples);
size_t const avail = pcm_buf.read_avail();
if ((avail / (VBOX_SAMPLE_SIZE*VBOX_CHANNELS)) < decr)
PERR("%s: avail: %zu < decr %d", __func__, avail, decr);
char buf[decr*VBOX_SAMPLE_SIZE*VBOX_CHANNELS];
pcm_buf.read(buf, sizeof(buf), true);
int const samples = write_samples(out, (int16_t*)buf, decr);
if (samples == 0) return 0;
pcm_buf.read_advance(samples * (VBOX_SAMPLE_SIZE*VBOX_CHANNELS));
out->hw.rpos = (out->hw.rpos + samples) % out->hw.samples;
return samples;
@ -153,34 +247,40 @@ static int genode_run_out(HWVoiceOut *hw)
static int genode_write(SWVoiceOut *sw, void *buf, int size)
{
GenodeVoiceOut *out = (GenodeVoiceOut*)sw->hw;
return write_samples(out, (int16_t*)buf, size / 4) * 4;
GenodeVoiceOut * const out = (GenodeVoiceOut*)sw->hw;
Pcm_out_buffer &pcm_buf = out->pcm_buf;
size_t const avail = pcm_buf.write_avail();
if (size > avail)
PWRN("%s: size: %d available: %zu", __func__, size, avail);
size_t const n = pcm_buf.write(buf, size);
if (n < size)
PWRN("%s: written: %zu expected: %d", __func__, n, size);
/* needed by audio_pcm_hw_get_live_out() to calculate ``live'' samples */
sw->total_hw_samples_mixed += (size / 4);
return size;
}
static int genode_init_out(HWVoiceOut *hw, audsettings_t *as)
{
GenodeVoiceOut *out = (GenodeVoiceOut *)hw;
GenodeVoiceOut * const out = (GenodeVoiceOut *)hw;
PLOG("--- using Audio_out session ---");
PLOGV("freq: %d", as->freq);
PLOGV("channels: %d", as->nchannels);
PLOGV("format: %d", as->fmt);
PLOGV("endianness: %d", as->endianness);
if (as->nchannels != AUDIO_CHANNELS) {
if (as->nchannels != VBOX_CHANNELS) {
PERR("only %d channels supported (%d were requested)",
AUDIO_CHANNELS, as->nchannels);
VBOX_CHANNELS, as->nchannels);
return -1;
}
if (as->freq != AUDIO_FREQ) {
PERR("only %d frequency supported (%d were requested)",
AUDIO_FREQ, as->freq);
if (as->freq != Audio_out::SAMPLE_RATE) {
PERR("only %d frequency supported (%d was requested)",
Audio_out::SAMPLE_RATE, as->freq);
return -1;
}
for (int i = 0; i < AUDIO_CHANNELS; i++) {
for (int i = 0; i < VBOX_CHANNELS; i++) {
try {
out->audio[i] = new (Genode::env()->heap())
Audio_out::Connection(channel_names[i]);
@ -190,19 +290,17 @@ static int genode_init_out(HWVoiceOut *hw, audsettings_t *as)
Genode::destroy(Genode::env()->heap(), out->audio[i]);
return -1;
}
out->packet[i] = nullptr;
}
out->wpos = 0;
out->packets = 0;
out->last_pos = 0;
audio_pcm_init_info(&out->hw.info, as);
out->hw.samples = Audio_out::PERIOD;
out->hw.samples = Audio_out::PERIOD;
out->packets = 0;
size_t const bytes = (out->hw.samples << out->hw.info.shift);
out->packet_buf = (uint8_t*)RTMemAllocZ(2 * bytes);
PLOG("--- using Audio_out session ---");
PLOGV("freq: %d", as->freq);
PLOGV("channels: %d", as->nchannels);
PLOGV("format: %d", as->fmt);
PLOGV("endianness: %d", as->endianness);
return 0;
}
@ -210,12 +308,9 @@ static int genode_init_out(HWVoiceOut *hw, audsettings_t *as)
static void genode_fini_out(HWVoiceOut *hw)
{
GenodeVoiceOut *out = (GenodeVoiceOut *)hw;
for (int i = 0; i < AUDIO_CHANNELS; i++)
GenodeVoiceOut * const out = (GenodeVoiceOut *)hw;
for (int i = 0; i < VBOX_CHANNELS; i++)
Genode::destroy(Genode::env()->heap(), out->audio[i]);
RTMemFree((void*)out->packet_buf);
}
@ -224,32 +319,19 @@ static int genode_ctl_out(HWVoiceOut *hw, int cmd, ...)
GenodeVoiceOut *out = (GenodeVoiceOut*)hw;
switch (cmd) {
case VOICE_ENABLE:
{
PLOGV("enable playback");
out->wpos = 0;
out->packets = 0;
for (int i = 0; i < AUDIO_CHANNELS; i++) {
out->audio[i]->stream()->reset();
PLOGV("enable playback");
out->packets = 0;
for (int i = 0; i < VBOX_CHANNELS; i++)
out->audio[i]->start();
}
break;
}
break;
case VOICE_DISABLE:
{
PLOGV("disable playback (packets played: %u)", out->packets);
for (int i = 0; i < AUDIO_CHANNELS; i++) {
out->audio[i]->stop();
out->audio[i]->stream()->invalidate_all();
out->packet[i] = nullptr;
}
break;
PLOGV("disable playback (packets: %u)", out->packets);
for (int i = 0; i < VBOX_CHANNELS; i++) {
out->audio[i]->stop();
out->audio[i]->stream()->invalidate_all();
}
break;
}
return 0;
}
@ -262,12 +344,6 @@ static int genode_init_in(HWVoiceIn *hw, audsettings_t *as)
{
GenodeVoiceIn *in = (GenodeVoiceIn*)hw;
PLOG("--- using Audio_in session ---");
PLOGV("freq: %d", as->freq);
PLOGV("channels: %d", as->nchannels);
PLOGV("format: %d", as->fmt);
PLOGV("endianness: %d", as->endianness);
try {
in->audio = new (Genode::env()->heap()) Audio_in::Connection("left");
} catch (...) {
@ -277,54 +353,47 @@ static int genode_init_in(HWVoiceIn *hw, audsettings_t *as)
audio_pcm_init_info(&in->hw.info, as);
in->hw.samples = Audio_in::PERIOD;
in->pcm_buf = (uint8_t*)RTMemAllocZ(in->hw.samples << in->hw.info.shift);
in->pcm_rpos = 0;
in->packet_buf = (uint8_t*)RTMemAllocZ(RECORD_BUFFER_SAMPLES << in->hw.info.shift);
in->avail = 0;
in->rpos = 0;
in->wpos = 0;
in->packets = 0;
PLOG("--- using Audio_in session ---");
PLOGV("freq: %d", as->freq);
PLOGV("channels: %d", as->nchannels);
PLOGV("format: %d", as->fmt);
PLOGV("endianness: %d", as->endianness);
return 0;
}
static void genode_fini_in(HWVoiceIn *hw)
{
GenodeVoiceIn *in = (GenodeVoiceIn*)hw;
GenodeVoiceIn * const in = (GenodeVoiceIn*)hw;
Genode::destroy(Genode::env()->heap(), in->audio);
RTMemFree(in->pcm_buf);
RTMemFree(in->packet_buf);
}
static int read_samples(GenodeVoiceIn *in, int16_t *dst, int samples)
static int read_samples(GenodeVoiceIn *in, int samples)
{
int remaining_samples = samples;
In_packet_buffer &packet_buf = in->packet_buf;
Pcm_in_buffer &pcm_buf = in->pcm_buf;
/*
* If there are fewer samples samples available acquire next Audio_in
* packet and copy the content into the packet buffer. If that is not
* possible, check if there are at least enough samples left to satisfy
* the request.
*/
while (in->avail < Audio_in::PERIOD) {
while (packet_buf.read_avail() < IN_PACKET_SIZE) {
Audio_in::Stream &stream = *in->audio->stream();
Audio_in::Packet *p = stream.get(stream.pos());
if (!p->valid()) {
if (in->avail < samples)
if (packet_buf.read_avail() < (samples*VBOX_SAMPLE_SIZE*VBOX_CHANNELS)) {
return 0;
}
break;
}
in->packets++;
int16_t buf[Audio_in::PERIOD*VBOX_CHANNELS];
int16_t *buf = advance_buffer(in->packet_buf, in->wpos);
float *content = p->content();
if (packet_buf.write_avail() < sizeof(buf))
return 0;
float *content = p->content();
for (int i = 0; i < Audio_in::PERIOD; i++) {
int16_t const v = content[i] * 32767;
int const j = i * 2;
@ -332,43 +401,27 @@ static int read_samples(GenodeVoiceIn *in, int16_t *dst, int samples)
buf[j + 1] = v;
}
size_t const w = packet_buf.write(buf, sizeof(buf));
if (w != sizeof(buf))
PERR("%s: write n: %zu buf: %zu", __func__, w, sizeof(buf));
p->invalidate();
p->mark_as_recorded();
stream.increment_position();
in->wpos = (in->wpos + Audio_in::PERIOD) % RECORD_BUFFER_SAMPLES;
in->avail += Audio_in::PERIOD;
in->packets++;
break;
}
/*
* Get number of samples from the packet buffer, wrap the packet
* buffer if necessary
*/
{
if ((in->rpos + remaining_samples) > RECORD_BUFFER_SAMPLES) {
int n = RECORD_BUFFER_SAMPLES - in->rpos;
int16_t const *src = advance_buffer(in->packet_buf, in->rpos);
int16_t buf[samples*VBOX_CHANNELS];
size_t const r = packet_buf.read(buf, sizeof(buf), true);
size_t const w = pcm_buf.write(buf, r);
if (w != r)
PERR("%s: w: %zu != r: %zu", __func__, w, r);
copy_samples(dst, src, n);
in->rpos = 0;
in->avail -= n;
packet_buf.read_advance(w);
remaining_samples -= n;
dst = advance_buffer(dst, n);
}
int16_t const *src = advance_buffer(in->packet_buf, in->rpos);
copy_samples(dst, src, remaining_samples);
in->rpos = (in->rpos + remaining_samples) % RECORD_BUFFER_SAMPLES;
in->avail -= remaining_samples;
/* there are no remaining samples because all samples were copied */
remaining_samples = 0;
}
return samples - remaining_samples;
return w / (VBOX_SAMPLE_SIZE*VBOX_CHANNELS);
}
@ -381,7 +434,7 @@ static int genode_run_in(HWVoiceIn *hw)
return 0;
int const dead = in->hw.samples - live;
int const samples = read_samples(in, (int16_t*)in->pcm_buf, dead);
int const samples = read_samples(in, dead);
in->hw.wpos = (in->hw.wpos + samples) % in->hw.samples;
return samples;
@ -390,58 +443,43 @@ static int genode_run_in(HWVoiceIn *hw)
static int genode_read(SWVoiceIn *sw, void *buf, int size)
{
GenodeVoiceIn *in = (GenodeVoiceIn*)sw->hw;
GenodeVoiceIn * const in = (GenodeVoiceIn*)sw->hw;
Pcm_in_buffer &pcm_buf = in->pcm_buf;
void const *src = in->pcm_buf + in->pcm_rpos;
Genode::memcpy(buf, src, size);
in->pcm_rpos += size;
size_t const avail = pcm_buf.read_avail();
if (avail < size)
PERR("%s: avail: %zu size: %zu", __func__, avail, size);
size_t const r = pcm_buf.read(buf, size);
if (r != size)
PERR("%s: r: %zu size: %d", __func__, r, size);
/* needed by audio_pcm_hw_get_live_in() to calculate ``live'' samples */
sw->total_hw_samples_acquired += (size / 4);
/*
* This assumption totally blows if the guest uses other
* parameters to configure the device model.
*/
enum { PCM_BUFFER_SIZE = 448 /* samples */ * 2 /* ch */ * 2 /* int16_t */ };
if (in->pcm_rpos >= PCM_BUFFER_SIZE) {
in->pcm_rpos = 0;
}
sw->total_hw_samples_acquired += (r / (VBOX_SAMPLE_SIZE*VBOX_CHANNELS));
return size;
}
static int genode_ctl_in(HWVoiceIn *hw, int cmd, ...)
{
GenodeVoiceIn *in = (GenodeVoiceIn*)hw;
GenodeVoiceIn * const in = (GenodeVoiceIn*)hw;
switch (cmd) {
case VOICE_ENABLE:
{
PLOGV("enable recording");
in->wpos = 0;
in->rpos = 0;
in->avail = 0;
in->pcm_rpos = 0;
in->packets = 0;
in->audio->start();
break;
}
PLOGV("enable recording");
in->packets = 0;
in->audio->start();
break;
case VOICE_DISABLE:
{
PLOGV("disable recording (packets recorded: %u)", in->packets);
in->audio->stop();
break;
}
PLOGV("disable recording (packets: %u)", in->packets);
in->audio->stop();
break;
}
return 0;
}
static void *genode_audio_init(void) {
return &oss_audio_driver; }
static void *genode_audio_init(void) { return &oss_audio_driver; }
static void genode_audio_fini(void *) { }