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:
parent
ac853252c3
commit
5e8bc9ef51
|
@ -1 +1 @@
|
|||
2019-06-04 f880caa4785e8994a955fd557608cec0e42d64a7
|
||||
2019-06-19-a 883a7771cf6c015b063491adff3e069206151227
|
||||
|
|
|
@ -1 +1 @@
|
|||
2019-06-03 08a12eda7a98bad6b5571a6ca044f77107fe1afd
|
||||
2019-06-19-a 970f5dff896223e05ec0c3da6ac984e56f7e92a2
|
||||
|
|
|
@ -1 +1 @@
|
|||
2019-06-06-a d11fd5d2fa5a8a8cdbb948a76804ed081def9339
|
||||
2019-06-19 3b2b827ec74993dff233dbac299c8f06ec5f81a0
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
_/src/bsd_audio_drv
|
||||
_/src/init
|
||||
_/src/stereo_patch
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1 +1 @@
|
|||
-
|
||||
2019-06-19 7392f28d42f78ffd2912df15ad91ecac9f7b4052
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
base
|
||||
os
|
||||
audio_in_session
|
||||
audio_out_session
|
||||
audio
|
||||
platform_session
|
||||
report_session
|
||||
timer_session
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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() { }
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -1 +1 @@
|
|||
-
|
||||
2019-06-19 61c849a28958fcfc3e5d9ea555a305ae76ed9d7f
|
||||
|
|
|
@ -1 +1 @@
|
|||
2019-05-05 4c07a7badce5ed6e84a16681db67528b7395fd46
|
||||
2019-06-19 18750ec486b7d438efd2c051b94d373eaa8ee7fd
|
||||
|
|
|
@ -1 +1 @@
|
|||
2019-06-11 fd2ce886a47163c7036380eeb345b00aa5eb56dc
|
||||
2019-06-19 2cc3ad3dfc57a9be3a6bc327347b858c5ad59a6a
|
||||
|
|
|
@ -1 +1 @@
|
|||
-
|
||||
2019-06-19 1dc3c775c028128adb8c9a689956d73ec44ca366
|
||||
|
|
|
@ -1 +1 @@
|
|||
-
|
||||
2019-06-19 08b20bb9543e9d45ef2db78992791f66a71ce046
|
||||
|
|
|
@ -1 +1 @@
|
|||
-
|
||||
2019-06-19 5d9465c7e6e224ff42b91e4520436ba3c37120f3
|
||||
|
|
|
@ -1 +1 @@
|
|||
-
|
||||
2019-06-19 4783538a41ff3586ae2fddaeb12879bf3ce568bd
|
||||
|
|
|
@ -1 +1 @@
|
|||
2019-05-29 53d789ea778cd48f5d977fadef29de40b439000a
|
||||
2019-06-19 ea731dff5c170181afe4f3778619941c0e263201
|
||||
|
|
|
@ -1 +1 @@
|
|||
2019-06-05 f56484d63f0b1f3cda717166423ef9c91efd1b7c
|
||||
2019-06-19 e88e256d873b34651ba0d347bea0dd19de934c4b
|
||||
|
|
|
@ -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_ */
|
|
@ -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_
|
|
@ -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;
|
||||
}
|
||||
};
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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_ */
|
|
@ -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_ */
|
||||
|
|
|
@ -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_ */
|
||||
|
|
|
@ -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())
|
||||
{ }
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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_ */
|
||||
|
|
|
@ -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_ */
|
||||
|
|
|
@ -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())
|
||||
{ }
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
2019-06-19 86b61718dbb81b00ba951ab781db5480bb79cdc3
|
|
@ -1 +1 @@
|
|||
2019-06-11 185683e55f3e30c7e25818e9fe52a31c2c519dce
|
||||
2019-06-19-a a2333013344441751285c5513dd3eb22c20a35f7
|
||||
|
|
|
@ -1 +1 @@
|
|||
2019-06-11 2d70cfb5f5273e8cc062db560a2b2f907cabe083
|
||||
2019-06-19-a 3061bc4e9363d005c52b1f3b41bffff655e4e2d5
|
||||
|
|
|
@ -1 +1 @@
|
|||
2019-06-05 543090d6e662da831709a3d8eff301813bf9b240
|
||||
2019-06-19 691f47ca885eee15bc391ad01f0c4838a7b9bbf1
|
||||
|
|
|
@ -1 +1 @@
|
|||
2019-05-29 6cf16fc0dd397de8cae65ea9ac6db49cb39570a5
|
||||
2019-06-19 36dd1f921965a7691d83695062d2b6daf18c63de
|
||||
|
|
|
@ -1 +1 @@
|
|||
2019-05-29 c6de3d407506f2cba25bfa903449110998632cc1
|
||||
2019-06-19 9b8caa55f6fc3eaaa36aa91b3e7ac33edf341e19
|
||||
|
|
|
@ -1 +1 @@
|
|||
2019-06-05 d6f30181ae3bf8168f071903ec5b16f7c1bd9fa1
|
||||
2019-06-19 11fefe86cd6b4f8630e5430c214ce640f93f134f
|
||||
|
|
|
@ -1 +1 @@
|
|||
2019-06-05 e8490a608ee00b382943ff0355c520cf42a00023
|
||||
2019-06-19 859319642ce21170060d168ec86c91a003e68a29
|
||||
|
|
|
@ -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)
|
|
@ -0,0 +1 @@
|
|||
2019-06-19-b 6c9619cc5e111b88429950fc761f731edbbe5ee2
|
|
@ -0,0 +1,3 @@
|
|||
base
|
||||
os
|
||||
audio
|
|
@ -1 +1 @@
|
|||
2019-06-05 4a16d02131da517cca89dfcc6d78bae48b96d31b
|
||||
2019-06-19 e670ef23fc041d8dea4bd74ae9d089403b5df863
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
TARGET = dummy-audio_drv
|
||||
LIBS += base
|
||||
SRC_CC += component.cc
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
|
@ -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);
|
||||
}
|
|
@ -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); }
|
|
@ -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 =
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
TARGET = stereo_patch
|
||||
SRC_CC += component.cc
|
||||
LIBS += base
|
||||
|
||||
INC_DIR += $(REP_DIR)/src/server/cached_fs_rom
|
|
@ -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() { }
|
||||
|
|
Loading…
Reference in New Issue