genode/repos/os/include/play_session/connection.h
Norman Feske 07669ac991 os: record-and-play session interfaces and mixer
- New session interfaces:
  - os/include/play_session   (for audio playing   / mic-input driver)
  - os/include/record_session (for audio recording / audio-output driver)
- Mixer at os/src/record_play_mixer providing both play and record services
- Simple waveform player at os/src/app/waveform_player
- Simple audio-signal capturing component at os/src/app/record_rom
- Simple oscilloscpe at gems/src/app/rom_osci (using record_rom)
- Simple test-audio_play for playing raw stereo f32 data

The _gems/run/waveform_player.run_ script illustrates the use of the new
components and interfaces.

Issue #5097
2024-02-28 16:31:45 +01:00

172 lines
4.7 KiB
C++

/*
* \brief Connection to audio-play service
* \author Norman Feske
* \date 2023-12-11
*/
/*
* Copyright (C) 2023 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.
*/
#ifndef _INCLUDE__PLAY_SESSION__CONNECTION_H_
#define _INCLUDE__PLAY_SESSION__CONNECTION_H_
#include <play_session/play_session.h>
#include <base/connection.h>
#include <base/attached_dataspace.h>
#include <base/sleep.h>
namespace Play { struct Connection; }
struct Play::Connection : Genode::Connection<Session>, Rpc_client<Session>
{
private:
static constexpr Ram_quota RAM_QUOTA = { DATASPACE_SIZE + 4096 };
Attached_dataspace _ds;
Shared_buffer &_buffer = *_ds.local_addr<Shared_buffer>();
Seq _seq { };
unsigned _slot_id { };
Sample_start _sample_start { }; /* position within 'samples' ring buffer */
Num_samples _submit_samples(auto const &fn)
{
_seq = { _seq.value() + 1 };
_slot_id = (_slot_id + 1) % Shared_buffer::NUM_SLOTS;
Shared_buffer::Slot &slot = _buffer.slots[_slot_id];
slot.acquired_seq = _seq;
slot.time_window = { };
slot.sample_start = { };
slot.num_samples = { };
class Submission
{
private:
float * const _dst;
unsigned _pos;
unsigned _count = 0;
/*
* Noncopyable
*/
Submission &operator = (Submission const &);
Submission(Submission const &);
public:
Submission(float *dst, unsigned pos) : _dst(dst), _pos(pos) { }
void operator () (float value)
{
_dst[_pos] = value;
if (++_pos >= Shared_buffer::MAX_SAMPLES)
_pos = 0;
_count++;
}
Num_samples num_samples() const { return { _count }; }
};
Submission submission(_buffer.samples, _sample_start.index);
fn(submission);
return submission.num_samples();
}
void _commit_to_current_slot(Num_samples n, Time_window tw)
{
Shared_buffer::Slot &slot = _buffer.slots[_slot_id];
slot.sample_start = _sample_start;
slot.num_samples = n;
slot.time_window = tw;
slot.committed_seq = _seq;
/* advance destination position for next submission */
_sample_start.index = (_sample_start.index + n.value())
% Shared_buffer::MAX_SAMPLES;
}
public:
Connection(Genode::Env &env, Label const &label = Label())
:
Genode::Connection<Session>(env, label, RAM_QUOTA, Args()),
Rpc_client<Session>(cap()),
_ds(env.rm(), call<Rpc_dataspace>())
{
if (_ds.size() < DATASPACE_SIZE) {
error("play buffer has insufficient size");
sleep_forever();
}
}
/**
* Schedule playback of data after the given 'previous' time window
*
* \param previous time window returned by previous call, or
* a default-constructed 'Time_window' when starting
* \param duration duration of the sample data in microseconds
* \param fn functor to be repeatedly called with float sample values
*
* The sample rate depends on the given 'duration' and the number of
* 'fn' calls in the scope of the 'schedule' call. Note that the
* duration is evaluated only as a hint when starting a new playback.
* During continuous playback, the duration is inferred by the rate
* of the periodic 'schedule_and_enqueue' calls.
*/
Time_window schedule_and_enqueue(Time_window previous, Duration duration,
auto const &fn)
{
Num_samples const n = _submit_samples(fn);
Time_window const tw = call<Rpc_schedule>(previous, duration, n);
_commit_to_current_slot(n, tw);
return tw;
}
/**
* Passively enqueue data for the playback at the given time window
*
* In contrast to 'schedule_and_enqueue', this method does not allocate
* a new time window but schedules sample data for an already known
* time window. It is designated for the synchronized playback of
* multiple audio channels whereas each channel is a separate play
* session.
*
* One channel (e.g., the left) drives the allocation of time windows
* using 'schedule_and_enqueue' whereas the other channels (e.g., the
* right) merely submit audio data for the already allocated time
* windows using 'enqueue'. This way, only one RPC per period is needed
* to drive the synchronized output of an arbitrary number of channels.
*/
void enqueue(Time_window time_window, auto const &fn)
{
_commit_to_current_slot(_submit_samples(fn), time_window);
}
/**
* Inform the server that no further data is expected
*
* By calling 'stop', the client allows the server to distinguish the
* (temporary) end of playback from jitter.
*/
void stop() { call<Rpc_stop>(); }
};
#endif /* _INCLUDE__RECORD_SESSION__CONNECTION_H_ */