dde_bsd: add recording support to audio driver

The driver is now able to record audio samples. In contrast
to playback it has to be enabled explicitly by setting the
configuration attribute 'recording' to 'yes'. Playback is by
default enabled but may be disabled by setting 'playback' to
'no'. Furthermore it is now possible to configure the mixer
from the configuration. For now, the interface used by vanilla
OpenBSD is just exported.

The following snippet shows how to enable and configure recording
on an Thinkpad X220 where the headset rather than the internal
mic is used as recording source:

! <start name="audio_out_drv">
!   <resource name="RAM" quantum="8M"/>
!   <provides>
!     <service name="Audio_out"/>
!     <service name="Audio_in"/>
!   </provides>
!   <config recording="yes">
!     <mixer field="outputs.master" value="255"/>
!     <mixer field="record.adc-0:1_source" value="sel2"/>
!     <mixer field="record.adc-0:1" value="255"/>
!   </config>
! </start>

In addition to selecting the recording source the playback as
well as the recording volume are set to 255 (maximum).
Information about the available mixers and settings in general
may be obtained by setting the 'verbose' to 'yes' in the config
node.

Issue #1644.
This commit is contained in:
Josef Söntgen 2015-05-15 22:22:46 +02:00 committed by Christian Helmuth
parent c4e2322a5d
commit 8a34d21577
9 changed files with 740 additions and 113 deletions

View File

@ -24,17 +24,31 @@
** private Audio namespace **
*****************************/
namespace Audio {
namespace Audio_out {
enum Channel_number { LEFT, RIGHT, MAX_CHANNELS, INVALID = MAX_CHANNELS };
}
namespace Audio_in {
enum Channel_number { LEFT, MAX_CHANNELS, INVALID = MAX_CHANNELS };
}
namespace Audio {
void init_driver(Server::Entrypoint &ep);
bool driver_active();
void dma_notifier(Genode::Signal_context_capability cap);
void play_sigh(Genode::Signal_context_capability cap);
void record_sigh(Genode::Signal_context_capability cap);
int play(short *data, Genode::size_t size);
int record(short *data, Genode::size_t size);
}
#endif /* _AUDIO__AUDIO_H_ */

View File

@ -0,0 +1,20 @@
--- a/dev/audio.c
+++ b/dev/audio.c
@@ -2293,6 +2293,8 @@
sc->sc_pqui = 1;
audio_wake(&sc->sc_wchan);
}
+
+ notify_play();
}
/*
@@ -2400,6 +2402,8 @@
sc->sc_rqui = 1;
audio_wake(&sc->sc_rchan);
}
+
+ notify_record();
}
int

View File

@ -1 +1 @@
8a8864f346418a483832c0fa28419f35fa3d5b78
c560befb7c9f80152fcd720a2cef4272eded0e0f

View File

@ -21,5 +21,6 @@ PATCHES := $(addprefix patches/,$(notdir $(wildcard $(REP_DIR)/patches/*.patch))
AUDIO_OPT := -p1 -d$(SRC_DIR_AUDIO)
PATCH_OPT(patches/oppress_warning.patch) := $(AUDIO_OPT)
PATCH_OPT(patches/azalia_c.patch) := $(AUDIO_OPT)
PATCH_OPT(patches/notify.patch) := $(AUDIO_OPT)
# vi: set ft=make :

View File

@ -4,22 +4,39 @@
* \date 2014-11-09
*/
/*
* Copyright (C) 2014-2015 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
/* Genode includes */
#include <audio_in_session/rpc_object.h>
#include <audio_out_session/rpc_object.h>
#include <base/env.h>
#include <base/sleep.h>
#include <cap_session/connection.h>
#include <os/config.h>
#include <os/server.h>
#include <root/component.h>
#include <util/misc_math.h>
#include <trace/timestamp.h>
/* local includes */
#include <audio/audio.h>
static bool const verbose = false;
using namespace Genode;
using namespace Audio;
/**************
** Playback **
**************/
namespace Audio_out {
class Session_component;
class Out;
@ -33,7 +50,7 @@ class Audio_out::Session_component : public Audio_out::Session_rpc_object
{
private:
Channel_number _channel;
Channel_number _channel;
public:
@ -50,13 +67,14 @@ class Audio_out::Session_component : public Audio_out::Session_rpc_object
}
};
class Audio_out::Out
{
private:
Server::Entrypoint &_ep;
Genode::Signal_rpc_member<Audio_out::Out> _data_avail_dispatcher;
Genode::Signal_rpc_member<Audio_out::Out> _dma_notify_dispatcher;
Genode::Signal_rpc_member<Audio_out::Out> _notify_dispatcher;
bool _active() {
return channel_acquired[LEFT] && channel_acquired[RIGHT] &&
@ -92,22 +110,26 @@ class Audio_out::Out
void _play_silence()
{
static short silence[2 * Audio_out::PERIOD] = { 0 };
static short silence[Audio_out::PERIOD * Audio_out::MAX_CHANNELS] = { 0 };
if (int err = Audio::play(silence, sizeof(silence)))
int err = Audio::play(silence, sizeof(silence));
if (err && err != 35)
PWRN("Error %d during silence playback", err);
}
void _play_packet()
{
Packet *p_left = left()->get(left()->pos());
Packet *p_right = right()->get(right()->pos());
unsigned lpos = left()->pos();
unsigned rpos = right()->pos();
if ((p_left->valid() && p_right->valid())) {
Packet *p_left = left()->get(lpos);
Packet *p_right = right()->get(rpos);
if (p_left->valid() && p_right->valid()) {
/* convert float to S16LE */
static short data[2 * Audio_out::PERIOD];
static short data[Audio_out::PERIOD * Audio_out::MAX_CHANNELS];
for (int i = 0; i < 2 * Audio_out::PERIOD; i += 2) {
for (int i = 0; i < Audio_out::PERIOD * Audio_out::MAX_CHANNELS; i += 2) {
data[i] = p_left->content()[i / 2] * 32767;
data[i + 1] = p_right->content()[i / 2] * 32767;
}
@ -115,8 +137,9 @@ class Audio_out::Out
/* send to driver */
if (int err = Audio::play(data, sizeof(data)))
PWRN("Error %d during playback", err);
} else
} else {
_play_silence();
}
p_left->invalidate();
p_right->invalidate();
@ -138,18 +161,11 @@ class Audio_out::Out
/*
* DMA block played
*
* After each block played from the DMA buffer, the hw will
* generated an interrupt. The IRQ handling code will notify
* us as soon as this happens and we will play the next
* packet.
*/
void _handle_dma_notify(unsigned)
void _handle_notify(unsigned)
{
if (!_active())
return;
_play_packet();
if (_active())
_play_packet();
}
public:
@ -158,7 +174,7 @@ class Audio_out::Out
:
_ep(ep),
_data_avail_dispatcher(ep, *this, &Audio_out::Out::_handle_data_avail),
_dma_notify_dispatcher(ep, *this, &Audio_out::Out::_handle_dma_notify)
_notify_dispatcher(ep, *this, &Audio_out::Out::_handle_notify)
{
/* play a silence packet to get the driver running */
_play_silence();
@ -166,32 +182,29 @@ class Audio_out::Out
Signal_context_capability data_avail() { return _data_avail_dispatcher; }
Signal_context_capability dma_notifier() { return _dma_notify_dispatcher; }
Signal_context_capability sigh() { return _notify_dispatcher; }
const char *debug() { return "Audio out"; }
};
static bool channel_number(const char *name,
Channel_number *out_number)
{
static struct Names {
const char *name;
Channel_number number;
} names[] = {
{ "left", LEFT }, { "front left", LEFT },
{ "right", RIGHT }, { "front right", RIGHT },
{ 0, INVALID }
};
for (Names *n = names; n->name; ++n)
if (!Genode::strcmp(name, n->name)) {
*out_number = n->number;
return true;
}
static bool channel_number_from_string(const char *name,
Channel_number *out_number)
{
static struct Names {
const char *name;
Channel_number number;
} names[] = {
{ "left", LEFT }, { "front left", LEFT },
{ "right", RIGHT }, { "front right", RIGHT },
{ 0, INVALID }
};
for (Names *n = names; n->name; ++n)
if (!Genode::strcmp(name, n->name)) {
*out_number = n->number;
return true;
return false;
}
return false;
}
};
/**
@ -218,7 +231,7 @@ struct Audio_out::Root_policy
Arg_string::find_arg(args, "channel").string(channel_name,
sizeof(channel_name),
"left");
if (!channel_number_from_string(channel_name, &channel_number))
if (!Out::channel_number(channel_name, &channel_number))
throw ::Root::Invalid_args();
if (Audio_out::channel_acquired[channel_number])
throw ::Root::Unavailable();
@ -253,7 +266,7 @@ class Audio_out::Root : public Audio_out::Root_component
Arg_string::find_arg(args, "channel").string(channel_name,
sizeof(channel_name),
"left");
channel_number_from_string(channel_name, &channel_number);
Out::channel_number(channel_name, &channel_number);
return new (md_alloc())
Session_component(channel_number, _cap);
@ -270,6 +283,209 @@ class Audio_out::Root : public Audio_out::Root_component
};
/***************
** Recording **
***************/
namespace Audio_in {
class Session_component;
class In;
class Root;
struct Root_policy;
static Session_component *channel_acquired;
}
class Audio_in::Session_component : public Audio_in::Session_rpc_object
{
private:
Channel_number _channel;
public:
Session_component(Channel_number channel,
Genode::Signal_context_capability cap)
: Session_rpc_object(cap), _channel(channel) {
channel_acquired = this; }
~Session_component() { channel_acquired = nullptr; }
};
class Audio_in::In
{
private:
Server::Entrypoint &_ep;
Genode::Signal_rpc_member<Audio_in::In> _notify_dispatcher;
bool _active() { return channel_acquired && channel_acquired->active(); }
Stream *stream() { return channel_acquired->stream(); }
void _record_packet()
{
static short data[2 * Audio_in::PERIOD];
if (int err = Audio::record(data, sizeof(data))) {
if (err && err != 35)
PWRN("Error %d during recording", err);
return;
}
/*
* Check for an overrun first and notify the client later.
*/
bool overrun = stream()->overrun();
Packet *p = stream()->alloc();
float const scale = 32768.0f * 2;
float * const content = p->content();
for (int i = 0; i < 2*Audio_in::PERIOD; i += 2) {
float sample = data[i] + data[i+1];
content[i/2] = sample / scale;
}
stream()->submit(p);
channel_acquired->progress_submit();
if (overrun) channel_acquired->overrun_submit();
}
void _handle_notify(unsigned)
{
if (_active())
_record_packet();
}
public:
In(Server::Entrypoint &ep)
:
_ep(ep),
_notify_dispatcher(ep, *this, &Audio_in::In::_handle_notify)
{ _record_packet(); }
Signal_context_capability sigh() { return _notify_dispatcher; }
static bool channel_number(const char *name,
Channel_number *out_number)
{
static struct Names {
const char *name;
Channel_number number;
} names[] = {
{ "left", LEFT },
{ 0, INVALID }
};
for (Names *n = names; n->name; ++n)
if (!Genode::strcmp(name, n->name)) {
*out_number = n->number;
return true;
}
return false;
}
};
struct Audio_in::Root_policy
{
void aquire(char const *args)
{
size_t ram_quota = Arg_string::find_arg(args, "ram_quota").ulong_value(0);
size_t session_size = align_addr(sizeof(Audio_in::Session_component), 12);
if ((ram_quota < session_size) ||
(sizeof(Stream) > (ram_quota - session_size))) {
PERR("insufficient 'ram_quota', got %zu, need %zu",
ram_quota, sizeof(Stream) + session_size);
throw Genode::Root::Quota_exceeded();
}
char channel_name[16];
Channel_number channel_number;
Arg_string::find_arg(args, "channel").string(channel_name,
sizeof(channel_name),
"left");
if (!In::channel_number(channel_name, &channel_number))
throw ::Root::Invalid_args();
if (Audio_in::channel_acquired)
throw Genode::Root::Unavailable();
}
void release() { }
};
namespace Audio_in {
typedef Root_component<Session_component, Root_policy> Root_component;
}
/**
* Root component, handling new session requests.
*/
class Audio_in::Root : public Audio_in::Root_component
{
private:
Server::Entrypoint &_ep;
Signal_context_capability _cap;
protected:
Session_component *_create_session(char const *args)
{
char channel_name[16];
Channel_number channel_number = INVALID;
Arg_string::find_arg(args, "channel").string(channel_name,
sizeof(channel_name),
"left");
In::channel_number(channel_name, &channel_number);
return new (md_alloc()) Session_component(channel_number, _cap);
}
public:
Root(Server::Entrypoint &ep, Allocator &md_alloc,
Signal_context_capability cap)
: Root_component(&ep.rpc_ep(), &md_alloc), _ep(ep), _cap(cap) { }
};
/**********
** Main **
**********/
static bool disable_playback()
{
using namespace Genode;
try {
return config()->xml_node().attribute("playback").has_value("no");
} catch (...) { }
return false;
}
static bool enable_recording()
{
using namespace Genode;
try {
return config()->xml_node().attribute("recording").has_value("yes");
} catch (...) { }
return false;
}
struct Main
{
Server::Entrypoint &ep;
@ -279,19 +495,36 @@ struct Main
Audio::init_driver(ep);
if (Audio::driver_active()) {
static Audio_out::Out out(ep);
Audio::dma_notifier(out.dma_notifier());
static Audio_out::Root audio_root(ep, *env()->heap(), out.data_avail());
PINF("--- BSD Audio_out driver started ---");
env()->parent()->announce(ep.manage(audio_root));
/* playback */
if (!disable_playback()) {
static Audio_out::Out out(ep);
Audio::play_sigh(out.sigh());
static Audio_out::Root out_root(ep, *env()->heap(), out.data_avail());
env()->parent()->announce(ep.manage(out_root));
PINF("--- BSD Audio driver enable playback ---");
}
/* recording */
if (enable_recording()) {
static Audio_in::In in(ep);
Audio::record_sigh(in.sigh());
static Audio_in::Root in_root(ep, *env()->heap(),
Genode::Signal_context_capability());
env()->parent()->announce(ep.manage(in_root));
PINF("--- BSD Audio driver enable recording ---");
}
}
}
};
/************
** Server **
************/
namespace Server {
char const *name() { return "audio_drv_ep"; }
size_t stack_size() { return 4*1024*sizeof(long); }
size_t stack_size() { return 8*1024*sizeof(long); }
void construct(Entrypoint &ep) { static Main server(ep); }
}

View File

@ -12,9 +12,11 @@
*/
/* Genode includes */
#include <base/signal.h> /* FIXME needed by audio_out_session.h */
#include <audio_out_session/audio_out_session.h>
#include <audio_in_session/audio_in_session.h>
#include <os/config.h>
#include <os/server.h>
#include <util/xml_node.h>
/* local includes */
#include <audio/audio.h>
@ -27,59 +29,305 @@
#include <extern_c_end.h>
static bool const verbose = false;
static bool verbose = false;
extern struct cfdriver audio_cd;
static dev_t const adev = 0x80; /* audio0 128 */
static dev_t const mdev = 0x10; /* mixer0 16 */
static dev_t const adev = 0x80; /* audio0 (minor nr 128) */
static dev_t const mdev = 0x10; /* mixer0 (minor nr 16) */
static bool adev_usuable = false;
static bool drv_loaded() { return audio_cd.cd_ndevs > 0 ? true : false; }
static bool drv_loaded()
{
/*
* The condition is true when at least one PCI device where a
* driver is available for was found by the autoconf mechanisum
* (see bsd_emul.c).
*/
return audio_cd.cd_ndevs > 0 ? true : false;
}
static void dump_prinfo(struct audio_prinfo *prinfo)
/******************************
** Dump audio configuration **
******************************/
#define DUMP_INFO(field) \
PLOG("--- " #field " information ---"); \
PLOG("sample_rate: %u", ai.field.sample_rate); \
PLOG("channels: %u", ai.field.channels); \
PLOG("precision: %u", ai.field.precision); \
PLOG("bps: %u", ai.field.bps); \
PLOG("encoding: %u", ai.field.encoding); \
PLOG("gain: %u", ai.field.gain); \
PLOG("port: %u", ai.field.port); \
PLOG("seek: %u", ai.field.seek); \
PLOG("avail_ports: %u", ai.field.avail_ports); \
PLOG("buffer_size: %u", ai.field.buffer_size); \
PLOG("block_size: %u", ai.field.block_size); \
PLOG("samples: %u", ai.field.samples); \
PLOG("eof: %u", ai.field.eof); \
PLOG("pause: %u", ai.field.pause); \
PLOG("error: %u", ai.field.error); \
PLOG("waiting: %u", ai.field.waiting); \
PLOG("balance: %u", ai.field.balance); \
PLOG("open: %u", ai.field.open); \
PLOG("active: %u", ai.field.active)
static void dump_pinfo()
{
struct audio_info ai;
int err = audioioctl(adev, AUDIO_GETINFO, (char*)&ai, 0, 0);
if (err) {
if (audioioctl(adev, AUDIO_GETINFO, (char*)&ai, 0, 0)) {
PERR("could not gather play information");
return;
}
PLOG("--- play information ---");
PLOG("sample_rate: %u", prinfo->sample_rate);
PLOG("channels: %u", prinfo->channels);
PLOG("precision: %u", prinfo->precision);
PLOG("bps: %u", prinfo->bps);
PLOG("encoding: %u", prinfo->encoding);
PLOG("gain: %u", prinfo->gain);
PLOG("port: %u", prinfo->port);
PLOG("seek: %u", prinfo->seek);
PLOG("avail_ports: %u", prinfo->avail_ports);
PLOG("buffer_size: %u", prinfo->buffer_size);
PLOG("block_size: %u", prinfo->block_size);
/* current state of the device */
PLOG("samples: %u", prinfo->samples);
PLOG("eof: %u", prinfo->eof);
PLOG("pause: %u", prinfo->pause);
PLOG("error: %u", prinfo->error);
PLOG("waiting: %u", prinfo->waiting);
PLOG("balance: %u", prinfo->balance);
PLOG("open: %u", prinfo->open);
PLOG("active: %u", prinfo->active);
DUMP_INFO(play);
}
static void dump_rinfo()
{
struct audio_info ai;
if (audioioctl(adev, AUDIO_GETINFO, (char*)&ai, 0, 0)) {
PERR("could not gather play information");
return;
}
DUMP_INFO(record);
}
/*************************
** Mixer configuration **
*************************/
struct Mixer
{
mixer_devinfo_t *info;
unsigned num;
};
static Mixer mixer;
static unsigned count_mixer()
{
mixer_devinfo_t info;
for (int i = 0; ; i++) {
info.index = i;
if (audioioctl(mdev, AUDIO_MIXER_DEVINFO, (char*)&info, 0, 0))
return i;
}
return 0;
}
static mixer_devinfo_t *alloc_mixer(unsigned num)
{
return (mixer_devinfo_t*)
mallocarray(num, sizeof(mixer_devinfo_t), 0, M_ZERO);
}
static bool query_mixer(Mixer &mixer)
{
for (unsigned i = 0; i < mixer.num; i++) {
mixer.info[i].index = i;
if (audioioctl(mdev, AUDIO_MIXER_DEVINFO, (char*)&mixer.info[i], 0, 0))
return false;
}
return true;
}
static inline int level(char const *value)
{
unsigned long res = 0;
Genode::ascii_to(value, res);
return res > AUDIO_MAX_GAIN ? AUDIO_MAX_GAIN : res;
}
static bool set_mixer_value(Mixer &mixer, char const * const field,
char const * const value)
{
char buffer[64];
for (unsigned i = 0; i < mixer.num; i++) {
mixer_devinfo_t &info = mixer.info[i];
if (info.type == AUDIO_MIXER_CLASS)
continue;
unsigned mixer_class = info.mixer_class;
char const * const class_name = mixer.info[mixer_class].label.name;
char const * const name = info.label.name;
Genode::snprintf(buffer, sizeof(buffer), "%s.%s", class_name, name);
if (Genode::strcmp(field, buffer) != 0)
continue;
mixer_ctrl_t ctrl;
ctrl.dev = info.index;
ctrl.type = info.type;
ctrl.un.value.num_channels = 2;
if (audioioctl(mdev, AUDIO_MIXER_READ, (char*)&ctrl, 0, 0)) {
ctrl.un.value.num_channels = 1;
if (audioioctl(mdev, AUDIO_MIXER_READ, (char*)&ctrl, 0, 0)) {
PERR("could not read mixer %d'", ctrl.dev);
return 0;
}
}
int oldv = -1;
int newv = 0;
switch (ctrl.type) {
case AUDIO_MIXER_ENUM:
{
for (int i = 0; i < info.un.e.num_mem; i++)
if (Genode::strcmp(value, info.un.e.member[i].label.name) == 0) {
oldv = ctrl.un.ord;
newv = info.un.e.member[i].ord;
ctrl.un.ord = newv;
break;
}
break;
}
case AUDIO_MIXER_SET:
{
for (int i = 0; i < info.un.s.num_mem; i++) {
if (Genode::strcmp(value, info.un.e.member[i].label.name) == 0) {
oldv= ctrl.un.mask;
newv |= info.un.s.member[i].mask;
ctrl.un.mask = newv;
break;
}
}
break;
}
case AUDIO_MIXER_VALUE:
{
oldv = ctrl.un.value.level[0];
newv = level(value);
ctrl.un.value.level[0] = newv;
if (ctrl.un.value.num_channels == 2)
ctrl.un.value.level[1] = newv;
break;
}
}
if (oldv == -1)
break;
if (audioioctl(mdev, AUDIO_MIXER_WRITE, (char*)&ctrl, FWRITE, 0)) {
PERR("could not set '%s' from %d to %d", field, oldv, newv);
break;
}
PLOG("%s: %d -> %d", field, oldv, newv);
return true;
}
return false;
}
static char const *get_mixer_value(mixer_devinfo_t *info)
{
static char buffer[128];
mixer_ctrl_t ctrl;
ctrl.dev = info->index;
ctrl.type = info->type;
ctrl.un.value.num_channels = 2;
if (audioioctl(mdev, AUDIO_MIXER_READ, (char*)&ctrl, 0, 0)) {
ctrl.un.value.num_channels = 1;
if (audioioctl(mdev, AUDIO_MIXER_READ, (char*)&ctrl, 0, 0)) {
PERR("could not read mixer %d'", ctrl.dev);
return 0;
}
}
switch (ctrl.type) {
case AUDIO_MIXER_ENUM:
{
for (int i = 0; i < info->un.e.num_mem; i++)
if (ctrl.un.ord == info->un.e.member[i].ord) {
Genode::snprintf(buffer, sizeof(buffer),
"%s", info->un.e.member[i].label.name);
break;
}
break;
}
case AUDIO_MIXER_SET:
{
char *p = buffer;
Genode::size_t n = 0;
for (int i = 0; i < info->un.s.num_mem; i++)
if (ctrl.un.mask & info->un.s.member[i].mask)
n += Genode::snprintf(p + n, sizeof(buffer) - n,
"%s%s", n ? "," : "",
info->un.s.member[i].label.name);
break;
}
case AUDIO_MIXER_VALUE:
{
if (ctrl.un.value.num_channels == 2)
Genode::snprintf(buffer, sizeof(buffer), "%d,%d",
ctrl.un.value.level[0],
ctrl.un.value.level[1]);
else
Genode::snprintf(buffer, sizeof(buffer), "%d",
ctrl.un.value.level[0]);
break;
}
}
return buffer;
}
static void dump_mixer(Mixer const &mixer)
{
PLOG("--- mixer information ---");
for (unsigned i = 0; i < mixer.num; i++) {
if (mixer.info[i].type == AUDIO_MIXER_CLASS)
continue;
unsigned mixer_class = mixer.info[i].mixer_class;
char const * const class_name = mixer.info[mixer_class].label.name;
char const * const name = mixer.info[i].label.name;
char const * const value = get_mixer_value(&mixer.info[i]);
if (value)
PLOG("%s.%s=%s", class_name, name, value);
}
}
/******************
** Audio device **
******************/
static bool open_audio_device(dev_t dev)
{
if (!drv_loaded())
return false;
int err = audioopen(dev, FWRITE, 0 /* ifmt */, 0 /* proc */);
int err = audioopen(dev, FWRITE|FREAD, 0 /* ifmt */, 0 /* proc */);
if (err)
return false;
@ -87,6 +335,38 @@ static bool open_audio_device(dev_t dev)
}
static bool config_verbose()
{
using namespace Genode;
try {
return config()->xml_node().attribute("verbose").has_value("yes");
} catch (...) { }
return false;
}
static void parse_config(Mixer &mixer)
{
using namespace Genode;
PLOG("--- parse config ---");
Xml_node config_node = config()->xml_node();
config_node.for_each_sub_node("mixer", [&] (Xml_node node) {
char field[32];
char value[16];
try {
node.attribute("field").value(field, sizeof(field));
node.attribute("value").value(value, sizeof(value));
set_mixer_value(mixer, field, value);
} catch (Xml_attribute::Nonexistent_attribute) { }
});
}
static bool configure_audio_device(dev_t dev)
{
struct audio_info ai;
@ -97,18 +377,44 @@ static bool configure_audio_device(dev_t dev)
using namespace Audio;
/* configure the device according to our Audio_session settings */
/* configure the device according to our Audio_out session settings */
ai.play.sample_rate = Audio_out::SAMPLE_RATE;
ai.play.channels = MAX_CHANNELS;
ai.play.channels = Audio_out::MAX_CHANNELS;
ai.play.encoding = AUDIO_ENCODING_SLINEAR_LE;
ai.play.block_size = MAX_CHANNELS * sizeof(short) * Audio_out::PERIOD;
ai.play.block_size = Audio_out::MAX_CHANNELS * sizeof(short) * Audio_out::PERIOD;
/* Configure the device according to our Audio_in session settings
*
* We use Audio_out::MAX_CHANNELS here because the backend provides us
* with two channels that we will mix to one in the front end for now.
*/
ai.record.sample_rate = Audio_in::SAMPLE_RATE;
ai.record.channels = Audio_out::MAX_CHANNELS;
ai.record.encoding = AUDIO_ENCODING_SLINEAR_LE;
ai.record.block_size = Audio_out::MAX_CHANNELS * sizeof(short) * Audio_in::PERIOD;
err = audioioctl(adev, AUDIO_SETINFO, (char*)&ai, 0, 0);
if (err)
return false;
if (verbose)
dump_prinfo(&ai.play);
int fullduplex = 1;
err = audioioctl(adev, AUDIO_SETFD, (char*)&fullduplex, 0, 0);
if (err)
return false;
/* query mixer information */
mixer.num = count_mixer();
mixer.info = alloc_mixer(mixer.num);
if (!mixer.info || !query_mixer(mixer))
return false;
if (config_verbose()) verbose = true;
if (verbose) dump_pinfo();
if (verbose) dump_rinfo();
if (verbose) dump_mixer(mixer);
parse_config(mixer);
return true;
}
@ -133,7 +439,33 @@ static void run_bsd(void *)
}
}
static Bsd::Task *_task;
/***************************
** Notification handling **
***************************/
static Genode::Signal_context_capability _play_sigh;
static Genode::Signal_context_capability _record_sigh;
/*
* These functions are directly called by the audio
* backend in case an play/record interrupt occures
* and are used to notify our driver frontend.
*/
extern "C" void notify_play()
{
if (_play_sigh.valid())
Genode::Signal_transmitter(_play_sigh).submit();
}
extern "C" void notify_record()
{
if (_record_sigh.valid())
Genode::Signal_transmitter(_record_sigh).submit();
}
/*****************************
@ -148,21 +480,30 @@ void Audio::init_driver(Server::Entrypoint &ep)
static Bsd::Task task_bsd(run_bsd, nullptr, "bsd",
Bsd::Task::PRIORITY_0, Bsd::scheduler(),
2048 * sizeof(long));
_task = &task_bsd;
Bsd::scheduler().schedule();
}
bool Audio::driver_active()
{
return drv_loaded() && adev_usuable;
}
bool Audio::driver_active() { return drv_loaded() && adev_usuable; }
void Audio::play_sigh(Genode::Signal_context_capability sigh) {
_play_sigh = sigh; }
void Audio::record_sigh(Genode::Signal_context_capability sigh) {
_record_sigh = sigh; }
int Audio::play(short *data, Genode::size_t size)
{
struct uio uio = { 0, size, data, size };
struct uio uio = { 0, size, UIO_READ, data, size };
return audiowrite(adev, &uio, IO_NDELAY);
}
int Audio::record(short *data, Genode::size_t size)
{
struct uio uio = { 0, size, UIO_WRITE, data, size };
return audioread(adev, &uio, IO_NDELAY);
}

View File

@ -21,6 +21,17 @@
extern "C" {
#endif /* __cplusplus */
/*
* These emul specific functions are called by the OpenBSD
* audio framework if an play/record interrupt has occured
* (see patches/notify.patch).
*/
void notify_play();
void notify_record();
/*****************
** sys/types.h **
*****************/
@ -154,10 +165,17 @@ struct task { };
** sys/uio.h **
***************/
enum uio_rw
{
UIO_READ = 0,
UIO_WRITE = 1,
};
struct uio
{
off_t uio_offset;
size_t uio_resid;
enum uio_rw uio_rw;
/* emul specific fields */
void *buf;

View File

@ -31,10 +31,6 @@ namespace Bsd {
static void run_irq(void *args);
static Genode::Signal_context_capability _dma_notifier_cap;
void Audio::dma_notifier(Genode::Signal_context_capability cap) {
_dma_notifier_cap = cap; }
class Bsd::Irq
{
@ -93,13 +89,6 @@ class Bsd::Irq
{
_intrh(_intarg);
_irq.ack_irq();
/*
* Notify the frontend when a block from the DMA
* was played.
*/
if (_dma_notifier_cap.valid())
Genode::Signal_transmitter(_dma_notifier_cap).submit();
}
};

View File

@ -373,10 +373,21 @@ extern "C" void bcopy(const void *src, void *dst, size_t len)
extern "C" int uiomovei(void *buf, int n, struct uio *uio)
{
void *dst = buf;
void *src = ((char*)uio->buf) + uio->uio_offset;
void *dst = nullptr;
void *src = nullptr;
size_t len = uio->uio_resid < (size_t)n ? uio->uio_resid : (size_t)n;
switch (uio->uio_rw) {
case UIO_READ:
dst = buf;
src = ((char*)uio->buf) + uio->uio_offset;
break;
case UIO_WRITE:
dst = ((char*)uio->buf) + uio->uio_offset;
src = buf;
break;
}
Genode::memcpy(dst, src, len);
uio->uio_resid -= len;