/* * \brief Genode C API audio functions needed by OKLinux * \author Stefan Kalkowski * \date 2009-11-12 */ /* * Copyright (C) 2009-2012 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 #include #include extern "C" { #include } static unsigned long stream_data = 0; static void (*interrupt_handler)(unsigned long) = 0; class Audio_cache { public: typedef Audio_out::Session::Channel::Source Stream; enum Channels { LEFT, RIGHT, CHANNEL }; enum { FRAME_SIZE_OUT = Audio_out::FRAME_SIZE, FRAME_SIZE_IN = sizeof(Genode::int16_t), PACKET_SIZE_OUT = FRAME_SIZE_OUT * Audio_out::PERIOD, PACKET_SIZE_IN = FRAME_SIZE_IN * Audio_out::PERIOD * CHANNEL, PACKET_CNT_MAX = Audio_out::QUEUE_SIZE - 1, BUF_SIZE = PACKET_CNT_MAX * PACKET_SIZE_OUT + 0x400 }; private: class Entry { private: Stream *_stream; Packet_descriptor _descriptor; /* packet stream descriptor */ bool _ready; /* ready to submit */ bool _submitted; /* submitted to hardware */ public: Entry(Stream *stream) : _stream(stream), _descriptor(_stream->alloc_packet(PACKET_SIZE_OUT)), _ready(false), _submitted(false) {} Entry() : _ready(false), _submitted(false) { } ~Entry() {} Packet_descriptor descriptor() { return _descriptor; } float* content() { return _stream->packet_content(_descriptor); } void submit(bool delay=false) { _ready = true; if(interrupt_handler && !_submitted && !delay) { _submitted = true; _stream->submit_packet(_descriptor); } } bool submitted() { return _submitted; } bool ready() { return _ready; } void acknowledge() { _submitted = false; _ready = false; } }; Genode::Allocator_avl _alloc_left; Genode::Allocator_avl _alloc_right; Audio_out::Connection _left; Audio_out::Connection _right; Entry _entries[CHANNEL][PACKET_CNT_MAX]; unsigned _idx[CHANNEL]; Genode::size_t _offset; unsigned _hw_pointer[CHANNEL]; Stream *_stream(unsigned channel) { return channel == LEFT ? _left.stream() : _right.stream(); } public: Audio_cache() : _alloc_left(Genode::env()->heap()), _alloc_right(Genode::env()->heap()), _left("front left", &_alloc_left, BUF_SIZE), _right("front right", &_alloc_right, BUF_SIZE) { for (unsigned ch=LEFT; ch < CHANNEL; ch++) { for (unsigned i = 0; i < PACKET_CNT_MAX; i++) _entries[ch][i] = Entry(_stream(ch)); _idx[ch] = 0; _hw_pointer[ch] = 0; } _offset = 0; _right.sync_session(_left.session_capability()); } ~Audio_cache() { for (unsigned ch=LEFT; ch < CHANNEL; ch++) for (unsigned i = 0; i < PACKET_CNT_MAX; i++) _stream(ch)->release_packet(_entries[ch][i].descriptor()); } void write (void *src, Genode::size_t size) { using namespace Genode; /* The actual packet should not be in use */ for (unsigned ch=0; ch < CHANNEL; ch++) if (_entries[ch][_idx[ch]].submitted()) { PERR("Error: (un-)acknowledged packet chan=%d idx=%d", ch, _idx[ch]); } short *src_ptr = (short*)src; while (size) { /* Use left space in packet or content-size */ size_t packet_space = min(size / CHANNEL / FRAME_SIZE_IN * FRAME_SIZE_OUT, PACKET_SIZE_OUT - _offset); for (unsigned chan=0; chan < CHANNEL; chan++) { float *dest = _entries[chan][_idx[chan]].content() + (_offset / FRAME_SIZE_OUT); for (unsigned frame = 0; frame < packet_space / FRAME_SIZE_OUT; frame++) dest[frame] = src_ptr[(frame * CHANNEL) + chan] / 32767.0; } /* If packet is full, submit it and switch to next one */ if ((_offset + packet_space) == PACKET_SIZE_OUT) { for (unsigned chan=0; chan < CHANNEL; chan++) { _entries[chan][_idx[chan]].submit(true); _idx[chan] = (_idx[chan] + 1) % PACKET_CNT_MAX; } _offset = 0; } else _offset += packet_space; src_ptr += (packet_space / FRAME_SIZE_OUT) * CHANNEL; size -= packet_space / FRAME_SIZE_OUT * FRAME_SIZE_IN * CHANNEL; } } void submit_all() { for (unsigned i = 0; i < PACKET_CNT_MAX; i++) { for (unsigned ch = 0; ch < CHANNEL; ch++) { if (_entries[ch][i].ready() && !_entries[ch][i].submitted()) _entries[ch][i].submit(); } } } void flush() { _left.flush(); _right.flush(); for (unsigned i = 0; i < PACKET_CNT_MAX; i++) { for (unsigned ch = 0; ch < CHANNEL; ch++) { if (_entries[ch][i].submitted()) _stream(ch)->get_acked_packet(); _entries[ch][i].acknowledge(); } } for (unsigned ch = 0; ch < CHANNEL; ch++) { _idx[ch] = 0; _hw_pointer[ch] = 0; } _offset = 0; } void acknowledge() { bool acked = false; for (unsigned ch = 0; ch < CHANNEL; ch++) { if (_stream(ch)->ack_avail()) { _stream(ch)->get_acked_packet(); for (unsigned i = _idx[ch]; ; i = (i + 1) % PACKET_CNT_MAX) { if (_entries[ch][i].submitted()) { _entries[ch][i].acknowledge(); break; } } _hw_pointer[ch]++; acked = true; } } if (acked) interrupt_handler(stream_data); } Genode::size_t pointer() { using namespace Genode; Genode::size_t size = min(_hw_pointer[LEFT], _hw_pointer[RIGHT]); return size * Audio_cache::PACKET_SIZE_OUT * CHANNEL / FRAME_SIZE_OUT * FRAME_SIZE_IN ; } }; static Audio_cache *audio_cache(void) { try { static Audio_cache _cache; return &_cache; } catch(...) {} return 0; } static Genode::uint8_t zeroes[Audio_cache::PACKET_SIZE_IN]; extern "C" { #include int genode_audio_ready() { return genode_config_audio() && audio_cache(); } void genode_audio_collect_acks() { if (!interrupt_handler) return; audio_cache()->submit_all(); audio_cache()->acknowledge(); } void genode_audio_prepare() { audio_cache()->flush(); } void genode_audio_trigger_start(void (*func)(unsigned long), unsigned long data) { interrupt_handler = func; stream_data = data; } void genode_audio_trigger_stop() { interrupt_handler = 0; stream_data = 0; audio_cache()->flush(); } unsigned long genode_audio_position() { unsigned long ret = audio_cache()->pointer(); return ret; } void genode_audio_write(void* src, unsigned long sz) { audio_cache()->write(src, sz); } void genode_audio_fill_silence(unsigned long sz) { audio_cache()->write(&zeroes, sz); } unsigned int genode_audio_packet_size() { return Audio_cache::PACKET_SIZE_IN; } unsigned int genode_audio_packet_count() { return Audio_cache::PACKET_CNT_MAX; } } // extern "C"