318 lines
7.2 KiB
C++
318 lines
7.2 KiB
C++
/*
|
|
* \brief MP3 audio decoder
|
|
* \author Emery Hemingway
|
|
* \date 2018-03-24
|
|
*/
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
/*
|
|
* TODO:
|
|
* - Metadata report
|
|
* - Optimize buffer sizes
|
|
*/
|
|
|
|
/* Genode includes */
|
|
#include <gems/magic_ring_buffer.h>
|
|
#include <os/static_root.h>
|
|
#include <libc/component.h>
|
|
#include <audio/source.h>
|
|
#include <terminal_session/connection.h>
|
|
#include <base/attached_rom_dataspace.h>
|
|
#include <base/attached_ram_dataspace.h>
|
|
#include <base/sleep.h>
|
|
|
|
/* Mpg123 includes */
|
|
#include <stdlib.h>
|
|
#include <sys/types.h>
|
|
#include <mpg123.h>
|
|
|
|
namespace Mp3_audio_sink {
|
|
|
|
enum { LEFT, RIGHT, NUM_CHANNELS };
|
|
|
|
enum {
|
|
FEED_POOL_SIZE = 2,
|
|
CLIENT_BUFFER_SIZE = 1 << 14, /* 16 KiB */
|
|
};
|
|
|
|
enum {
|
|
AUDIO_OUT_BUFFER_SIZE = NUM_CHANNELS
|
|
* Audio_out::QUEUE_SIZE
|
|
* Audio_out::PERIOD
|
|
* Audio_out::SAMPLE_SIZE
|
|
};
|
|
|
|
using namespace Genode;
|
|
|
|
struct Decoder;
|
|
|
|
class Terminal_component;
|
|
struct Main;
|
|
}
|
|
|
|
|
|
struct Mp3_audio_sink::Decoder : Audio::Source
|
|
{
|
|
template <typename FUNC>
|
|
static void for_each_channel(FUNC const &func) {
|
|
for (int i = 0; i < NUM_CHANNELS; ++i) func(i); }
|
|
|
|
Genode::Env &_env;
|
|
|
|
Attached_rom_dataspace _config_rom { _env, "config" };
|
|
|
|
Audio::Stereo_out _stereo_out { _env, *this };
|
|
|
|
void die_mpg123(mpg123_handle *mh, char const *msg)
|
|
{
|
|
int code = mpg123_errcode(mh);
|
|
Genode::error(msg, ", ", mpg123_strerror(mh));
|
|
|
|
mpg123_close(mh);
|
|
mpg123_delete(mh);
|
|
mpg123_exit();
|
|
|
|
_env.parent().exit(code);
|
|
Genode::sleep_forever();
|
|
}
|
|
|
|
mpg123_handle *create_mpg123_handle()
|
|
{
|
|
mpg123_handle *mh = NULL;
|
|
|
|
int err = mpg123_init();
|
|
if(err != MPG123_OK || (mh = mpg123_new(NULL, &err)) == NULL)
|
|
Genode::error("Mpg123 setup failed, ", mpg123_plain_strerror(err));
|
|
|
|
mpg123_param(mh, MPG123_FEEDPOOL, FEED_POOL_SIZE, 0);
|
|
mpg123_param(mh, MPG123_FEEDBUFFER, CLIENT_BUFFER_SIZE, 0);
|
|
|
|
/* Set mpg123 output format to match Audio_out exactly */
|
|
mpg123_param(mh, MPG123_FORCE_RATE,
|
|
Audio_out::SAMPLE_RATE, Audio_out::SAMPLE_RATE);
|
|
mpg123_format_none(mh);
|
|
if (mpg123_format(mh, Audio_out::SAMPLE_RATE,
|
|
MPG123_STEREO, MPG123_ENC_FLOAT_32) != MPG123_OK)
|
|
die_mpg123(mh, "Audio_out format is unsupported");
|
|
|
|
if (mpg123_open_feed(mh) != MPG123_OK)
|
|
die_mpg123(mh, "mpg123 feeder mode failed");
|
|
return mh;
|
|
}
|
|
|
|
mpg123_handle *_mh = create_mpg123_handle();
|
|
|
|
/* last error code logged */
|
|
int _mh_err = MPG123_OK;
|
|
|
|
/**
|
|
* Use half of the available RAM as buffer
|
|
*/
|
|
size_t _pcm_buffer_size()
|
|
{
|
|
size_t n = _env.pd().avail_ram().value / 2;
|
|
if (n > (1<<20))
|
|
log("internal buffer size is ", n>>20, "MiB");
|
|
else if (n > (1<<10))
|
|
log("internal buffer size is ", n>>10, "KiB");
|
|
else
|
|
log("internal buffer size is ", n, " bytes");
|
|
return n;
|
|
}
|
|
|
|
Magic_ring_buffer<float> _pcm { _env, _pcm_buffer_size() };
|
|
|
|
void _log_error()
|
|
{
|
|
int code = mpg123_errcode(_mh);
|
|
if (code != MPG123_OK && _mh_err != code) {
|
|
_mh_err = code;
|
|
error(mpg123_plain_strerror(code));
|
|
}
|
|
}
|
|
|
|
Genode::size_t feedbuffer_size()
|
|
{
|
|
long value = 0;
|
|
double fvalue = 0;
|
|
if (mpg123_getparam(_mh, MPG123_FEEDBUFFER, &value, &fvalue))
|
|
die_mpg123(_mh, "failed to get feed buffer size");
|
|
return value;
|
|
}
|
|
|
|
bool fill(float *left, float *right, Genode::size_t samples) override;
|
|
|
|
/**
|
|
* Process client data, blocks until all data is consumed.
|
|
*/
|
|
void process(unsigned char const *src, size_t num_bytes)
|
|
{
|
|
/* TODO: patch mpgg123 not to use stdio for printing errors */
|
|
|
|
Libc::with_libc([&] () {
|
|
|
|
/* feed client data into mpg123 buffer */
|
|
if (mpg123_feed(_mh, src, num_bytes))
|
|
die_mpg123(_mh, "failed to feed");
|
|
|
|
::off_t num = 0;
|
|
unsigned char *audio = nullptr;
|
|
size_t bytes = 0;
|
|
|
|
while (mpg123_decode_frame(_mh, &num, &audio, &bytes) == MPG123_OK) {
|
|
Genode::size_t const samples = bytes / Audio_out::SAMPLE_SIZE;
|
|
|
|
while (_pcm.write_avail() < samples) {
|
|
/* submit audio blocks for packet allocation */
|
|
_env.ep().wait_and_dispatch_one_io_signal();
|
|
}
|
|
|
|
Genode::memcpy(_pcm.write_addr(), audio, bytes);
|
|
_pcm.fill(samples);
|
|
|
|
}
|
|
|
|
if (mpg123_errcode(_mh) != MPG123_ERR_READER
|
|
|| mpg123_errcode(_mh) != MPG123_OK)
|
|
_log_error();
|
|
});
|
|
}
|
|
|
|
Signal_handler<Decoder> _config_handler {
|
|
_env.ep(), *this, &Decoder::_handle_config };
|
|
|
|
void _handle_config()
|
|
{
|
|
_config_rom.update();
|
|
Xml_node const config = _config_rom.xml();
|
|
|
|
enum { EQ_COUNT = 32 };
|
|
|
|
mpg123_reset_eq(_mh);
|
|
config.for_each_sub_node("eq", [&] (Xml_node const &node) {
|
|
unsigned band = node.attribute_value("band", 32U);
|
|
double value = node.attribute_value("value", 0.0);
|
|
if (band < EQ_COUNT && value != 0.0) {
|
|
mpg123_eq(_mh, MPG123_LR , band, value);
|
|
log("EQ ", band, ": ", mpg123_geteq(_mh, MPG123_LR , band));
|
|
}
|
|
});
|
|
|
|
double volume = 0.5;
|
|
config.for_each_sub_node("volume", [&] (Xml_node const &node) {
|
|
volume = node.attribute_value("linear", volume); });
|
|
mpg123_volume(_mh, 0.5);
|
|
}
|
|
|
|
Decoder(Genode::Env &env) : _env(env)
|
|
{
|
|
_config_rom.sigh(_config_handler);
|
|
_handle_config();
|
|
}
|
|
};
|
|
|
|
|
|
bool Mp3_audio_sink::Decoder::fill(float *left,
|
|
float *right,
|
|
Genode::size_t samples)
|
|
{
|
|
using namespace Audio_out;
|
|
|
|
if (_pcm.read_avail() < samples*2) return false;
|
|
|
|
float const *buf = _pcm.read_addr();
|
|
for (unsigned i = 0; i < samples; ++i) {
|
|
left[i] = buf[(i<<1)|0];
|
|
right[i] = buf[(i<<1)|1];
|
|
}
|
|
|
|
_pcm.drain(samples*2);
|
|
return true;
|
|
}
|
|
|
|
|
|
class Mp3_audio_sink::Terminal_component :
|
|
public Rpc_object<Terminal::Session, Terminal_component>
|
|
{
|
|
private:
|
|
|
|
Decoder &_decoder;
|
|
|
|
Genode::Attached_ram_dataspace _io_buffer;
|
|
|
|
public:
|
|
|
|
Terminal_component(Genode::Env &env, Decoder &decoder)
|
|
:
|
|
_decoder(decoder),
|
|
_io_buffer(env.pd(), env.rm(), _decoder.feedbuffer_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 dst_len) { 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 decoder */
|
|
_decoder.process(
|
|
_io_buffer.local_addr<unsigned 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 Mp3_audio_sink::Main
|
|
{
|
|
Genode::Env &_env;
|
|
|
|
Decoder _decoder { _env };
|
|
|
|
Terminal_component _terminal { _env, _decoder };
|
|
|
|
Static_root<Terminal::Session> _terminal_root {
|
|
_env.ep().manage(_terminal) };
|
|
|
|
Main(Libc::Env &env) : _env(env)
|
|
{
|
|
env.parent().announce(env.ep().manage(_terminal_root));
|
|
}
|
|
};
|
|
|
|
|
|
/***************
|
|
** Component **
|
|
***************/
|
|
|
|
void Libc::Component::construct(Libc::Env &env) {
|
|
static Mp3_audio_sink::Main _main(env); }
|