os: add Audio_in session for recording audio

In line with the Audio_out session a Audio_in session is used to
record audio frames. Like in the Audio_out session shared memory
in form of the Audio_in::Stream is used to transport the frames
from the server to the client. These frames consist of single
channel (mono) samples. An Audio_in::Packet always contains a full
period of frames.

A Audio_in server captures frames and puts them into the
Audio_in::Stream. To do so the server allocates a Audio_in::Packet
from the packet queue embedded in the Audio_in::Stream. If the queue
is already full, the server will override packets and notify the
client by submitting the 'overrun' signal. The client has to cope
with this situation, e.g., by saving packets more frequently.

A client will also receive a 'progress' signal from the server when
a new Audio_in::Packet was submitted to the packet queue.

Fixes #1644.
This commit is contained in:
Josef Söntgen 2015-05-15 22:24:00 +02:00 committed by Christian Helmuth
parent 81599f89ea
commit 61f5ca1e4d
5 changed files with 635 additions and 0 deletions

View File

@ -0,0 +1,328 @@
/*
* \brief Audio_in session interface
* \author Josef Soentgen
* \date 2015-05-08
*
* An Audio_in session corresponds to one input channel, which can be used to
* receive audio frames. Each session consists of an 'Audio_in::Stream' object
* that resides in shared memory between the client and the server. The
* '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.
*/
/*
* Copyright (C) 2015 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_IN_SESSION__AUDIO_IN_SESSION_H_
#define _INCLUDE__AUDIO_IN_SESSION__AUDIO_IN_SESSION_H_
#include <base/allocator.h>
#include <dataspace/capability.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),
};
}
/**
* 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
{
protected:
Stream *_stream;
public:
static const char *service_name() { return "Audio_in"; }
/**
* Return stream of this session, see 'Stream' above
*/
Stream *stream() const { return _stream; }
/**
* 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);
};
#endif /* _INCLUDE__AUDIO_IN_SESSION__AUDIO_IN_SESSION_H_ */

View File

@ -0,0 +1,29 @@
/*
* \brief Audio-in session capability type
* \author Josef Soentgen
* \date 2015-05-08
*/
/*
* Copyright (C) 2015 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_IN_SESSION__CAPABILITY_H_
#define _INCLUDE__AUDIO_IN_SESSION__CAPABILITY_H_
#include <session/capability.h>
namespace Audio_in {
/*
* We cannot include 'audio_in_session/audio_in_session.h'
* because this file relies on the 'Audio_in::Session_capability' type.
*/
class Session;
typedef Genode::Capability<Session> Session_capability;
}
#endif /* _INCLUDE__AUDIO_IN_SESSION__CAPABILITY_H_ */

View File

@ -0,0 +1,119 @@
/*
* \brief Client-side Audio_in-session
* \author Josef Soentgen
* \date 2015-05-08
*/
/*
* Copyright (C) 2015 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_IN_SESSION__CLIENT_H_
#define _INCLUDE__AUDIO_IN_SESSION__CLIENT_H_
/* Genode includes */
#include <base/env.h>
#include <base/rpc_client.h>
#include <audio_in_session/audio_in_session.h>
namespace Audio_in {
struct Signal;
struct Session_client;
}
struct Audio_in::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_in::Session_client : public Genode::Rpc_client<Session>
{
private:
Signal _progress;
Genode::Signal_transmitter _data_avail;
public:
/**
* Constructor
*
* \param session session capability
* \param progress_signal true, install 'progress_signal' receiver
*/
Session_client(Genode::Capability<Session> session,
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);
}
/*************
** 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(); }
/***********************
** 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_in::Connection')");
return;
}
_progress.wait();
}
};
#endif /* _INCLUDE__AUDIO_IN_SESSION__CLIENT_H_ */

View File

@ -0,0 +1,43 @@
/*
* \brief Connection to Audio_in service
* \author Josef Soentgen
* \date 2015-05-08
*/
/*
* Copyright (C) 2015 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_IN_SESSION__CONNECTION_H_
#define _INCLUDE__AUDIO_IN_SESSION__CONNECTION_H_
#include <audio_in_session/client.h>
#include <base/connection.h>
#include <base/allocator.h>
namespace Audio_in { struct Connection; }
struct Audio_in::Connection : Genode::Connection<Session>, Audio_in::Session_client
{
/**
* Constructor
*
* \param progress_signal install progress signal, the client may then
* call 'wait_for_progress', which is sent when the
* server processed one or more packets
*/
Connection(char const *channel, bool progress_signal = false)
:
Genode::Connection<Session>(
session("ram_quota=%zd, channel=\"%s\"",
2*4096 + sizeof(Stream), channel)),
Session_client(cap(), progress_signal)
{ }
};
#endif /* _INCLUDE__AUDIO_IN_SESSION__CONNECTION_H_ */

View File

@ -0,0 +1,116 @@
/*
* \brief Server-side Audio_in session interface
* \author Josef Soentgen
* \date 2015-05-08
*/
/*
* Copyright (C) 2015 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_IN_SESSION__RPC_OBJECT_H_
#define _INCLUDE__AUDIO_IN_SESSION__RPC_OBJECT_H_
/* Genode includes */
#include <base/env.h>
#include <base/rpc_server.h>
#include <audio_in_session/audio_in_session.h>
namespace Audio_in { class Session_rpc_object; }
class Audio_in::Session_rpc_object : public Genode::Rpc_object<Audio_in::Session,
Session_rpc_object>
{
protected:
bool _stopped; /* state */
Genode::Ram_dataspace_capability _ds; /* contains Audio_in stream */
Genode::Signal_context_capability _data_cap;
Genode::Signal_context_capability _progress_cap;
Genode::Signal_context_capability _overrun_cap;
public:
Session_rpc_object(Genode::Signal_context_capability data_cap)
:
_stopped(true), _data_cap(data_cap)
{
using namespace Genode;
_ds = env()->ram_session()->alloc(sizeof(Stream));
_stream = static_cast<Stream *>(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_cap = sigh; }
void overrun_sigh(Genode::Signal_context_capability sigh) {
_overrun_cap = sigh; }
Genode::Signal_context_capability data_avail_sigh() {
return _data_cap; }
/***********************
** 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_cap.valid())
Genode::Signal_transmitter(_progress_cap).submit();
}
/**
* Send 'overrun' signal
*/
void overrun_submit()
{
if (_overrun_cap.valid())
Genode::Signal_transmitter(_overrun_cap).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_IN_SESSION__RPC_OBJECT_H_ */