dde_bsd: support record and play session interface

This patch enhances the audio driver with the option to operate as a
client of the record and play services instead of providing the audio-in
and audio-out services. The record/play mode can be enabled by setting
the 'record_play="yes"' config attribute.

The audio_in.run and audio_out.run scripts support the selection of the
mode via the 'use_record_play_sessions' hook function.

Issue #5097
This commit is contained in:
Norman Feske 2024-02-14 14:10:10 +01:00 committed by Christian Helmuth
parent 18c4393106
commit 5073cba536
4 changed files with 313 additions and 105 deletions

View File

@ -4,5 +4,7 @@ format
audio_in_session
audio_out_session
platform_session
play_session
record_session
report_session
timer_session

View File

@ -10,30 +10,71 @@ if {[have_spec linux]} {
exit 0
}
# select use of 'Audio_in/Audio_out' or 'Record/Play' sessions
proc use_record_play_sessions { } { return 1 }
#
# Build
#
proc build_targets { } {
set build_components {
core init timer
drivers/acpi
drivers/platform
app/pci_decode
server/report_rom
drivers/audio
test/audio_in
set targets {
core init timer
drivers/acpi drivers/platform app/pci_decode server/report_rom
drivers/audio
}
if {[use_record_play_sessions]} {
lappend targets server/record_play_mixer
} else {
lappend targets test/audio_in
}
}
build $build_components
build [build_targets]
create_boot_directory
#
# Config
#
proc audio_driver_config_attr { } {
append config {
if {[use_record_play_sessions]} {
return {report_mixer="yes" record_play="yes"}
} else {
return {report_mixer="yes"}
}
}
proc record_play_start_nodes { } {
if {![use_record_play_sessions]} { return "" }
return {
<start name="mixer">
<resource name="RAM" quantum="2M"/>
<resource name="CPU" quantum="20"/>
<binary name="record_play_mixer"/>
<provides> <service name="Record"/> <service name="Play"/> </provides>
<config jitter_ms="10">
<mix name="left"> <play label_suffix="left" /> </mix>
<mix name="right"> <play label_suffix="right"/> </mix>
<policy label_suffix="left" record="left" volume="1.0"/>
<policy label_suffix="right" record="right" volume="1.0"/>
</config>
</start>
}
}
proc audio_in_out_start_nodes { } {
if {[use_record_play_sessions]} { return "" }
return {
<start name="test-audio_in">
<resource name="RAM" quantum="8M"/>
</start>
}
}
install_config {
<config>
<parent-provides>
<service name="ROM"/>
@ -67,12 +108,7 @@ append config {
<resource name="RAM" quantum="4M"/>
<route>
<service name="Report"> <child name="report_rom"/> </service>
<service name="IO_MEM"> <parent/> </service>
<service name="LOG"> <parent/> </service>
<service name="PD"> <parent/> </service>
<service name="RM"> <parent/> </service>
<service name="CPU"> <parent/> </service>
<service name="ROM"> <parent/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
@ -81,12 +117,7 @@ append config {
<route>
<service name="Report"> <child name="report_rom"/> </service>
<service name="ROM" label="system"> <child name="report_rom"/> </service>
<service name="IO_MEM"> <parent/> </service>
<service name="LOG"> <parent/> </service>
<service name="PD"> <parent/> </service>
<service name="RM"> <parent/> </service>
<service name="CPU"> <parent/> </service>
<service name="ROM"> <parent/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
@ -98,13 +129,7 @@ append config {
<route>
<service name="ROM" label="devices"> <child name="report_rom"/> </service>
<service name="Timer"> <child name="timer"/> </service>
<service name="IRQ"> <parent/> </service>
<service name="IO_MEM"> <parent/> </service>
<service name="ROM"> <parent/> </service>
<service name="PD"> <parent/> </service>
<service name="CPU"> <parent/> </service>
<service name="LOG"> <parent/> </service>
<service name="Timer"> <parent/> </service>
<any-service> <parent/> </any-service>
</route>
<config>
<policy label="audio_drv -> "> <pci class="AUDIO"/> <pci class="HDAUDIO"/> </policy>
@ -118,31 +143,19 @@ append config {
<service name="Audio_out"/>
<service name="Audio_in"/>
</provides>
<config>
<config } [audio_driver_config_attr] {>
<mixer field="outputs.master" value="128"/>
<mixer field="record.adc-0:1_source" value="sel2"/>
<mixer field="record.adc-0:1" value="128"/>
<mixer field="record.enable" value="on"/>
</config>
</start>
<start name="test-audio_in">
<resource name="RAM" quantum="8M"/>
</start>
} [record_play_start_nodes] {
} [audio_in_out_start_nodes] {
</config>}
install_config $config
#
# Boot modules
#
set boot_modules {
core ld.lib.so init timer
platform_drv acpi_drv pci_decode report_rom
pci_audio_drv test-audio_in
}
build_boot_image $boot_modules
build_boot_image [build_artifacts]
run_genode_until forever

View File

@ -10,16 +10,87 @@ if {[have_spec linux]} {
exit 0
}
# select use of 'Audio_in/Audio_out' or 'Record/Play' sessions
proc use_record_play_sessions { } { return 1 }
create_boot_directory
build {
core init timer
drivers/acpi
drivers/platform
app/pci_decode
server/report_rom
drivers/audio
test/audio_out
proc build_targets { } {
set targets {
core init timer
drivers/acpi drivers/platform app/pci_decode server/report_rom
drivers/audio
}
if {[use_record_play_sessions]} {
lappend targets server/record_play_mixer app/waveform_player \
test/audio_play lib/vfs
} else {
lappend targets test/audio_out
}
}
build [build_targets]
proc audio_driver_config_attr { } {
if {[use_record_play_sessions]} {
return {report_mixer="yes" record_play="yes"}
} else {
return {report_mixer="yes"}
}
}
proc record_play_start_nodes { } {
if {![use_record_play_sessions]} {
return "" }
return {
<start name="mixer">
<resource name="RAM" quantum="2M"/>
<resource name="CPU" quantum="20"/>
<binary name="record_play_mixer"/>
<provides> <service name="Record"/> <service name="Play"/> </provides>
<config jitter_ms="10">
<mix name="left"> <play label_suffix="left" /> </mix>
<mix name="right"> <play label_suffix="right"/> </mix>
<policy label_suffix="left" record="left" volume="1.0"/>
<policy label_suffix="right" record="right" volume="1.0"/>
</config>
</start>
<start name="test-audio_play">
<resource name="RAM" quantum="40M"/>
<config sample_path="sample.f32">
<vfs> <rom name="sample.f32"/> </vfs>
</config>
<route>
<any-service><parent/><any-child/></any-service>
</route>
</start>
}
}
proc audio_in_out_start_nodes { } {
if {[use_record_play_sessions]} {
return "" }
return {
<start name="test-audio_out">
<resource name="RAM" quantum="4M"/>
<config>
<filename>sample.f32</filename>
</config>
<route>
<any-service><parent/><any-child/></any-service>
</route>
</start>
}
}
install_config {
@ -45,7 +116,7 @@ install_config {
<start name="report_rom">
<resource name="RAM" quantum="2M"/>
<provides> <service name="Report"/> <service name="ROM"/> </provides>
<config>
<config verbose="yes">
<policy label="pci_decode -> system" report="acpi_drv -> acpi"/>
<policy label="platform_drv -> devices" report="pci_decode -> devices"/>
</config>
@ -55,12 +126,7 @@ install_config {
<resource name="RAM" quantum="4M"/>
<route>
<service name="Report"> <child name="report_rom"/> </service>
<service name="IO_MEM"> <parent/> </service>
<service name="LOG"> <parent/> </service>
<service name="PD"> <parent/> </service>
<service name="RM"> <parent/> </service>
<service name="CPU"> <parent/> </service>
<service name="ROM"> <parent/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
@ -69,12 +135,7 @@ install_config {
<route>
<service name="Report"> <child name="report_rom"/> </service>
<service name="ROM" label="system"> <child name="report_rom"/> </service>
<service name="IO_MEM"> <parent/> </service>
<service name="LOG"> <parent/> </service>
<service name="PD"> <parent/> </service>
<service name="RM"> <parent/> </service>
<service name="CPU"> <parent/> </service>
<service name="ROM"> <parent/> </service>
<any-service> <parent/> </any-service>
</route>
</start>
@ -86,13 +147,7 @@ install_config {
<route>
<service name="ROM" label="devices"> <child name="report_rom"/> </service>
<service name="Timer"> <child name="timer"/> </service>
<service name="IRQ"> <parent/> </service>
<service name="IO_MEM"> <parent/> </service>
<service name="ROM"> <parent/> </service>
<service name="PD"> <parent/> </service>
<service name="CPU"> <parent/> </service>
<service name="LOG"> <parent/> </service>
<service name="Timer"> <parent/> </service>
<any-service> <parent/> </any-service>
</route>
<config>
<policy label="audio_drv -> "> <pci class="AUDIO"/> <pci class="HDAUDIO"/> </policy>
@ -102,43 +157,28 @@ install_config {
<start name="audio_drv">
<binary name="pci_audio_drv"/>
<resource name="RAM" quantum="2M"/>
<provides> <service name="Audio_out"/> </provides>
<config/>
<provides> <service name="Audio_out"/> <service name="Audio_in"/> </provides>
<config } [audio_driver_config_attr] {/>
</start>
<start name="test-audio_out">
<resource name="RAM" quantum="4M"/>
<config>
<filename>sample.raw</filename>
</config>
<route>
<any-service><parent/><any-child/></any-service>
</route>
</start>
} [record_play_start_nodes] {
} [audio_in_out_start_nodes] {
</config>}
#
# Get sample file
#
if {![file exists bin/sample.raw]} {
if {![file exists bin/sample.f32]} {
puts ""
puts "The sample file is missing. Please take a look at"
puts "repos/dde_bsd/README, create 'sample.raw' and put"
puts "repos/dde_bsd/README, create 'sample.f32' and put"
puts "the file into './bin'. afterwards"
puts ""
exit 1
}
build_boot_image {
core ld.lib.so init timer
platform_drv acpi_drv pci_decode report_rom
pci_audio_drv test-audio_out sample.raw
}
build_boot_image [list {*}[build_artifacts] sample.f32]
#
# For obvious reasons the timeout depends on the total
# length of the used sample file.
#
run_genode_until {.*played.*1 time\(s\)} 60
run_genode_until forever

View File

@ -15,6 +15,8 @@
/* Genode includes */
#include <audio_in_session/rpc_object.h>
#include <audio_out_session/rpc_object.h>
#include <record_session/connection.h>
#include <play_session/connection.h>
#include <base/attached_rom_dataspace.h>
#include <base/session_label.h>
#include <base/component.h>
@ -467,6 +469,144 @@ class Audio_in::Root : public Audio_in::Root_component
};
struct Stereo_output : Noncopyable
{
static constexpr unsigned SAMPLES_PER_PERIOD = Audio_in::PERIOD;
static constexpr unsigned CHANNELS = 2;
Env &_env;
Record::Connection _left { _env, "left" };
Record::Connection _right { _env, "right" };
struct Recording : private Noncopyable
{
bool depleted = false;
/* 16 bit per sample, interleaved left and right */
int16_t data[SAMPLES_PER_PERIOD*CHANNELS] { };
void clear() { for (auto &e : data) e = 0; }
void from_record_sessions(Record::Connection &left, Record::Connection &right)
{
using Samples_ptr = Record::Connection::Samples_ptr;
bool const orig_depleted = depleted;
Record::Num_samples const num_samples { SAMPLES_PER_PERIOD };
auto clamped = [&] (float v)
{
return (v > 1.0) ? 1.0
: (v < -1.0) ? -1.0
: v;
};
auto float_to_s16 = [&] (float v) { return int16_t(clamped(v)*32767); };
left.record(num_samples,
[&] (Record::Time_window const tw, Samples_ptr const &samples) {
depleted = false;
for (unsigned i = 0; i < SAMPLES_PER_PERIOD; i++)
data[i*CHANNELS] = float_to_s16(samples.start[i]);
right.record_at(tw, num_samples,
[&] (Samples_ptr const &samples) {
for (unsigned i = 0; i < SAMPLES_PER_PERIOD; i++)
data[i*CHANNELS + 1] = float_to_s16(samples.start[i]);
});
},
[&] {
depleted = true;
clear();
}
);
if (orig_depleted != depleted && depleted)
log("recording depleted");
}
};
Recording _recording { };
Signal_handler<Stereo_output> _output_handler {
_env.ep(), *this, &Stereo_output::_handle_output };
void _handle_output()
{
_recording.from_record_sessions(_left, _right);
Audio::play(_recording.data, sizeof(_recording.data));
}
Stereo_output(Env &env) : _env(env)
{
Audio::play_sigh(_output_handler);
/* submit two silent packets to get the driver going */
Audio::play(_recording.data, sizeof(_recording.data));
Audio::play(_recording.data, sizeof(_recording.data));
}
};
struct Stereo_input : Noncopyable
{
static constexpr unsigned SAMPLES_PER_PERIOD = Audio_in::PERIOD;
static constexpr unsigned CHANNELS = 2;
Env &_env;
Play::Connection _left { _env, "left" };
Play::Connection _right { _env, "right" };
/* 16 bit per sample, interleaved left and right */
int16_t data[SAMPLES_PER_PERIOD*CHANNELS] { };
struct Frame { float left, right; };
void _for_each_frame(auto const &fn) const
{
float const scale = 1.0f/32768;
for (unsigned i = 0; i < SAMPLES_PER_PERIOD; i++)
fn(Frame { .left = scale*float(data[i*CHANNELS]),
.right = scale*float(data[i*CHANNELS + 1]) });
}
Play::Time_window _time_window { };
Signal_handler<Stereo_input> _input_handler {
_env.ep(), *this, &Stereo_input::_handle_input };
void _handle_input()
{
if (int const err = Audio::record(data, sizeof(data))) {
if (err && err != 35)
warning("error ", err, " during recording");
return;
}
Play::Duration const duration_us { 11*1000 }; /* hint for first period */
_time_window = _left.schedule_and_enqueue(_time_window, duration_us,
[&] (auto &submit) {
_for_each_frame([&] (Frame const frame) {
submit(frame.left); }); });
_right.enqueue(_time_window,
[&] (auto &submit) {
_for_each_frame([&] (Frame const frame) {
submit(frame.right); }); });
}
Stereo_input(Env &env) : _env(env)
{
Audio::record_sigh(_input_handler);
}
};
/**********
** Main **
**********/
@ -487,17 +627,30 @@ struct Main
Audio::update_config(_env, _config.xml());
}
bool const _record_play = _config.xml().attribute_value("record_play", false);
Constructible<Audio_out::Out> _out { };
Constructible<Audio_out::Root> _out_root { };
Constructible<Audio_in::In> _in { };
Constructible<Audio_in::Root> _in_root { };
Constructible<Stereo_output> _stereo_output { };
Constructible<Stereo_input> _stereo_input { };
Signal_handler<Main> _announce_session_handler {
_env.ep(), *this, &Main::_handle_announce_session };
void _handle_announce_session()
{
if (_record_play) {
_stereo_output.construct(_env);
_stereo_input .construct(_env);
return;
}
/* Audio_out/Audio_in mode */
_out.construct(_env);
Audio::play_sigh(_out->sigh());