Add rudimentary audio_player based on libav

For further information please look at 'src/app/audio_player/README'.

Fixes #41.
This commit is contained in:
Josef Söntgen
2016-08-30 14:26:00 +02:00
committed by Norman Feske
parent 099ea5a6a5
commit da743cced4
9 changed files with 1580 additions and 0 deletions

162
run/audio_player.run Normal file
View File

@@ -0,0 +1,162 @@
#
# Build
#
set build_components {
core init
drivers/timer
drivers/audio
server/report_rom
server/dynamic_rom
app/audio_player
}
source ${genode_dir}/repos/base/run/platform_drv.inc
append_platform_drv_build_components
build $build_components
create_boot_directory
#
# Config
#
append config {
<config>
<parent-provides>
<service name="ROM"/>
<service name="RAM"/>
<service name="IRQ"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
<service name="CAP"/>
<service name="PD"/>
<service name="RM"/>
<service name="CPU"/>
<service name="LOG"/>
<service name="SIGNAL" />
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides><service name="Timer"/></provides>
</start>}
append_platform_drv_config
append config {
<start name="report_rom">
<resource name="RAM" quantum="4M"/>
<provides> <service name="Report"/> <service name="ROM"/> </provides>
<config verbose="yes">
<rom>
<policy label="" report="audio_player -> current_track"/>
</rom>
</config>
</start>
<start name="dynamic_rom">
<resource name="RAM" quantum="4M"/>
<provides><service name="ROM"/></provides>
<config verbose="yes">
<rom name="playlist">
<inline description="initial playlist">
<playlist>
<track path="foo.mp3"/>
<track path="foo.ogg"/>
<track path="foo.flac"/>
</playlist>
</inline>
<sleep milliseconds="30000"/>
<inline description="initial playlist">
<playlist>
<track path="foo.ogg"/>
</playlist>
</inline>
<sleep milliseconds="30000"/>
</rom>
<rom name="audio_player.config">
<inline description="initial config">
<config ld_verbose="yes" state="playing" playlist_mode="repeat">
<report progress="yes" interval="1" playlist="yes"/>
<libc>
<vfs>
<rom name="foo.mp3"/>
<rom name="foo.flac"/>
<rom name="foo.ogg"/>
</vfs>
</libc>
</config>
</inline>
<sleep milliseconds="10000"/>
<inline description="select third track">
<config state="playing" playlist_mode="repeat" selected_track="3"/>
</inline>
<sleep milliseconds="5000"/>
<inline description="select first track">
<config state="playing" playlist_mode="repeat" selected_track="1"/>
</inline>
<sleep milliseconds="5000"/>
<inline description="select second track">
<config state="playing" playlist_mode="repeat" selected_track="2"/>
</inline>
<sleep milliseconds="5000"/>
</rom>
</config>
</start>
<start name="audio_drv">
<resource name="RAM" quantum="8M"/>
<provides> <service name="Audio_out"/> </provides>
<config/>
</start>
<start name="audio_player">
<resource name="RAM" quantum="16M"/>
<configfile name="audio_player.config"/>
<route>
<service name="ROM" label="audio_player.config"> <child name="dynamic_rom"/> </service>
<service name="ROM" label="playlist"> <child name="dynamic_rom"/> </service>
<service name="Report" label="current_track"> <child name="report_rom"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
</config>}
install_config $config
#
# Check audio files
#
if {[expr ![file exists bin/foo.flac] || ![file exists bin/foo.mp3] || ![file exists bin/foo.ogg]]} {
puts ""
puts "One or all audio files are missing. Please put 'foo.flac', 'foo.mp3' and"
puts "'foo.ogg' into './bin' and execute this run script again. Note, that the"
puts "duration of each of those files has to be below 10 seconds for this run"
puts "script to work properly."
puts ""
exit 1
}
#
# Boot modules
#
set boot_modules {
core init timer dynamic_rom report_rom audio_drv
ld.lib.so libc.lib.so libm.lib.so zlib.lib.so
avcodec.lib.so avformat.lib.so avutil.lib.so
avresample.lib.so pthread.lib.so
audio_player
foo.mp3 foo.flac foo.ogg
}
append_platform_drv_boot_modules
build_boot_image $boot_modules
run_genode_until forever

View File

@@ -0,0 +1,79 @@
This directory contains a rudimentary audio player. It plays all tracks from
a given playlist and reports the currently playing track.
Configuration
~~~~~~~~~~~~~
A typical example configuration looks as follows:
! <config>
! <report progress="yes" interval="3" playlist="yes"/>
! <libc>
! <vfs>
! <rom name="foo.flac"/>
! <rom name="foo.mp3"/>
! <rom name="foo.ogg"/>
! </vfs>
! </libc>
! </config>
The audio player obtains its audio files from the libc VFS. In this example,
the VFS is configured to present three ROM modules as files in the root
directory. In addition the audio player requests another ROM module that
contains the actual playlist:
! <playlist mode="repeat">
! <track path="foo.ogg"/>
! <track path="foo.flac"/>
! </playlist>
The prior mentioned files are then referred to by the subsequent '<track>'
nodes within the '<playlist>' node. How the player processes the playlist is
specified via the 'mode' attribute. Valid values are 'once' and 'repeat'
whereat 'once' is the default mode. Everytime the playlist is altered, the
audio player updates its internal representation of the playlist but keeps
playing the current track till its end.
Playback can be paused, stopped and of course started again by setting the
'state' attribute of the '<config>' node to 'playing', 'paused' or 'stopped'.
The player is automatically stopped if the 'state' attribute is missing.
When playback is started the player starts playing at the first track of the
playlist. Furthermore the player can be instructed to start at a different
track by specifying the number of the track in the 'selected_track' attribute.
This attribute may also be used to select a different track while the player
is playing.
The initial configuration must include the VFS configuration, all following
configurations may safely ommit it.
Status reporting
~~~~~~~~~~~~~~~~
Every time the player changes the track it generates a report that contains
information about the track that is now being played. A typical report looks
like this:
! <current_track id="1" path="foo.ogg" artist="Foobar" album="SNAFU" title="blubb" duration="60000"/>
The duration is given in milliseconds.
When the 'report' node is present in the configuration additional reports
are generated. If the 'progress' attribute is set to 'yes' the player will
report the current progress in the 'current_track' report. The frequencey
of reporting is specified by setting the 'interval' attribute. The interval
is given in seconds. if the 'playlist' attribute is set to 'yes' the
player will generate a report containing the playlist but each '<track>' node
is enriched by the meta information of each track like in the 'current_track'
report.
Example
~~~~~~~
Please refer to the _gems/run/audio_player.run_ script for a practical example of
using the audio player.
There is also a selection of bash scripts in _gems/src/app/audio_player/noux_
which can be used from Noux to control the audio player.

102
src/app/audio_player/list.h Normal file
View File

@@ -0,0 +1,102 @@
/*
* \brief Slightly improved list
* \author Christian Helmuth
* \date 2014-09-25
*/
/*
* Copyright (C) 2014 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 _LIST_H_
#define _LIST_H_
#include <util/list.h>
namespace Util {
template <typename> class List;
template <typename> class List_element;
}
template <typename LT>
class Util::List : private Genode::List<LT>
{
private:
typedef Genode::List<LT> Base;
public:
using Base::Element;
void append(LT const *le)
{
LT *at = nullptr;
for (LT *l = first(); l; l = l->next())
at = l;
Base::insert(le, at);
}
void prepend(LT const *le)
{
Base::insert(le);
}
void insert_before(LT const *le, LT const *at)
{
if (at == first()) {
prepend(le);
return;
} else if (!at) {
append(le);
return;
}
for (LT *l = first(); l; l = l->next())
if (l->next() == at)
at = l;
Base::insert(le, at);
}
/****************************
** Genode::List interface **
****************************/
LT *first() { return Base::first(); }
LT const *first() const { return Base::first(); }
void insert(LT const *le, LT const *at = 0)
{
Base::insert(le, at);
}
void remove(LT const *le)
{
Base::remove(le);
}
};
template <typename T>
class Util::List_element : public Util::List<List_element<T> >::Element
{
private:
T *_object;
public:
List_element(T *object) : _object(object) { }
T *object() const { return _object; }
};
#endif /* _LIST_H_ */

View File

@@ -0,0 +1,861 @@
/*
* \brief Audio player
* \author Josef Soentgen
* \date 2015-11-19
*/
/*
* Copyright (C) 2015-2016 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.
*/
/* Genode includes */
#include <base/attached_rom_dataspace.h>
#include <base/component.h>
#include <base/heap.h>
#include <base/printf.h>
#include <base/sleep.h>
#include <os/reporter.h>
#include <util/retry.h>
#include <util/xml_node.h>
#include <audio_out_session/connection.h>
/* local includes */
#include <list.h>
#include <ring_buffer.h>
extern "C" {
/*
* UINT64_C is needed by libav headers
*
* Use the compiler's definition as fallback because the UINT64_C macro is only
* defined in <machine/_stdint.h> when used with C.
*/
#ifndef UINT64_C
#define UINT64_C(c) __UINT64_C(c)
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/dict.h>
#include <libavutil/frame.h>
#include <libavutil/opt.h>
#include <libavresample/avresample.h>
}; /* extern "C" */
namespace Audio_player {
class Output;
class Playlist;
class Decoder;
struct Main;
typedef Util::Ring_buffer<64 * 1024> Frame_data;
typedef Genode::String<1024> Path;
enum { LEFT, RIGHT, NUM_CHANNELS };
enum { AUDIO_OUT_PACKET_SIZE = Audio_out::PERIOD * NUM_CHANNELS * sizeof(float) };
enum { QUEUED_PACKET_THRESHOLD = 10 };
}
struct Audio_player::Output
{
Audio_out::Connection _left;
Audio_out::Connection _right;
Audio_out::Connection *_out[NUM_CHANNELS];
Audio_out::Packet *_alloc_position;
unsigned _packets_submitted = 0;
template <typename FUNC>
static void for_each_channel(FUNC const &func) {
for (int i = 0; i < Audio_player::NUM_CHANNELS; i++) func(i); }
/**
* Constructor
*
* \param sigh progress signal handler
*/
Output(Genode::Signal_context_capability sigh)
: _left("left", true, true), _right("right", false, false)
{
/*
* We only care about the left (first) channel and sync all other
* channels with it when needed
*/
_left.progress_sigh(sigh);
_out[LEFT] = &_left;
_out[RIGHT] = &_right;
}
void start()
{
for_each_channel([&] (int const i) { _out[i]->start(); });
}
void stop()
{
for_each_channel([&] (int const i) { _out[i]->stop(); });
_alloc_position = nullptr;
}
/**
* Return rounded estimate of packets per secound
*/
unsigned packets_per_sec() const { return Audio_out::SAMPLE_RATE / Audio_out::PERIOD; }
/**
* Return the size of one output frame
*/
size_t frame_size() const { return AUDIO_OUT_PACKET_SIZE; }
/**
* Return number of currently queued packets in Audio_out stream
*/
unsigned queued()
{
if (_alloc_position == nullptr) _alloc_position = _out[LEFT]->stream()->next();
unsigned const packet_pos = _out[LEFT]->stream()->packet_position(_alloc_position);
unsigned const play_pos = _out[LEFT]->stream()->pos();
unsigned const queued = packet_pos < play_pos
? ((Audio_out::QUEUE_SIZE + packet_pos) - play_pos)
: packet_pos - play_pos;
return queued;
}
/**
* Fetch decoded frames from frame data buffer and fill Audio_out packets
*/
void drain_buffer(Frame_data &frame_data)
{
if (_alloc_position == nullptr) _alloc_position = _out[LEFT]->stream()->next();
while (frame_data.read_avail() > (AUDIO_OUT_PACKET_SIZE)) {
Audio_out::Packet *p[NUM_CHANNELS];
p[LEFT] = _out[LEFT]->stream()->next(_alloc_position);
unsigned const ppos = _out[LEFT]->stream()->packet_position(p[LEFT]);
p[RIGHT] = _out[RIGHT]->stream()->get(ppos);
float tmp[Audio_out::PERIOD * NUM_CHANNELS];
size_t const n = frame_data.read(tmp, sizeof(tmp));
if (n != sizeof(tmp)) {
Genode::warning("less frame data read than expected");
}
float *left_content = p[LEFT]->content();
float *right_content = p[RIGHT]->content();
for (int i = 0; i < Audio_out::PERIOD; i++) {
left_content[i] = tmp[i * NUM_CHANNELS + LEFT];
right_content[i] = tmp[i * NUM_CHANNELS + RIGHT];
}
for_each_channel([&] (int const i) { _out[i]->submit(p[i]); });
_alloc_position = p[LEFT];
_packets_submitted++;
}
}
/**
* Return number of already submitted Audio_out packets
*/
unsigned packets_submitted() { return _packets_submitted; }
};
class Audio_player::Playlist
{
public:
struct Track : public Util::List<Track>::Element
{
Path path;
unsigned id = 0;
Track() { }
Track(char const *path, unsigned id) : path(path), id(id) { }
Track(Track const &track) : path(track.path), id(track.id) { }
bool valid() { return path.length(); }
};
template <typename FUNC>
void for_each_track(FUNC const &func) {
for (Track *t = _track_list.first(); t; t = t->next())
func(*t);
}
private:
Genode::Allocator &_alloc;
Util::List<Track> _track_list;
Track *_curr_track = nullptr;
typedef enum { MODE_ONCE, MODE_REPEAT } Mode;
Mode _mode;
void _insert(char const *path, unsigned id)
{
try {
Track *t = new (&_alloc) Track(path, id);
_track_list.append(t);
} catch (...) { Genode::warning("could not insert track"); }
}
void _remove_all()
{
while (Track *t = _track_list.first()) {
_track_list.remove(t);
Genode::destroy(&_alloc, t);
}
}
public:
Playlist(Genode::Allocator &alloc) : _alloc(alloc), _mode(MODE_ONCE) { }
~Playlist() { _remove_all(); }
/**
* Update playlist
*/
void update(Genode::Xml_node &node)
{
/* handle tracks */
_remove_all();
_curr_track = nullptr;
unsigned count = 0;
auto add_track = [&] (Genode::Xml_node &track) {
try {
Path path;
track.attribute("path").value(&path);
_insert(path.string(), ++count);
} catch (...) { Genode::warning("invalid file node in playlist"); }
};
try {
node.for_each_sub_node("track", add_track);
} catch (...) { }
/* handle playlist mode */
_mode = MODE_ONCE;
try {
Genode::String<16> mode;
node.attribute("mode").value(&mode);
if (mode == "repeat") _mode = MODE_REPEAT;
} catch (...) { }
Genode::log("update playlist: ", count,
" tracks mode: ", _mode == MODE_ONCE ? "once" : "repeat");
}
/**
* Return track with given id
*/
Track get_track(unsigned id)
{
for (Track *t = _track_list.first(); t; t = t->next())
if (t->id == id) {
_curr_track = t;
return *_curr_track;
}
return Track();
}
/**
* Return next track from playlist
*/
Track next_track()
{
_curr_track = _curr_track ? _curr_track->next()
: _track_list.first();
/* try to loop at the end of the playlist */
if (_curr_track == nullptr && _mode == MODE_REPEAT) {
_curr_track = _track_list.first();
}
return _curr_track ? *_curr_track : Track();
}
};
class Audio_player::Decoder
{
public:
/*
* Thrown as exception if the decoder could not be initialized
*/
struct Not_initialized { };
/*
* File_info contains metadata of the file that is currently decoded
*/
struct File_info : public Playlist::Track
{
typedef Genode::String<256> Artist;
typedef Genode::String<256> Album;
typedef Genode::String<256> Title;
typedef Genode::String<16> Track;
Artist artist;
Album album;
Title title;
Track track;
int64_t duration;
File_info(Playlist::Track const &playlist_track,
char const *artist, char const *album,
char const *title, char const *track,
int64_t duration)
:
Playlist::Track(playlist_track),
artist(artist), album(album), title(title), track(track),
duration(duration) { }
};
private:
Genode::Allocator &_alloc;
AVFrame *_frame = nullptr;
AVFrame *_conv_frame = nullptr;
AVStream *_stream = nullptr;
AVFormatContext *_format_ctx = nullptr;
AVCodecContext *_codec_ctx = nullptr;
AVPacket _packet;
AVAudioResampleContext *_avr = nullptr;
unsigned _samples_decoded = 0;
Playlist::Track const &_track;
File_info *_track_info = nullptr;
void _close()
{
avformat_close_input(&_format_ctx);
av_free(_conv_frame);
av_free(_frame);
}
/**
* Initialize libav once before it gets used
*/
static void init()
{
static bool registered = false;
if (registered) { return; }
/* initialise libav first so that all decoders are present */
av_register_all();
/* make libav quiet so we do not need stderr access */
av_log_set_level(AV_LOG_QUIET);
registered = true;
}
public:
/*
* Constructor
*/
Decoder(Genode::Allocator &alloc, Playlist::Track const &playlist_track)
: _alloc(alloc), _track(playlist_track)
{
Decoder::init();
_frame = av_frame_alloc();
if (!_frame) throw Not_initialized();
_conv_frame = av_frame_alloc();
if (!_conv_frame) {
av_free(_frame);
throw Not_initialized();
}
int err = 0;
err = avformat_open_input(&_format_ctx, _track.path.string(), NULL, NULL);
if (err != 0) {
Genode::error("could not open '", _track.path.string(), "'");
av_free(_conv_frame);
av_free(_frame);
throw Not_initialized();
}
err = avformat_find_stream_info(_format_ctx, NULL);
if (err < 0) {
Genode::error("could not find the stream info");
_close();
throw Not_initialized();
}
for (unsigned i = 0; i < _format_ctx->nb_streams; ++i)
if (_format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
_stream = _format_ctx->streams[i];
break;
}
if (_stream == nullptr) {
Genode::error("could not find any audio stream");
_close();
throw Not_initialized();
}
_codec_ctx = _stream->codec;
_codec_ctx->codec = avcodec_find_decoder(_codec_ctx->codec_id);
if (_codec_ctx->codec == NULL) {
Genode::error("could not find decoder");
_close();
throw Not_initialized();
}
err = avcodec_open2(_codec_ctx, _codec_ctx->codec, NULL);
if (err != 0) {
Genode::error("could not open decoder");
_close();
throw Not_initialized();
}
_avr = avresample_alloc_context();
if (!_avr) {
_close();
throw Not_initialized();
}
av_opt_set_int(_avr, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(_avr, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(_avr, "in_sample_rate", _codec_ctx->sample_rate, 0);
av_opt_set_int(_avr, "out_sample_rate", Audio_out::SAMPLE_RATE, 0);
av_opt_set_int(_avr, "in_sample_fmt", _codec_ctx->sample_fmt, 0);
av_opt_set_int(_avr, "out_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
if (avresample_open(_avr) < 0) {
_close();
throw Not_initialized();
}
_conv_frame->channel_layout = AV_CH_LAYOUT_STEREO;
_conv_frame->sample_rate = Audio_out::SAMPLE_RATE;
_conv_frame->format = AV_SAMPLE_FMT_FLT;
av_init_packet(&_packet);
/* extract metainformation */
bool const is_vorbis = _codec_ctx->codec_id == AV_CODEC_ID_VORBIS;
AVDictionary *md = is_vorbis ? _stream->metadata : _format_ctx->metadata;
int const flags = AV_DICT_IGNORE_SUFFIX;
AVDictionaryEntry *artist = av_dict_get(md, "artist", NULL, flags);
AVDictionaryEntry *album = av_dict_get(md, "album", NULL, flags);
AVDictionaryEntry *title = av_dict_get(md, "title", NULL, flags);
AVDictionaryEntry *track = av_dict_get(md, "track", NULL, flags);
_track_info = new (&_alloc) File_info(_track,
artist ? artist->value : "", album ? album->value : "",
title ? title->value : "", track ? track->value : "",
_format_ctx->duration / 1000);
}
/**
* Destructor
*/
~Decoder()
{
avresample_close(_avr);
avresample_free(&_avr);
avcodec_close(_codec_ctx);
avformat_close_input(&_format_ctx);
av_free(_conv_frame);
av_free(_frame);
Genode::destroy(&_alloc, _track_info);
}
/**
* Dump format information - needs <libc stderr="/dev/log"> configuration
*/
void dump_info() const { av_dump_format(_format_ctx, 0, _track.path.string(), 0); }
/**
* Return metainformation of the file
*/
File_info const & file_info() const { return *_track_info; }
/**
* Return decoding progress in ms
*/
uint64_t progress() const
{
/*
* Until there is need for seeking support that is enough albeit
* inaccurate and do not get me started on the float abuse...
*
* XXX We could also count the Audio_out packets submitted per
* track to get a more accurate playback progress
* (see Out::packets_submitted()).
*/
return (float)_samples_decoded / _codec_ctx->sample_rate * 1000;
}
/**
* Fill frame data buffer with decoded frames
*
* \param frame_data reference to destination buffer
* \param min minimal number of bytes that have to be decoded at least
*/
int fill_buffer(Frame_data &frame_data, size_t min)
{
size_t written = 0;
while (written < min
&& (av_read_frame(_format_ctx, &_packet) == 0)) {
if (_packet.stream_index == _stream->index) {
int finished = 0;
avcodec_decode_audio4(_codec_ctx, _frame, &finished, &_packet);
if (finished) {
if (avresample_convert_frame(_avr, _conv_frame, _frame) < 0) {
Genode::error("could not resample frame");
return 0;
}
void const *data = _conv_frame->extended_data[0];
size_t const bytes = _conv_frame->linesize[0];
size_t const s = 2 /* stereo */ * sizeof(float);
_samples_decoded += bytes / s;
written += frame_data.write(data, bytes);
}
}
av_free_packet(&_packet);
}
return written;
}
};
struct Audio_player::Main
{
Genode::Env &env;
Genode::Heap alloc { env.ram(), env.rm() } ;
void handle_progress();
Genode::Signal_handler<Main> progress_dispatcher = {
env.ep(), *this, &Main::handle_progress };
Output output { progress_dispatcher };
Frame_data frame_data;
Decoder *decoder = nullptr;
Playlist playlist { alloc };
Playlist::Track track;
void scan_playlist();
Genode::Reporter playlist_reporter { "playlist" };
bool report_playlist = false;
void handle_playlist();
Genode::Signal_handler<Main> playlist_dispatcher = {
env.ep(), *this, &Main::handle_playlist };
Genode::Attached_rom_dataspace playlist_rom { "playlist" };
bool is_paused = false;
bool is_stopped = false;
bool state_changed = false;
Genode::Reporter reporter { "current_track" };
unsigned report_progress_interval = 0; /* in Audio_out packets */
bool report_progress = false;
unsigned packet_count = 0;
void report_track(Decoder const *d);
Genode::Attached_rom_dataspace config_rom { env, "config" };
void handle_config();
Genode::Signal_handler<Main> config_dispatcher = {
env.ep(), *this, &Main::handle_config };
Main(Genode::Env &env) : env(env)
{
Genode::Signal_transmitter(config_dispatcher).submit();
config_rom.sigh(config_dispatcher);
Genode::Signal_transmitter(playlist_dispatcher).submit();
playlist_rom.sigh(playlist_dispatcher);
reporter.enabled(true);
playlist_reporter.enabled(true);
Genode::log("--- start Audio_player ---");
}
};
void Audio_player::Main::scan_playlist()
{
try {
Genode::Reporter::Xml_generator xml(playlist_reporter, [&] () {
playlist.for_each_track([&] (Playlist::Track const &t) {
Decoder d(alloc, t);
Decoder::File_info const &info = d.file_info();
xml.node("track", [&] () {
xml.attribute("id", info.id);
xml.attribute("path", info.path);
xml.attribute("artist", info.artist);
xml.attribute("album", info.album);
xml.attribute("title", info.title);
xml.attribute("track", info.track);
xml.attribute("duration", info.duration);
});
});
});
} catch (...) { Genode::warning("could not report playlist"); }
}
void Audio_player::Main::handle_playlist()
{
playlist_rom.update();
if (!playlist_rom.is_valid()) { return; }
Genode::Xml_node node(playlist_rom.local_addr<char>(),
playlist_rom.size());
playlist.update(node);
track = playlist.next_track();
if (report_playlist) { scan_playlist(); }
/* trigger playback because playlist has changed */
handle_progress();
}
void Audio_player::Main::report_track(Decoder const *d)
{
try {
Genode::Reporter::Xml_generator xml(reporter, [&] () {
/*
* There is no valid decoder, create empty report to notify
* agents.
*/
if (d == nullptr) { return; }
Decoder::File_info const &info = d->file_info();
xml.attribute("id", info.id);
xml.attribute("path", info.path);
xml.attribute("artist", info.artist);
xml.attribute("album", info.album);
xml.attribute("title", info.title);
xml.attribute("track", info.track);
xml.attribute("progress", d->progress());
xml.attribute("duration", info.duration);
char const *s = "playing";
if (is_paused) { s = "paused"; }
if (is_stopped) { s = "stopped"; }
xml.attribute("state", s);
});
} catch (...) { Genode::warning("could not report current track"); }
}
void Audio_player::Main::handle_progress()
{
if (is_stopped) {
Genode::destroy(&alloc, decoder);
decoder = nullptr;
is_stopped = false;
report_track(nullptr);
}
if (is_paused) { return; }
/* do not bother, that is not the track you are looking for */
if (!track.valid()) { return; }
/* track is valid but there is no decoder yet */
if (decoder == nullptr) {
Genode::retry<Decoder::Not_initialized>(
[&] {
if (track.valid()) {
decoder = new (&alloc) Decoder(alloc, track);
report_track(decoder);
packet_count = 0;
}
},
[&] {
/* in case it did not work try next track */
track = playlist.next_track();
});
report_track(decoder);
}
/* update current track progress */
if (report_progress
&& decoder
&& (++packet_count == report_progress_interval)) {
report_track(decoder);
packet_count = 0;
}
/* only decode and play if we are below the threshold */
if (output.queued() < QUEUED_PACKET_THRESHOLD) {
if (decoder && frame_data.read_avail() <= output.frame_size()) {
size_t const req_size = output.frame_size();
int const n = decoder->fill_buffer(frame_data, req_size);
if (n == 0) {
Genode::destroy(&alloc, decoder);
decoder = nullptr;
track = playlist.next_track();
if (!track.valid()) {
Genode::warning("reached end of playlist");
report_track(nullptr);
}
}
}
if (frame_data.read_avail() >= AUDIO_OUT_PACKET_SIZE)
output.drain_buffer(frame_data);
}
}
void Audio_player::Main::handle_config()
{
config_rom.update();
if (!config_rom.valid()) { return; }
Genode::Xml_node config(config_rom.local_addr<char>(), config_rom.size());
/* handle playlist scan request */
try {
config.attribute("scan_playlist").has_value("yes");
scan_playlist();
} catch (...) { }
/* handle state */
bool state_changed = false;
try {
static Genode::String<16> last_state { "-" };
Genode::String<16> state;
config.attribute("state").value(&state);
state_changed = state != last_state;
if (state_changed) {
if (state == "playing") {
is_paused = false;
output.start();
}
else if (state == "paused") {
is_paused = true;
output.stop();
}
else /*(state == "stopped")*/ {
Genode::error("state == stopped, should not happen");
is_stopped = true;
is_paused = true;
output.stop();
}
last_state = state;
}
report_track(decoder);
} catch (...) {
/* if there is no state attribute we are stopped */
Genode::warning("player state invalid, player is stopped");
is_stopped = true;
is_paused = true;
output.stop();
}
/* handle selected track */
try {
unsigned id = 0;
config.attribute("selected_track").value(&id);
if (id != track.id) { /* XXX what happens if the playlist changed? */
is_stopped = true; /* XXX do not abuse this flag */
track = playlist.get_track(id);
if (!track.valid()) {
Genode::error("invalid track ", id, " selected");
}
}
} catch (...) { }
/* handle progress report */
enum { DEFAULT_SEC = 5 };
try {
Genode::Xml_node node = config.sub_node("report");
report_progress = node.attribute("progress").has_value("yes");
unsigned v = node.attribute_value<unsigned>("interval", DEFAULT_SEC);
report_progress_interval = output.packets_per_sec() * v;
report_playlist = node.attribute("playlist").has_value("yes");
} catch (...) {
report_progress = false;
report_progress_interval = output.packets_per_sec() * DEFAULT_SEC;
report_playlist = false;
}
/* trigger playback because state might has changed */
if (state_changed) { handle_progress(); }
}
/***************
** Component **
***************/
Genode::size_t Component::stack_size() { return 8 * 1024 * sizeof(long); }
void Component::construct(Genode::Env &env)
{
static Audio_player::Main main(env);
}

View File

@@ -0,0 +1,89 @@
#!/bin/bash
die() {
printf -- "${1}\n"
exit 2
}
print_usage() {
local help="$1"
printf "usage: $PROG_NAME [-hv] [-i <file>\ [-o <file>] [-m <0..100>] [-c <0..100>]\n"
if [ "$help" != "" ]; then
printf "\t-c set client volume\n"
printf "\t-h print this help screen\n"
printf "\t-i read config from <file>\n"
printf "\t-m set master volume\n"
printf "\t-o print config to <file>\n"
printf "\t-v be verbose, e.g. print current track\n"
fi
}
parse_arguments() {
local args=$(getopt hc:i:m:o:sv ${*})
[ $? != 0 ] && exit 1
set -- $args
while [ $# -ge 0 ]; do
case "$1" in
-c) ARG_CLIENT="$2"; shift; shift;;
-h) print_usage "help"; exit 0;;
-i) ARG_INFILE="$2"; shift; shift;;
-m) ARG_MASTER="$2"; shift; shift;;
-o) ARG_OUTFILE="$2"; shift; shift;;
-v) ARG_VERBOSE=1; shift;;
--) shift; break;;
esac
done
[ "$ARG_INFILE" = "" ] && ARG_INFILE="/config/mixer.config"
}
write() {
if [ "$ARG_OUTFILE" != "" ]; then
printf "${1}" >> $ARG_OUTFILE
else
printf "${1}"
fi
}
generate_config() {
if [ "$ARG_MASTER" = "" ] && [ "$ARG_CLIENT" = "" ]; then
cat $ARG_INFILE
return
fi
[ "$ARG_OUTFILE" = "$ARG_INFILE" ] && inplace="-i" || inplace=
# XXX distinguish between master and client
[ "$ARG_MASTER" != "" ] && \
sed_args="-e 's/ volume=\\\".*\\\" / volume=\\\"'$ARG_MASTER'\\\" /'"
[ "$ARG_CLIENT" != "" ] && \
sed_args="$sed_args -e 's/ volume=\\\".*\\\" / volume=\\\"'$ARG_CLIENT'\\\" /'"
if [ "$ARG_OUTFILE" != "" ]; then
echo sed $sed_args $inplace ${ARG_INFILE} | /bin/bash > $ARG_OUTFILE
else
echo sed $sed_args $inplace ${ARG_INFILE} | /bin/bash
fi
}
main() {
parse_arguments "$@"
generate_config
exit 0
}
PROG_NAME=$(basename $0)
ARG_CLIENT=
ARG_INFILE=
ARG_MASTER=
ARG_OUTFILE=
ARG_VERBOSE=0
main "$@"
exit 0
# End of file

View File

@@ -0,0 +1,94 @@
#!/bin/bash
die() {
printf -- "${1}\n"
exit 2
}
print_usage() {
local help="$1"
printf "usage: $PROG_NAME [-hnsv] [-o <file>]\n"
if [ "$help" != "" ]; then
printf "\t-h print this help screen\n"
printf "\t-n play next track from playlist\n"
printf "\t-o print config to <file>\n"
printf "\t-s stop playback\n"
printf "\t-v be verbose, e.g. print current track\n"
fi
}
parse_arguments() {
local args=$(getopt hn:o:sv ${*})
if [ $# -eq 0 ]; then
ARG_CURRENT=1
return
fi
[ $? != 0 ] && exit 1
set -- $args
while [ $# -ge 0 ]; do
case "$1" in
-h) print_usage "help"; exit 0;;
-n) ARG_NEXT="$2"; shift; shift;;
-o) ARG_OUTFILE="$2"; shift; shift;;
-s) ARG_STOP=1; shift;;
-v) ARG_VERBOSE=1; shift;;
--) shift; break;;
esac
done
}
write() {
if [ "$ARG_OUTFILE" != "" ]; then
printf "${1}" >> $ARG_OUTFILE
else
printf "${1}"
fi
}
generate_config() {
local selected=
[ "$ARG_NEXT" != "" ] && selected=" selected_track=\"${ARG_NEXT}\""
local state=
[ $ARG_STOP -eq 1 ] && state="stopped" || state="playing"
write "<config state=\"${state}\"${selected}>\n"
write "\t<libc> <vfs> <fs/> </vfs> </libc>\n"
write "</config>\n"
}
show_current_track() {
cat /reports/current_track 2> /dev/null
}
main() {
parse_arguments "$@"
if [ $ARG_CURRENT -eq 1 ]; then
show_current_track
exit 0
fi
generate_config
if [ $ARG_VERBOSE -eq 1 ]; then
sleep 1
show_current_track
fi
exit 0
}
PROG_NAME=$(basename $0)
ARG_NEXT=""
ARG_CURRENT=0
ARG_SHUFFLE=0
ARG_STOP=0
ARG_VERBOSE=0
main "$@"
exit 0
# End of file

View File

@@ -0,0 +1,85 @@
#!/bin/bash
die() {
printf -- "${1}\n"
exit 2
}
print_usage() {
local help="$1"
printf "usage: $PROG_NAME [-hz] [-o <file>] <directory>\n"
if [ "$help" != "" ]; then
printf "\t-h print this help screen\n"
printf "\t-o print playlist to <file>\n"
printf "\t-z shuffle entries in playlist\n"
fi
}
parse_arguments() {
local args=$(getopt ho:z ${*})
[ $? != 0 ] && exit 1
if [ $# -lt 1 ]; then
print_usage
exit 1
fi
set -- $args
while [ $# -ge 0 ]; do
case "$1" in
-h) print_usage "help"; exit 0;;
-o) ARG_OUTFILE=$2; shift; shift;;
-z) ARG_SHUFFLE=1; shift;;
--) shift; break;;
esac
done
if [ $# -lt 1 ]; then
die "aborting, directory missing."
fi
ARG_DIR=$@
}
write() {
if [ "$ARG_OUTFILE" != "" ]; then
printf "${1}" >> $ARG_OUTFILE
else
printf "${1}"
fi
}
generate_playlist() {
write "<playlist>\n"
while read file; do
local path=$(readlink -f "$file")
write "\t<track path=\"${path}\"/>\n"
done
write "</playlist>\n"
}
main() {
parse_arguments "$@"
local order=
[ $ARG_SHUFFLE -eq 1 ] && order=shuf || order=sort
find "$ARG_DIR" -name '*.aac' \
-o -name '*.flac' \
-o -name '*.mp3' \
-o -name '*.ogg' \
-o -name '*.wav' \
| $order | generate_playlist
exit 0
}
PROG_NAME=$(basename $0)
ARG_DIR=
ARG_OUTFILE=
ARG_SHUFFLE=0
main "$@"
exit 0
# End of file

View File

@@ -0,0 +1,104 @@
/*
* \brief Quick and dirty ring buffer
* \author Josef Soentgen
* \date 2015-11-19
*/
/*
* 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 _RING_BUFFER_H_
#define _RING_BUFFER_H_
#include <base/stdint.h>
#include <util/string.h>
namespace Util {
template <Genode::size_t> struct Ring_buffer;
}
template <Genode::size_t CAPACITY>
struct Util::Ring_buffer
{
Genode::size_t wpos { 0 };
Genode::size_t rpos { 0 };
char _data[CAPACITY];
Ring_buffer() { }
Genode::size_t read_avail() const
{
if (wpos > rpos) return wpos - rpos;
else return (wpos - rpos + CAPACITY) % CAPACITY;
}
Genode::size_t write_avail() const
{
if (wpos > rpos) return ((rpos - wpos + CAPACITY) % CAPACITY) - 2;
else if (wpos < rpos) return rpos - wpos;
else return CAPACITY - 2;
}
Genode::size_t write(void const *src, Genode::size_t len)
{
Genode::size_t const avail = write_avail();
if (avail == 0) return 0;
Genode::size_t const limit_len = len > avail ? avail : len;
Genode::size_t const total = wpos + len;
Genode::size_t first, rest;
if (total > CAPACITY) {
first = CAPACITY - wpos;
rest = total % CAPACITY;
} else {
first = limit_len;
rest = 0;
}
Genode::memcpy(&_data[wpos], src, first);
wpos = (wpos + first) % CAPACITY;
if (rest) {
Genode::memcpy(&_data[wpos], ((char const*)src) + first, rest);
wpos = (wpos + rest) % CAPACITY;
}
return limit_len;
}
Genode::size_t read(void *dst, Genode::size_t len)
{
Genode::size_t const avail = read_avail();
if (avail == 0) return 0;
Genode::size_t const limit_len = len > avail ? avail : len;
Genode::size_t const total = rpos + len;
Genode::size_t first, rest;
if (total > CAPACITY) {
first = CAPACITY - rpos;
rest = total % CAPACITY;
} else {
first = limit_len;
rest = 0;
}
Genode::memcpy(dst, &_data[rpos], first);
rpos = (rpos + first) % CAPACITY;
if (rest) {
Genode::memcpy(((char*)dst) + first, &_data[rpos], rest);
rpos = (rpos + rest) % CAPACITY;
}
return limit_len;
}
};
#endif /* _RING_BUFFER_H_ */

View File

@@ -0,0 +1,4 @@
TARGET = audio_player
SRC_CC = main.cc
INC_DIR += $(PRG_DIR)
LIBS = libc avcodec avformat avutil avresample pthread