diff --git a/repos/gems/src/lib/vfs/trace/vfs.cc b/repos/gems/src/lib/vfs/trace/vfs.cc index ff8b89726d..8d6b62c1f4 100644 --- a/repos/gems/src/lib/vfs/trace/vfs.cc +++ b/repos/gems/src/lib/vfs/trace/vfs.cc @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -22,7 +23,6 @@ #include "directory_tree.h" #include "trace_buffer.h" -#include "value_file_system.h" namespace Vfs_trace { diff --git a/repos/libports/src/lib/libc/vfs_plugin.cc b/repos/libports/src/lib/libc/vfs_plugin.cc index e192966302..6250349364 100644 --- a/repos/libports/src/lib/libc/vfs_plugin.cc +++ b/repos/libports/src/lib/libc/vfs_plugin.cc @@ -1188,7 +1188,13 @@ Libc::Vfs_plugin::_ioctl_sndctl(File_descriptor *fd, unsigned long request, char */ int result = 0; - if (request == SNDCTL_DSP_CHANNELS) { + if (request == OSS_GETVERSION) { + + *(int *)argp = SOUND_VERSION; + + handled = true; + + } else if (request == SNDCTL_DSP_CHANNELS) { monitor().monitor([&] { _with_info(*fd, [&] (Xml_node info) { @@ -1203,7 +1209,7 @@ Libc::Vfs_plugin::_ioctl_sndctl(File_descriptor *fd, unsigned long request, char return; } - int const num_chans = *(int const*)argp; + int const num_chans = *(int const *)argp; if (num_chans < 0) { result = EINVAL; return; @@ -1214,7 +1220,7 @@ Libc::Vfs_plugin::_ioctl_sndctl(File_descriptor *fd, unsigned long request, char return; } - *(int*)argp = avail_chans; + *(int *)argp = avail_chans; handled = true; }); @@ -1222,6 +1228,108 @@ Libc::Vfs_plugin::_ioctl_sndctl(File_descriptor *fd, unsigned long request, char return Fn::COMPLETE; }); + } else if (request == SNDCTL_DSP_CURRENT_OPTR) { + + monitor().monitor([&] { + _with_info(*fd, [&] (Xml_node info) { + + if (info.type() != "oss") { + return; + } + + long long const optr_samples = + info.attribute_value("optr_samples", -1L); + int const optr_fifo_samples = + info.attribute_value("optr_fifo_samples", -1L); + if ((optr_samples == -1) || (optr_fifo_samples == -1)) { + result = ENOTSUP; + return; + } + + oss_count_t *optr = (oss_count_t *)argp; + optr->samples = optr_samples; + optr->fifo_samples = optr_fifo_samples; + + handled = true; + }); + + return Fn::COMPLETE; + }); + + } else if (request == SNDCTL_DSP_GETERROR) { + + int play_underruns = 0; + + monitor().monitor([&] { + _with_info(*fd, [&] (Xml_node info) { + if (info.type() != "oss") { + return; + } + + play_underruns = info.attribute_value("play_underruns", 0U); + }); + + return Fn::COMPLETE; + }); + + + if (play_underruns > 0) { + + /* reset */ + + char const play_underruns_string[] = "0"; + Absolute_path play_underruns_path = ioctl_dir(*fd); + play_underruns_path.append_element("play_underruns"); + File_descriptor *play_underruns_fd = open(play_underruns_path.base(), O_RDWR); + if (!play_underruns_fd) + return { true, ENOTSUP }; + write(play_underruns_fd, play_underruns_string, sizeof(play_underruns_string)); + close(play_underruns_fd); + } + + struct audio_errinfo *err_info = + (struct audio_errinfo *)argp; + + err_info->play_underruns = play_underruns; + err_info->rec_overruns = 0; + err_info->play_ptradjust = 0; + err_info->rec_ptradjust = 0; + err_info->play_errorcount = 0; + err_info->rec_errorcount = 0; + err_info->play_lasterror = 0; + err_info->rec_lasterror = 0; + err_info->play_errorparm = 0; + err_info->rec_errorparm = 0; + + handled = true; + + } else if (request == SNDCTL_DSP_GETISPACE) { + + /* dummy implementation */ + + audio_buf_info &abinfo = *(audio_buf_info *)argp; + abinfo = { + .fragments = 4, + .fragstotal = 256, + .fragsize = 2048, + .bytes = 4*2048, + }; + + handled = true; + + } else if (request == SNDCTL_DSP_GETOPTR) { + + /* dummy implementation */ + + count_info &ci = *(count_info *)argp; + ci = { + .bytes = 0, /* Total # of bytes processed */ + .blocks = 0, /* # of fragment transitions since last time */ + .ptr = 0, /* Current DMA pointer value */ + }; + + handled = true; + } else if (request == SNDCTL_DSP_GETOSPACE) { monitor().monitor([&] { @@ -1230,28 +1338,32 @@ Libc::Vfs_plugin::_ioctl_sndctl(File_descriptor *fd, unsigned long request, char return; } - unsigned int const frag_size = - info.attribute_value("frag_size", 0U); - unsigned int const frag_avail = - info.attribute_value("frag_avail", 0U); - if (!frag_avail || !frag_size) { + unsigned int const ofrag_size = + info.attribute_value("ofrag_size", 0U); + unsigned int const ofrag_avail = + info.attribute_value("ofrag_avail", 0U); + unsigned int const ofrag_total = + info.attribute_value("ofrag_total", 0U); + if (!ofrag_size || !ofrag_total) { result = ENOTSUP; return; } - int const fragsize = (int)frag_size; - int const fragments = (int)frag_avail; - if (fragments < 0 || fragsize < 0) { + int const fragments = (int)ofrag_avail; + int const fragstotal = (int)ofrag_total; + int const fragsize = (int)ofrag_size; + if (fragments < 0 || fragstotal < 0 || fragsize < 0) { result = EINVAL; return; } struct audio_buf_info *buf_info = - (struct audio_buf_info*)argp; + (struct audio_buf_info *)argp; - buf_info->fragments = fragments; - buf_info->fragsize = fragsize; - buf_info->bytes = fragments * fragsize; + buf_info->fragments = fragments; + buf_info->fragstotal = fragstotal; + buf_info->fragsize = fragsize; + buf_info->bytes = fragments * fragsize; handled = true; }); @@ -1259,6 +1371,32 @@ Libc::Vfs_plugin::_ioctl_sndctl(File_descriptor *fd, unsigned long request, char return Fn::COMPLETE; }); + } else if (request == SNDCTL_DSP_GETPLAYVOL) { + + /* dummy implementation */ + + int *vol = (int *)argp; + + *vol = 100; + + handled = true; + + } else if (request == SNDCTL_DSP_LOW_WATER) { + + /* dummy implementation */ + + int *val = (int *)argp; + + *val = 0; + + handled = true; + + } else if (request == SNDCTL_DSP_NONBLOCK) { + + /* dummy implementation */ + + handled = true; + } else if (request == SNDCTL_DSP_POST) { handled = true; @@ -1282,7 +1420,7 @@ Libc::Vfs_plugin::_ioctl_sndctl(File_descriptor *fd, unsigned long request, char return; } - int const requested_fmt = *(int const*)argp; + int const requested_fmt = *(int const *)argp; if (requested_fmt != (int)format) { result = ENOTSUP; @@ -1297,33 +1435,77 @@ Libc::Vfs_plugin::_ioctl_sndctl(File_descriptor *fd, unsigned long request, char } else if (request == SNDCTL_DSP_SETFRAGMENT) { + int *frag = (int *)argp; + int max_fragments = *frag >> 16; + int size_selector = *frag & ((1<<16) - 1); + + char ofrag_total_string[16]; + char ofrag_size_string[16]; + + ::snprintf(ofrag_total_string, sizeof(ofrag_total_string), + "%u", max_fragments); + + ::snprintf(ofrag_size_string, sizeof(ofrag_size_string), + "%u", 1 << size_selector); + + Absolute_path ofrag_total_path = ioctl_dir(*fd); + ofrag_total_path.append_element("ofrag_total"); + File_descriptor *ofrag_total_fd = open(ofrag_total_path.base(), O_RDWR); + if (!ofrag_total_fd) + return { true, ENOTSUP }; + write(ofrag_total_fd, ofrag_total_string, sizeof(ofrag_total_string)); + close(ofrag_total_fd); + + Absolute_path ofrag_size_path = ioctl_dir(*fd); + ofrag_size_path.append_element("ofrag_size"); + File_descriptor *ofrag_size_fd = open(ofrag_size_path.base(), O_RDWR); + if (!ofrag_size_fd) + return { true, ENOTSUP }; + write(ofrag_size_fd, ofrag_size_string, sizeof(ofrag_size_string)); + close(ofrag_size_fd); + monitor().monitor([&] { + _with_info(*fd, [&] (Xml_node info) { if (info.type() != "oss") { return; } - unsigned int const frag_size = - info.attribute_value("frag_size", 0U); - unsigned int const frag_size_log2 = - frag_size ? Genode::log2(frag_size) : 0; + unsigned int const ofrag_size = + info.attribute_value("ofrag_size", 0U); + unsigned int const ofrag_size_log2 = + ofrag_size ? Genode::log2(ofrag_size) : 0; - unsigned int const queue_size = - info.attribute_value("queue_size", 0U); + unsigned int const ofrag_total = + info.attribute_value("ofrag_total", 0U); - if (!queue_size || !frag_size_log2) { + if (!ofrag_total || !ofrag_size_log2) { result = ENOTSUP; return; } - - /* ignore the given hint */ - - handled = true; }); return Fn::COMPLETE; }); + handled = true; + + } else if (request == SNDCTL_DSP_SETPLAYVOL) { + + /* dummy implementation */ + + int *vol = (int *)argp; + + *vol = 100; + + handled = true; + + } else if (request == SNDCTL_DSP_SETTRIGGER) { + + /* dummy implementation */ + + handled = true; + } else if (request == SNDCTL_DSP_SPEED) { monitor().monitor([&] { @@ -1339,7 +1521,7 @@ Libc::Vfs_plugin::_ioctl_sndctl(File_descriptor *fd, unsigned long request, char return; } - int const speed = *(int const*)argp; + int const speed = *(int const *)argp; if (speed < 0) { result = EINVAL; return; @@ -1350,7 +1532,7 @@ Libc::Vfs_plugin::_ioctl_sndctl(File_descriptor *fd, unsigned long request, char return; } - *(int*)argp = samplerate; + *(int *)argp = samplerate; handled = true; }); @@ -1358,6 +1540,14 @@ Libc::Vfs_plugin::_ioctl_sndctl(File_descriptor *fd, unsigned long request, char return Fn::COMPLETE; }); + } else if (request == SNDCTL_SYSINFO) { + + /* dummy implementation */ + + oss_sysinfo *si = (oss_sysinfo *)argp; + Genode::memset(si, 0, sizeof(*si)); + + handled = true; } /* @@ -1383,13 +1573,24 @@ int Libc::Vfs_plugin::ioctl(File_descriptor *fd, unsigned long request, char *ar case DIOCGMEDIASIZE: result = _ioctl_dio(fd, request, argp); break; + case OSS_GETVERSION: case SNDCTL_DSP_CHANNELS: + case SNDCTL_DSP_CURRENT_OPTR: + case SNDCTL_DSP_GETERROR: + case SNDCTL_DSP_GETISPACE: + case SNDCTL_DSP_GETOPTR: case SNDCTL_DSP_GETOSPACE: + case SNDCTL_DSP_GETPLAYVOL: + case SNDCTL_DSP_LOW_WATER: + case SNDCTL_DSP_NONBLOCK: case SNDCTL_DSP_POST: case SNDCTL_DSP_RESET: case SNDCTL_DSP_SAMPLESIZE: case SNDCTL_DSP_SETFRAGMENT: + case SNDCTL_DSP_SETPLAYVOL: + case SNDCTL_DSP_SETTRIGGER: case SNDCTL_DSP_SPEED: + case SNDCTL_SYSINFO: result = _ioctl_sndctl(fd, request, argp); break; default: diff --git a/repos/libports/src/lib/vfs/oss/README b/repos/libports/src/lib/vfs/oss/README index baceba051a..2b86bb9e64 100644 --- a/repos/libports/src/lib/vfs/oss/README +++ b/repos/libports/src/lib/vfs/oss/README @@ -1,39 +1,56 @@ The VFS OSS plugin offers access to Genode's Audio_out session by providing a file-system that can be mounted at arbitrary location within the VFS of a -component. It exposes a data file that can by used as 'dsp' file, e.g., _/dev/dsp_ +component. It exposes a data file that can be used as 'dsp' file, e.g., _/dev/dsp_ as is common with OSS. The support I/O control operations or rather the -properties of the pseudo-device are provide in form of a structured 'info' +properties of the pseudo-device are provided in form of a structured 'info' file located in the directory named after the data file, e.g., _/dev/.dsp/info_. This file may by used to query the configured parameters and has the following structure: ! +! ofrag_total="256" ofrag_size="2048" ofrag_avail="256" +| optr_samples="0" optr_fifo_samples="0" play_underruns="0"/> Each parameter can also be accessed via its own file. The following list presents all files: - * :channels: number of available channels. Set to 2 (stereo). + * :channels (ro): number of available channels. Set to 2 (stereo). + Corresponding OSS commands: 'SNDCTL_DSP_CHANNELS' - * :sample_rate: sample rate of the underlying Audio_out session. + * :sample_rate (ro): sample rate of the underlying Audio_out session. + Corresponding OSS commands: 'SNDCTL_DSP_SPEED' - * :format: sample format, e.g. s16le. Defaults to AFMT_S16_LE. + * :format (ro): sample format, e.g. s16le. Defaults to AFMT_S16_LE. + Corresponding OSS commands: 'SNDCTL_DSP_SAMPLESIZE' - * :queue_size: number of packets in the underlying Audio_out session's - packet-stream. + * :ofrag_total (rw): total number of output fragments. Set to number of packets + in the underlying Audio_out session's packet-stream by default. + Corresponding OSS commands: 'SNDCTL_DSP_SETFRAGMENT', 'SNDCTL_DSP_GETOSPACE' - * :frag_size: size of a fragment. Set to 1024 (number of channels times - size of Audio_out period times size of s16le sample). + * :ofrag_size (rw): size of an output fragment. Set to 2048 (number of channels + times size of Audio_out period times size of s16le sample) by default. + Corresponding OSS commands: 'SNDCTL_DSP_SETFRAGMENT', 'SNDCTL_DSP_GETOSPACE' - * :frag_avail: number of available bytes. Initially set to queue size - times frag size. + * :ofrag_avail (ro): number of available output fragments. Initially set to total + fragment count. + Corresponding OSS commands: 'SNDCTL_DSP_GETOSPACE' -In its current state it is merely enough to use simply applications requiring -nothing more than a minimal set of the OSSv3 API. It does not allow altering -of parameters. Therefore it will only work when 44000Hz/s16le is used. + * :optr_samples (ro): total number of samples submitted to the Audio_out session + Corresponding OSS commands: 'SNDCTL_DSP_CURRENT_OPTR' -The following config snippets illustrates its configuration: + * :optr_fifo_samples (ro): number of samples residing in the Audio_out stream + Corresponding OSS commands: 'SNDCTL_DSP_CURRENT_OPTR' + + * :play_underruns (rw): number of detected underrun errors, Writing anything + into this file resets the value to zero + Corresponding OSS commands: 'SNDCTL_DSP_GETERROR' + +In its current state it is merely enough to use simple applications requiring +nothing more than a minimal set of the OSSv4 API. It does not allow altering +of all parameters and will only work when 44100Hz/s16le is used. + +The following config snippet illustrates its configuration: ! ! diff --git a/repos/libports/src/lib/vfs/oss/vfs_oss.cc b/repos/libports/src/lib/vfs/oss/vfs_oss.cc index bd07eaebba..b94c0fcbfb 100644 --- a/repos/libports/src/lib/vfs/oss/vfs_oss.cc +++ b/repos/libports/src/lib/vfs/oss/vfs_oss.cc @@ -15,16 +15,20 @@ #include #include #include -#include +#include #include #include #include #include +#include /* libc includes */ #include +static constexpr size_t _stream_packet_size { Audio_out::PERIOD * Audio_out::SAMPLE_SIZE }; + + namespace Vfs { struct Oss_file_system; } @@ -46,51 +50,67 @@ struct Vfs::Oss_file_system::Audio struct Info { - using Ro_fs = Readonly_value_file_system; + unsigned channels; + unsigned format; + unsigned sample_rate; + unsigned ofrag_total; + unsigned ofrag_size; + unsigned ofrag_avail; + long long optr_samples; + unsigned optr_fifo_samples; + unsigned play_underruns; - unsigned channels; - unsigned format; - unsigned sample_rate; - unsigned queue_size; - unsigned ofrag_size; - unsigned ofrag_avail; + Readonly_value_file_system &_channels_fs; + Readonly_value_file_system &_format_fs; + Readonly_value_file_system &_sample_rate_fs; + Value_file_system &_ofrag_total_fs; + Value_file_system &_ofrag_size_fs; + Readonly_value_file_system &_ofrag_avail_fs; + Readonly_value_file_system &_optr_samples_fs; + Readonly_value_file_system &_optr_fifo_samples_fs; + Value_file_system &_play_underruns_fs; - Ro_fs &_channels_fs; - Ro_fs &_format_fs; - Ro_fs &_sample_rate_fs; - Ro_fs &_queue_size_fs; - Ro_fs &_ofrag_size_fs; - Ro_fs &_ofrag_avail_fs; - - Info(Ro_fs &channels_fs, - Ro_fs &format_fs, - Ro_fs &queue_size_fs, - Ro_fs &sample_rate_fs, - Ro_fs &ofrag_size_fs, - Ro_fs &ofrag_avail_fs) + Info(Readonly_value_file_system &channels_fs, + Readonly_value_file_system &format_fs, + Readonly_value_file_system &sample_rate_fs, + Value_file_system &ofrag_total_fs, + Value_file_system &ofrag_size_fs, + Readonly_value_file_system &ofrag_avail_fs, + Readonly_value_file_system &optr_samples_fs, + Readonly_value_file_system &optr_fifo_samples_fs, + Value_file_system &play_underruns_fs) : - channels { 0 }, - format { 0 }, - sample_rate { 0 }, - queue_size { 0 }, - ofrag_size { 0 }, - ofrag_avail { 0 }, - _channels_fs { channels_fs }, - _format_fs { format_fs }, - _sample_rate_fs { sample_rate_fs }, - _queue_size_fs { queue_size_fs }, - _ofrag_size_fs { ofrag_size_fs }, - _ofrag_avail_fs { ofrag_avail_fs } + channels { 0 }, + format { 0 }, + sample_rate { 0 }, + ofrag_total { 0 }, + ofrag_size { 0 }, + ofrag_avail { 0 }, + optr_samples { 0 }, + optr_fifo_samples { 0 }, + play_underruns { 0 }, + _channels_fs { channels_fs }, + _format_fs { format_fs }, + _sample_rate_fs { sample_rate_fs }, + _ofrag_total_fs { ofrag_total_fs }, + _ofrag_size_fs { ofrag_size_fs }, + _ofrag_avail_fs { ofrag_avail_fs }, + _optr_samples_fs { optr_samples_fs }, + _optr_fifo_samples_fs { optr_fifo_samples_fs }, + _play_underruns_fs { play_underruns_fs } { } void update() { - _channels_fs .value(channels); - _format_fs .value(format); - _sample_rate_fs.value(sample_rate); - _queue_size_fs .value(queue_size); - _ofrag_size_fs .value(ofrag_size); - _ofrag_avail_fs.value(ofrag_avail); + _channels_fs .value(channels); + _format_fs .value(format); + _sample_rate_fs .value(sample_rate); + _ofrag_total_fs .value(ofrag_total); + _ofrag_size_fs .value(ofrag_size); + _ofrag_avail_fs .value(ofrag_avail); + _optr_samples_fs .value(optr_samples); + _optr_fifo_samples_fs.value(optr_fifo_samples); + _play_underruns_fs .value(play_underruns); } void print(Genode::Output &out) const @@ -98,12 +118,15 @@ struct Vfs::Oss_file_system::Audio char buf[256] { }; Genode::Xml_generator xml(buf, sizeof(buf), "oss", [&] () { - xml.attribute("channels", channels); - xml.attribute("format", format); - xml.attribute("sample_rate", sample_rate); - xml.attribute("queue_size", queue_size); - xml.attribute("frag_size", ofrag_size); - xml.attribute("frag_avail", ofrag_avail); + xml.attribute("channels", channels); + xml.attribute("format", format); + xml.attribute("sample_rate", sample_rate); + xml.attribute("ofrag_total", ofrag_total); + xml.attribute("ofrag_size", ofrag_size); + xml.attribute("ofrag_avail", ofrag_avail); + xml.attribute("optr_samples", optr_samples); + xml.attribute("optr_fifo_samples", optr_fifo_samples); + xml.attribute("play_underruns", play_underruns); }); Genode::print(out, Genode::Cstring(buf)); @@ -115,17 +138,7 @@ struct Vfs::Oss_file_system::Audio Audio(Audio const &); Audio &operator = (Audio const &); - /* - * Staging buffer - * - * Will later be used for resampling and re-coding, for - * the moment it de-couples the handle from the packet-stream. - */ - Genode::Magic_ring_buffer _left_buffer; - Genode::Magic_ring_buffer _right_buffer; - bool _started { false }; - bool _buffer_full { false }; enum { CHANNELS = 2, }; const char *_channel_names[CHANNELS] = { "front left", "front right" }; @@ -133,16 +146,14 @@ struct Vfs::Oss_file_system::Audio Genode::Constructible _out[CHANNELS]; Info &_info; - Readonly_value_file_system &_info_fs; + Readonly_value_file_system &_info_fs; public: Audio(Genode::Env &env, Info &info, - Readonly_value_file_system &info_fs) + Readonly_value_file_system &info_fs) : - _left_buffer { env, 1u << 20 }, - _right_buffer { env, 1u << 20 }, _info { info }, _info_fs { info_fs } { @@ -158,11 +169,11 @@ struct Vfs::Oss_file_system::Audio _info.channels = CHANNELS; _info.format = (unsigned)AFMT_S16_LE; _info.sample_rate = Audio_out::SAMPLE_RATE; - _info.queue_size = Audio_out::QUEUE_SIZE; + _info.ofrag_total = Audio_out::QUEUE_SIZE; _info.ofrag_size = (unsigned)Audio_out::PERIOD * (unsigned)CHANNELS - * sizeof (short); - _info.ofrag_avail = _info.queue_size; + * sizeof (int16_t); + _info.ofrag_avail = _info.ofrag_total; _info.update(); _info_fs.value(_info); } @@ -191,14 +202,52 @@ struct Vfs::Oss_file_system::Audio return _out[0]->stream()->queued(); } - bool _queue_threshold_reached() const + void update_info_ofrag_avail_from_optr_fifo_samples() { - return _out[0]->stream()->queued() > 20; + unsigned const samples_per_fragment = + _info.ofrag_size / (CHANNELS * sizeof(int16_t)); + _info.ofrag_avail = _info.ofrag_total - + ((_info.optr_fifo_samples / samples_per_fragment) + + ((_info.optr_fifo_samples % samples_per_fragment) ? 1 : 0)); } - bool need_data() const + /* + * Handle progress signal. + * + * Returns true if at least one stream packet is available. + */ + bool handle_progress() { - return !_queue_threshold_reached(); + unsigned fifo_samples_new = queued() * Audio_out::PERIOD; + + if (fifo_samples_new == _info.optr_fifo_samples) { + /* + * This is usually the progress signal for the first + * packet after 'start()', which is invalid. + */ + return false; + } + + /* + * The queue count can wrap from 0 to 255 if packets are not + * submitted fast enough. + */ + + if ((fifo_samples_new == 0) || + (fifo_samples_new > _info.optr_fifo_samples)) { + pause(); + if (fifo_samples_new > _info.optr_fifo_samples) { + _info.play_underruns++; + fifo_samples_new = 0; + } + } + + _info.optr_fifo_samples = fifo_samples_new; + update_info_ofrag_avail_from_optr_fifo_samples(); + _info.update(); + _info_fs.value(_info); + + return true; } bool write(char const *buf, file_size buf_size, file_size &out_size) @@ -207,40 +256,57 @@ struct Vfs::Oss_file_system::Audio bool block_write = false; - if (_queue_threshold_reached()) { + /* + * Calculate how many strean packets would be needed to + * write the buffer. + */ + + unsigned stream_packets_to_write = + (buf_size / _stream_packet_size) + + ((buf_size % _stream_packet_size != 0) ? 1 : 0); + + /* + * Calculate how many stream packets are available + * depending on the configured fragment count and + * fragment size and the number of packets already + * in use. + */ + + unsigned const stream_packets_total = + (_info.ofrag_total * _info.ofrag_size) / _stream_packet_size; + + unsigned const stream_packets_used = + _info.optr_fifo_samples / Audio_out::PERIOD; + + unsigned const stream_packets_avail = + stream_packets_total - stream_packets_used; + + /* + * If not enough stream packets are available, use the + * available packets and report the blocking condition + * to the caller. + */ + + if (stream_packets_to_write > stream_packets_avail) { + stream_packets_to_write = stream_packets_avail; + buf_size = stream_packets_to_write * _stream_packet_size; block_write = true; - } else { - - out_size = 0; - - size_t const samples = - min(_left_buffer.write_avail(), buf_size/2); - - float *dest[2] = { _left_buffer.write_addr(), _right_buffer.write_addr() }; - - for (size_t i = 0; i < samples/2; i++) { - - for (int c = 0; c < CHANNELS; c++) { - float *p = dest[c]; - int16_t const v = ((int16_t const*)buf)[i * CHANNELS + c]; - p[i] = ((float)v) / 32768.0f; - } - } - - _left_buffer.fill(samples/2); - _right_buffer.fill(samples/2); - - out_size += (samples * 2); } - while (_left_buffer.read_avail() >= Audio_out::PERIOD) { + if (stream_packets_to_write == 0) { + out_size = 0; + throw Vfs::File_io_service::Insufficient_buffer(); + } - if (!_started) { - _started = true; + if (!_started) { + _started = true; + _out[0]->start(); + _out[1]->start(); + } - _out[0]->start(); - _out[1]->start(); - } + for (unsigned packet_count = 0; + packet_count < stream_packets_to_write; + packet_count++) { Audio_out::Packet *lp = nullptr; @@ -257,21 +323,48 @@ struct Vfs::Oss_file_system::Audio unsigned const pos = _out[0]->stream()->packet_position(lp); Audio_out::Packet *rp = _out[1]->stream()->get(pos); - float const *src[CHANNELS] = { _left_buffer.read_addr(), - _right_buffer.read_addr() }; + float *dest[CHANNELS] = { lp->content(), rp->content() }; - lp->content(src[0], Audio_out::PERIOD); - _left_buffer.drain(Audio_out::PERIOD); - rp->content(src[1], Audio_out::PERIOD); - _right_buffer.drain(Audio_out::PERIOD); + for (unsigned sample_count = 0; + sample_count < Audio_out::PERIOD; + sample_count++) { + + for (unsigned c = 0; c < CHANNELS; c++) { + + unsigned const buf_index = + (packet_count * Audio_out::PERIOD * CHANNELS) + + (sample_count * CHANNELS) + c; + + int16_t src_sample; + if (buf_index * sizeof(uint16_t) < buf_size) { + src_sample = ((int16_t const*)buf)[buf_index]; + } else { + /* + * Fill up the packet with zeroes if the buffer + * is not aligned to minimum fragment size + * (packet size) granularity. + */ + src_sample = 0; + } + + dest[c][sample_count] = ((float)src_sample) / 32768.0f; + } + } _out[0]->submit(lp); _out[1]->submit(rp); } - /* update */ - unsigned const new_avail = Audio_out::QUEUE_SIZE - _out[0]->stream()->queued(); - _info.ofrag_avail = new_avail; + out_size = Genode::min(stream_packets_to_write * + _stream_packet_size, buf_size); + + /* update info */ + + unsigned const stream_samples_written = + stream_packets_to_write * Audio_out::PERIOD; + _info.optr_samples += stream_samples_written; + _info.optr_fifo_samples += stream_samples_written; + update_info_ofrag_avail_from_optr_fifo_samples(); _info.update(); _info_fs.value(_info); @@ -308,10 +401,16 @@ class Vfs::Oss_file_system::Data_file_system : public Single_file_system _audio { audio } { } - Read_result read(char *, file_size, file_size &) override + Read_result read(char *buf, file_size buf_size, file_size &out_count) override { - /* not supported */ - return READ_ERR_INVALID; + /* dummy implementation with audible noise for testing */ + + for (file_size i = 0; i < buf_size; i++) + buf[i] = i; + + out_count = buf_size; + + return READ_OK; } Write_result write(char const *buf, file_size buf_size, @@ -346,17 +445,15 @@ class Vfs::Oss_file_system::Data_file_system : public Single_file_system void _handle_progress() { - unsigned const queued = _audio.queued(); - if (!queued) { - _audio.pause(); + if (_audio.handle_progress()) { + /* at least one stream packet is available */ + _handle_registry.for_each([this] (Registered_handle &handle) { + if (handle.blocked) { + handle.blocked = false; + handle.io_progress_response(); + } + }); } - - _handle_registry.for_each([this] (Registered_handle &handle) { - if (handle.blocked) { - handle.blocked = false; - handle.io_progress_response(); - } - }); } public: @@ -424,20 +521,98 @@ struct Vfs::Oss_file_system::Local_factory : File_system_factory Vfs::Env &_env; - Readonly_value_file_system _channels_fs { "channels", 0U }; - Readonly_value_file_system _format_fs { "format", 0U }; - Readonly_value_file_system _sample_rate_fs { "sample_rate", 0U }; - Readonly_value_file_system _queue_size_fs { "queue_size", 0U }; - Readonly_value_file_system _ofrag_size_fs { "frag_size", 0U} ; - Readonly_value_file_system _ofrag_avail_fs { "frag_avail", 0U }; + Readonly_value_file_system _channels_fs { "channels", 0U }; + Readonly_value_file_system _format_fs { "format", 0U }; + Readonly_value_file_system _sample_rate_fs { "sample_rate", 0U }; + Value_file_system _ofrag_total_fs { "ofrag_total", 0U }; + Value_file_system _ofrag_size_fs { "ofrag_size", 0U} ; + Readonly_value_file_system _ofrag_avail_fs { "ofrag_avail", 0U }; + Readonly_value_file_system _optr_samples_fs { "optr_samples", 0LL }; + Readonly_value_file_system _optr_fifo_samples_fs { "optr_fifo_samples", 0U }; + Value_file_system _play_underruns_fs { "play_underruns", 0U }; Audio::Info _info { _channels_fs, _format_fs, _sample_rate_fs, - _queue_size_fs, _ofrag_size_fs, _ofrag_avail_fs }; + _ofrag_total_fs, _ofrag_size_fs, _ofrag_avail_fs, + _optr_samples_fs, _optr_fifo_samples_fs, + _play_underruns_fs }; - Readonly_value_file_system _info_fs { "info", _info }; + Readonly_value_file_system _info_fs { "info", _info }; Audio _audio { _env.env(), _info, _info_fs }; + Genode::Watch_handler _ofrag_total_handler { + _ofrag_total_fs, "/ofrag_total", + _env.alloc(), + *this, + &Vfs::Oss_file_system::Local_factory::_ofrag_total_changed }; + + Genode::Watch_handler _ofrag_size_handler { + _ofrag_size_fs, "/ofrag_size", + _env.alloc(), + *this, + &Vfs::Oss_file_system::Local_factory::_ofrag_size_changed }; + + Genode::Watch_handler _play_underruns_handler { + _play_underruns_fs, "/play_underruns", + _env.alloc(), + *this, + &Vfs::Oss_file_system::Local_factory::_play_underruns_changed }; + + static constexpr size_t _native_stream_size { Audio_out::QUEUE_SIZE * _stream_packet_size }; + static constexpr size_t _ofrag_total_min { 2 }; + static constexpr size_t _ofrag_size_min { _stream_packet_size }; + static constexpr size_t _ofrag_total_max { _native_stream_size / _ofrag_size_min }; + static constexpr size_t _ofrag_size_max { _native_stream_size / _ofrag_total_min }; + + /******************** + ** Watch handlers ** + ********************/ + + void _ofrag_total_changed() + { + unsigned ofrag_total_new = _ofrag_total_fs.value(); + + ofrag_total_new = Genode::max(ofrag_total_new, _ofrag_total_min); + ofrag_total_new = Genode::min(ofrag_total_new, _ofrag_total_max); + + if (ofrag_total_new * _info.ofrag_size > _native_stream_size) + _info.ofrag_size = 1 << Genode::log2(_native_stream_size / ofrag_total_new); + + _info.ofrag_total = ofrag_total_new; + _info.ofrag_avail = ofrag_total_new; + + _info.update(); + _info_fs.value(_info); + } + + void _ofrag_size_changed() + { + unsigned ofrag_size_new = _ofrag_size_fs.value(); + + ofrag_size_new = Genode::max(ofrag_size_new, _ofrag_size_min); + ofrag_size_new = Genode::min(ofrag_size_new, _ofrag_size_max); + + if (ofrag_size_new * _info.ofrag_total > _native_stream_size) { + _info.ofrag_total = _native_stream_size / ofrag_size_new; + _info.ofrag_avail = _info.ofrag_total; + } + + _info.ofrag_size = ofrag_size_new; + + _info.update(); + _info_fs.value(_info); + } + + void _play_underruns_changed() + { + /* reset counter */ + + _info.play_underruns = 0; + + _info.update(); + _info_fs.value(_info); + } + static Name name(Xml_node config) { return config.attribute_value("name", Name("oss")); @@ -469,10 +644,6 @@ struct Vfs::Oss_file_system::Local_factory : File_system_factory return &_channels_fs; } - if (_queue_size_fs.matches(node)) { - return &_queue_size_fs; - } - if (_sample_rate_fs.matches(node)) { return &_sample_rate_fs; } @@ -481,12 +652,31 @@ struct Vfs::Oss_file_system::Local_factory : File_system_factory return &_ofrag_avail_fs; } + if (_format_fs.matches(node)) { + return &_format_fs; + } + + if (_optr_samples_fs.matches(node)) { + return &_optr_samples_fs; + } + + if (_optr_fifo_samples_fs.matches(node)) { + return &_optr_fifo_samples_fs; + } + } + + if (node.has_type(Value_file_system::type_name())) { + + if (_ofrag_total_fs.matches(node)) { + return &_ofrag_total_fs; + } + if (_ofrag_size_fs.matches(node)) { return &_ofrag_size_fs; } - if (_format_fs.matches(node)) { - return &_format_fs; + if (_play_underruns_fs.matches(node)) { + return &_play_underruns_fs; } } @@ -533,16 +723,28 @@ class Vfs::Oss_file_system::Compound_file_system : private Local_factory, xml.attribute("name", "format"); }); - xml.node("readonly_value", [&] { - xml.attribute("name", "queue_size"); + xml.node("value", [&] { + xml.attribute("name", "ofrag_total"); + }); + + xml.node("value", [&] { + xml.attribute("name", "ofrag_size"); }); xml.node("readonly_value", [&] { - xml.attribute("name", "frag_size"); + xml.attribute("name", "ofrag_avail"); }); xml.node("readonly_value", [&] { - xml.attribute("name", "frag_avail"); + xml.attribute("name", "optr_samples"); + }); + + xml.node("readonly_value", [&] { + xml.attribute("name", "optr_fifo_samples"); + }); + + xml.node("value", [&] { + xml.attribute("name", "play_underruns"); }); }); }); diff --git a/repos/gems/src/lib/vfs/trace/value_file_system.h b/repos/os/include/vfs/value_file_system.h similarity index 99% rename from repos/gems/src/lib/vfs/trace/value_file_system.h rename to repos/os/include/vfs/value_file_system.h index 31d613d9c6..20f3593b2c 100644 --- a/repos/gems/src/lib/vfs/trace/value_file_system.h +++ b/repos/os/include/vfs/value_file_system.h @@ -75,7 +75,7 @@ class Vfs::Value_file_system : public Vfs::Single_file_system return WRITE_ERR_INVALID; Genode::size_t const len = min(BUF_SIZE- seek(), count); - _buffer = Buffer(Cstring(src, len)); + _buffer = Buffer(Genode::Cstring(src, len)); out_count = len; /* inform watchers */