Raw_audio_sink: raw audio playback as a Terminal service

Terminal server that queues 32-bit floating point audio to an Audio_out
session.

Fix #101
This commit is contained in:
Emery Hemingway
2018-03-18 12:15:25 +01:00
committed by Norman Feske
parent 16d6fbdb47
commit 94396aa7d5
6 changed files with 250 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
SRC_DIR = src/app/raw_audio_sink
include $(GENODE_DIR)/repos/base/recipes/src/content.inc

View File

@@ -0,0 +1 @@
2018-03-18 aa6368a400f51499a1beb8969c85a9cb4209b5c0

View File

@@ -0,0 +1,5 @@
audio_out_session
base
gems
os
terminal_session

View File

@@ -0,0 +1,27 @@
This component is a write-only Terminal server that sends its input
to an Audio_out session. The input is assumed to be interleaved
stereo samples in 32-bit floating point format. It can be combined
with the _pipe_ utitily to play audio files.
! <start name="raw_audio_sink">
! <resource name="RAM" quantum="4M"/>
! <provides>
! <service name="Terminal"/>
! </provides>
! <route>
! <any-service> <parent/> <any-child/> </any-service>
! </route>
! </start>
! <start name="pipe">
! <resource name="RAM" quantum="4M"/>
! <config>
! <libc stdin="/fs/test.f32" stdout="/terminal"/>
! <vfs>
! <dir name="fs"> <fs/> </dir>
! <terminal/>
! </vfs>
! </config>
! <route>
! <any-service> <parent/> <any-child/> </any-service>
! </route>
! </start>

View File

@@ -0,0 +1,212 @@
/*
* \brief Raw audio terminal sink
* \author Emery Hemingway
* \date 2018-02-10
*/
/*
* Copyright (C) 2018 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <gems/magic_ring_buffer.h>
#include <audio_out_session/connection.h>
#include <terminal_session/connection.h>
#include <os/static_root.h>
#include <base/attached_ram_dataspace.h>
#include <base/component.h>
namespace Raw_audio {
using namespace Genode;
enum { LEFT, RIGHT, NUM_CHANNELS };
enum { AUDIO_OUT_BUFFER_SIZE =
Audio_out::QUEUE_SIZE
* Audio_out::PERIOD
* Audio_out::SAMPLE_SIZE
* NUM_CHANNELS };
struct Sink;
class Terminal_component;
struct Main;
}
struct Raw_audio::Sink
{
Sink(Sink const &);
Sink &operator = (Sink const &);
template <typename FUNC>
static void for_each_channel(FUNC const &func) {
for (int i = 0; i < NUM_CHANNELS; ++i) func(i); }
Genode::Env &_env;
Audio_out::Connection _out_left { _env, "left", true };
Audio_out::Connection _out_right { _env, "right", false };
Audio_out::Connection *_out[NUM_CHANNELS];
Magic_ring_buffer<char, AUDIO_OUT_BUFFER_SIZE> _pcm { _env };
/**
* Process client data, blocks until all data is consumed.
*/
void process(char const *src, size_t num_bytes)
{
size_t remain = num_bytes;
size_t off = 0;
while (remain > 0) {
if (_pcm.read_avail() < Audio_out::SAMPLE_SIZE)
for_each_channel([&] (int const c) {
_out[c]->start(); });
char *dst = _pcm.write_addr();
size_t const n = min(remain, _pcm.write_avail());
memcpy(dst, &src[off], n);
_pcm.fill(n);
off += n;
remain -= n;
if (_pcm.write_avail() < remain)
_env.ep().wait_and_dispatch_one_io_signal();
}
}
void submit_audio();
Io_signal_handler<Sink> _progress_handler {
_env.ep(), *this, &Sink::submit_audio };
Sink(Genode::Env &env) : _env(env)
{
_out[LEFT] = &_out_left;
_out[RIGHT] = &_out_right;
_out_left.progress_sigh(_progress_handler);
}
};
void Raw_audio::Sink::submit_audio()
{
using namespace Audio_out;
enum {
STEREO_PERIOD = Audio_out::PERIOD*2,
STEREO_CHUNK = STEREO_PERIOD * Audio_out::SAMPLE_SIZE
};
while (_pcm.read_avail() >= STEREO_CHUNK) {
Audio_out::Packet *p[NUM_CHANNELS];
while (true) {
try { p[LEFT] = _out[LEFT]->stream()->alloc(); break; }
catch (Audio_out::Stream::Alloc_failed) {
_out[LEFT]->wait_for_alloc(); }
}
unsigned const ppos = _out[LEFT]->stream()->packet_position(p[LEFT]);
p[RIGHT] = _out[RIGHT]->stream()->get(ppos);
auto *content = (float const *)_pcm.read_addr();
/* copy channel contents into sessions */
for (unsigned i = 0; i < STEREO_PERIOD; i += NUM_CHANNELS) {
for_each_channel([&] (int const c) {
p[c]->content()[i/NUM_CHANNELS] = content[i+c]; });
}
for_each_channel([&] (int const c) {
_out[c]->submit(p[c]); });
_pcm.drain(STEREO_CHUNK);
}
if (_pcm.read_avail() < Audio_out::SAMPLE_SIZE)
for_each_channel([&] (int const c) {
_out[c]->stop(); });
}
class Raw_audio::Terminal_component :
public Rpc_object<Terminal::Session, Terminal_component>
{
private:
Sink &_sink;
Genode::Attached_ram_dataspace _io_buffer;
public:
Terminal_component(Genode::Env &env, Sink &sink)
: _sink(sink), _io_buffer(env.pd(), env.rm(), AUDIO_OUT_BUFFER_SIZE) { }
/********************************
** Terminal session interface **
********************************/
Genode::Dataspace_capability _dataspace()
{
return _io_buffer.cap();
}
Size size() { return Size(0, 0); }
bool avail() { return false; }
Genode::size_t read(void *, Genode::size_t) { return 0; }
Genode::size_t _read(Genode::size_t) { return 0; }
Genode::size_t write(void const *, Genode::size_t) { return 0; }
Genode::size_t _write(Genode::size_t num_bytes)
{
/* sanitize argument */
num_bytes = Genode::min(num_bytes, _io_buffer.size());
/* copy to sink */
_sink.process(_io_buffer.local_addr<char>(), num_bytes);
return num_bytes;
}
void connected_sigh(Genode::Signal_context_capability cap) {
Genode::Signal_transmitter(cap).submit(); }
void read_avail_sigh(Genode::Signal_context_capability) { }
void size_changed_sigh(Genode::Signal_context_capability) { }
};
struct Raw_audio::Main
{
Genode::Env &_env;
Sink _sink { _env };
Terminal_component _terminal { _env, _sink };
Static_root<Terminal::Session> _terminal_root {
_env.ep().manage(_terminal) };
Main(Genode::Env &env) : _env(env)
{
env.parent().announce(env.ep().manage(_terminal_root));
}
};
/***************
** Component **
***************/
void Component::construct(Genode::Env &env) {
static Raw_audio::Main _main(env); }

View File

@@ -0,0 +1,3 @@
TARGET = raw_audio_sink
LIBS += base
SRC_CC += component.cc