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:
committed by
Norman Feske
parent
16d6fbdb47
commit
94396aa7d5
2
recipes/src/raw_audio_sink/content.mk
Normal file
2
recipes/src/raw_audio_sink/content.mk
Normal file
@@ -0,0 +1,2 @@
|
||||
SRC_DIR = src/app/raw_audio_sink
|
||||
include $(GENODE_DIR)/repos/base/recipes/src/content.inc
|
||||
1
recipes/src/raw_audio_sink/hash
Normal file
1
recipes/src/raw_audio_sink/hash
Normal file
@@ -0,0 +1 @@
|
||||
2018-03-18 aa6368a400f51499a1beb8969c85a9cb4209b5c0
|
||||
5
recipes/src/raw_audio_sink/used_apis
Normal file
5
recipes/src/raw_audio_sink/used_apis
Normal file
@@ -0,0 +1,5 @@
|
||||
audio_out_session
|
||||
base
|
||||
gems
|
||||
os
|
||||
terminal_session
|
||||
27
src/app/raw_audio_sink/README
Normal file
27
src/app/raw_audio_sink/README
Normal 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>
|
||||
212
src/app/raw_audio_sink/component.cc
Normal file
212
src/app/raw_audio_sink/component.cc
Normal 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); }
|
||||
3
src/app/raw_audio_sink/target.mk
Normal file
3
src/app/raw_audio_sink/target.mk
Normal file
@@ -0,0 +1,3 @@
|
||||
TARGET = raw_audio_sink
|
||||
LIBS += base
|
||||
SRC_CC += component.cc
|
||||
Reference in New Issue
Block a user