diff --git a/start.sh b/start.sh index 4f55a34..3879ad5 100755 --- a/start.sh +++ b/start.sh @@ -72,16 +72,23 @@ export PULSE_SERVER="unix:${PULSE_RUNTIME_PATH}/native" # ~800% on 8 cores. A larger quantum gives the audio graph more buffer # headroom to absorb brief scheduling jitter, trading a few ms of latency # (irrelevant for one-way streaming) for fewer audible underruns/crackling. -# Default PipeWire quantum is often 1024; bump to 4096. pw-metadata -n settings 0 clock.force-quantum 4096 || true -# Virtual microphone — use || true so a pre-existing module doesn't abort -# the script (set -e is active). The "Failure: No such entity" in the old -# logs was module-virtual-source failing; under PipeWire-Pulse we use -# module-null-sink + module-remap-source which is better supported. -pactl load-module module-null-sink sink_name=mic_sink sink_properties=device.description=mic_sink || true +# ── Audio sinks ─────────────────────────────────────────────────────────────── +# game_output: CSS's own audio output (game sounds, music) — set as CSS's +# default playback sink. +# mic_sink: browser mic input ONLY — pacat writes here, nothing else. +# +# Without this split, CSS's own audio output was also landing in mic_sink +# (the only sink that existed), which fed back into virtual_mic — CSS's own +# game audio was drowning out / interfering with the actual browser mic +# signal, so other players never heard a clean voice transmission. +pactl load-module module-null-sink sink_name=game_output sink_properties=device.description=game_output || true +pactl load-module module-null-sink sink_name=mic_sink sink_properties=device.description=mic_sink rate=48000 channels=1 channel_map=mono || true pactl load-module module-remap-source source_name=virtual_mic master=mic_sink.monitor || true -pactl set-default-source virtual_mic || true + +pactl set-default-sink game_output || true +pactl set-default-source virtual_mic || true # ── Launch CSS ──────────────────────────────────────────────────────────────── wine ~/Counter-Strike\ Source/revLoader.exe & @@ -94,5 +101,17 @@ for i in $(seq 1 60); do done sleep 3 +# gst_agent.py targets game_output explicitly via pipewiresrc target-object, +# so no extra env var is needed here for game audio capture. + +# ── Mic audio: socat → pacat (on-demand, per TCP connection) ───────────────── +# signaling-server.js connects a TCP socket to port 12345 only while the user +# holds PTT. socat spawns a fresh pacat per connection and kills it when the +# connection closes — pacat only runs while actively transmitting, which also +# keeps mic_sink idle (not RUNNING) the rest of the time. +socat TCP-LISTEN:12345,reuseaddr,fork \ + EXEC:"pacat --playback --device=mic_sink --format=s16le --rate=48000 --channels=1 --latency-msec=20" & +echo "socat mic listener started on TCP :12345" + # ── Start streaming agent ───────────────────────────────────────────────────── python3 ~/streaming-agent/gst_agent.py diff --git a/streaming-agent/gst_agent.py b/streaming-agent/gst_agent.py index 53552b0..f386665 100755 --- a/streaming-agent/gst_agent.py +++ b/streaming-agent/gst_agent.py @@ -125,10 +125,18 @@ class CSSStreamAgent: f'webrtcbin. ' # Game audio: pipewiresrc → Opus - # pipewiresrc is the native PipeWire GStreamer source (Ubuntu 24+/26). - # It connects to the default audio output monitor via PipeWire directly, - # avoiding the PulseAudio compatibility layer entirely. - f'pipewiresrc ! ' + # pipewiresrc with no target follows PipeWire's default *capture* + # node (the microphone, virtual_mic) — not the default sink's + # monitor — so without explicit targeting it silently captured + # the mic into this track instead of game audio. + # game_output is a single PipeWire node with both playback ports + # (what CSS writes to) and monitor ports (the loopback read side) + # on the same node — there's no separate "game_output.monitor" + # node. stream.capture.sink=true tells PipeWire's autoconnect to + # link to that node's monitor/capture-sink ports rather than + # treating it as a regular source. + f'pipewiresrc target-object=game_output ' + f'stream-properties="props,stream.capture.sink=(boolean)true" ! ' f'audio/x-raw,format=F32LE,rate=48000,channels=2 ! ' f'audioconvert ! ' f'opusenc ! '