genode/repos/os/src/server/mixer/mixer.cc

728 lines
18 KiB
C++
Raw Normal View History

2011-12-22 16:19:25 +01:00
/*
* \brief Audio_out Mixer
2011-12-22 16:19:25 +01:00
* \author Sebastian Sumpf
* \author Josef Soentgen
* \date 2012-12-20
2011-12-22 16:19:25 +01:00
*
* 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].
2011-12-22 16:19:25 +01:00
*/
/*
* Copyright (C) 2009-2015 Genode Labs GmbH
2011-12-22 16:19:25 +01:00
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
/* Genode includes */
#include <mixer/channel.h>
#include <os/config.h>
#include <os/reporter.h>
#include <os/server.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>
2011-12-22 16:19:25 +01:00
#include <cap_session/connection.h>
#include <timer_session/connection.h>
2011-12-22 16:19:25 +01:00
static bool verbose = false;
#define PLOGV(...) do { if (verbose) PLOG(__VA_ARGS__); } while (0)
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,
};
2011-12-22 16:19:25 +01:00
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 }
};
2011-12-22 16:19:25 +01:00
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;
}
2011-12-22 16:19:25 +01:00
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;
}
2011-12-22 16:19:25 +01:00
/**
* 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;
}
2011-12-22 16:19:25 +01:00
/**
* 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,
Genode::List<Audio_out::Session_elem>::Element
{
Label label;
Channel::Number number;
float volume { 0.f };
bool muted { true };
2011-12-22 16:19:25 +01:00
Session_elem(char const *label, Genode::Signal_context_capability data_cap)
: Session_rpc_object(data_cap), label(label) { }
Packet *get_packet(unsigned offset) {
return stream()->get(stream()->pos() + offset); }
};
2011-12-22 16:19:25 +01:00
/**
* The mixer
*/
class Audio_out::Mixer
{
private:
2011-12-22 16:19:25 +01:00
/*
* Signal handler
*/
Genode::Signal_rpc_member<Audio_out::Mixer> _dispatcher;
Genode::Signal_rpc_member<Audio_out::Mixer> _dispatcher_config;
2011-12-22 16:19:25 +01:00
/*
* Mixer output Audio_out connection
*/
Connection _left;
Connection _right;
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 };
2011-12-22 16:19:25 +01:00
/**
* 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 { "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()
{
reporter.enabled(true);
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", (long)0);
});
});
/* 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 (...) { PWRN("could report current channels"); }
}
2011-12-22 16:19:25 +01:00
/*
* Check if any of the available session is currently active, i.e.,
* playing.
*/
bool _check_active()
{
bool active = false;
_for_each_channel([&] (Channel::Number ch, Session_channel *sc) {
sc->for_each_session([&] (Session_elem const &session) {
active |= session.active();
});
});
return active;
}
2011-12-22 16:19:25 +01:00
/*
* Advance the stream of the session to a new position
*/
void _advance_session(Session_elem *session, unsigned pos)
{
if (session->stopped()) return;
2011-12-22 16:19:25 +01:00
Stream *stream = session->stream();
bool const full = stream->full();
2011-12-22 16:19:25 +01:00
/* mark packets as played and icrement position pointer */
while (stream->pos() != pos) {
stream->get(stream->pos())->mark_as_played();
stream->increment_position();
2011-12-22 16:19:25 +01:00
}
session->progress_submit();
2011-12-22 16:19:25 +01:00
if (full) session->alloc_submit();
}
2011-12-22 16:19:25 +01:00
/*
* 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);
});
});
}
2011-12-22 16:19:25 +01:00
/*
* 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)
2011-12-22 16:19:25 +01:00
{
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;
});
2011-12-22 16:19:25 +01:00
}
/* mark the packet as processed by invalidating it */
in->invalidate();
}
2011-12-22 16:19:25 +01:00
/*
* 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) 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();
2011-12-22 16:19:25 +01:00
/* skip if packet has been processed or was already played */
if ((!in->valid() && !mix_all) || in->played()) return;
2011-12-22 16:19:25 +01:00
_mix_packet(out, in, clear, out_vol, session.volume);
2011-12-22 16:19:25 +01:00
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;
});
2011-12-22 16:19:25 +01:00
return !clear;
}
2011-12-22 16:19:25 +01:00
/*
* 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();
2011-12-22 16:19:25 +01:00
/*
* 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);
});
});
}
2011-12-22 16:19:25 +01:00
/**
* Handle progress signals from Audio_out session and data available signals
* from each mixer client
*/
void _handle(unsigned)
{
_advance_position();
_mix();
}
2011-12-22 16:19:25 +01:00
/**
* Set default values for various options
*/
void _set_default_config(Genode::Xml_node const &node)
{
try {
Genode::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<long>("muted", 1);
_default_muted = v ;
PLOGV("default settings: out_volume: %d volume: %d muted: %d",
(int)(MAX_VOLUME*_default_out_volume),
(int)(MAX_VOLUME*_default_volume),
_default_muted);
} catch (...) { PWRN("could not read mixer default values"); }
}
/**
* Handle ROM update signals
*/
void _handle_config_update(unsigned sig_num)
{
using namespace Genode;
config()->reload();
Xml_node config_node = config()->xml_node();
try { verbose = config_node.attribute("verbose").has_value("yes"); }
catch (...) { verbose = false; }
_set_default_config(config_node);
/* set initial out volume */
if (sig_num == 0) {
for_each_index(MAX_CHANNELS, [&] (int const i) {
_out_volume[i] = _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;
PLOGV("label: '%s' nr: %d vol: %d muted: %d",
ch.label.string(), (int)ch.number,
(int)(MAX_VOLUME*session.volume), 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;
PLOGV("label: '%s' nr: %d vol: %d muted: %d",
"master", (int)ch.number,
(int)(MAX_VOLUME*_out_volume[i]), ch.muted);
});
}
});
} catch (...) { PWRN("mixer channel_list was invalid"); }
/*
* 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);
}
public:
2011-12-22 16:19:25 +01:00
/**
* Constructor
*/
Mixer(Server::Entrypoint &ep)
:
_dispatcher(ep, *this, &Audio_out::Mixer::_handle),
_dispatcher_config(ep, *this, &Audio_out::Mixer::_handle_config_update),
_left("left", false, true),
_right("right", false, true)
{
_out[LEFT] = &_left;
_out[RIGHT] = &_right;
_out_volume[LEFT] = _default_out_volume;
_out_volume[RIGHT] = _default_out_volume;
Genode::config()->sigh(_dispatcher_config);
_handle_config_update(0);
_report_channels();
}
/**
* Start output stream
*/
void start()
{
_out[LEFT]->progress_sigh(_dispatcher);
for_each_index(MAX_CHANNELS, [&] (int const i) { _out[i]->start(); });
}
2011-12-22 16:19:25 +01:00
/**
* Stop output stream
*/
void stop()
{
for_each_index(MAX_CHANNELS, [&] (int const i) { _out[i]->stop(); });
_out[LEFT]->progress_sigh(Genode::Signal_context_capability());
}
2011-12-22 16:19:25 +01:00
/**
* Get current playback position of output stream
*/
unsigned pos(Channel::Number channel) const { return _out[channel]->stream()->pos(); }
2011-12-22 16:19:25 +01:00
/**
* Add input session
*/
void add_session(Channel::Number ch, Session_elem &session)
{
session.volume = _default_volume;
session.muted = _default_muted;
PLOG("add label: \"%s\" channel: \"%s\" nr: %u volume: %d muted: %d",
session.label.string(), string_from_number(ch), ch,
(int)(MAX_VOLUME*session.volume), session.muted);
_channels[ch].insert(&session);
_report_channels();
}
2011-12-22 16:19:25 +01:00
/**
* Remove input session
*/
void remove_session(Channel::Number ch, Session_elem &session)
{
PLOG("remove label: \"%s\" channel: \"%s\" nr: %u",
session.label.string(), string_from_number(ch), 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 _dispatcher; }
/**
* Report current channels
*/
void report_channels() { _report_channels(); }
};
2011-12-22 16:19:25 +01:00
/**************************************
** Audio_out session implementation **
**************************************/
class Audio_out::Session_component : public Audio_out::Session_elem
{
private:
2011-12-22 16:19:25 +01:00
Mixer &_mixer;
2011-12-22 16:19:25 +01:00
public:
2011-12-22 16:19:25 +01:00
Session_component(char const *label,
Channel::Number number,
Mixer &mixer)
: Session_elem(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);
}
2011-12-22 16:19:25 +01:00
void start()
{
Session_rpc_object::start();
stream()->pos(_mixer.pos(Session_elem::number));
_mixer.report_channels();
}
void stop()
{
Session_rpc_object::stop();
_mixer.report_channels();
}
};
2011-12-22 16:19:25 +01:00
namespace Audio_out {
typedef Genode::Root_component<Session_component, Genode::Multiple_clients> Root_component;
}
2011-12-22 16:19:25 +01:00
class Audio_out::Root : public Audio_out::Root_component
{
private:
2011-12-22 16:19:25 +01:00
Mixer &_mixer;
int _sessions = { 0 };
Session_component *_create_session(const char *args)
{
using namespace Genode;
char label[MAX_LABEL_LEN];
Arg_string::find_arg(args, "label").string(label,
sizeof(label),
"<noname>");
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)) {
PERR("insufficient 'ram_quota', got %zu, need %zu",
ram_quota, sizeof(Stream) + session_size);
throw Root::Quota_exceeded();
}
2011-12-22 16:19:25 +01:00
Channel::Number ch = number_from_string(channel_name);
if (ch == Channel::Number::INVALID)
throw Root::Invalid_args();
2011-12-22 16:19:25 +01:00
Session_component *session = new (md_alloc())
Session_component(label, (Channel::Number)ch, _mixer);
2011-12-22 16:19:25 +01:00
if (++_sessions == 1) _mixer.start();
return session;
}
void _destroy_session(Session_component *session)
{
if (--_sessions == 0) _mixer.stop();
destroy(md_alloc(), session);
}
2011-12-22 16:19:25 +01:00
public:
2011-12-22 16:19:25 +01:00
Root(Server::Entrypoint &ep,
Mixer &mixer,
Genode::Allocator &md_alloc)
: Root_component(&ep.rpc_ep(), &md_alloc), _mixer(mixer) { }
};
2011-12-22 16:19:25 +01:00
/************
** Server **
************/
2011-12-22 16:19:25 +01:00
namespace Server { struct Main; }
2011-12-22 16:19:25 +01:00
struct Server::Main
{
Server::Entrypoint &ep;
2011-12-22 16:19:25 +01:00
Audio_out::Mixer mixer = { ep };
Audio_out::Root root = { ep, mixer, *Genode::env()->heap() };
Main(Server::Entrypoint &ep) : ep(ep)
{
Genode::env()->parent()->announce(ep.manage(root));
PINF("--- Mixer started ---");
}
};
2011-12-22 16:19:25 +01:00
namespace Server {
char const *name() { return "mixer_ep"; }
size_t stack_size() { return 4*1024*sizeof(addr_t); }
void construct(Entrypoint &ep) { static Main server(ep); }
}