genode/repos/ports/src/virtualbox/audiodrv.cpp

521 lines
12 KiB
C++

/*
* \brief Genode audio driver backend
* \author Josef Soentgen
* \date 2015-05-17
*/
/*
* Copyright (C) 2015 Genode Labs GmbH
*
* This file is distributed under the terms of the GNU General Public License
* version 2.
*/
/* Genode includes */
#include <audio_in_session/connection.h>
#include <audio_out_session/connection.h>
#include <terminal_session/connection.h>
#include <timer_session/connection.h>
/* VirtualBox includes */
#include "VBoxDD.h"
#include "vl_vbox.h"
extern "C" {
#include "audio.h"
}
#include <iprt/alloc.h>
#define AUDIO_CAP "genode"
extern "C" {
#include "audio_int.h"
}
static bool const verbose = false;
#define PLOGV(...) if (verbose) PLOG(__VA_ARGS__);
template <size_t CAPACITY>
struct A_ring_buffer_to_bind_them
{
size_t wpos { 0 };
size_t rpos { 0 };
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; }
};
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[VBOX_CHANNELS];
Out_packet_buffer packet_buf;
Pcm_out_buffer pcm_buf;
unsigned packets;
};
struct GenodeVoiceIn {
HWVoiceIn hw;
Audio_in::Connection *audio;
In_packet_buffer packet_buf;
Pcm_in_buffer pcm_buf;
unsigned packets;
};
static int write_samples(GenodeVoiceOut *out, int16_t *src, int samples)
{
Out_packet_buffer &packet_buf = out->packet_buf;
/* try to fill and submit packet */
if (packet_buf.read_avail() >= OUT_PACKET_SIZE) {
Audio_out::Connection *c = out->audio[0];
/* 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 = 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 * VBOX_CHANNELS + 0]) / 32768.0f;
right_content[i] = (float)(buf[i * VBOX_CHANNELS + 1]) / 32768.0f;
}
/* submit */
for (int i = 0; i < VBOX_CHANNELS; i++)
out->audio[i]->submit(p[i]);
out->packets++;
}
/* copy new 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 * 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 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;
}
static int genode_write(SWVoiceOut *sw, void *buf, int size)
{
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 * const out = (GenodeVoiceOut *)hw;
if (as->nchannels != VBOX_CHANNELS) {
PERR("only %d channels supported (%d were requested)",
VBOX_CHANNELS, as->nchannels);
return -1;
}
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 < VBOX_CHANNELS; i++) {
try {
out->audio[i] = new (Genode::env()->heap())
Audio_out::Connection(channel_names[i]);
} catch (...) {
PERR("could not establish Audio_out connection");
while (--i > 0)
Genode::destroy(Genode::env()->heap(), out->audio[i]);
return -1;
}
}
audio_pcm_init_info(&out->hw.info, as);
out->hw.samples = Audio_out::PERIOD;
out->packets = 0;
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;
}
static void genode_fini_out(HWVoiceOut *hw)
{
GenodeVoiceOut * const out = (GenodeVoiceOut *)hw;
for (int i = 0; i < VBOX_CHANNELS; i++)
Genode::destroy(Genode::env()->heap(), out->audio[i]);
}
static int genode_ctl_out(HWVoiceOut *hw, int cmd, ...)
{
GenodeVoiceOut *out = (GenodeVoiceOut*)hw;
switch (cmd) {
case VOICE_ENABLE:
PLOGV("enable playback");
out->packets = 0;
for (int i = 0; i < VBOX_CHANNELS; i++)
out->audio[i]->start();
break;
case VOICE_DISABLE:
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;
}
/***************
** Recording **
***************/
static int genode_init_in(HWVoiceIn *hw, audsettings_t *as)
{
GenodeVoiceIn *in = (GenodeVoiceIn*)hw;
try {
in->audio = new (Genode::env()->heap()) Audio_in::Connection("left");
} catch (...) {
PERR("could not establish Audio_in connection");
return -1;
}
audio_pcm_init_info(&in->hw.info, as);
in->hw.samples = Audio_in::PERIOD;
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 * const in = (GenodeVoiceIn*)hw;
Genode::destroy(Genode::env()->heap(), in->audio);
}
static int read_samples(GenodeVoiceIn *in, int samples)
{
In_packet_buffer &packet_buf = in->packet_buf;
Pcm_in_buffer &pcm_buf = in->pcm_buf;
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 (packet_buf.read_avail() < (samples*VBOX_SAMPLE_SIZE*VBOX_CHANNELS)) {
return 0;
}
break;
}
int16_t buf[Audio_in::PERIOD*VBOX_CHANNELS];
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;
buf[j + 0] = v;
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->packets++;
break;
}
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);
packet_buf.read_advance(w);
return w / (VBOX_SAMPLE_SIZE*VBOX_CHANNELS);
}
static int genode_run_in(HWVoiceIn *hw)
{
GenodeVoiceIn *in = (GenodeVoiceIn*)hw;
int const live = audio_pcm_hw_get_live_in(&in->hw);
if (!(in->hw.samples - live))
return 0;
int const dead = in->hw.samples - live;
int const samples = read_samples(in, dead);
in->hw.wpos = (in->hw.wpos + samples) % in->hw.samples;
return samples;
}
static int genode_read(SWVoiceIn *sw, void *buf, int size)
{
GenodeVoiceIn * const in = (GenodeVoiceIn*)sw->hw;
Pcm_in_buffer &pcm_buf = in->pcm_buf;
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 += (r / (VBOX_SAMPLE_SIZE*VBOX_CHANNELS));
return size;
}
static int genode_ctl_in(HWVoiceIn *hw, int cmd, ...)
{
GenodeVoiceIn * const in = (GenodeVoiceIn*)hw;
switch (cmd) {
case VOICE_ENABLE:
PLOGV("enable recording");
in->packets = 0;
in->audio->start();
break;
case VOICE_DISABLE:
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_fini(void *) { }
static struct audio_pcm_ops genode_pcm_ops = {
genode_init_out,
genode_fini_out,
genode_run_out,
genode_write,
genode_ctl_out,
genode_init_in,
genode_fini_in,
genode_run_in,
genode_read,
genode_ctl_in
};
/*
* We claim to be the OSS driver so that we do not have to
* patch the VirtualBox source because we already claim to
* be FreeBSD.
*/
struct audio_driver oss_audio_driver = {
INIT_FIELD (name = ) "oss",
INIT_FIELD (descr = ) "Genode Audio_out/Audio_in",
INIT_FIELD (options = ) NULL,
INIT_FIELD (init = ) genode_audio_init,
INIT_FIELD (fini = ) genode_audio_fini,
INIT_FIELD (pcm_ops = ) &genode_pcm_ops,
INIT_FIELD (can_be_default = ) 1,
INIT_FIELD (max_voices_out = ) INT_MAX,
INIT_FIELD (max_voices_in = ) INT_MAX,
INIT_FIELD (voice_size_out = ) sizeof(GenodeVoiceOut),
INIT_FIELD (voice_size_in = ) sizeof(GenodeVoiceIn)
};