diff --git a/repos/os/include/audio_in_session/audio_in_session.h b/repos/os/include/audio_in_session/audio_in_session.h new file mode 100644 index 000000000..c4d42effd --- /dev/null +++ b/repos/os/include/audio_in_session/audio_in_session.h @@ -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 +#include +#include +#include + + +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_ */ diff --git a/repos/os/include/audio_in_session/capability.h b/repos/os/include/audio_in_session/capability.h new file mode 100644 index 000000000..d7849f7cc --- /dev/null +++ b/repos/os/include/audio_in_session/capability.h @@ -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 + +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_capability; +} + +#endif /* _INCLUDE__AUDIO_IN_SESSION__CAPABILITY_H_ */ diff --git a/repos/os/include/audio_in_session/client.h b/repos/os/include/audio_in_session/client.h new file mode 100644 index 000000000..1367d76b4 --- /dev/null +++ b/repos/os/include/audio_in_session/client.h @@ -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 +#include +#include + + +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 +{ + 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, + bool progress_signal) + : + Genode::Rpc_client(session), + _data_avail(call()) + { + /* ask server for stream data space and attach it */ + _stream = static_cast(Genode::env()->rm_session()->attach(call())); + + if (progress_signal) + progress_sigh(_progress.cap); + } + + + /************* + ** Signals ** + *************/ + + void progress_sigh(Genode::Signal_context_capability sigh) { + call(sigh); } + + void overrun_sigh(Genode::Signal_context_capability sigh) { + call(sigh); } + + Genode::Signal_context_capability data_avail_sigh() { + return Genode::Signal_context_capability(); } + + + /*********************** + ** Session interface ** + ***********************/ + + void start() + { + call(); + + /* reset tail pointer */ + stream()->reset(); + } + + void stop() { call(); } + + + /********************************** + ** 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_ */ diff --git a/repos/os/include/audio_in_session/connection.h b/repos/os/include/audio_in_session/connection.h new file mode 100644 index 000000000..852804ace --- /dev/null +++ b/repos/os/include/audio_in_session/connection.h @@ -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 +#include +#include + + +namespace Audio_in { struct Connection; } + + +struct Audio_in::Connection : Genode::Connection, 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("ram_quota=%zd, channel=\"%s\"", + 2*4096 + sizeof(Stream), channel)), + Session_client(cap(), progress_signal) + { } +}; + +#endif /* _INCLUDE__AUDIO_IN_SESSION__CONNECTION_H_ */ diff --git a/repos/os/include/audio_in_session/rpc_object.h b/repos/os/include/audio_in_session/rpc_object.h new file mode 100644 index 000000000..2034aa056 --- /dev/null +++ b/repos/os/include/audio_in_session/rpc_object.h @@ -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 +#include +#include + + +namespace Audio_in { class Session_rpc_object; } + + +class Audio_in::Session_rpc_object : public Genode::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(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_ */