Skip to content

Commit

Permalink
AudioVM and client configuration with separate VM audio chennels
Browse files Browse the repository at this point in the history
Signed-off-by: Jon Sahlberg <jon.sahlberg@unikie.com>
  • Loading branch information
josa41 committed Aug 9, 2024
1 parent 565e456 commit ce02d31
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 62 deletions.
116 changes: 108 additions & 8 deletions modules/common/services/audio.nix
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
...
}: let
cfg = config.ghaf.services.audio;
inherit (lib) mkIf mkEnableOption mkOption types;
inherit (lib) mkIf mkEnableOption mkOption literalExpression types;
in {
options.ghaf.services.audio = {
enable = mkEnableOption "Enable audio service for audio VM";
Expand All @@ -17,6 +17,17 @@ in {
default = 4713;
description = "TCP port used by Pipewire-pulseaudio service";
};
appStreams = mkOption {
description = "Audio streams for ghaf applications";
type = types.listOf types.str;
default = [];
example = literalExpression ''
[
"chromium"
"element"
]
'';
};
};

config = mkIf cfg.enable {
Expand All @@ -35,19 +46,63 @@ in {
{ name = libpipewire-module-protocol-pulse
args = {
server.address = [
"tcp:4713" # IPv4 and IPv6 on all addresses
{
# Listen TCP port (4713 by default) for IPv4 and IPv6 on all addresses
address = "tcp:0.0.0.0:${toString cfg.pulseaudioTcpPort}"
max-clients = 64
listen-backlog = 32
client.access = "unrestricted"
}
];
pulse.min.req = 128/48000; # 2.7ms
pulse.default.req = 960/48000; # 20 milliseconds
pulse.min.frag = 128/48000; # 2.7ms
pulse.default.frag = 512/48000; # ~10 ms
pulse.default.tlength = 512/48000; # ~10 ms
pulse.min.quantum = 128/48000; # 2.7ms
pulse = {
min.req = 128/48000; # 2.7ms
default.req = 960/48000; # 20 milliseconds
default.tlength = 4800/48000; # 100 ms
# recording buffer options
min.frag = 128/48000; # 2.7ms
default.frag = 960/48000; # 20 ms
# Sheduling
min.quantum = 128/48000; # 2.7ms (test with 1024)
}
}
}
];
'')
];

extraConfig.pipewire = builtins.listToAttrs (
map (name:
lib.attrsets.nameValuePair "91-chennels-${name}-vm"
{
"context.objects" = [
{
factory = "adapter";
args = {
"factory.name" = "support.null-audio-sink";
"node.name" = "${name}.mic";
"node.description" = "${name} Microphone";
"media.class" = "Audio/Source/Virtual";
"audio.position" = "MONO";
"target.object" = "@DEFAULT_SOURCE@";
};
}
{
factory = "adapter";
args = {
"factory.name" = "support.null-audio-sink";
"node.name" = "${name}.speaker";
"node.description" = "${name} Speaker";
"media.class" = "Audio/Sink";
"audio.position" = "FL,FR";
"target.object" = "@DEFAULT_SINK@";
};
}
];
}
)
cfg.appStreams
);
};

hardware.pulseaudio.extraConfig = ''
Expand Down Expand Up @@ -77,6 +132,51 @@ in {
script = ''${pkgs.pulseaudio}/bin/pa-info > /dev/null 2>&1'';
};

# TODO Automate and fix alsa IO ports
systemd.services."pipewire-link-starter" = let

audioLinks = lib.strings.concatLines (
lib.lists.forEach cfg.appStreams (name:
''
${pkgs.pipewire}/bin/pw-link --passive $ALSA_PCI_MIC_R ${name}.mic:input_MONO
${pkgs.pipewire}/bin/pw-link --passive ${name}.speaker:monitor_FR $ALSA_PCI_SPK_R
${pkgs.pipewire}/bin/pw-link --passive ${name}.speaker:monitor_FL $ALSA_PCI_SPK_L
''
)
);

# Read first alsa IO ports from pipewire link output and connect to those
pipewireLinkStarterScript = pkgs.writeShellScriptBin "pipewire-link-starter" ''
ALSA_PCI_MIC_R=$(${pkgs.pipewire}/bin/pw-link --output | ${pkgs.gawk}/bin/awk '/alsa_input.pci/ && /capture_FR/{print $1;exit}')
ALSA_PCI_SPK_R=$(${pkgs.pipewire}/bin/pw-link --input | ${pkgs.gawk}/bin/awk '/alsa_output.pci/ && /playback_FR/{print $1;exit}')
ALSA_PCI_SPK_L=$(${pkgs.pipewire}/bin/pw-link --input | ${pkgs.gawk}/bin/awk '/alsa_output.pci/ && /playback_FL/{print $1;exit}')
if [ -z "''${ALSA_PCI_MIC_R}" ] || [ -z "''${ALSA_PCI_SPK_R}" ] || [ -z "''${ALSA_PCI_SPK_L}" ]; then
echo "No Pipewire-Alsa devices available."
exit 1
fi
${audioLinks}
exit 0
'';
in {
enable = true;
description = "Connect pipewire VM audio links";
path = [pipewireLinkStarterScript];
wantedBy = ["default.target"];
after = ["pipewire.service" "network-online.target" "pulseaudio-starter.service"];
requires = ["pipewire.service" "network-online.target" "pulseaudio-starter.service"];
serviceConfig = {
Type = "simple";
StandardOutput = "journal";
StandardError = "journal";
ExecStart = "${pipewireLinkStarterScript}/bin/pipewire-link-starter";
Restart = "on-failure";
RestartSec = "5";
StartLimitBurst = "5";
};
};

# Open TCP port for the PDF XDG socket
networking.firewall.allowedTCPPorts = [cfg.pulseaudioTcpPort];
};
Expand Down
33 changes: 33 additions & 0 deletions modules/microvm/virtualization/microvm/appvm.nix
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,28 @@
pkgs.opensc
];

# Enable Ghaf audio for Appvm
# TODO maybe make a separate module that can be loaded as per $vm.ghafAudio

# config.ghaf.virtualization.microvm.audiovm.appStreams =
# lib.optional (vm.ghafAudio) "${vm.name}";

sound.enable = vm.ghafAudio;
security.rtkit.enable = vm.ghafAudio;
users.extraUsers.ghaf.extraGroups =
lib.optionals vm.ghafAudio ["audio" "video"];
hardware.pulseaudio.enable = vm.ghafAudio;
hardware.pulseaudio.extraConfig = ''
${lib.optionalString vm.ghafAudio ''
load-module module-tunnel-sink sink=${vm.name}.speaker sink_name=${vm.name}.speaker server=audio-vm:4713 format=s16le channels=2 rate=48000
load-module module-tunnel-source source=${vm.name}.mic source_name=${vm.name}.mic server=audio-vm:4713 format=s16le channels=1 rate=48000
# Set sink and source default max volume to about 90% (0-65536)
set-sink-volume ${vm.name}.speaker 60000
set-source-volume ${vm.name}.mic 60000
''}
'';

security.tpm2 = {
enable = true;
abrmd.enable = true;
Expand Down Expand Up @@ -220,6 +242,11 @@ in {
type = types.nullOr types.str;
default = null;
};
ghafAudio = mkOption {
description = "Enable Ghaf VM Audio support";
type = types.bool;
default = false;
};
vtpm.enable = lib.mkEnableOption "vTPM support in the virtual machine";
};
});
Expand All @@ -234,6 +261,12 @@ in {
default = [];
};

ghafAudio = mkOption {
description = "Enable Ghaf VM Audio support";
type = types.bool;
default = false;
};

# Base VSOCK CID which is used for auto assigning CIDs for all AppVMs
# For example, when it's set to 100, AppVMs will get 100, 101, 102, etc.
# It is also possible to override the auto assinged CID using the vms.cid option
Expand Down
21 changes: 20 additions & 1 deletion modules/microvm/virtualization/microvm/audiovm.nix
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
pkgs,
...
}: let
inherit (lib) mkOption literalExpression types optionals;

configHost = config;
vmName = "audio-vm";
macAddress = "02:00:00:03:03:03";
Expand Down Expand Up @@ -48,14 +50,19 @@
withTimesyncd = true;
withDebug = configHost.ghaf.profiles.debug.enable;
};
services.audio.enable = true;
services.audio = {
enable = true;
# TODO Get list of appstreams from global cfg
appStreams = config.ghaf.virtualization.microvm.audiovm.appStreams;
};
};

environment = {
systemPackages = [
pkgs.pulseaudio
pkgs.pamixer
pkgs.pipewire
pkgs.pw-volume
];
};

Expand Down Expand Up @@ -127,6 +134,18 @@ in {
options.ghaf.virtualization.microvm.audiovm = {
enable = lib.mkEnableOption "AudioVM";

appStreams = lib.mkOption {
description = "Audio streams for ghaf applications";
type = types.listOf types.str;
default = [ "chromium" "element" "business" ];
example = literalExpression ''
[
"chromium"
"element"
]
'';
};

extraModules = lib.mkOption {
description = ''
List of additional modules to be imported and evaluated as part of
Expand Down
19 changes: 1 addition & 18 deletions modules/reference/appvms/business.nix
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ in {
'';
in [
pkgs.chromium
pkgs.pulseaudio
pkgs.xdg-utils
xdgPdfItem
xdgOpenPdf
Expand All @@ -42,23 +41,6 @@ in {
extraModules = [
{
imports = [../programs/chromium.nix];
# Enable pulseaudio for Chromium VM
security.rtkit.enable = true;
sound.enable = true;
users.extraUsers.ghaf.extraGroups = ["audio" "video"];

hardware.pulseaudio = {
enable = true;
extraConfig = ''
load-module module-tunnel-sink sink_name=chromium-speaker server=audio-vm:4713 format=s16le channels=2 rate=48000
load-module module-tunnel-source source_name=chromium-mic server=audio-vm:4713 format=s16le channels=1 rate=48000
# Set sink and source default max volume to about 90% (0-65536)
set-sink-volume chromium-speaker 60000
set-source-volume chromium-mic 60000
'';
};

time.timeZone = config.time.timeZone;

microvm = {
Expand Down Expand Up @@ -246,5 +228,6 @@ in {
}
];
borderColor = "#00FF00";
ghafAudio = true;
vtpm.enable = true;
}
22 changes: 4 additions & 18 deletions modules/reference/appvms/chromium.nix
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ in {
'';
in [
pkgs.chromium
pkgs.pulseaudio
pkgs.xdg-utils
xdgPdfItem
xdgOpenPdf
Expand All @@ -38,23 +37,9 @@ in {
cores = 4;
extraModules = [
{
imports = [../programs/chromium.nix];
# Enable pulseaudio for Chromium VM
security.rtkit.enable = true;
sound.enable = true;
users.extraUsers.ghaf.extraGroups = ["audio" "video"];

hardware.pulseaudio = {
enable = true;
extraConfig = ''
load-module module-tunnel-sink sink_name=chromium-speaker server=audio-vm:4713 format=s16le channels=2 rate=48000
load-module module-tunnel-source source_name=chromium-mic server=audio-vm:4713 format=s16le channels=1 rate=48000
# Set sink and source default max volume to about 90% (0-65536)
set-sink-volume chromium-speaker 60000
set-source-volume chromium-mic 60000
'';
};
imports = [
../programs/chromium.nix
];

time.timeZone = config.time.timeZone;

Expand All @@ -70,5 +55,6 @@ in {
}
];
borderColor = "#630505";
ghafAudio = true;
vtpm.enable = true;
}
18 changes: 1 addition & 17 deletions modules/reference/appvms/element.nix
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,6 @@ in {
cores = 4;
extraModules = [
{
# Enable pulseaudio for user ghaf to access mic
security.rtkit.enable = true;
sound.enable = true;
users.extraUsers.ghaf.extraGroups = ["audio" "video"];

hardware.pulseaudio = {
enable = true;
extraConfig = ''
load-module module-tunnel-sink sink_name=element-speaker server=audio-vm:4713 format=s16le channels=2 rate=48000
load-module module-tunnel-source source_name=element-mic server=audio-vm:4713 format=s16le channels=1 rate=48000
# Set sink and source default max volume to about 90% (0-65536)
set-sink-volume element-speaker 60000
set-source-volume element-mic 60000
'';
};

systemd = {
services = {
element-gps = {
Expand Down Expand Up @@ -97,4 +80,5 @@ in {
}
];
borderColor = "#337aff";
ghafAudio = true;
}
33 changes: 33 additions & 0 deletions modules/reference/services/vm-audio.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors
# SPDX-License-Identifier: Apache-2.0
{
pkgs,
config,
vmName,
...
}: let
speakerName = "${vmName}.speaker";
micName = "${vmName}.mic";
in {
# packages = [
# pkgs.pulseaudio
# pkgs.pamixer
# ];

# Enable pulseaudio for application VM
security.rtkit.enable = true;
sound.enable = true;
users.extraUsers.ghaf.extraGroups = ["audio" "video"];

hardware.pulseaudio = {
enable = true;
extraConfig = "
load-module module-tunnel-sink sink=${speakerName} sink_name=${speakerName} server=audio-vm:4713 format=s16le channels=2 rate=48000
load-module module-tunnel-source source=${micName} source_name=${micName} server=audio-vm:4713 format=s16le channels=1 rate=48000
# Set sink and source default max volume to about 90% (0-65536)
set-sink-volume ${speakerName} 60000
set-source-volume ${micName} 60000
";
};
}

0 comments on commit ce02d31

Please sign in to comment.