WiP! Audio session redesign

Define a unified packet-buffer format for both Audio_out and Audio_in
sessions. This allows an Audio_out and an Audio_in client to exchange
packets with a common buffer and lexicon.

The sessions have also been stripped down to a minimum. The
'progress' and 'data_avail' signal handlers have been replaced with a
single 'progress' signal sent by Audio_in and received by Audio_in.

The Audio_in session has a reset signal delivered to the client to
indicate that the progress signal handler has been replaced (dubious).

These changes reflect a different relationship between the two sessions.
Drivers that play audio are now Audio_in clients and drive the rate that
packets can be consumed by sending progress signals.

Ref #2858
This commit is contained in:
Ehmry - 2019-06-28 16:17:03 +02:00
parent ac853252c3
commit 5e8bc9ef51
59 changed files with 1517 additions and 2505 deletions

View File

@ -1 +1 @@
2019-06-04 f880caa4785e8994a955fd557608cec0e42d64a7
2019-06-19-a 883a7771cf6c015b063491adff3e069206151227

View File

@ -1 +1 @@
2019-06-03 08a12eda7a98bad6b5571a6ca044f77107fe1afd
2019-06-19-a 970f5dff896223e05ec0c3da6ac984e56f7e92a2

View File

@ -1 +1 @@
2019-06-06-a d11fd5d2fa5a8a8cdbb948a76804ed081def9339
2019-06-19 3b2b827ec74993dff233dbac299c8f06ec5f81a0

View File

@ -23,7 +23,7 @@ Example
The driver can be tested by executing the run script 'run/audio_out.run'.
This example plays a sample file in a loop. The file format is header less
two channel float 32 at 44100 Hz. You may use the 'sox' utility to create
two channel float 32 at 48 kHz. You may use the 'sox' utility to create
these audio files:
! sox -c 2 -r 44100 foo.wav foo.f32
! sox -c 2 -r 48000 foo.wav foo.f32

View File

@ -28,19 +28,9 @@
** private Audio namespace **
*****************************/
namespace Audio_out {
namespace Audio {
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 update_config(Genode::Env &, Genode::Xml_node);

View File

@ -1 +1,3 @@
_/src/bsd_audio_drv
_/src/init
_/src/stereo_patch

View File

@ -1,4 +1,4 @@
<runtime ram="16M" caps="256" binary="audio_drv">
<runtime ram="24M" caps="256" binary="init">
<requires> <platform/> <rm/> <timer/> </requires>
@ -10,8 +10,38 @@
<config/>
<content>
<rom label="ld.lib.so"/>
<rom label="audio_drv"/>
<rom label="stereo_patch"/>
</content>
<config>
<parent-provides>
<service name="CPU"/>
<service name="LOG"/>
<service name="PD"/>
<service name="RM"/>
<service name="Platform"/>
<service name="Report"/>
<service name="Timer"/>
</parent-provides>
<start name="stereo_patch" caps="96">
<resource name="RAM" quantum="8M"/>
<provides>
<service name="Audio_in"/>
<service name="Audio_out"/>
</provides>
<route>
<any-service> <parent/> </any-service>
</route>
</start>
<start name="audio_drv" caps="128">
<binary name="audio_drv"/>
<resource name="RAM" quantum="16M"/>
<config playback="yes"/>
</start>
<default-route>
<any-service> <parent/> <child name="stereo_patch"/> </any-service>
</default-route>
</config>
</runtime>

View File

@ -1 +1 @@
-
2019-06-19 7392f28d42f78ffd2912df15ad91ecac9f7b4052

View File

@ -1,7 +1,6 @@
base
os
audio_in_session
audio_out_session
audio
platform_session
report_session
timer_session

View File

@ -10,7 +10,8 @@ set wget [installed_command wget]
# Configure
#
set use_mixer 0
set use_mixer 1
set use_patch 0
#
# Build
@ -23,6 +24,7 @@ set build_components {
}
lappend_if $use_mixer build_components server/mixer
lappend_if $use_patch build_components server/stereo_patch
source ${genode_dir}/repos/base/run/platform_drv.inc
append_platform_drv_build_components
@ -36,7 +38,7 @@ create_boot_directory
#
append config {
<config>
<config verbose="yes">
<parent-provides>
<service name="ROM"/>
<service name="IRQ"/>
@ -61,34 +63,50 @@ append_platform_drv_config
append_if $use_mixer config {
<start name="mixer">
<resource name="RAM" quantum="2M"/>
<provides><service name="Audio_out"/></provides>
<provides>
<service name="Audio_in"/>
<service name="Audio_out"/>
</provides>
<config>
<default out_volume="75" volume="25" muted="0"/>
<policy label_suffix="left" channel="0"/>
<policy label_suffix="right" channel="1"/>
</config>
<route>
<service name="Audio_out"> <child name="audio_drv"/> </service>
<any-service> <parent/> <any-child/> </any-service>
<any-service> <parent/> </any-service>
</route>
</start>}
</start>
}
append_if $use_patch config {
<start name="stereo_patch">
<resource name="RAM" quantum="8M"/>
<provides>
<service name="Audio_in"/>
<service name="Audio_out"/>
</provides>
<route>
<any-service> <parent/> </any-service>
</route>
</start>
}
append config {
<start name="audio_drv">
<binary name="} [audio_drv_binary] {"/>
<resource name="RAM" quantum="8M"/>
<provides> <service name="Audio_out"/> </provides>
<config />
<resource name="RAM" quantum="16M"/>
<config playback="yes"/>
</start>
<start name="test-audio_out">
<resource name="RAM" quantum="4M"/>
<start name="sample_a">
<binary name="test-audio_out"/>
<resource name="RAM" quantum="8M"/>
<config>
<filename>sample.raw</filename>
<filename>sample_a.raw</filename>
</config>
</start>
<start name="sample_b">
<binary name="test-audio_out"/>
<resource name="RAM" quantum="8M"/>
<config>
<filename>sample_b.raw</filename>
</config>
<route>}
append_if $use_mixer config {
<service name="Audio_out"><child name="mixer"/></service>}
append config {
<any-service><parent/><any-child/></any-service>
</route>
</start>
</config>}
@ -100,15 +118,15 @@ install_config $config
#
if {[info exists env(GENODE_SAMPLE_RAW)]} {
catch { exec $wget $::env(GENODE_SAMPLE_RAW) -O bin/sample.raw }
catch { exec $wget $::env(GENODE_SAMPLE_RAW) -O bin/sample_a.raw }
catch { exec $wget $::env(GENODE_SAMPLE_RAW) -O bin/sample_b.raw }
}
if {![file exists bin/sample.raw]} {
puts ""
puts "The sample file is missing. Please take a look at"
puts "repos/dde_bsd/README, create 'sample.raw' and put"
puts "the file into './bin'. afterwards"
if {![file exists bin/sample_a.raw] || ![file exists bin/sample_b.raw]} {
puts ""
puts "The sample files are missing. Please take a look at"
puts "repos/dde_bsd/README, create 'sample.f32' and put"
puts "the file at './bin/sample_a.raw' and './bin/sample_b.raw'."
exit 1
}
@ -118,10 +136,11 @@ if {![file exists bin/sample.raw]} {
append boot_modules {
core ld.lib.so init timer } [audio_drv_binary] {
test-audio_out sample.raw
test-audio_out sample_a.raw sample_b.raw
}
lappend_if $use_mixer boot_modules mixer
lappend_if $use_patch boot_modules stereo_patch
append_platform_drv_boot_modules

View File

@ -1,11 +1,12 @@
/*
* \brief Startup audio driver library
* \author Josef Soentgen
* \author Emery Hemingway
* \date 2014-11-09
*/
/*
* Copyright (C) 2014-2017 Genode Labs GmbH
* Copyright (C) 2014-2019 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
@ -13,482 +14,49 @@
/* Genode includes */
#include <audio_in_session/rpc_object.h>
#include <audio_out_session/rpc_object.h>
#include <audio/sink.h>
#include <audio/source.h>
#include <timer_session/connection.h>
#include <base/attached_rom_dataspace.h>
#include <base/session_label.h>
#include <base/component.h>
#include <base/heap.h>
#include <base/log.h>
#include <root/component.h>
/* local includes */
#include <audio/audio.h>
enum { EWOULDBLOCK = 35 };
using namespace Genode;
using namespace Audio;
/**************
** Playback **
**************/
namespace Audio_out {
class Session_component;
class Out;
class Root;
struct Root_policy;
static Session_component *channel_acquired[MAX_CHANNELS];
}
class Audio_out::Session_component : public Audio_out::Session_rpc_object
{
private:
Channel_number _channel;
public:
Session_component(Genode::Env &env, Channel_number channel, Signal_context_capability cap)
:
Session_rpc_object(env, cap), _channel(channel)
{
Audio_out::channel_acquired[_channel] = this;
}
~Session_component()
{
Audio_out::channel_acquired[_channel] = 0;
}
};
class Audio_out::Out
{
private:
Genode::Env &_env;
Genode::Signal_handler<Audio_out::Out> _data_avail_dispatcher;
Genode::Signal_handler<Audio_out::Out> _notify_dispatcher;
bool _active() {
return channel_acquired[LEFT] && channel_acquired[RIGHT] &&
channel_acquired[LEFT]->active() && channel_acquired[RIGHT]->active();
}
Stream *left() { return channel_acquired[LEFT]->stream(); }
Stream *right() { return channel_acquired[RIGHT]->stream(); }
void _advance_position(Packet *l, Packet *r)
{
bool full_left = left()->full();
bool full_right = right()->full();
left()->pos(left()->packet_position(l));
right()->pos(right()->packet_position(r));
left()->increment_position();
right()->increment_position();
Session_component *channel_left = channel_acquired[LEFT];
Session_component *channel_right = channel_acquired[RIGHT];
if (full_left)
channel_left->alloc_submit();
if (full_right)
channel_right->alloc_submit();
}
void _play_silence()
{
static short silence[Audio_out::PERIOD * Audio_out::MAX_CHANNELS] = { 0 };
int err = Audio::play(silence, sizeof(silence));
if (err && err != 35) {
Genode::warning("Error ", err, " during silence playback");
}
}
void _play_packet()
{
unsigned lpos = left()->pos();
unsigned rpos = right()->pos();
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[Audio_out::PERIOD * Audio_out::MAX_CHANNELS];
for (unsigned 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;
}
/* send to driver */
if (int err = Audio::play(data, sizeof(data))) {
Genode::warning("Error ", err, " during playback");
}
p_left->invalidate();
p_right->invalidate();
p_left->mark_as_played();
p_right->mark_as_played();
} else {
_play_silence();
}
_advance_position(p_left, p_right);
/* always report when a period has passed */
Session_component *channel_left = channel_acquired[LEFT];
Session_component *channel_right = channel_acquired[RIGHT];
channel_left->progress_submit();
channel_right->progress_submit();
}
/*
* Data available in session buffer
*
* We do not care about this signal because we already have
* started to play and we will keep doing it, even if it is
* silence.
*/
void _handle_data_avail() { }
/*
* DMA block played
*/
void _handle_notify()
{
if (_active())
_play_packet();
}
public:
Out(Genode::Env &env)
:
_env(env),
_data_avail_dispatcher(env.ep(), *this, &Audio_out::Out::_handle_data_avail),
_notify_dispatcher(env.ep(), *this, &Audio_out::Out::_handle_notify)
{
/* play a silence packet to get the driver running */
_play_silence();
}
Signal_context_capability data_avail() { return _data_avail_dispatcher; }
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 }, { "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;
}
};
/**
* Session creation policy for our service
*/
struct Audio_out::Root_policy
{
void aquire(const char *args)
{
size_t ram_quota =
Arg_string::find_arg(args, "ram_quota" ).ulong_value(0);
size_t session_size =
align_addr(sizeof(Audio_out::Session_component), 12);
if ((ram_quota < session_size) ||
(sizeof(Stream) > ram_quota - session_size)) {
Genode::error("insufficient 'ram_quota', got ", ram_quota,
" need ", sizeof(Stream) + session_size);
throw Genode::Insufficient_ram_quota();
}
char channel_name[16];
Channel_number channel_number;
Arg_string::find_arg(args, "channel").string(channel_name,
sizeof(channel_name),
"left");
if (!Out::channel_number(channel_name, &channel_number)) {
Genode::error("invalid output channel '",(char const *)channel_name,"' requested, "
"denying '",Genode::label_from_args(args),"'");
throw Genode::Service_denied();
}
if (Audio_out::channel_acquired[channel_number]) {
Genode::error("output channel '",(char const *)channel_name,"' is unavailable, "
"denying '",Genode::label_from_args(args),"'");
throw Genode::Service_denied();
}
}
void release() { }
};
namespace Audio_out {
typedef Root_component<Session_component, Root_policy> Root_component;
}
/**
* Root component, handling new session requests.
*/
class Audio_out::Root : public Audio_out::Root_component
{
private:
Genode::Env &_env;
Signal_context_capability _cap;
protected:
Session_component *_create_session(const char *args)
{
char channel_name[16];
Channel_number channel_number = INVALID;
Arg_string::find_arg(args, "channel").string(channel_name,
sizeof(channel_name),
"left");
Out::channel_number(channel_name, &channel_number);
return new (md_alloc())
Session_component(_env, channel_number, _cap);
}
public:
Root(Genode::Env &env, Allocator &md_alloc,
Signal_context_capability cap)
:
Root_component(env.ep(), md_alloc),
_env(env), _cap(cap)
{ }
};
/***************
** 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(Genode::Env &env, Channel_number channel,
Genode::Signal_context_capability cap)
: Session_rpc_object(env, cap), _channel(channel) {
channel_acquired = this; }
~Session_component() { channel_acquired = nullptr; }
};
class Audio_in::In
{
private:
Genode::Env &_env;
Genode::Signal_handler<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) {
Genode::warning("Error ", err, " during recording");
}
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()
{
if (_active())
_record_packet();
}
public:
In(Genode::Env &env)
:
_env(env),
_notify_dispatcher(env.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))) {
Genode::error("insufficient 'ram_quota', got ", ram_quota,
" need ", sizeof(Stream) + session_size,
", denying '",Genode::label_from_args(args),"'");
throw Genode::Insufficient_ram_quota();
}
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)) {
Genode::error("invalid input channel '",(char const *)channel_name,"' requested, "
"denying '",Genode::label_from_args(args),"'");
throw Genode::Service_denied();
}
if (Audio_in::channel_acquired) {
Genode::error("input channel '",(char const *)channel_name,"' is unavailable, "
"denying '",Genode::label_from_args(args),"'");
throw Genode::Service_denied();
}
}
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:
Genode::Env &_env;
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(_env, channel_number, _cap);
}
public:
Root(Genode::Env &env, Allocator &md_alloc,
Signal_context_capability cap)
: Root_component(env.ep(), md_alloc), _env(env), _cap(cap) { }
};
/**********
** Main **
**********/
struct Main
struct Main : Audio::Sink, Audio::Source
{
Genode::Env &env;
Genode::Heap heap { &env.ram(), &env.rm() };
Timer::Connection timer { env };
// TODO: process packets on interrupt, not timeout
Timer::Periodic_timeout<Main> packet_timeout {
timer, *this, &Main::process_packets,
Genode::Microseconds{(1000000 / Audio::SAMPLE_RATE) * (Audio::PERIOD-1)}
};
Genode::Constructible<Stereo_in> stereo_in { };
Genode::Constructible<Stereo_out> stereo_out { };
void process_packets(Genode::Duration)
{
if (stereo_in.constructed())
stereo_in->progress();
if (stereo_out.constructed())
stereo_out->progress();
}
Genode::Attached_rom_dataspace config { env, "config" };
Genode::Signal_handler<Main> config_update_dispatcher {
@ -499,6 +67,34 @@ struct Main
config.update();
if (!config.valid()) { return; }
Audio::update_config(env, config.xml());
bool playback = config.xml().attribute_value("playback", false);
bool recording = config.xml().attribute_value("recording", false);
if (!playback && !recording) {
Genode::warning("driver not configured for playback or recording");
}
/* playback */
if (playback) {
if (!stereo_in.constructed()) {
/* start the driver */
play_silence();
stereo_in.construct(env, *this);
}
} else {
if (stereo_in.constructed())
stereo_in.destruct();
}
/* recording */
if (recording) {
if (!stereo_out.constructed())
stereo_out.construct(env, *this);
} else {
if (stereo_out.constructed())
stereo_out.destruct();
}
}
Main(Genode::Env &env) : env(env)
@ -506,31 +102,69 @@ struct Main
Audio::init_driver(env, heap, timer, config.xml());
if (!Audio::driver_active()) {
Genode::error("driver not active");
env.parent().exit(~0);
return;
}
/* playback */
if (config.xml().attribute_value("playback", true)) {
static Audio_out::Out out(env);
Audio::play_sigh(out.sigh());
static Audio_out::Root out_root(env, heap, out.data_avail());
env.parent().announce(env.ep().manage(out_root));
Genode::log("--- BSD Audio driver enable playback ---");
}
/* recording */
if (config.xml().attribute_value("recording", true)) {
static Audio_in::In in(env);
Audio::record_sigh(in.sigh());
static Audio_in::Root in_root(env, heap,
Genode::Signal_context_capability());
env.parent().announce(env.ep().manage(in_root));
Genode::log("--- BSD Audio driver enable recording ---");
}
config.sigh(config_update_dispatcher);
handle_config_update();
}
void play_silence()
{
static short silence[Audio::PERIOD * 2] = { 0 };
Audio::play(silence, sizeof(silence));
}
/*****************
** Audio::Sink **
*****************/
bool drain(float const *left, float const *right, Genode::size_t samples) override
{
/* convert float to S16LE */
short data[samples * 2];
for (unsigned i = 0; i < samples*2; i += 2) {
data[i|0] = left[i>>1] * 32767;
data[i|1] = right[i>>1] * 32767;
}
/* send to driver */
if (int err = Audio::play(data, sizeof(data))) {
if (err != EWOULDBLOCK)
Genode::error("error ", err, " during playback");
return false;
}
return true;
}
/*******************
** Audio::Source **
*******************/
bool fill(float *left, float *right, Genode::size_t samples) override
{
static short data[2 * Audio_in::PERIOD];
if (int err = Audio::record(data, sizeof(data))) {
if (err && err != EWOULDBLOCK)
Genode::warning("Error ", err, " during recording");
return false;
}
float const scale = 32768.0f * 2;
for (unsigned i = 0; i < 2*Audio_in::PERIOD; i += 2) {
left [i/2] = data[i+0] / scale;
right[i/2] = data[i+1] / scale;
}
return true;
}
};
@ -542,3 +176,6 @@ void Component::construct(Genode::Env &env)
static Main server(env);
}
extern "C" void notify_play() { }
extern "C" void notify_record() { }

View File

@ -462,34 +462,6 @@ static void run_bsd(void *p)
}
/***************************
** 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();
}
/*****************************
** private Audio namespace **
*****************************/
@ -522,14 +494,6 @@ void Audio::init_driver(Genode::Env &env, Genode::Allocator &alloc,
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, UIO_READ, data, size };

View File

@ -1 +1 @@
-
2019-06-19 61c849a28958fcfc3e5d9ea555a305ae76ed9d7f

View File

@ -1 +1 @@
2019-05-05 4c07a7badce5ed6e84a16681db67528b7395fd46
2019-06-19 18750ec486b7d438efd2c051b94d373eaa8ee7fd

View File

@ -1 +1 @@
2019-06-11 fd2ce886a47163c7036380eeb345b00aa5eb56dc
2019-06-19 2cc3ad3dfc57a9be3a6bc327347b858c5ad59a6a

View File

@ -1 +1 @@
-
2019-06-19 1dc3c775c028128adb8c9a689956d73ec44ca366

View File

@ -1 +1 @@
-
2019-06-19 08b20bb9543e9d45ef2db78992791f66a71ce046

View File

@ -1 +1 @@
-
2019-06-19 5d9465c7e6e224ff42b91e4520436ba3c37120f3

View File

@ -1 +1 @@
-
2019-06-19 4783538a41ff3586ae2fddaeb12879bf3ce568bd

View File

@ -1 +1 @@
2019-05-29 53d789ea778cd48f5d977fadef29de40b439000a
2019-06-19 ea731dff5c170181afe4f3778619941c0e263201

View File

@ -1 +1 @@
2019-06-05 f56484d63f0b1f3cda717166423ef9c91efd1b7c
2019-06-19 e88e256d873b34651ba0d347bea0dd19de934c4b

View File

@ -0,0 +1,39 @@
/*
* \brief Server-side audio packet packet buffer
* \author Emery Hemingway
* \date 2018-06-05
*/
/*
* Copyright (C) 2018 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _INCLUDE__AUDIO__BUFFER_H_
#define _INCLUDE__AUDIO__BUFFER_H_
#include <audio/stream.h>
#include <base/attached_ram_dataspace.h>
namespace Audio { struct Stream_buffer; }
struct Audio::Stream_buffer
{
Genode::Attached_ram_dataspace _shared_ds;
Stream_buffer(Genode::Ram_allocator &ram, Genode::Region_map &rm)
: _shared_ds(ram, rm, sizeof(Audio::Stream)) { }
Stream_sink &stream_sink() {
return *_shared_ds.local_addr<Stream_sink>(); }
Stream_source &stream_source() {
return *_shared_ds.local_addr<Stream_source>(); }
Genode::Dataspace_capability cap() {
return _shared_ds.cap(); }
};
#endif /* _INCLUDE__AUDIO__BUFFER_H_ */

View File

@ -0,0 +1,36 @@
/*
* \brief Common format parameters for Audio_* sessions
* \author Emery Hemingway
* \date 2019-06-19
*/
/*
* Copyright (C) 2019 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _INCLUDE__AUDIO__PARAMETERS_H_
#define _INCLUDE__AUDIO__PARAMETERS_H_
#include <base/stdint.h>
#define GENODE_AUDIO_SAMPLE_RATE 48000
#ifdef __cplusplus
namespace Audio {
enum {
QUEUE_SIZE = 431,
SAMPLE_RATE = GENODE_AUDIO_SAMPLE_RATE,
SAMPLE_SIZE = sizeof(float),
};
static constexpr Genode::size_t PERIOD = 800; /* samples per period (60 Hz) */
}
#endif
#endif // _INCLUDE__AUDIO__PARAMETERS_H_

View File

@ -0,0 +1,93 @@
/*
* \brief Audio session consumer class
* \author Emery Hemingway
* \date 2019-06-13
*/
/*
* Copyright (C) 2019 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <audio_in_session/connection.h>
namespace Audio
{
struct Sink;
class Stereo_in;
};
struct Audio::Sink
{
virtual ~Sink() { }
virtual bool drain(float const *left, float const *right, Genode::size_t samples) = 0;
};
class Audio::Stereo_in
{
private:
Audio::Sink &_sink;
Audio_in::Connection _left, _right;
Genode::Signal_handler<Stereo_in> _reset_handler;
Genode::Signal_context_capability _progress_sigh { };
void _handle_reset()
{
_progress_sigh = _left.progress_sigh();
if (_progress_sigh.valid())
Genode::Signal_transmitter(_progress_sigh).submit();
}
public:
Stereo_in(Genode::Env &env, Sink &sink)
: _sink(sink), _left (env, "left"), _right(env, "right"),
_reset_handler(env.ep(), *this, &Stereo_in::_handle_reset)
{
_left.reset_sigh(_reset_handler);
_handle_reset();
}
/**
* Drain incoming audio from packet stream and
* transmit a progress signal to the source.
*/
bool progress()
{
using namespace Audio_in;
bool res = false;
while (!_left.stream().empty()) {
unsigned pos = _left.stream().play_pos();
Packet *left = _left.stream().get(pos);
Packet *right = _right.stream().get(pos);
if (!_sink.drain(left->content(), right->content(), PERIOD))
break;
left->played();
right->played();
_left.stream().increment_position();
_right.stream().increment_position();
res = true;
}
{
if (_progress_sigh.valid())
Genode::Signal_transmitter(_progress_sigh).submit();
}
return res;
}
};

View File

@ -42,40 +42,35 @@ class Audio::Stereo_out
public:
Stereo_out(Genode::Env &env, Audio::Source &source)
: _source(source)
, _left (env, "left", false, false)
, _right(env, "right", false, false)
, _progress_handler(env.ep(), *this, &Stereo_out::progress)
: _source(source), _left (env, "left"), _right(env, "right"),
_progress_handler(env.ep(), *this, &Stereo_out::progress)
{
reset();
}
void reset()
{
_left.stream().reset();
_right.stream().reset();
_left.progress_sigh(_progress_handler);
}
void start()
{
_left.start();
_right.start();
}
void stop()
{
_left.stop();
_right.stop();
}
void progress()
{
using namespace Audio_out;
while (!_left.stream()->full()) {
Packet *left = _left.stream()->alloc();
unsigned pos = _left.stream()->packet_position(left);
Packet *right = _right.stream()->get(pos);
while (!_left.stream().full()) {
unsigned pos = _left.stream().record_pos();
Packet *left = _left.stream().get(pos);
Packet *right = _right.stream().get(pos);
if (!_source.fill(left->content(), right->content(), PERIOD))
break;
_left.submit(left);
_right.submit(right);
left->recorded();
right->recorded();
_left.stream().increment_position();
_right.stream().increment_position();
}
}
};

View File

@ -0,0 +1,253 @@
/*
* \brief Common definitions for Audio_* sessions
* \author Sebastian Sumpf
* \author Josef Soentgen
* \author Emery Hemingway
* \date 2018-06-05
*
* The Audio_out and Audio_in session use a common layout for
* shared-memory packet queues. This file defines that layout
* and convenience classes for each side of the audio pipeline.
*/
/*
* Copyright (C) 2019 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _INCLUDE__AUDIO__STREAM_H_
#define _INCLUDE__AUDIO__STREAM_H_
#include <audio/parameters.h>
#include <util/string.h>
namespace Audio {
class Packet;
class Stream;
struct Stream_source;
struct Stream_sink;
}
/**
* Audio packet containing frames
*/
class Audio::Packet
{
friend class Stream;
friend class Stream_sink;
friend class Stream_source;
protected:
float _data[PERIOD];
bool _active = false;
public:
Packet() { }
void recorded() { _active = true; }
void played() { _active = false; }
/**
* Copy data into packet, if there are less frames given than 'PERIOD',
* the remainder is filled with zeros
*
* \param data frames to copy in
* \param size number of frames to copy
*/
void content(float *data, Genode::size_t samples)
{
using namespace Genode;
memcpy(_data, data, (samples > PERIOD ? (size_t)PERIOD : samples) * SAMPLE_SIZE);
if (samples < PERIOD)
memset(_data + samples, 0, (PERIOD - samples) * SAMPLE_SIZE);
}
/**
* Get content
*
* \return pointer to frame data
*/
float *content() { return _data; }
Genode::size_t size() const { return sizeof(_data); }
};
/**
* The audio-stream object containing packets
*
* The stream object is created upon session creation. The server will allocate
* a dataspace on the client's account. The client session will then request
* this dataspace and both client and server will attach it in their respective
* protection domain. After that, the stream pointer within a session will be
* pointed to the attached dataspace on both sides. Because the 'Stream' object
* is backed by shared memory, its constructor is never supposed to be called.
*/
class Audio::Stream
{
friend class Stream_source;
friend class Stream_sink;
private:
Genode::uint32_t _record_pos { 0 }; /* current record (server) position */
Genode::uint32_t _play_pos { 0 }; /* current playback (client) position */
Packet _buf[QUEUE_SIZE]; /* packet queue */
public:
/**
* Current record position
*
* \return record position
*/
unsigned record_pos() const { return _record_pos; }
/**
* Current playback position
*
* \return playback position
*/
unsigned play_pos() const { return _play_pos; }
/**
* Reset stream queue
*
* This means that plaback will start at current record position.
*/
void reset() { _play_pos = _record_pos; }
/**
* Retrieves the position of a given packet in the stream queue
*
* \param packet a packet
*
* \return position in stream queue
*/
unsigned packet_position(Packet *packet) const { return packet - &_buf[0]; }
/**
* Retrieve an packet at given position
*
* \param pos position in stream
*
* \return Audio_in packet
*/
Packet *get(unsigned pos) { return &_buf[pos % QUEUE_SIZE]; }
/**
* Check if stream queue is empty
*/
bool empty() const {
return (_play_pos == _record_pos); }
/**
* Check if stream queue is full
*/
bool full() const {
return ((_record_pos+1) % QUEUE_SIZE) == _play_pos; }
int queued() const
{
int gap = _record_pos < _play_pos
? _record_pos+QUEUE_SIZE-_play_pos
: _record_pos-_play_pos;
return gap;
}
};
/**
* Stream object for retreiving audio packets
*/
struct Audio::Stream_source : Stream
{
template <typename PROC>
void play(int pos, PROC const &proc)
{
Packet &p = *get(pos);
if (p._active) {
proc(p);
p._active = false;
_play_pos = (pos+1) % QUEUE_SIZE;
}
}
/**
* Set current stream position
*
* \param pos current position
*/
void pos(unsigned p) { _play_pos = p; }
/**
* Increment current stream position by one
*/
void increment_position() {
_play_pos = (_play_pos + 1) % QUEUE_SIZE; }
/* position the play position on the first unplayed packet */
void seek()
{
unsigned pos = _record_pos;
for (unsigned i = 0; i < QUEUE_SIZE; ++i) {
pos = (pos+1) % QUEUE_SIZE;
if (_buf[pos]._active)
break;
}
}
};
/**
* Stream object for submitting audio packets
*/
struct Audio::Stream_sink : Stream
{
friend class Stream;
template <typename PROC>
void record(int pos, PROC const &proc)
{
Packet &p = *get(pos);
if (!p._active) {
proc(p);
p._active = true;
_record_pos = (pos+1) % QUEUE_SIZE;
}
}
/**
* Set current stream position
*
* \param pos current position
*/
void pos(unsigned p) { _record_pos = p; }
/**
* Increment current stream position by one
*/
void increment_position() {
_record_pos = (_record_pos + 1) % QUEUE_SIZE; }
/* position the play position on the first unrecorded packet */
void seek()
{
unsigned pos = _play_pos;
for (unsigned i = 0; i < QUEUE_SIZE; ++i) {
pos = (pos+1) % QUEUE_SIZE;
if (!_buf[pos]._active)
break;
}
}
};
#endif /* _INCLUDE__AUDIO__STREAM_H_ */

View File

@ -9,12 +9,11 @@
* 'Audio_in::Stream' in turn consists of 'Audio_in::Packet's that contain
* the actual frames. Each packet within a stream is freely accessible. When
* recording the source will allocate a new packet and override already
* recorded ones if the queue is already full. In contrast to the
* 'Audio_out::Stream' the current position pointer is updated by the client.
* recorded ones if the queue is already full.
*/
/*
* Copyright (C) 2015-2017 Genode Labs GmbH
* Copyright (C) 2015-2018 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
@ -23,311 +22,55 @@
#ifndef _INCLUDE__AUDIO_IN_SESSION__AUDIO_IN_SESSION_H_
#define _INCLUDE__AUDIO_IN_SESSION__AUDIO_IN_SESSION_H_
#include <audio/stream.h>
#include <base/allocator.h>
#include <dataspace/capability.h>
#include <base/signal.h>
#include <base/rpc.h>
#include <session/session.h>
namespace Audio_in {
class Packet;
class Stream;
class Session;
enum {
QUEUE_SIZE = 431, /* buffer queue size (~5s) */
PERIOD = 512, /* samples per periode (~11.6ms) */
SAMPLE_RATE = 44100,
SAMPLE_SIZE = sizeof(float),
};
using namespace Audio;
struct Session;
}
/**
* Audio_in packet containing frames
*/
class Audio_in::Packet
{
private:
friend class Session_client;
friend class Stream;
bool _valid;
bool _wait_for_record;
float _data[PERIOD];
void _submit() { _valid = true; _wait_for_record = true; }
void _alloc() { _wait_for_record = false; _valid = false; }
public:
Packet() : _valid(false), _wait_for_record(false) { }
/**
* Copy data into packet, if there are less frames given than 'PERIOD',
* the remainder is filled with zeros
*
* \param data frames to copy in
* \param size number of frames to copy
*/
void content(float *data, Genode::size_t samples)
{
Genode::memcpy(_data, data, (samples > PERIOD ? PERIOD : samples) * SAMPLE_SIZE);
if (samples < PERIOD)
Genode::memset(data + samples, 0, (PERIOD - samples) * SAMPLE_SIZE);
}
/**
* Get content
*
* \return pointer to frame data
*/
float *content() { return _data; }
/**
* Record state
*
* \return true if the packet has been recorded; false otherwise
*/
bool recorded() const { return !_wait_for_record; }
/**
* Valid state
*
* The valid state of a packet describes that the packet has been
* processed by the server even though it may not have been played back
* if the packet is invalid. For example, if a server is a filter, the
* audio may not have been processed by the output driver.
*
* \return true if packet has *not* been processed yet; false otherwise
*/
bool valid() const { return _valid; }
Genode::size_t size() const { return sizeof(_data); }
/**********************************************
** Intended to be called by the server side **
**********************************************/
/**
* Invalidate packet, thus marking it as processed
*/
void invalidate() { _valid = false; }
/**
* Mark a packet as recorded
*/
void mark_as_recorded() { _wait_for_record = false; }
};
/**
* The audio-stream object containing packets
*
* The stream object is created upon session creation. The server will allocate
* a dataspace on the client's account. The client session will then request
* this dataspace and both client and server will attach it in their respective
* protection domain. After that, the stream pointer within a session will be
* pointed to the attached dataspace on both sides. Because the 'Stream' object
* is backed by shared memory, its constructor is never supposed to be called.
*/
class Audio_in::Stream
{
private:
unsigned _pos { 0 }; /* current record position */
unsigned _tail { 0 }; /* tail pointer used for allocations */
Packet _buf[QUEUE_SIZE]; /* packet queue */
public:
/**
* Current audio record position
*
* \return position
*/
unsigned pos() const { return _pos; }
/**
* Current tail position
*
* \return tail position
*/
unsigned tail() const { return _tail; }
/**
* Retrieve next packet for given packet
*
* \param packet preceding packet
*
* \return Successor of packet or successor of current position if
* 'packet' is zero
*/
Packet *next(Packet *packet = 0)
{
return packet ? get(packet_position(packet) + 1) : get(pos() + 1);
}
/**
* Retrieves the position of a given packet in the stream queue
*
* \param packet a packet
*
* \return position in stream queue
*/
unsigned packet_position(Packet *packet) { return packet - &_buf[0]; }
/**
* Check if stream queue is empty
*/
bool empty() const
{
bool valid = false;
for (int i = 0; i < QUEUE_SIZE; i++)
valid |= _buf[i].valid();
return !valid;
}
/**
* Retrieve an packet at given position
*
* \param pos position in stream
*
* \return Audio_in packet
*/
Packet *get(unsigned pos) { return &_buf[pos % QUEUE_SIZE]; }
/**
* Allocate a packet in stream
*
* \return Packet
*/
Packet *alloc()
{
unsigned pos = _tail;
_tail = (_tail + 1) % QUEUE_SIZE;
Packet *p = get(pos);
p->_alloc();
return p;
}
/**
* Reset stream queue
*
* This means that allocation will start at current queue position.
*/
void reset() { _tail = _pos; }
/**********************************************
** Intended to be called by the server side **
**********************************************/
/**
* Submit a packet to the packet queue
*/
void submit(Packet *p) { p->_submit(); }
/**
* Check if stream queue has overrun
*/
bool overrun() const { return (_tail + 1) % QUEUE_SIZE == _pos; }
/**********************************************
** Intended to be called by the client side **
**********************************************/
/**
* Set current stream position
*
* \param pos current position
*/
void pos(unsigned p) { _pos = p; }
/**
* Increment current stream position by one
*/
void increment_position() { _pos = (_pos + 1) % QUEUE_SIZE; }
};
/**
* Audio_in session base
*/
class Audio_in::Session : public Genode::Session
struct Audio_in::Session : Genode::Session
{
protected:
/**
* \noapi
*/
static const char *service_name() { return "Audio_in"; }
Stream *_stream;
enum { CAP_QUOTA = 4 };
public:
/**
* The 'start' signal is sent from the server to the client if
* the client should begin playback.
*/
virtual void start_sigh(Genode::Signal_context_capability sigh) = 0;
/**
* \noapi
*/
static const char *service_name() { return "Audio_in"; }
/**
* The 'reset' signal is sent from the server to the client if
* the client should refresh its progress signal capablity.
*/
virtual void reset_sigh(Genode::Signal_context_capability sigh) = 0;
enum { CAP_QUOTA = 4 };
/**
* The 'progress' signal is sent from the client to the server when
* a packet is processed.
*/
virtual Genode::Signal_context_capability progress_sigh() = 0;
/**
* Return stream of this session, see 'Stream' above
*/
Stream *stream() const { return _stream; }
GENODE_RPC(Rpc_dataspace, Genode::Dataspace_capability, dataspace);
GENODE_RPC(Rpc_start_sigh, void, start_sigh, Genode::Signal_context_capability);
GENODE_RPC(Rpc_reset_sigh, void, reset_sigh, Genode::Signal_context_capability);
GENODE_RPC(Rpc_progress_sigh, Genode::Signal_context_capability, progress_sigh);
/**
* Start recording (alloc and submit packets after calling 'start')
*/
virtual void start() = 0;
/**
* Stop recording
*/
virtual void stop() = 0;
/*************
** Signals **
*************/
/**
* The 'progress' signal is sent from the server to the client if a
* packet has been recorded.
*
* See: client.h, connection.h
*/
virtual void progress_sigh(Genode::Signal_context_capability sigh) = 0;
/**
* The 'overrun' signal is sent from the server to the client if an
* overrun has occured.
*
* See: client.h, connection.h
*/
virtual void overrun_sigh(Genode::Signal_context_capability sigh) = 0;
/**
* The 'data_avail' signal is sent from the server to the client if the
* stream queue leaves the 'empty' state.
*/
virtual Genode::Signal_context_capability data_avail_sigh() = 0;
GENODE_RPC(Rpc_start, void, start);
GENODE_RPC(Rpc_stop, void, stop);
GENODE_RPC(Rpc_dataspace, Genode::Dataspace_capability, dataspace);
GENODE_RPC(Rpc_progress_sigh, void, progress_sigh, Genode::Signal_context_capability);
GENODE_RPC(Rpc_overrun_sigh, void, overrun_sigh, Genode::Signal_context_capability);
GENODE_RPC(Rpc_data_avail_sigh, Genode::Signal_context_capability, data_avail_sigh);
GENODE_RPC_INTERFACE(Rpc_start, Rpc_stop, Rpc_dataspace,
Rpc_progress_sigh, Rpc_overrun_sigh,
Rpc_data_avail_sigh);
GENODE_RPC_INTERFACE(Rpc_dataspace, Rpc_start_sigh, Rpc_reset_sigh, Rpc_progress_sigh);
};
#endif /* _INCLUDE__AUDIO_IN_SESSION__AUDIO_IN_SESSION_H_ */

View File

@ -23,32 +23,17 @@
namespace Audio_in {
struct Signal;
struct Session_client;
class Session_client;
}
struct Audio_in::Signal
{
Genode::Signal_receiver recv;
Genode::Signal_context context;
Genode::Signal_context_capability cap;
Signal() : cap(recv.manage(&context)) { }
~Signal() { recv.dissolve(&context); }
void wait() { recv.wait_for_signal(); }
};
class Audio_in::Session_client : public Genode::Rpc_client<Session>
{
private:
Genode::Attached_dataspace _shared_ds;
Signal _progress;
Genode::Signal_transmitter _data_avail;
Stream_source &_stream = *_shared_ds.local_addr<Stream_source>();
public:
@ -59,66 +44,28 @@ class Audio_in::Session_client : public Genode::Rpc_client<Session>
* \param progress_signal true, install 'progress_signal' receiver
*/
Session_client(Genode::Region_map &rm,
Genode::Capability<Session> session,
bool progress_signal)
Genode::Capability<Session> session)
:
Genode::Rpc_client<Session>(session),
_shared_ds(rm, call<Rpc_dataspace>()),
_data_avail(call<Rpc_data_avail_sigh>())
{
_stream = _shared_ds.local_addr<Stream>();
_shared_ds(rm, call<Rpc_dataspace>())
{ }
if (progress_signal)
progress_sigh(_progress.cap);
}
/*************
** Signals **
*************/
void progress_sigh(Genode::Signal_context_capability sigh) {
call<Rpc_progress_sigh>(sigh); }
void overrun_sigh(Genode::Signal_context_capability sigh) {
call<Rpc_overrun_sigh>(sigh); }
Genode::Signal_context_capability data_avail_sigh() {
return Genode::Signal_context_capability(); }
Stream_source &stream() const { return _stream; }
/***********************
** Session interface **
***********************/
void start()
{
call<Rpc_start>();
void start_sigh(Genode::Signal_context_capability sigh) override {
call<Rpc_start_sigh>(sigh); }
/* reset tail pointer */
stream()->reset();
}
void reset_sigh(Genode::Signal_context_capability sigh) override {
call<Rpc_reset_sigh>(sigh); }
void stop() { call<Rpc_stop>(); }
Genode::Signal_context_capability progress_sigh() override {
return call<Rpc_progress_sigh>(); }
/**********************************
** Session interface extensions **
**********************************/
/**
* Wait for progress signal
*/
void wait_for_progress()
{
if (!_progress.cap.valid()) {
Genode::warning("Progress signal is not installed, will not block "
"(enable in 'Audio_in::Connection')");
return;
}
_progress.wait();
}
};
#endif /* _INCLUDE__AUDIO_IN_SESSION__CLIENT_H_ */

View File

@ -28,10 +28,10 @@ struct Audio_in::Connection : Genode::Connection<Session>, Audio_in::Session_cli
*
* \noapi
*/
Genode::Capability<Audio_in::Session> _session(Genode::Parent &parent, char const *channel)
Genode::Capability<Audio_in::Session> _session(Genode::Parent &parent, char const *label)
{
return session(parent, "ram_quota=%ld, cap_quota=%ld, channel=\"%s\"",
10*1024 + sizeof(Stream), CAP_QUOTA, channel);
return session(parent, "ram_quota=%ld, cap_quota=%ld, label=\"%s\"",
10*1024 + sizeof(Stream), CAP_QUOTA, label);
}
/**
@ -41,10 +41,10 @@ struct Audio_in::Connection : Genode::Connection<Session>, Audio_in::Session_cli
* call 'wait_for_progress', which is sent when the
* server processed one or more packets
*/
Connection(Genode::Env &env, char const *channel, bool progress_signal = false)
Connection(Genode::Env &env, char const *label)
:
Genode::Connection<Session>(env, _session(env.parent(), channel)),
Session_client(env.rm(), cap(), progress_signal)
Genode::Connection<Session>(env, _session(env.parent(), label)),
Session_client(env.rm(), cap())
{ }
};

View File

@ -1,11 +1,12 @@
/*
* \brief Server-side Audio_in session interface
* \author Josef Soentgen
* \author Emery Hemingway
* \date 2015-05-08
*/
/*
* Copyright (C) 2015-2017 Genode Labs GmbH
* Copyright (C) 2015-2018 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
@ -17,7 +18,7 @@
/* Genode includes */
#include <base/env.h>
#include <base/rpc_server.h>
#include <base/attached_ram_dataspace.h>
#include <audio/buffer.h>
#include <audio_in_session/audio_in_session.h>
@ -29,36 +30,40 @@ class Audio_in::Session_rpc_object : public Genode::Rpc_object<Audio_in::Session
{
protected:
Genode::Attached_ram_dataspace _ds; /* contains Audio_in stream */
Audio::Stream_buffer &_buffer;
Genode::Signal_context_capability _data_cap;
Genode::Signal_context_capability _progress_cap;
Genode::Signal_context_capability _overrun_cap;
Genode::Signal_context_capability _progress_cap { };
Genode::Signal_context_capability _overrun_cap { };
Genode::Signal_context_capability _reset_cap { };
bool _stopped; /* state */
public:
Session_rpc_object(Genode::Env &env, Genode::Signal_context_capability data_cap)
Session_rpc_object(Audio::Stream_buffer &buffer,
Genode::Signal_context_capability data_cap)
:
_ds(env.ram(), env.rm(), sizeof(Stream)),
_data_cap(data_cap), _stopped(true)
{
_stream = _ds.local_addr<Stream>();
}
_buffer(buffer), _data_cap(data_cap), _stopped(true)
{ }
Stream_sink &stream() { return _buffer.stream_sink(); }
/**************
** Signals **
**************/
void progress_sigh(Genode::Signal_context_capability sigh) {
void progress_sigh(Genode::Signal_context_capability sigh) override {
_progress_cap = sigh; }
void overrun_sigh(Genode::Signal_context_capability sigh) {
void overrun_sigh(Genode::Signal_context_capability sigh) override {
_overrun_cap = sigh; }
Genode::Signal_context_capability data_avail_sigh() {
void reset_sigh(Genode::Signal_context_capability sigh) override {
_reset_cap = sigh; }
Genode::Signal_context_capability data_avail_sigh() override {
return _data_cap; }
@ -69,7 +74,8 @@ class Audio_in::Session_rpc_object : public Genode::Rpc_object<Audio_in::Session
void start() { _stopped = false; }
void stop() { _stopped = true; }
Genode::Dataspace_capability dataspace() { return _ds.cap(); }
Genode::Dataspace_capability dataspace() {
return _buffer.cap(); }
/**********************************
@ -94,6 +100,15 @@ class Audio_in::Session_rpc_object : public Genode::Rpc_object<Audio_in::Session
Genode::Signal_transmitter(_overrun_cap).submit();
}
/**
* Send 'reset' signal
*/
void send_reset()
{
if (_reset_cap.valid())
Genode::Signal_transmitter(_reset_cap).submit();
}
/**
* Return true if client state is stopped
*/

View File

@ -41,332 +41,43 @@
#include <base/signal.h>
#include <dataspace/capability.h>
#include <session/session.h>
#include <audio/stream.h>
namespace Audio_out {
class Packet;
class Stream;
class Session;
enum {
QUEUE_SIZE = 256, /* buffer queue size */
SAMPLE_RATE = 44100,
SAMPLE_SIZE = sizeof(float),
};
/**
* Samples per perios (~11.6ms)
*/
static constexpr Genode::size_t PERIOD = 512;
}
/**
* Audio_out packet containing frames
*/
class Audio_out::Packet
{
private:
friend class Session_client;
friend class Stream;
bool _valid;
bool _wait_for_play;
float _data[PERIOD];
void _submit() { _valid = true; _wait_for_play = true; }
void _alloc() { _wait_for_play = false; _valid = false; }
public:
Packet() : _valid(false), _wait_for_play(false) { }
/**
* Copy data into packet, if there are less frames given than 'PERIOD',
* the remainder is filled with zeros
*
* \param data frames to copy in
* \param size number of frames to copy
*/
void content(float const *data, Genode::size_t samples)
{
Genode::memcpy(_data, data, (samples > PERIOD ? PERIOD : samples) * SAMPLE_SIZE);
if (samples < PERIOD)
Genode::memset(_data + samples, 0, (PERIOD - samples) * SAMPLE_SIZE);
}
/**
* Get content
*
* \return pointer to frame data
*/
float *content() { return _data; }
/**
* Play state
*
* \return true if the packet has been played back; false otherwise
*/
bool played() const { return !_wait_for_play; }
/**
* Valid state
*
* The valid state of a packet describes that the packet has been
* processed by the server even though it may not have been played back
* if the packet is invalid. For example, if a server is a filter, the
* audio may not have been processed by the output driver.
*
* \return true if packet has *not* been processed yet; false otherwise
*/
bool valid() const { return _valid; }
Genode::size_t size() const { return sizeof(_data); }
/**********************************************
** Intended to be called by the server side **
**********************************************/
/**
* Invalidate packet, thus marking it as processed
*/
void invalidate() { _valid = false; }
/**
* Mark a packet as played
*/
void mark_as_played() { _wait_for_play = false; }
};
/**
* The audio-stream object containing packets
*
* The stream object is created upon session creation. The server will allocate
* a dataspace on the client's account. The client session will then request
* this dataspace and both client and server will attach it in their respective
* protection domain. After that, the stream pointer within a session will be
* pointed to the attached dataspace on both sides. Because the 'Stream' object
* is backed by shared memory, its constructor is never supposed to be called.
*/
class Audio_out::Stream
{
private:
unsigned _pos; /* current playback position */
unsigned _tail; /* tail pointer used for allocations */
Packet _buf[QUEUE_SIZE]; /* packet queue */
public:
/**
* Exceptions
*/
class Alloc_failed { };
/**
* Current audio playback position
*
* \return position
*/
unsigned pos() const { return _pos; }
/**
* Current audio allocation position
*
* \return position
*/
unsigned tail() const { return _tail; }
/**
* Number of packets between playback and allocation position
*
* \return number
*/
unsigned queued() const
{
if (_tail > _pos)
return _tail - _pos;
else if (_pos > _tail)
return QUEUE_SIZE - (_pos - _tail);
else
return 0;
}
/**
* Retrieve next packet for given packet
*
* \param packet preceding packet
*
* \return Successor of packet or successor of current position if
* 'packet' is zero
*/
Packet *next(Packet *packet = 0)
{
return packet ? get(packet_position(packet) + 1) : get(pos() + 1);
}
/**
* Retrieves the position of a given packet in the stream queue
*
* \param packet a packet
*
* \return position in stream queue
*/
unsigned packet_position(Packet *packet) { return packet - &_buf[0]; }
/**
* Check if stream queue is full/empty
*/
bool empty() const
{
bool valid = false;
for (int i = 0; i < QUEUE_SIZE; i++)
valid |= _buf[i].valid();
return !valid;
}
bool full() const { return (_tail + 1) % QUEUE_SIZE == _pos; }
/**
* Retrieve an audio at given position
*
* \param pos position in stream
*
* \return Audio_out packet
*/
Packet *get(unsigned pos) { return &_buf[pos % QUEUE_SIZE]; }
/**
* Allocate a packet in stream
*
* \return Packet
* \throw Alloc_failed when stream queue is full
*/
Packet *alloc()
{
if (full())
throw Alloc_failed();
unsigned pos = _tail;
_tail = (_tail + 1) % QUEUE_SIZE;
Packet *p = get(pos);
p->_alloc();
return p;
}
/**
* Reset stream queue
*
* This means that allocation will start at current queue position.
*/
void reset() { _tail = (_pos + 1) % QUEUE_SIZE; }
/**
* Invalidate all packets in stream queue
*/
void invalidate_all()
{
for (int i = 0; i < QUEUE_SIZE; i++)
_buf[i]._valid = false;
}
/**********************************************
** Intended to be called by the server side **
***********************************************/
/**
* Set current stream position
*
* \param pos current position
*/
void pos(unsigned p) { _pos = p; }
/**
* Increment current stream position by one
*/
void increment_position() { _pos = (_pos + 1) % QUEUE_SIZE; }
using namespace Audio;
class Session;
};
/**
* Audio_out session base
*/
class Audio_out::Session : public Genode::Session
struct Audio_out::Session : Genode::Session
{
protected:
/**
* \noapi
*/
static const char *service_name() { return "Audio_out"; }
Stream *_stream = nullptr;
enum { CAP_QUOTA = 4 };
public:
/**
* Request the server begin playback
*/
virtual void start() = 0;
/**
* \noapi
*/
static const char *service_name() { return "Audio_out"; }
/**
* The 'progress' signal is sent from the client to the server when
* when a packet period is processed.
*/
virtual void progress_sigh(Genode::Signal_context_capability) = 0;
enum { CAP_QUOTA = 4 };
GENODE_RPC(Rpc_dataspace, Genode::Dataspace_capability, dataspace);
GENODE_RPC(Rpc_start, void, start);
GENODE_RPC(Rpc_progress_sigh, void, progress_sigh, Genode::Signal_context_capability);
/**
* Return stream of this session, see 'Stream' above
*/
Stream *stream() const { return _stream; }
/**
* Start playback (alloc and submit packets after calling 'start')
*/
virtual void start() = 0;
/**
* Stop playback
*/
virtual void stop() = 0;
/*************
** Signals **
*************/
/**
* The 'progress' signal is sent from the server to the client if a
* packet has been played.
*
* See: client.h, connection.h
*/
virtual void progress_sigh(Genode::Signal_context_capability sigh) = 0;
/**
* The 'alloc' signal is sent from the server to the client when the
* stream queue leaves the 'full' state.
*
* See: client.h, connection.h
*/
virtual void alloc_sigh(Genode::Signal_context_capability sigh) = 0;
/**
* The 'data_avail' signal is sent from the client to the server if the
* stream queue leaves the 'empty' state.
*/
virtual Genode::Signal_context_capability data_avail_sigh() = 0;
GENODE_RPC(Rpc_start, void, start);
GENODE_RPC(Rpc_stop, void, stop);
GENODE_RPC(Rpc_dataspace, Genode::Dataspace_capability, dataspace);
GENODE_RPC(Rpc_progress_sigh, void, progress_sigh, Genode::Signal_context_capability);
GENODE_RPC(Rpc_alloc_sigh, void, alloc_sigh, Genode::Signal_context_capability);
GENODE_RPC(Rpc_data_avail_sigh, Genode::Signal_context_capability, data_avail_sigh);
GENODE_RPC_INTERFACE(Rpc_start, Rpc_stop, Rpc_dataspace, Rpc_progress_sigh,
Rpc_alloc_sigh, Rpc_data_avail_sigh);
GENODE_RPC_INTERFACE(Rpc_dataspace, Rpc_start, Rpc_progress_sigh);
};
#endif /* _INCLUDE__AUDIO_OUT_SESSION__AUDIO_OUT_SESSION_H_ */

View File

@ -25,29 +25,13 @@ namespace Audio_out {
}
struct Audio_out::Signal
{
Genode::Signal_receiver recv { };
Genode::Signal_context context { };
Genode::Signal_context_capability cap;
Signal() : cap(recv.manage(&context)) { }
~Signal() { recv.dissolve(&context); }
void wait() { recv.wait_for_signal(); }
};
class Audio_out::Session_client : public Genode::Rpc_client<Session>
{
private:
Genode::Attached_dataspace _shared_ds;
Signal _progress { };
Signal _alloc { };
Genode::Signal_transmitter _data_avail;
Stream_sink &_stream = *_shared_ds.local_addr<Stream_sink>();
public:
@ -60,98 +44,23 @@ class Audio_out::Session_client : public Genode::Rpc_client<Session>
* \param progress_signal true, install 'progress_signal' receiver
*/
Session_client(Genode::Region_map &rm,
Genode::Capability<Session> session,
bool alloc_signal, bool progress_signal)
Genode::Capability<Session> session)
:
Genode::Rpc_client<Session>(session),
_shared_ds(rm, call<Rpc_dataspace>()),
_data_avail(call<Rpc_data_avail_sigh>())
{
_stream = _shared_ds.local_addr<Stream>();
if (progress_signal)
progress_sigh(_progress.cap);
if (alloc_signal)
alloc_sigh(_alloc.cap);
}
/*************
** Signals **
*************/
void progress_sigh(Genode::Signal_context_capability sigh) override {
call<Rpc_progress_sigh>(sigh); }
void alloc_sigh(Genode::Signal_context_capability sigh) override {
call<Rpc_alloc_sigh>(sigh); }
Genode::Signal_context_capability data_avail_sigh() override {
return Genode::Signal_context_capability(); }
_shared_ds(rm, call<Rpc_dataspace>())
{ }
Stream_sink &stream() { return _stream; }
/***********************
** Session interface **
***********************/
void start() override
{
call<Rpc_start>();
void start() override {
call<Rpc_start>(); }
/* reset tail pointer */
stream()->reset();
}
void stop() override { call<Rpc_stop>(); }
/**********************************
** Session interface extensions **
**********************************/
/**
* Wait for progress signal
*/
void wait_for_progress()
{
if (!_progress.cap.valid()) {
Genode::warning("Progress signal is not installed, will not block "
"(enable in 'Audio_out::Connection')");
return;
}
_progress.wait();
}
/**
* Wait for allocation signal
*
* This can be used when the 'Stream' is full and the application wants
* to block until the stream has free elements again.
*/
void wait_for_alloc()
{
if (!_alloc.cap.valid()) {
Genode::warning("Alloc signal is not installed, will not block "
"(enable in 'Audio_out::Connection')");
return;
}
_alloc.wait();
}
/**
* Submit a packet
*/
void submit(Packet *packet)
{
bool empty = stream()->empty();
packet->_submit();
if (empty)
_data_avail.submit();
}
void progress_sigh(Genode::Signal_context_capability sigh) override {
call<Rpc_progress_sigh>(sigh); }
};
#endif /* _INCLUDE__AUDIO_OUT_SESSION__CLIENT_H_ */

View File

@ -23,10 +23,21 @@ namespace Audio_out { struct Connection; }
struct Audio_out::Connection : Genode::Connection<Session>, Audio_out::Session_client
{
/**
* Issue session request
*
* \noapi
*/
Genode::Capability<Audio_out::Session> _session(Genode::Parent &parent, char const *label)
{
return session(parent, "ram_quota=%ld, cap_quota=%ld, label=\"%s\"",
2*4096 + 2048 + sizeof(Stream), CAP_QUOTA, label);
}
/**
* Constructor
*
* \param channel channel identifier (e.g., "front left")
* \param label channel identifier (e.g., "front left")
* \param alloc_signal install 'alloc_signal', the client may then use
* 'wait_for_alloc' when the stream is full
* \param progress_signal install progress signal, the client may then
@ -34,15 +45,10 @@ struct Audio_out::Connection : Genode::Connection<Session>, Audio_out::Session_c
* server processed one or more packets
*/
Connection(Genode::Env &env,
char const *channel,
bool alloc_signal = true,
bool progress_signal = false)
char const *label)
:
Genode::Connection<Session>(env,
session(env.parent(),
"ram_quota=%ld, cap_quota=%ld, channel=\"%s\"",
2*4096 + 2048 + sizeof(Stream), CAP_QUOTA, channel)),
Session_client(env.rm(), cap(), alloc_signal, progress_signal)
Genode::Connection<Session>(env, _session(env.parent(), label)),
Session_client(env.rm(), cap())
{ }
};

View File

@ -1,11 +1,12 @@
/*
* \brief Server-side audio-session interface
* \author Sebastian Sumpf
* \author Emery Hemingway
* \date 2012-12-10
*/
/*
* Copyright (C) 2012-2017 Genode Labs GmbH
* Copyright (C) 2012-2018 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
@ -16,7 +17,7 @@
#include <base/env.h>
#include <base/rpc_server.h>
#include <base/attached_ram_dataspace.h>
#include <audio/buffer.h>
#include <audio_out_session/audio_out_session.h>
@ -28,11 +29,13 @@ class Audio_out::Session_rpc_object : public Genode::Rpc_object<Audio_out::Sessi
{
protected:
Genode::Attached_ram_dataspace _ds; /* contains Audio_out stream */
Audio::Stream_buffer &_buffer;
Genode::Signal_transmitter _progress { };
Genode::Signal_transmitter _alloc { };
Genode::Signal_context_capability _data_cap;
Genode::Signal_context_capability _reset_cap { };
bool _stopped; /* state */
bool _progress_sigh; /* progress signal on/off */
@ -40,14 +43,14 @@ class Audio_out::Session_rpc_object : public Genode::Rpc_object<Audio_out::Sessi
public:
Session_rpc_object(Genode::Env &env, Genode::Signal_context_capability data_cap)
Session_rpc_object(Audio::Stream_buffer &buffer,
Genode::Signal_context_capability data_cap)
:
_ds(env.ram(), env.rm(), sizeof(Stream)),
_data_cap(data_cap),
_buffer(buffer), _data_cap(data_cap),
_stopped(true), _progress_sigh(false), _alloc_sigh(false)
{
_stream = _ds.local_addr<Stream>();
}
{ }
Stream_source &stream() { return _buffer.stream_source(); }
/**************
@ -60,7 +63,7 @@ class Audio_out::Session_rpc_object : public Genode::Rpc_object<Audio_out::Sessi
_progress_sigh = true;
}
Genode::Signal_context_capability data_avail_sigh() override {
Genode::Signal_context_capability data_avail_sigh() {
return _data_cap; }
void alloc_sigh(Genode::Signal_context_capability sigh) override
@ -69,6 +72,9 @@ class Audio_out::Session_rpc_object : public Genode::Rpc_object<Audio_out::Sessi
_alloc_sigh = true;
}
void reset_sigh(Genode::Signal_context_capability sigh) override {
_reset_cap = sigh; }
/***********************
** Session interface **
@ -77,7 +83,7 @@ class Audio_out::Session_rpc_object : public Genode::Rpc_object<Audio_out::Sessi
void start() override { _stopped = false; }
void stop() override { _stopped = true; }
Genode::Dataspace_capability dataspace() { return _ds.cap(); }
Genode::Dataspace_capability dataspace() { return _buffer.cap(); }
/**********************************
@ -102,6 +108,15 @@ class Audio_out::Session_rpc_object : public Genode::Rpc_object<Audio_out::Sessi
_alloc.submit();
}
/**
* Send 'reset' signal
*/
void send_reset()
{
if (_reset_cap.valid())
Genode::Signal_transmitter(_reset_cap).submit();
}
/**
* Return true if client state is stopped
*/

View File

@ -0,0 +1,6 @@
MIRRORED_FROM_REP_DIR := \
include/audio \
include/audio_in_session \
include/audio_out_session \
include $(REP_DIR)/recipes/api/session.inc

View File

@ -0,0 +1 @@
2019-06-19 86b61718dbb81b00ba951ab781db5480bb79cdc3

View File

@ -1 +1 @@
2019-06-11 185683e55f3e30c7e25818e9fe52a31c2c519dce
2019-06-19-a a2333013344441751285c5513dd3eb22c20a35f7

View File

@ -1 +1 @@
2019-06-11 2d70cfb5f5273e8cc062db560a2b2f907cabe083
2019-06-19-a 3061bc4e9363d005c52b1f3b41bffff655e4e2d5

View File

@ -1 +1 @@
2019-06-05 543090d6e662da831709a3d8eff301813bf9b240
2019-06-19 691f47ca885eee15bc391ad01f0c4838a7b9bbf1

View File

@ -1 +1 @@
2019-05-29 6cf16fc0dd397de8cae65ea9ac6db49cb39570a5
2019-06-19 36dd1f921965a7691d83695062d2b6daf18c63de

View File

@ -1 +1 @@
2019-05-29 c6de3d407506f2cba25bfa903449110998632cc1
2019-06-19 9b8caa55f6fc3eaaa36aa91b3e7ac33edf341e19

View File

@ -1 +1 @@
2019-06-05 d6f30181ae3bf8168f071903ec5b16f7c1bd9fa1
2019-06-19 11fefe86cd6b4f8630e5430c214ce640f93f134f

View File

@ -1 +1 @@
2019-06-05 e8490a608ee00b382943ff0355c520cf42a00023
2019-06-19 859319642ce21170060d168ec86c91a003e68a29

View File

@ -0,0 +1,6 @@
SRC_DIR = src/server/stereo_patch
include $(GENODE_DIR)/repos/base/recipes/src/content.inc
content:
mkdir -p src/server/mixer
cp $(REP_DIR)/src/server/cached_fs_rom/session_requests.h $(SRC_DIR)

View File

@ -0,0 +1 @@
2019-06-19-b 6c9619cc5e111b88429950fc761f731edbbe5ee2

View File

@ -0,0 +1,3 @@
base
os
audio

View File

@ -1 +1 @@
2019-06-05 4a16d02131da517cca89dfcc6d78bae48b96d31b
2019-06-19 e670ef23fc041d8dea4bd74ae9d089403b5df863

117
repos/os/run/patch.run Normal file
View File

@ -0,0 +1,117 @@
#
# Build
#
# generic components
set build_components {
core init timer
app/mp3_audio_sink
app/pipe
drivers/audio/dummy
server/stereo_patch
test/audio_out
}
source ${genode_dir}/repos/base/run/platform_drv.inc
append_platform_drv_build_components
build $build_components
create_boot_directory
#
# Config
#
set config {
<config>
<parent-provides>
<service name="ROM"/>
<service name="IRQ"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
<service name="PD"/>
<service name="RM"/>
<service name="CPU"/>
<service name="LOG"/>
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<default caps="128"/>}
append_platform_drv_config
append config {
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides><service name="Timer"/></provides>
</start>
<start name="audio_drv">
<binary name="dummy-audio_drv"/>
<resource name="RAM" quantum="8M"/>
</start>
<start name="stereo_patch">
<resource name="RAM" quantum="2M"/>
<provides>
<service name="Audio_in"/>
<service name="Audio_out"/>
</provides>
<route>
<any-service> <parent/> </any-service>
</route>
</start>
<start name="client">
<binary name="mp3_audio_sink"/>
<resource name="RAM" quantum="8M"/>
<provides> <service name="Terminal"/> </provides>
<config/>
</start>
<start name="pipe">
<resource name="RAM" quantum="4M"/>
<config>
<libc stdin="/test.mp3" stdout="/terminal"/>
<vfs> <rom name="test.mp3"/> <terminal/> </vfs>
</config>
</start>
</config>}
install_config $config
if {[expr ![file exists bin/client1.f32] || ![file exists bin/client2.f32]]} {
puts ""
puts "The sample files are missing. Please take a look at repos/dde_bsd/README"
puts "and create 'client1.f32' and 'client2.f32'. Afterwards put them into './bin'."
puts ""
exit 1
}
#
# Boot modules
#
# generic modules
append boot_modules {
core ld.lib.so init timer
dummy-audio_drv test-audio_out
stereo_patch client1.f32 client2.f32
pipe mp3_audio_sink
libmpg123.lib.so
libm.lib.so libc.lib.so
test.mp3
vfs.lib.so
posix.lib.so
}
# platform-specific components
append_platform_drv_boot_modules
build_boot_image $boot_modules
append qemu_args "-soundhw es1370 -nographic"
run_genode_until forever

View File

@ -0,0 +1,65 @@
/*
* \brief Dummy audio driver
* \author Emery Hemingway
* \date 2018-06-05
*/
/*
* Copyright (C) 2019 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <audio/sink.h>
#include <timer_session/connection.h>
#include <base/component.h>
namespace Dummy {
using namespace Genode;
using namespace Audio_in;
struct Driver;
};
struct Dummy::Driver : Audio::Sink
{
Genode::Env &env;
Stereo_in stereo_in { env, *this };
Timer::Connection timer { env, "sync" };
enum { PROGRESS_RATE_US =
((1000*1000) / SAMPLE_RATE) * (PERIOD-1) };
Timer::Periodic_timeout<Driver> timeout {
timer, *this, &Driver::sync,
Microseconds(PROGRESS_RATE_US) };
Signal_context_capability progress_sigh { };
void sync(Duration)
{
stereo_in.progress();
}
Driver(Genode::Env &e) : env(e) { };
/*****************
** Audio::Sink **
*****************/
bool drain(float const *, float const *, Genode::size_t) override
{
return true;
}
};
void Component::construct(Genode::Env &env)
{
static Dummy::Driver inst(env);
}

View File

@ -0,0 +1,3 @@
TARGET = dummy-audio_drv
LIBS += base
SRC_CC += component.cc

View File

@ -6,13 +6,14 @@
*/
/*
* Copyright (C) 2009-2017 Genode Labs GmbH
* Copyright (C) 2009-2019 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#include <alsa/asoundlib.h>
#include <audio/parameters.h>
#include "alsa.h"
@ -20,7 +21,7 @@ static snd_pcm_t *playback_handle;
int audio_drv_init(char const * const device)
{
unsigned int rate = 44100;
unsigned int rate = GENODE_AUDIO_SAMPLE_RATE;
int err;
snd_pcm_hw_params_t *hw_params;

View File

@ -3,301 +3,76 @@
* \author Christian Helmuth
* \author Sebastian Sumpf
* \author Josef Soentgen
* \author Emery Hemingway
* \date 2010-05-11
*
* FIXME session and driver shutdown not implemented (audio_drv_stop)
*/
/*
* Copyright (C) 2010-2017 Genode Labs GmbH
* Copyright (C) 2010-2019 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <audio/sink.h>
#include <timer_session/connection.h>
#include <base/attached_rom_dataspace.h>
#include <base/component.h>
#include <base/log.h>
#include <base/sleep.h>
#include <base/heap.h>
#include <root/component.h>
#include <audio_out_session/rpc_object.h>
#include <util/misc_math.h>
#include <timer_session/connection.h>
/* local includes */
#include <alsa.h>
using namespace Genode;
enum Channel_number { LEFT, RIGHT, MAX_CHANNELS, INVALID = MAX_CHANNELS };
namespace Audio_out
namespace Audio
{
class Session_component;
class Out;
class Root;
struct Root_policy;
struct Main;
static Session_component *channel_acquired[MAX_CHANNELS];
using namespace Genode;
struct Driver;
};
class Audio_out::Session_component : public Audio_out::Session_rpc_object
struct Audio::Driver : Audio::Sink
{
private:
Stereo_in stereo_in;
Channel_number _channel;
Timer::Connection timer;
public:
Session_component(Genode::Env &env, Channel_number channel, Signal_context_capability data_cap)
:
Session_rpc_object(env, data_cap),
_channel(channel)
{
Audio_out::channel_acquired[_channel] = this;
}
~Session_component()
{
Audio_out::channel_acquired[_channel] = 0;
}
};
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 }
Timer::Periodic_timeout<Driver> packet_timeout {
timer, *this, &Driver::process,
Microseconds{(1000000 / Audio::SAMPLE_RATE) * (Audio::PERIOD-1)}
};
for (Names *n = names; n->name; ++n)
if (!strcmp(name, n->name)) {
*out_number = n->number;
return true;
}
return false;
}
/*
* Root component, handling new session requests.
*/
class Audio_out::Out
{
private:
Genode::Env &_env;
Genode::Signal_handler<Audio_out::Out> _data_avail_dispatcher;
Genode::Signal_handler<Audio_out::Out> _timer_dispatcher;
Timer::Connection _timer { _env };
bool _active() {
return channel_acquired[LEFT] && channel_acquired[RIGHT] &&
channel_acquired[LEFT]->active() && channel_acquired[RIGHT]->active();
}
Stream *left() { return channel_acquired[LEFT]->stream(); }
Stream *right() { return channel_acquired[RIGHT]->stream(); }
void _advance_position(Packet *l, Packet *r)
{
bool full_left = left()->full();
bool full_right = right()->full();
left()->pos(left()->packet_position(l));
right()->pos(right()->packet_position(r));
left()->increment_position();
right()->increment_position();
Session_component *channel_left = channel_acquired[LEFT];
Session_component *channel_right = channel_acquired[RIGHT];
if (full_left)
channel_left->alloc_submit();
if (full_right)
channel_right->alloc_submit();
channel_left->progress_submit();
channel_right->progress_submit();
}
bool _play_packet()
{
Packet *p_left = left()->get(left()->pos());
Packet *p_right = right()->get(left()->pos());
/* convert float to S16LE */
static short data[2 * PERIOD];
if (p_left->valid() && p_right->valid()) {
for (unsigned i = 0; i < 2 * PERIOD; i += 2) {
data[i] = p_left->content()[i / 2] * 32767;
data[i + 1] = p_right->content()[i / 2] * 32767;
}
p_left->invalidate();
p_right->invalidate();
/* blocking-write packet to ALSA */
while (audio_drv_play(data, PERIOD)) {
/* try to restart the driver silently */
audio_drv_stop();
audio_drv_start();
}
p_left->mark_as_played();
p_right->mark_as_played();
}
_advance_position(p_left, p_right);
return true;
}
void _handle_data_avail() { }
void _handle_timer()
{
if (_active()) _play_packet();
}
public:
Out(Genode::Env &env)
:
_env(env),
_data_avail_dispatcher(env.ep(), *this, &Audio_out::Out::_handle_data_avail),
_timer_dispatcher(env.ep(), *this, &Audio_out::Out::_handle_timer)
{
_timer.sigh(_timer_dispatcher);
uint64_t const us = (Audio_out::PERIOD * 1000 / Audio_out::SAMPLE_RATE)*1000;
_timer.trigger_periodic(us);
}
Signal_context_capability data_avail_sigh() { return _data_avail_dispatcher; }
};
/**
* Session creation policy for our service
*/
struct Audio_out::Root_policy
{
void aquire(const char *args)
Driver(Genode::Env &env) : stereo_in(env, *this), timer(env)
{
size_t ram_quota =
Arg_string::find_arg(args, "ram_quota" ).ulong_value(0);
size_t session_size =
align_addr(sizeof(Audio_out::Session_component), 12);
if ((ram_quota < session_size) ||
(sizeof(Stream) > ram_quota - session_size)) {
Genode::error("insufficient 'ram_quota', got ", ram_quota,
" need ", sizeof(Stream) + session_size);
throw Genode::Insufficient_ram_quota();
}
char channel_name[16];
Channel_number channel_number;
Arg_string::find_arg(args, "channel").string(channel_name,
sizeof(channel_name),
"left");
if (!channel_number_from_string(channel_name, &channel_number))
throw Genode::Service_denied();
if (Audio_out::channel_acquired[channel_number])
throw Genode::Service_denied();
audio_drv_start();
}
void release() { }
};
void process(Duration) {
stereo_in.progress(); }
namespace Audio_out {
typedef Root_component<Session_component, Root_policy> Root_component;
}
/*****************
** Audio::Sink **
*****************/
class Audio_out::Root : public Audio_out::Root_component
{
private:
Genode::Env &_env;
Signal_context_capability _data_cap;
protected:
Session_component *_create_session(const char *args) override
{
char channel_name[16];
Channel_number channel_number = INVALID;
Arg_string::find_arg(args, "channel").string(channel_name,
sizeof(channel_name),
"left");
channel_number_from_string(channel_name, &channel_number);
return new (md_alloc())
Session_component(_env, channel_number, _data_cap);
}
public:
Root(Genode::Env &env, Allocator &md_alloc,
Signal_context_capability data_cap)
: Root_component(env.ep(), md_alloc), _env(env), _data_cap(data_cap)
{ }
};
struct Audio_out::Main
{
Genode::Env &env;
Genode::Heap heap { env.ram(), env.rm() };
Genode::Attached_rom_dataspace config { env, "config" };
Main(Genode::Env &env) : env(env)
bool drain(float const *left, float const *right, size_t samples) override
{
char dev[32] = { 'h', 'w', 0 };
try {
config.xml().attribute("alsa_device").value(dev, sizeof(dev));
} catch (...) { }
/* convert float to S16LE */
short data[2 * samples];
/* init ALSA */
int err = audio_drv_init(dev);
if (err) {
if (err == -1) {
Genode::error("could not open ALSA device ", Genode::Cstring(dev));
} else {
Genode::error("could not initialize driver error ", err);
}
throw -1;
for (unsigned i = 0; i < 2 * samples; i += 2) {
data[i|0] = left[i>>1] * 32767;
data[i|1] = right[i>>1] * 32767;
}
audio_drv_start();
static Audio_out::Out out(env);
static Audio_out::Root root(env, heap, out.data_avail_sigh());
env.parent().announce(env.ep().manage(root));
Genode::log("--- start Audio_out ALSA driver ---");
/* blocking-write packet to ALSA */
if (audio_drv_play(data, samples)) {
/* try to restart the driver silently */
audio_drv_stop();
audio_drv_start();
return false;
}
return true;
}
};
@ -306,4 +81,24 @@ struct Audio_out::Main
** Component **
***************/
void Component::construct(Genode::Env &env) { static Audio_out::Main main(env); }
void Component::construct(Genode::Env &env)
{
Genode::Attached_rom_dataspace config { env, "config" };
typedef Genode::String<32> String;
auto dev = config.xml().attribute_value("alsa_device", String("hw"));
/* init ALSA */
if (int err = audio_drv_init(dev.string())) {
if (err == -1) {
Genode::error("could not open ALSA device ", dev);
} else {
Genode::error("could not initialize driver error ", err);
}
env.parent().exit(err);
return;
}
static Audio::Driver inst(env);
}

View File

@ -1,63 +0,0 @@
The mixer component implements a simple Audio_out session mixer. Input
packets from multiple sources are mixed together into one output packet.
The mixer can be tested by executing the 'repos/os/run/mixer.run' run
script.
Configuration
=============
The mixer gets its configuration via a ROM module called 'mixer.config'.
The following configuration snippet illustrates its structure:
! <config>
! <default out_volume="75" volume="25" muted="0"/>
! <channel_list>
! <channel type="input" label="client" number="0" volume="75" muted="1"/>
! <channel type="input" label="client" number="1" volume="15" muted="1"/>
! <channel type="output" label="master" number="0" volume="100" muted="0"/>
! <channel type="output" label="master" number="1" volume="100" muted="0"/>
! </channel_list>
! </config>
The '<default>' node is used to set up the initial settings for new clients.
According to this configuration every new client will start with a volume
level set to 25 and is not muted. The initial output volume level is set
to 75 (the volume level ranges from 0 to 100). The '<channel_list>' node
contains all (pre-)configured channels. Each '<channel>' node has several
mandatory attributes: 'type' specifies its type and is either 'input'
or 'output', the 'label' attribute contains the label of a client for an input
node and the label 'master' for an output node, 'number' specifies the channel
number (0 for left and 1 for right), the 'volume' attribute sets the volume
level and 'muted' marks the channel as muted. In addition, there are optional
read-only channel attributes which are mainly used by the channel list report.
Channel list report
===================
The mixer reports all available channels in its 'channel_list' report.
The report contains a `<channel_list>' node that is similar to the one
used in the 'mixer.config':
! <channel_list>
! <channel type="input" label="client0" name="left" number="0" active="1" volume="100" muted="0"/>
! <channel type="input" label="client0" name="right" number="1" active="1" volume="100" muted="0"/>
! <channel type="input" label="client1" name="left" number="0" active="0" volume="25" muted="0"/>
! <channel type="input" label="client1" name="right" number="1" active="0" volume="25" muted="0"/>
! <channel type="output" label="master" name="left" number="0" active="1" volume="100" muted="0"/>
! <channel type="output" label="master" name="right" number="1" active="1" volume="100" muted="0"/>
! </channel_list>
Each channel node features all mandatory attributes as well as a few optional
ones. The 'name' attribute contains the name of the channel. It is the
alphanumeric description of the numeric 'number' attribute. The 'active'
attribute indicates whether a channel is currently playing or not.
A 'channel_list' report may by used to create a new configuration for the
mixer. Every time the available channels change, e.g. when a new client
appears, a new report is generated by the mixer. In return this report can
then be used to configure the volume level of the new client. A new report
is also generated after a new configuration has been applied by the mixer.

View File

@ -0,0 +1,223 @@
/*
* \brief Audio mixer component
* \author Emery Hemingway
* \date 2019-06-28
*/
/*
* Copyright (C) 2019 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <audio_in_session/audio_in_session.h>
#include <audio_in_session/capability.h>
#include <audio_out_session/audio_out_session.h>
#include <audio_out_session/capability.h>
#include <audio/buffer.h>
#include <os/session_policy.h>
#include <base/registry.h>
#include <base/attached_ram_dataspace.h>
#include <base/rpc_server.h>
#include <base/heap.h>
#include <base/component.h>
/* local includes */
#include "session_requests.h"
namespace Mixer
{
using namespace Genode;
struct Output_component;
struct Input_component;
struct Main;
typedef Genode::Registered<Input_component> Registered_input;
typedef Genode::Registry<Registered_input> Registered_inputs;
}
struct Mixer::Output_component
: public Genode::Rpc_object<Audio_in::Session, Output_component>
{
Audio::Stream_buffer buffer;
Signal_context_capability progress_cap;
Session_label const label;
Parent::Server::Id const session_id;
Output_component(Parent::Server::Id id,
Session_label &label,
Genode::Ram_allocator &ram, Genode::Region_map &rm,
Signal_context_capability progress)
: buffer(ram, rm), progress_cap(progress), label(label), session_id(id)
{ }
/************************
** Audio_in interface **
************************/
Genode::Dataspace_capability dataspace() {
return buffer.cap(); }
void start_sigh(Signal_context_capability) override { }
void reset_sigh(Signal_context_capability) override { }
Signal_context_capability progress_sigh() override {
return progress_cap; }
};
struct Mixer::Input_component
: public Genode::Rpc_object<Audio_out::Session, Input_component>
{
Audio::Stream_buffer buffer;
Signal_context_capability progress_cap { };
Session_label const label;
Parent::Server::Id const session_id;
unsigned const channel;
Input_component (Parent::Server::Id id,
Session_label &label, unsigned channel,
Genode::Ram_allocator &ram, Genode::Region_map &rm)
: buffer(ram, rm), label(label), session_id(id), channel(channel)
{ }
/************************
** Audio_out interface **
************************/
Genode::Dataspace_capability dataspace() {
return buffer.cap(); }
void start() override { }
void progress_sigh(Signal_context_capability sigh) override
{
progress_cap = sigh;
if (progress_cap.valid())
Signal_transmitter(progress_cap).submit();
}
};
struct Mixer::Main : Genode::Session_request_handler
{
enum { CHANNELS = 1<<1 };
Genode::Env &_env;
Sliced_heap _sliced_heap { _env.pd(), _env.rm() };
Attached_rom_dataspace _config_rom { _env, "config" };
Session_requests_rom _requests_handler { _env, *this };
Signal_handler<Main> _progress_handler
{ _env.ep(), *this, &Main::_progress };
Constructible<Output_component> _output_0 { };
Constructible<Output_component> _output_1 { };
Constructible<Output_component> *_outputs[CHANNELS] { &_output_0, &_output_1 };
Registered_inputs _inputs { };
void _progress()
{
using namespace Audio;
for (int i = 0; i < CHANNELS; ++i)
if (!_outputs[i]->constructed())
return;
unsigned pos = (*_outputs[0])->buffer.stream_sink().record_pos();
Packet *out_pkts[CHANNELS];
for (int i = 0; i < CHANNELS; ++i) {
out_pkts[i] = (*_outputs[i])->buffer.stream_sink().get(pos);
out_pkts[i]->content(nullptr, 0);
}
_inputs.for_each([&] (Registered_input &input) {
input.buffer.stream_source().play(pos, [&] (Packet &in) {
Packet &out = *out_pkts[input.channel];
for (unsigned sample = 0; sample < Audio::PERIOD; ++sample) {
out.content()[sample] += in.content()[sample];
}
});
});
for (int i = 0; i < CHANNELS; ++i) {
(*_outputs[i])->buffer.stream_sink().increment_position();
}
}
Main(Genode::Env &env) : _env(env) { }
void handle_session_create(Session_state::Name const &name,
Parent::Server::Id id,
Session_state::Args const &args) override
{
_config_rom.update();
Session_label label = label_from_args(args.string());
Session_policy policy(label, _config_rom.xml());
// only two channels supported, so drop all but the first channel bit
unsigned const chan = (CHANNELS-1) & policy.attribute_value("channel", 0U);
if (name == "Audio_out") {
Input_component *session = new (_sliced_heap)
Registered_input(_inputs, id, label, chan, _env.pd(), _env.rm());
_env.parent().deliver_session_cap(id, _env.ep().manage(*session));
} else
if (name == "Audio_in") {
Audio_in::Session_capability session_cap { };
if (_outputs[chan]->constructed())
return; // defer request
// Allow channel 0 to drive progress handler
_outputs[chan]->construct(
id, label, _env.pd(), _env.rm(),
chan == 0 ? _progress_handler : Signal_context_capability());
_env.parent().deliver_session_cap(id, _env.ep().manage(*(*_outputs[chan])));
} else
throw Service_denied();
}
void handle_session_close(Parent::Server::Id id) override
{
Input_component *input { nullptr };
_inputs.for_each([&] (Registered_input &other) {
if (!input && other.session_id == id)
input = &other;
});
if (input)
destroy(_sliced_heap, input);
else {
for (int i = 0; i < CHANNELS; ++i) {
if ((*_outputs[i])->session_id == id) {
(*_outputs[i]).destruct();
}
}
}
}
};
void Component::construct(Genode::Env &env)
{
static Mixer::Main inst(env);
}

View File

@ -1,767 +0,0 @@
/*
* \brief Audio_out Mixer
* \author Sebastian Sumpf
* \author Josef Soentgen
* \date 2012-12-20
*
* The mixer implements the audio session on the server side. For each channel
* (currently 'left' and 'right' only) it supports multiple client sessions and
* mixes all input sessions into a single client audio output session.
*
*
* There is a session list (Mixer::Session_channel) for each output channel that
* contains multiple input sessions (Audio_out::Session_elem). For every packet
* in the output queue the mixer sums the corresponding packets from all input
* sessions up. The volume level of an input packet is applied in a linear way
* (sample_value * volume_level) and the output packet is clipped at [1.0,-1.0].
*/
/*
* Copyright (C) 2009-2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <mixer/channel.h>
#include <os/reporter.h>
#include <root/component.h>
#include <util/retry.h>
#include <util/string.h>
#include <util/xml_node.h>
#include <audio_out_session/connection.h>
#include <audio_out_session/rpc_object.h>
#include <timer_session/connection.h>
#include <base/attached_rom_dataspace.h>
#include <base/heap.h>
#include <base/component.h>
#include <base/log.h>
typedef Mixer::Channel Channel;
enum {
LEFT = Channel::Number::LEFT,
RIGHT = Channel::Number::RIGHT,
MAX_CHANNELS = Channel::Number::MAX_CHANNELS,
MAX_VOLUME = Channel::Volume_level::MAX,
};
static struct Names {
char const *name;
Channel::Number number;
} names[] = {
{ "left", (Channel::Number)LEFT },
{ "front left", (Channel::Number)LEFT },
{ "right", (Channel::Number)RIGHT },
{ "front right", (Channel::Number)RIGHT },
{ nullptr, (Channel::Number)MAX_CHANNELS }
};
static Channel::Number number_from_string(char const *name)
{
for (Names *n = names; n->name; ++n)
if (!Genode::strcmp(name, n->name))
return n->number;
return Channel::Number::INVALID;
}
static char const *string_from_number(Channel::Number ch)
{
for (Names *n = names; n->name; ++n)
if (ch == n->number)
return n->name;
return nullptr;
}
/**
* Helper method for walking over arrays
*/
template <typename FUNC>
static void for_each_index(int max_index, FUNC const &func) {
for (int i = 0; i < max_index; i++) func(i); }
namespace Audio_out
{
class Session_elem;
class Session_component;
class Root;
class Mixer;
enum { MAX_CHANNEL_NAME_LEN = 16, MAX_LABEL_LEN = 128 };
typedef Genode::String<MAX_LABEL_LEN> Label;
}
/**
* The actual session element
*
* It is part of the Audio_out::Session_component implementation but since
* it is also used by the mixer we defined it here.
*/
struct Audio_out::Session_elem : Audio_out::Session_rpc_object,
private Genode::List<Audio_out::Session_elem>::Element
{
friend class Genode::List<Audio_out::Session_elem>;
using Genode::List<Session_elem>::Element::next;
Label label;
Channel::Number number { Channel::INVALID };
float volume { 0.f };
bool muted { true };
Session_elem(Genode::Env & env,
char const *label, Genode::Signal_context_capability data_cap)
: Session_rpc_object(env, data_cap), label(label) { }
Packet *get_packet(unsigned offset) {
return stream()->get(stream()->pos() + offset); }
};
/**
* The mixer
*/
class Audio_out::Mixer
{
private:
/*
* Noncopyable
*/
Mixer(Mixer const &);
Mixer &operator = (Mixer const &);
Genode::Env &env;
Genode::Attached_rom_dataspace _config_rom { env, "config" };
struct Verbose
{
bool const sessions;
bool const changes;
Verbose(Genode::Xml_node config)
:
sessions(config.attribute_value("verbose_sessions", false)),
changes(config.attribute_value("verbose_changes", false))
{ }
};
Genode::Reconstructible<Verbose> _verbose { _config_rom.xml() };
/*
* Mixer output Audio_out connection
*/
Connection _left { env, "left", false, true };
Connection _right { env, "right", false, false };
Connection *_out[MAX_CHANNELS];
float _out_volume[MAX_CHANNELS];
/*
* Default settings used as fallback for new sessions
*/
float _default_out_volume { 0.f };
float _default_volume { 0.f };
bool _default_muted { true };
/**
* Remix all exception
*/
struct Remix_all { };
/**
* A channel contains multiple session components
*/
struct Session_channel : public Genode::List<Session_elem>
{
void insert(Session_elem *session) { List<Session_elem>::insert(session); }
void remove(Session_elem *session) { List<Session_elem>::remove(session); }
template <typename FUNC> void for_each_session(FUNC const &func)
{
for (Session_elem *elem = List<Session_elem>::first(); elem;
elem = elem->next()) func(*elem);
}
} _channels[MAX_CHANNELS];
/**
* Helper method for walking over session in a channel
*/
template <typename FUNC> void _for_each_channel(FUNC const &func)
{
for (int i = LEFT; i < MAX_CHANNELS; i++)
func((Channel::Number)i, &_channels[i]);
}
/*
* Channel reporter
*
* Each session in a channel is reported as an input node.
*/
Genode::Reporter _reporter { env, "channel_list" };
/**
* Report available channels
*
* This method is called if a new session is added or an old one
* removed as well as when the mixer configuration changes.
*/
void _report_channels()
{
try {
Genode::Reporter::Xml_generator xml(_reporter, [&] () {
/* output channels */
for_each_index(MAX_CHANNELS, [&] (int const i) {
Channel::Number const num = (Channel::Number)i;
char const * const name = string_from_number(num);
int const vol = (int)(MAX_VOLUME * _out_volume[i]);
xml.node("channel", [&] () {
xml.attribute("type", "output");
xml.attribute("label", "master");
xml.attribute("name", name);
xml.attribute("number", (int)num);
xml.attribute("volume", (int)vol);
xml.attribute("muted", false);
});
});
/* input channels */
_for_each_channel([&] (Channel::Number num, Session_channel *sc) {
sc->for_each_session([&] (Session_elem const &session) {
char const * const name = string_from_number(num);
int const vol = (int)(MAX_VOLUME * session.volume);
xml.node("channel", [&] () {
xml.attribute("type", "input");
xml.attribute("label", session.label.string());
xml.attribute("name", name);
xml.attribute("number", session.number);
xml.attribute("active", session.active());
xml.attribute("volume", (int)vol);
xml.attribute("muted", session.muted);
});
});
});
});
} catch (...) { Genode::warning("could report current channels"); }
}
/*
* Check if any of the available session is currently active, i.e.,
* playing.
*/
bool _check_active()
{
bool active = false;
_for_each_channel([&] (Channel::Number, Session_channel *sc) {
sc->for_each_session([&] (Session_elem const &session) {
active |= session.active();
});
});
return active;
}
/*
* Advance the stream of the session to a new position
*/
void _advance_session(Session_elem *session, unsigned pos)
{
if (session->stopped()) return;
Stream *stream = session->stream();
bool const full = stream->full();
/* mark packets as played and icrement position pointer */
while (stream->pos() != pos) {
stream->get(stream->pos())->mark_as_played();
stream->increment_position();
}
session->progress_submit();
if (full) session->alloc_submit();
}
/*
* Advance the position of each session to match the output position
*/
void _advance_position()
{
for_each_index(MAX_CHANNELS, [&] (int i) {
unsigned const pos = _out[i]->stream()->pos();
_channels[i].for_each_session([&] (Session_elem &session) {
_advance_session(&session, pos);
});
});
}
/*
* Mix input packet into output packet
*
* Packets are mixed in a linear way with min/max clipping.
*/
void _mix_packet(Packet *out, Packet *in, bool clear,
float const out_vol, float const vol)
{
if (clear) {
for_each_index(Audio_out::PERIOD, [&] (int const i) {
out->content()[i] = (in->content()[i] * vol);
if (out->content()[i] > 1) out->content()[i] = 1;
if (out->content()[i] < -1) out->content()[i] = -1;
out->content()[i] *= out_vol;
});
} else {
for_each_index(Audio_out::PERIOD, [&] (int const i) {
out->content()[i] += (in->content()[i] * vol);
if (out->content()[i] > 1) out->content()[i] = 1;
if (out->content()[i] < -1) out->content()[i] = -1;
out->content()[i] *= out_vol;
});
}
/* mark the packet as processed by invalidating it */
in->invalidate();
}
/*
* Mix all session of one channel
*/
bool _mix_channel(bool remix, Channel::Number nr, unsigned out_pos, unsigned offset)
{
Stream * const stream = _out[nr]->stream();
Packet * const out = stream->get(out_pos + offset);
Session_channel * const sc = &_channels[nr];
float const out_vol = _out_volume[nr];
bool clear = true;
bool mix_all = remix;
bool const out_valid = out->valid();
Genode::retry<Remix_all>(
/*
* Mix the input packet at the given position of every input
* session to one output packet.
*/
[&] {
sc->for_each_session([&] (Session_elem &session) {
if (session.stopped() || session.muted || session.volume < 0.01f)
return;
Packet *in = session.get_packet(offset);
/* remix again if input has changed for already mixed packet */
if (in->valid() && out_valid && !mix_all) throw Remix_all();
/* skip if packet has been processed or was already played */
if ((!in->valid() && !mix_all) || in->played()) return;
_mix_packet(out, in, clear, out_vol, session.volume);
clear = false;
});
},
/*
* An input packet of an already mixed output packet has
* changed, we have to remix all input packets again.
*/
[&] {
clear = true;
mix_all = true;
});
return !clear;
}
/*
* Mix input packets
*
* \param remix force remix of already mixed packets
*/
void _mix(bool remix = false)
{
unsigned pos[MAX_CHANNELS];
pos[LEFT] = _out[LEFT]->stream()->pos();
pos[RIGHT] = _out[RIGHT]->stream()->pos();
/*
* Look for packets that are valid and mix channels in an alternating
* way.
*/
for_each_index(Audio_out::QUEUE_SIZE, [&] (int const i) {
bool mix_one = true;
for_each_index(MAX_CHANNELS, [&] (int const j) {
mix_one = _mix_channel(remix, (Channel::Number)j, pos[j], i);
});
/* all channels mixed, submit to output queue */
if (mix_one)
for_each_index(MAX_CHANNELS, [&] (int const j) {
Packet *p = _out[j]->stream()->get(pos[j] + i);
_out[j]->submit(p);
});
});
}
/**
* Handle progress signals from Audio_out session and data available signals
* from each mixer client
*/
void _handle()
{
_advance_position();
_mix();
}
/**
* Set default values for various options
*/
void _set_default_config(Genode::Xml_node const &node)
{
using namespace Genode;
try {
Xml_node default_node = node.sub_node("default");
long v = 0;
v = default_node.attribute_value<long>("out_volume", 0);
_default_out_volume = (float)v / MAX_VOLUME;
v = default_node.attribute_value<long>("volume", 0);
_default_volume = (float)v / MAX_VOLUME;
v = default_node.attribute_value<bool>("muted", false);
_default_muted = v ;
if (_verbose->changes) {
log("Set default "
"out_volume: ", (int)(MAX_VOLUME*_default_out_volume), " "
"volume: ", (int)(MAX_VOLUME*_default_volume), " "
"muted: ", _default_muted);
}
} catch (...) { Genode::warning("could not read mixer default values"); }
}
/**
* Handle ROM update signals
*/
void _handle_config_update()
{
using namespace Genode;
_config_rom.update();
Xml_node config_node = _config_rom.xml();
_verbose.construct(config_node);
_set_default_config(config_node);
/* reset out volume in case there is no 'channel_list' node */
_out_volume[LEFT] = _default_out_volume;
_out_volume[RIGHT] = _default_out_volume;
try {
Xml_node channel_list_node = config_node.sub_node("channel_list");
channel_list_node.for_each_sub_node([&] (Xml_node const &node) {
Channel ch(node);
if (ch.type == Channel::Type::INPUT) {
_for_each_channel([&] (Channel::Number, Session_channel *sc) {
sc->for_each_session([&] (Session_elem &session) {
if (session.number != ch.number) return;
if (session.label != ch.label) return;
session.volume = (float)ch.volume / MAX_VOLUME;
session.muted = ch.muted;
if (_verbose->changes) {
log("Set label: '", ch.label, "' "
"channel: '", string_from_number(ch.number), "' "
"nr: ", (int)ch.number, " "
"volume: ", (int)(MAX_VOLUME*session.volume), " "
"muted: ", ch.muted);
}
});
});
}
else if (ch.type == Channel::Type::OUTPUT) {
for_each_index(MAX_CHANNELS, [&] (int const i) {
if (ch.number != i) return;
_out_volume[i] = (float)ch.volume / MAX_VOLUME;
if (_verbose->changes) {
log("Set label: 'master' "
"channel: '", string_from_number(ch.number), "' "
"nr: ", (int)ch.number, " "
"volume: ", (int)(MAX_VOLUME*_out_volume[i]), " "
"muted: ", ch.muted);
}
});
}
});
} catch (Xml_node::Nonexistent_sub_node) {
Genode::warning("channel_list node missing");
}
/*
* Report back any changes so a front-end can update its state
*/
_report_channels();
/*
* The configuration has changed, remix already mixed packets
* in the mixer output queue.
*/
_mix(true);
}
/*
* Signal handlers
*/
Genode::Signal_handler<Audio_out::Mixer> _handler
{ env.ep(), *this, &Audio_out::Mixer::_handle };
Genode::Signal_handler<Audio_out::Mixer> _handler_config
{ env.ep(), *this, &Audio_out::Mixer::_handle_config_update };
public:
/**
* Constructor
*/
Mixer(Genode::Env &env) : env(env)
{
_reporter.enabled(true);
_out[LEFT] = &_left;
_out[RIGHT] = &_right;
_out_volume[LEFT] = _default_out_volume;
_out_volume[RIGHT] = _default_out_volume;
_config_rom.sigh(_handler_config);
_handle_config_update();
}
/**
* Start output stream
*/
void start()
{
_out[LEFT]->progress_sigh(_handler);
for_each_index(MAX_CHANNELS, [&] (int const i) { _out[i]->start(); });
}
/**
* Stop output stream
*/
void stop()
{
for_each_index(MAX_CHANNELS, [&] (int const i) { _out[i]->stop(); });
_out[LEFT]->progress_sigh(Genode::Signal_context_capability());
}
/**
* Get current playback position of output stream
*/
unsigned pos(Channel::Number channel) const { return _out[channel]->stream()->pos(); }
/**
* Add input session
*/
void add_session(Channel::Number ch, Session_elem &session)
{
session.volume = _default_volume;
session.muted = _default_muted;
if (_verbose->sessions) {
log("Add label: '", session.label, "' "
"channel: '", string_from_number(ch), "' "
"nr: ", (int)ch, " "
"volume: ", (int)(MAX_VOLUME*session.volume), " "
"muted: ", session.muted);
}
_channels[ch].insert(&session);
_report_channels();
}
/**
* Remove input session
*/
void remove_session(Channel::Number ch, Session_elem &session)
{
if (_verbose->sessions) {
log("Remove label: '", session.label, "' "
"channel: '", string_from_number(ch), "' "
"nr: ", (int)ch);
}
_channels[ch].remove(&session);
_report_channels();
}
/**
* Get signal context that handles data avaiable as well as progress signal
*/
Genode::Signal_context_capability sig_cap() { return _handler; }
/**
* Report current channels
*/
void report_channels() { _report_channels(); }
};
/**************************************
** Audio_out session implementation **
**************************************/
class Audio_out::Session_component : public Audio_out::Session_elem
{
private:
Mixer &_mixer;
public:
Session_component(Genode::Env &env,
char const *label,
Channel::Number number,
Mixer &mixer)
: Session_elem(env, label, mixer.sig_cap()), _mixer(mixer)
{
Session_elem::number = number;
_mixer.add_session(Session_elem::number, *this);
}
~Session_component()
{
if (Session_rpc_object::active()) stop();
_mixer.remove_session(Session_elem::number, *this);
}
void start() override
{
Session_rpc_object::start();
stream()->pos(_mixer.pos(Session_elem::number));
_mixer.report_channels();
}
void stop() override
{
Session_rpc_object::stop();
_mixer.report_channels();
}
};
namespace Audio_out {
typedef Genode::Root_component<Session_component, Genode::Multiple_clients> Root_component;
}
class Audio_out::Root : public Audio_out::Root_component
{
private:
Genode::Env &_env;
Mixer &_mixer;
int _sessions = { 0 };
Session_component *_create_session(const char *args) override
{
using namespace Genode;
/*
* We only want to have the last part of the lable,
* e.g. 'client -> ' => 'client'.
*/
Session_label const session_label = label_from_args(args);
Session_label const label = session_label.prefix();
char channel_name[MAX_CHANNEL_NAME_LEN];
Arg_string::find_arg(args, "channel").string(channel_name,
sizeof(channel_name),
"left");
size_t ram_quota =
Arg_string::find_arg(args, "ram_quota").ulong_value(0);
size_t session_size = align_addr(sizeof(Session_component), 12);
if ((ram_quota < session_size) ||
(sizeof(Stream) > ram_quota - session_size)) {
Genode::error("insufficient 'ram_quota', got ", ram_quota, ", "
"need ", sizeof(Stream) + session_size);
throw Insufficient_ram_quota();
}
Channel::Number ch = number_from_string(channel_name);
if (ch == Channel::Number::INVALID)
throw Genode::Service_denied();
Session_component *session = new (md_alloc())
Session_component(_env, label.string(), (Channel::Number)ch, _mixer);
if (++_sessions == 1) _mixer.start();
return session;
}
void _destroy_session(Session_component *session) override
{
if (--_sessions == 0) _mixer.stop();
Genode::destroy(md_alloc(), session);
}
public:
Root(Genode::Env &env,
Mixer &mixer,
Genode::Allocator &md_alloc)
: Root_component(env.ep(), md_alloc), _env(env), _mixer(mixer) { }
};
/***************
** Component **
***************/
namespace Mixer { struct Main; }
struct Mixer::Main
{
Genode::Env &env;
Genode::Sliced_heap heap { env.ram(), env.rm() };
Audio_out::Mixer mixer = { env };
Audio_out::Root root = { env, mixer, heap };
Main(Genode::Env &env) : env(env)
{
env.parent().announce(env.ep().manage(root));
}
};
void Component::construct(Genode::Env &env) { static Mixer::Main inst(env); }

View File

@ -1,3 +1,6 @@
TARGET = mixer
SRC_CC = mixer.cc
LIBS = base
TARGET = mixer
SRC_CC += component.cc
LIBS += base
INC_DIR += $(REP_DIR)/src/server/cached_fs_rom
CC_CXX_WARN_STRICT =

View File

@ -0,0 +1,213 @@
/*
* \brief Audio patch component
* \author Emery Hemingway
* \date 2018-06-05
*
* Server for patching packets from an Audio_out client to an Audio_in client.
*/
/*
* Copyright (C) 2018 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <audio_in_session/audio_in_session.h>
#include <audio_out_session/audio_out_session.h>
#include <audio/buffer.h>
#include <base/attached_ram_dataspace.h>
#include <base/rpc_server.h>
#include <base/component.h>
/* local includes */
#include "session_requests.h"
using namespace Genode;
namespace Patch {
using namespace Audio;
struct State;
struct Main;
}
namespace Audio_in { class Session_component; }
namespace Audio_out { class Session_component; }
typedef Parent::Server::Id Id;
struct Patch::State
{
Audio::Stream_buffer buffer;
Genode::Signal_context_capability start_cap { };
Genode::Signal_context_capability reset_cap { };
Genode::Signal_context_capability progress_cap { };
State(Genode::Env &env) : buffer(env.pd(), env.rm()) { }
};
class Audio_in::Session_component :
public Genode::Rpc_object<Audio_in::Session, Session_component>
{
private:
Patch::State &_common_state;
public:
Session_component(Patch::State &state)
: _common_state(state) { }
/************************
** Audio_in interface **
************************/
Genode::Dataspace_capability dataspace() {
return _common_state.buffer.cap(); }
void start_sigh(Signal_context_capability sigh) override {
_common_state.start_cap = sigh; }
void reset_sigh(Signal_context_capability sigh) override {
_common_state.reset_cap = sigh; }
Signal_context_capability progress_sigh() override {
return _common_state.progress_cap; }
};
class Audio_out::Session_component :
public Genode::Rpc_object<Audio_out::Session, Session_component>
{
private:
Patch::State &_common_state;
public:
Session_component(Patch::State &state)
: _common_state(state) { }
/************************
** Audio_in interface **
************************/
Genode::Dataspace_capability dataspace() {
return _common_state.buffer.cap(); }
void start() override
{
if (_common_state.start_cap.valid())
Signal_transmitter(_common_state.start_cap).submit();
}
void progress_sigh(Signal_context_capability sigh) override
{
_common_state.progress_cap = sigh;
if (_common_state.reset_cap.valid()) {
Signal_transmitter(_common_state.reset_cap).submit();
} else {
Genode::warning("reset signal handler is not valid");
}
}
};
struct Patch::Main : Genode::Session_request_handler
{
Genode::Env &env;
struct State_pair
{
State left;
State right;
State_pair(Genode::Env &env)
: left(env), right(env) { }
};
State_pair state { env };
template <typename SESSION>
struct Session_pair
{
Constructible<SESSION> left { };
Constructible<SESSION> right { };
void deliver_session(Genode::Env &env,
State_pair &state,
Parent::Server::Id id,
Session_label const &label)
{
SESSION *session = nullptr;
if (label == "left" || label == "front left") {
if (left.constructed()) return;
left.construct(state.left);
session = &(*left);
} else
if (label == "right" || label == "front right") {
if (right.constructed()) return;
right.construct(state.right);
session = &(*right);
} else {
Genode::log("denying session \"", label, "\"");
throw Service_denied();
}
env.parent().deliver_session_cap(
id, env.ep().manage(*session));
}
};
Session_pair< Audio_in::Session_component> in_sessions { };
Session_pair<Audio_out::Session_component> out_sessions { };
Genode::Session_requests_rom session_requests { env, *this };
Main(Genode::Env &env) : env(env)
{
session_requests.schedule();
}
void handle_session_create(Session_state::Name const &name,
Parent::Server::Id id,
Session_state::Args const &args) override
{
Session_label const label_last =
label_from_args(args.string()).last_element();
if (name == "Audio_in") {
in_sessions.deliver_session(env, state, id, label_last);
session_requests.schedule();
} else
if (name == "Audio_out") {
/* these sessions must be delivered second */
if (in_sessions.left.constructed()
&& in_sessions.right.constructed())
{
out_sessions.deliver_session(env, state, id, label_last);
}
} else
{
throw Service_denied();
}
}
void handle_session_close(Parent::Server::Id) override
{
Genode::log("client session disconnected, exiting...");
env.parent().exit(0);
}
};
void Component::construct(Genode::Env &env)
{
static Patch::Main inst(env);
}

View File

@ -0,0 +1,5 @@
TARGET = stereo_patch
SRC_CC += component.cc
LIBS += base
INC_DIR += $(REP_DIR)/src/server/cached_fs_rom

View File

@ -60,7 +60,6 @@ struct Track final : Audio::Source
bool fill(float *left, float *right, Genode::size_t /*samples*/) override
{
if (_size <= _offset) {
_stereo_out.stop();
log("played '", _name, "' 1 time(s)");
return false;
}
@ -97,8 +96,6 @@ struct Track final : Audio::Source
if (verbose)
log(_name, " size is ", _size, " bytes "
"(attached to ", (void *)_base, ")");
_stereo_out.start();
}
~Track() { }