Audio: New session interface

Issue #602
This commit is contained in:
Sebastian Sumpf 2012-12-20 16:47:25 +01:00 committed by Norman Feske
parent 73ab30c22c
commit 238d6a29c5
4 changed files with 652 additions and 0 deletions

View File

@ -0,0 +1,330 @@
/*
* \brief Audio_out session interface
* \author Sebastian Sumpf
* \date 2012-12-20
*
* An audio session corresponds to one output channel, which can be used to send
* audio frames. Each session consists of an 'Audio_out::Stream' object that resides
* in shared memory between the client and the server. The 'Audio_out::Stream' in
* turn consists of 'Audio_out::Packet's that contain the actual frames. Each packet
* within a stream is freely accessible or may be allocated successively. Also
* there is a current position pointer for each stream that is updated by the
* server. This way it is possible to send one time events that need immediate
* processing as well as streams that rely on buffering.
*
* Audio_out channel identifiers (loosely related to WAV channels) are:
*
* * front left (or left), front right (or right), front center
* * lfe (low frequency effects, subwoofer)
* * rear left, rear right, rear center
*
* For example, consumer-oriented 6-channel (5.1) audio uses front
* left/right/center, rear left/right and lfe.
*
* Note: That most components right now only support: "(front) left" and
* "(front) right".
*
*/
/*
* Copyright (C) 2012 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _INCLUDE__AUDIO_SESSION__AUDIO_SESSION_H_
#define _INCLUDE__AUDIO_SESSION__AUDIO_SESSION_H_
#include <base/allocator.h>
#include <dataspace/capability.h>
#include <base/rpc.h>
#include <session/session.h>
namespace Audio_out {
class Packet;
class Stream;
class Session;
enum {
QUEUE_SIZE = 16, /* buffer queue size */
PERIOD = 2048, /* samples per period */
SAMPLE_RATE = 44100,
SAMPLE_SIZE = sizeof(float),
};
}
/**
* 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',
* then 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; }
/**
* 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, it may not have been played back if the packet
* is invalid. For example if a server is some filter, the audio may not
* have been processed by the audio output driver.
*
* \return true 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 clients account, the client session will then request this
* dataspace as well and both will attach it in there protection domains.
* After that the stream pointer within a session will be pointed to the
* attached dataspace on both sides. Therefore the constructor of the 'Stream'
* object will never 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; }
/**
* 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
*
* \retun 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; }
/**********************************************
** 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; }
};
/**
* Audio_out session base
*/
class Audio_out::Session : public Genode::Session
{
protected:
Stream *_stream;
public:
static const char *service_name() { return "Audio_out"; }
/**
* 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 may be 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 may be 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 surfer 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);
};
#endif /* _INCLUDE__AUDIO_SESSION__AUDIO_SESSION_H_ */

View File

@ -0,0 +1,147 @@
/**
* \brief Audio_out-session-client side
* \author Sebastian Sumpf
* \date 2012-12-20
*/
/*
* Copyright (C) 2012 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _INCLUDE__AUDIO_SESSEN__CLIENT_H_
#define _INCLUDE__AUDIO_SESSEN__CLIENT_H_
#include <base/env.h>
#include <base/rpc_client.h>
#include <audio_session/audio_session.h>
namespace Audio_out {
struct Signal;
struct Session_client;
}
struct Audio_out::Signal
{
Genode::Signal_receiver recv;
Genode::Signal_context context;
Genode::Signal_context_capability cap;
Signal() : cap(recv.manage(&context)) { }
void wait() { recv.wait_for_signal(); }
};
class Audio_out::Session_client : public Genode::Rpc_client<Session>
{
private:
Signal _progress;
Signal _alloc;
Genode::Signal_transmitter _data_avail;
public:
/**
* Constructor
*
* \param session session capability
* \param alloc_signal true, install 'alloc_signal' receiver
* \param progress_signal true, install 'progress_signal' receiver
*/
Session_client(Genode::Capability<Session> session, bool alloc_signal, bool progress_signal)
:
Genode::Rpc_client<Session>(session),
_data_avail(call<Rpc_data_avail_sigh>())
{
/* ask server for stream data space and attach it */
_stream = static_cast<Stream *>(Genode::env()->rm_session()->attach(call<Rpc_dataspace>()));
if (progress_signal)
progress_sigh(_progress.cap);
if (alloc_signal)
alloc_sigh(_alloc.cap);
}
/*************
** Signals **
*************/
void progress_sigh(Genode::Signal_context_capability sigh) { call<Rpc_progress_sigh>(sigh); }
void alloc_sigh(Genode::Signal_context_capability sigh) { call<Rpc_alloc_sigh>(sigh); }
Genode::Signal_context_capability data_avail_sigh() {
return Genode::Signal_context_capability(); }
/***********************
** Session interface **
***********************/
void start()
{
call<Rpc_start>();
/* reset tail pointer */
stream()->reset();
}
void stop() { call<Rpc_stop>(); }
/**********************************
** Session interface extensions **
**********************************/
/**
* Wait for progress signal
*/
void wait_for_progress()
{
if (!_progress.cap.valid()) {
PWRN("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 applications want for block until the stream has free elements
* again.
*/
void wait_for_alloc()
{
if (!_alloc.cap.valid()) {
PWRN("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();
}
};
#endif /* _INCLUDE__AUDIO_SESSEN__CLIENT_H_ */

View File

@ -0,0 +1,51 @@
/*
* \brief Connection to audio service
* \author Sebastian Sumpf
* \date 2012-12-20
*/
/*
* Copyright (C) 2012 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _INCLUDE__AUDIO_SESSION__CONNECTION_H_
#define _INCLUDE__AUDIO_SESSION__CONNECTION_H_
#include <audio_session/client.h>
#include <base/connection.h>
#include <base/allocator.h>
namespace Audio_out {
struct Connection;
}
struct Audio_out::Connection : Genode::Connection<Session>, Audio_out::Session_client
{
/**
* Constructor
*
* \param channel channel identifier (e.g., "front left")
* \param alloc_signal install 'alloc_signal', the client may thean use
* 'wait_for_alloc' when the stream is full
* \param progress_signal install progress signal, the client may then call
* 'wait_for_progress' which is send when the server
* processed one or more packets
*/
Connection(const char *channel,
bool alloc_signal = true,
bool progress_signal = false)
:
Genode::Connection<Session>(
session("ram_quota=%zd, channel=\"%s\"",
2*4096 + sizeof(Stream), channel)),
Session_client(cap(), alloc_signal, progress_signal)
{ }
};
#endif /* _INCLUDE__AUDIO_SESSION__CONNECTION_H_ */

View File

@ -0,0 +1,124 @@
/*
* \brief Server side audio-session interface
* \author Sebastian Sumpf
* \date 2012-12-10
*/
/*
* Copyright (C) 2012 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _INCLUDE__AUDIO_SESSION__RPC_OBJECT_H_
#define _INCLUDE__AUDIO_SESSION__RPC_OBJECT_H_
#include <base/env.h>
#include <base/rpc_server.h>
#include <audio_session/audio_session.h>
namespace Audio_out {
class Session_rpc_object;
}
class Audio_out::Session_rpc_object : public Genode::Rpc_object<Audio_out::Session,
Session_rpc_object>
{
protected:
bool _stopped; /* state */
Genode::Ram_dataspace_capability _ds; /* contains Audio_out_stream */
Genode::Signal_transmitter _progress;
Genode::Signal_transmitter _alloc;
Genode::Signal_context_capability _data_cap;
bool _progress_sigh; /* progress signal on/off */
bool _alloc_sigh; /* alloc signal on/off */
public:
Session_rpc_object(Genode::Signal_context_capability data_cap)
:
_stopped(true), _data_cap(data_cap),
_progress_sigh(false), _alloc_sigh(false)
{
_ds = Genode::env()->ram_session()->alloc(sizeof(Stream));
_stream = static_cast<Stream *>(Genode::env()->rm_session()->attach(_ds));
}
virtual ~Session_rpc_object()
{
if (_ds.valid()) {
Genode::env()->rm_session()->detach(_stream);
Genode::env()->ram_session()->free(_ds);
}
}
/**************
** Signals **
**************/
void progress_sigh(Genode::Signal_context_capability sigh)
{
_progress.context(sigh);
_progress_sigh = true;
}
Genode::Signal_context_capability data_avail_sigh() {
return _data_cap; }
void alloc_sigh(Genode::Signal_context_capability sigh)
{
_alloc.context(sigh);
_alloc_sigh = true;
}
/***********************
** Session interface **
***********************/
void start() { _stopped = false; }
void stop() { _stopped = true; }
Genode::Dataspace_capability dataspace() { return _ds; }
/**********************************
** Session interface extensions **
**********************************/
/**
* Send 'progress' signal
*/
void progress_submit()
{
if (_progress_sigh)
_progress.submit();
}
/**
* Send 'alloc' signal
*/
void alloc_submit()
{
if (_alloc_sigh)
_alloc.submit();
}
/**
* Return true if client state is stopped
*/
bool stopped() const { return _stopped; }
/**
* Return true if client session is active
*/
bool active() const { return !_stopped; }
};
#endif /* _INCLUDE__AUDIO_SESSION__RPC_OBJECT_H_ */