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

693 lines
17 KiB
C++
Raw Normal View History

2011-12-22 16:19:25 +01:00
/*
* \brief Audio Mixer
* \author Sebastian Sumpf
* \author Christian Helmuth
* \author Stefan Kalkowski
* \date 2009-12-02
*
* The mixer supports up to MAX_TRACKS (virtual) two-channel stereo input
* sessions and, therefore, provides audio-out sessions for "front left" and
* "front right". The mixer itself uses two audio-out sessions - front left and
* right.
*/
/*
2012-01-03 15:35:05 +01:00
* Copyright (C) 2009-2012 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.
*/
#include <base/env.h>
#include <base/sleep.h>
#include <base/semaphore.h>
#include <base/allocator_avl.h>
#include <util/misc_math.h>
#include <root/component.h>
#include <cap_session/connection.h>
#include <audio_out_session/rpc_object.h>
#include <audio_out_session/connection.h>
using namespace Genode;
static const bool verbose = false;
static bool audio_out_active = false;
static Genode::Lock session_lock;
static Rpc_entrypoint *audio_out_ep()
{
static Cap_connection cap;
enum { STACK_SIZE = 4096 };
static Rpc_entrypoint _audio_out_ep(&cap, STACK_SIZE, "audio_ep");
return &_audio_out_ep;
}
namespace Audio_out {
enum { OUT_QUEUE_SIZE = 1 };
template <typename LT>
class Ring
{
private:
LT *_next;
LT *_prev;
public:
Ring()
: _next(static_cast<LT *>(this)),
_prev(static_cast<LT *>(this)) { }
LT *next() { return _next; };
LT *prev() { return _prev; };
/**
* Conflate with another ring.
*/
bool conflate(LT *le)
{
/* test whether the given ring is part of this one */
LT *e = static_cast<LT *>(this);
while (e->Ring::_next != this) {
if (e->Ring::_next == le)
return false;
e = e->Ring::_next;
}
/* wire this->next with le->prev */
_next->Ring::_prev = le->Ring::_prev;
le->Ring::_prev->Ring::_next = _next;
_next = le;
le->Ring::_prev = static_cast<LT *>(this);
return true;
}
/**
* Remove this object from the ring.
*/
void remove()
{
_prev->Ring::_next = _next;
_next->Ring::_prev = _prev;
_next = static_cast<LT *>(this);
_prev = static_cast<LT *>(this);
}
};
enum Channel_number { LEFT, RIGHT, MAX_CHANNELS, INVALID = MAX_CHANNELS };
static bool channel_number_from_string(const char *name,
Channel_number *out_number)
{
static struct Names {
const char *name;
Channel_number number;
} names[] = {
{ "left", LEFT }, { "front left", LEFT },
{ "right", RIGHT }, { "front right", RIGHT },
{ 0, INVALID }
};
for (Names *n = names; n->name; ++n)
if (!strcmp(name, n->name)) {
*out_number = n->number;
return true;
}
return false;
}
static const char * channel_string_from_number(Channel_number number)
{
static const char *names[MAX_CHANNELS + 1] = {
"front left", "front right", "invalid"
};
return names[number];
}
static Semaphore open_tracks;
static int num_open_tracks[MAX_CHANNELS];
/*
* The mixer uses only one signal receiver and context for all input
* tracks.
*/
static Signal_receiver avail_recv;
static Signal_context avail_ctx;
static Signal_context_capability avail_cap(avail_recv.manage(&avail_ctx));
enum { MAX_TRACKS = 16 };
class Communication_buffer : Ram_dataspace_capability
{
public:
Communication_buffer(size_t size)
: Ram_dataspace_capability(env()->ram_session()->alloc(size))
{ }
~Communication_buffer() { env()->ram_session()->free(*this); }
Dataspace_capability dataspace() { return *this; }
};
class Session_component : public List<Session_component>::Element,
public Ring<Session_component>,
private Communication_buffer,
public Session_rpc_object
{
private:
Channel_number _channel;
public:
Session_component(Channel_number channel, size_t buffer_size,
Rpc_entrypoint &ep)
:
Communication_buffer(buffer_size),
Session_rpc_object(Communication_buffer::dataspace(), ep),
_channel(channel)
{
if (verbose) PDBG("new session %p", this);
track_list()->insert(this);
num_open_tracks[_channel]++;
open_tracks.up();
}
~Session_component()
{
Genode::Lock::Guard lock_guard(session_lock);
open_tracks.down();
num_open_tracks[_channel]--;
track_list()->remove(this);
Ring<Session_component>::remove();
if (verbose) PDBG("session %p closed", this);
}
static List<Session_component> *track_list()
{
static List<Session_component> _track_list;
return &_track_list;
}
/*
* We only need one central signal context within the mixer.
*/
Signal_context_capability _sigh_packet_avail()
{
return avail_cap;
}
Channel_number channel_number() const { return _channel; }
bool all_channel_packet_avail() {
if (!channel()->packet_avail())
return false;
Session_component *s = this;
while (s->Ring<Session_component>::next() != this) {
s = s->Ring<Session_component>::next();
if (!s->channel()->packet_avail())
return false;
}
return true;
}
void flush()
{
while (channel()->packet_avail())
channel()->acknowledge_packet(channel()->get_packet());
}
void sync_session(Session_capability audio_out_session)
{
Object_pool<Session_component>::Guard
sc(audio_out_ep()->lookup_and_lock(audio_out_session));
2011-12-22 16:19:25 +01:00
/* check if recipient is a valid session component */
if (!sc) return;
if (this->conflate(sc))
track_list()->remove(this);
}
};
/**
* Session creation policy for our service
*/
struct Root_policy
{
void aquire(const char *args)
{
size_t ram_quota =
Arg_string::find_arg(args, "ram_quota" ).ulong_value(0);
size_t buffer_size =
Arg_string::find_arg(args, "buffer_size").ulong_value(0);
size_t session_size =
align_addr(sizeof(Session_component), 12);
if ((ram_quota < session_size) ||
(buffer_size > ram_quota - session_size)) {
PERR("insufficient 'ram_quota', got %zd, need %zd",
ram_quota, buffer_size + session_size);
throw Root::Quota_exceeded();
}
char channel_name[16];
Channel_number channel_number;
Arg_string::find_arg(args, "channel").string(channel_name,
sizeof(channel_name),
"left");
if (!channel_number_from_string(channel_name, &channel_number))
throw Root::Invalid_args();
if (!(num_open_tracks[channel_number] < MAX_TRACKS)) {
PERR("throw");
throw Root::Unavailable();
}
}
void release() { }
};
typedef Root_component<Session_component, Root_policy> Root_component;
class Root : public Root_component
{
private:
Rpc_entrypoint &_channel_ep;
protected:
Session_component *_create_session(const char *args)
{
size_t buffer_size =
Arg_string::find_arg(args, "buffer_size").ulong_value(0);
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);
Session_component *session = new (md_alloc())
Session_component(channel_number, buffer_size,
_channel_ep);
PDBG("Added new \"%s\" channel %d/%d",
channel_name, num_open_tracks[channel_number], MAX_TRACKS);
return session;
}
public:
Root(Rpc_entrypoint *session_ep, Allocator *md_alloc)
: Root_component(session_ep, md_alloc), _channel_ep(*session_ep) { }
};
class Packet_cache
{
private:
class Track_packet;
class Channel_packet;
typedef Tslab<Track_packet, 1024> Track_packet_slab;
typedef Tslab<Channel_packet, 1024> Channel_packet_slab;
Track_packet_slab _track_packet_slab;
Channel_packet_slab _channel_packet_slab;
Genode::Lock _lock;
class Track_packet : public List<Track_packet>::Element
{
private:
Packet_descriptor _packet;
Session::Channel::Sink *_sink;
public:
Track_packet(Packet_descriptor p,
Session::Channel::Sink *sink)
: _packet(p), _sink(sink) {}
bool session_active()
{
for (Session_component *session = Session_component::track_list()->first();
session;
session = session->List<Session_component>::Element::next())
for (Session_component *sc = session;
true;
sc = sc->Ring<Session_component>::next()) {
if (sc->channel() == _sink)
return true;
if (sc->Ring<Session_component>::next() == session)
break;
}
return false;
}
void acknowledge() {
if (session_active())
_sink->acknowledge_packet(_packet); }
};
class Channel_packet : public List<Channel_packet>::Element
{
private:
List<Track_packet> _track_packets;
Packet_descriptor _packet;
Track_packet_slab *_slab;
public:
Channel_packet(Packet_descriptor p, Track_packet_slab *slab)
: _packet(p), _slab(slab) { }
void add(Track_packet *p) { _track_packets.insert(p); }
void acknowledge()
{
Track_packet *p = _track_packets.first();
while (p) {
p->acknowledge();
_track_packets.remove(p);
_slab->free(p);
p = _track_packets.first();
}
}
bool packet(Packet_descriptor p)
{
return p.size() == _packet.size() &&
p.offset() == _packet.offset();
}
};
List<Channel_packet> _channel_packets[MAX_CHANNELS];
Connection *_out_stream[MAX_CHANNELS];
public:
Packet_cache(Connection *output_stream[MAX_CHANNELS])
: _track_packet_slab(Genode::env()->heap()),
_channel_packet_slab(Genode::env()->heap())
{
for (int chn = 0; chn < MAX_CHANNELS; ++chn)
_out_stream[chn] = output_stream[chn];
}
void ack_packets()
{
for (int chn = 0; chn < MAX_CHANNELS; ++chn) {
Session::Channel::Source *stream = _out_stream[chn]->stream();
Packet_descriptor p = stream->get_acked_packet();
if (verbose) PDBG("ack channel %d", chn);
Genode::Lock::Guard lock_guard(_lock);
Channel_packet *ch_packet = _channel_packets[chn].first();
while (ch_packet) {
if (ch_packet->packet(p)) {
ch_packet->acknowledge();
_channel_packets[chn].remove(ch_packet);
_channel_packet_slab.free(ch_packet);
break;
}
ch_packet = ch_packet->next();
}
stream->release_packet(p);
}
}
void put(Packet_descriptor p, Session::Channel::Sink **sinks,
Packet_descriptor *cli_p, int count, int chn)
{
Genode::Lock::Guard lock_guard(_lock);
Channel_packet *ch_packet = new (&_channel_packet_slab)
Channel_packet(p, &_track_packet_slab);
for (int i = 0; i < count; ++i) {
Track_packet *t_packet = new (&_track_packet_slab)
Track_packet(cli_p[i], sinks[i]);
ch_packet->add(t_packet);
}
_channel_packets[chn].insert(ch_packet);
}
};
class Mixer : private Genode::Thread<4096>
{
private:
struct Mixer_packets
{
Session::Channel::Sink *sink[MAX_TRACKS];
Packet_descriptor packet[MAX_TRACKS];
int count;
} _packets[MAX_CHANNELS];
Connection *_out_stream[MAX_CHANNELS];
Semaphore _packet_sema; /* packets available */
Packet_cache *_cache;
Semaphore _startup_sema; /* thread startup sync */
class Receiver : private Genode::Thread<4096>
{
private:
Packet_cache *_cache;
Semaphore _startup_sema;
Semaphore *_packet_sema;
void entry()
{
/* indicate thread-startup completion */
_startup_sema.up();
while (true) {
_cache->ack_packets();
_packet_sema->up();
}
}
public:
Receiver(Packet_cache *cache, Semaphore *packet_sema)
: Genode::Thread<4096>("ack"), _cache(cache),
_packet_sema(packet_sema)
{
if (audio_out_active) {
start();
_startup_sema.down();
}
}
};
Receiver _receiver;
bool _get_packets()
{
bool packet_avail = false;
for (int i = 0; i < MAX_CHANNELS; ++i)
_packets[i].count = 0;
for (Session_component *session = Session_component::track_list()->first();
session;
session = session->List<Session_component>::Element::next()) {
if (!session->all_channel_packet_avail())
continue;
for (Session_component *sc = session;
true;
sc = sc->Ring<Session_component>::next()) {
Channel_number chn = sc->channel_number();
int cnt = _packets[chn].count;
_packets[chn].packet[cnt] = sc->channel()->get_packet();
_packets[chn].sink[cnt] = sc->channel();
_packets[chn].count++;
packet_avail = true;
if (sc->Ring<Session_component>::next() == session)
break;
}
}
return packet_avail;
}
Packet_descriptor _mix_one_channel(Mixer_packets *packets,
Session::Channel::Source *stream)
{
static int alloc_cnt, alloc_fail_cnt;
Packet_descriptor p;
while (1)
try {
p = stream->alloc_packet(FRAME_SIZE * PERIOD);
if (verbose)
alloc_cnt++;
break;
} catch (Session::Channel::Source::Packet_alloc_failed) {
PERR("Packet allocation failed %d %d",
++alloc_fail_cnt, alloc_cnt);
}
float *out = stream->packet_content(p);
for (int size = 0; size < PERIOD; ++size) {
float sample = 0;
for (int i = 0; i < packets->count; ++i) {
sample += packets->sink[i]->packet_content(packets->packet[i])[size];
}
if (sample > 1) sample = 1;
if (sample < -1) sample = -1;
out[size] = sample;
}
return p;
}
void _mix(Packet_descriptor p[MAX_CHANNELS])
{
/* block if no packets are available */
session_lock.lock();
while (!_get_packets()) {
session_lock.unlock();
avail_recv.wait_for_signal();
session_lock.lock();
}
_packet_sema.down();
/* mix packets */
for (int chn = 0; chn < MAX_CHANNELS; ++chn)
p[chn] = _mix_one_channel(&_packets[chn],
_out_stream[chn]->stream());
session_lock.unlock();
/* put packets into packet cache */
for (int chn = 0; chn < MAX_CHANNELS; ++chn)
_cache->put(p[chn], _packets[chn].sink, _packets[chn].packet,
_packets[chn].count, chn);
}
void entry()
{
/* indicate thread-startup completion */
_startup_sema.up();
/* just acknowledge packets if we don't have an audio out stream */
while (!audio_out_active) {
while (!_get_packets())
avail_recv.wait_for_signal();
for (int chn = 0; chn < MAX_CHANNELS; ++chn)
for (int i = 0; i < _packets[chn].count; i++)
_packets[chn].sink[i]->acknowledge_packet(_packets[chn].packet[i]);
}
while (1) {
open_tracks.down();
/* check and mix sources */
Packet_descriptor p[MAX_CHANNELS];
_mix(p);
/* submit to out */
for (int chn = 0; chn < MAX_CHANNELS; ++chn) {
Session::Channel::Source *stream = _out_stream[chn]->stream();
stream->submit_packet(p[chn]);
}
if (verbose)
PDBG("packet submitted");
open_tracks.up();
}
}
public:
Mixer(Connection *output_stream[MAX_CHANNELS], Packet_cache *cache)
: Genode::Thread<4096>("tx"), _packet_sema(OUT_QUEUE_SIZE),
_cache(cache), _receiver(cache, &_packet_sema)
{
for (int chn = 0; chn < MAX_CHANNELS; ++chn)
_out_stream[chn] = output_stream[chn];
/* synchronize with mixer thread startup */
start();
_startup_sema.down();
}
};
}
int main(int argc, char **argv)
{
PDBG("-- Genode Audio Mixer --");
using namespace Audio_out;
/* setup audio-out connections for output */
static Audio_out::Connection *output_stream[MAX_CHANNELS];
try {
for (int i = 0; i < MAX_CHANNELS; ++i) {
Allocator_avl *block_alloc = new (env()->heap()) Allocator_avl(env()->heap());
output_stream[i] = new (env()->heap())
Audio_out::Connection(channel_string_from_number((Channel_number)i), block_alloc, OUT_QUEUE_SIZE * FRAME_SIZE * PERIOD + 0x400);
}
audio_out_active = true;
} catch (...) {
PWRN("no audio driver found - dropping incoming packets");
}
/* initialize packet cache */
static Packet_cache cache(output_stream);
/* setup service */
static Audio_out::Root mixer_root(audio_out_ep(), env()->heap());
env()->parent()->announce(audio_out_ep()->manage(&mixer_root));
/* start mixer */
static Mixer mixer(output_stream, &cache);
sleep_forever();
return 0;
}