diff --git a/.gitattributes b/.gitattributes
index f5dffa7..4adf8b4 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -65,3 +65,5 @@
# cmd interpreta mal los .bat/.cmd con LF
*.bat text eol=crlf
*.cmd text eol=crlf
+# git apply necesita los parches en LF, como el codigo de FreeRDP
+*.patch text eol=lf
diff --git a/Core/Api/StreamSessionService.cs b/Core/Api/StreamSessionService.cs
index dd1bc9c..4bc0155 100644
--- a/Core/Api/StreamSessionService.cs
+++ b/Core/Api/StreamSessionService.cs
@@ -486,14 +486,25 @@ public void ReconnectRdp(Guid id, int width, int height, int frameRate, string?
display: DisplayOptions(session));
session.RdpSessionId = rdpId;
session.FreeRdpPid = clientPid;
- // El audio sale por el cliente RDP del host: el nuevo cliente también se silencia.
- if (clientPid > 0) AudioSessionMuter.StartPersistentMute((uint)clientPid);
+ if (clientPid > 0) MuteRdpClientOnHost(session, clientPid);
RdpSessionCreator.ApplyDisplayFrequency(rdpId, frameRate);
Logger.Log($"[{session.Name}] Pantalla RDP: {width}x{height} @ {frameRate} fps" +
(microphone is null ? "." : $", micrófono '{microphone}'."), id);
}
/// Usuario de Windows de la sesión (el dedicado si está aislada).
+ ///
+ /// Evita que el host oiga el audio de la sesión. mstsc reproduce el audio redirigido, así
+ /// que se silencia su sesión de audio (por proceso, no toca el volumen global). wfreerdp
+ /// ya lo descarta con /sound:sys:fake y no necesita nada.
+ ///
+ private static void MuteRdpClientOnHost(StreamSession session, int clientPid)
+ {
+ if (RdpSessionCreator.IsFreeRdpProcess(clientPid)) return;
+ Logger.Log($"[{session.Name}] Silenciando el cliente RDP (PID={clientPid}) en el host...", session.Id);
+ AudioSessionMuter.StartPersistentMute((uint)clientPid);
+ }
+
private static StreamUserConfig ResolveUser(StreamSession session)
{
if (session.Isolated)
@@ -942,28 +953,21 @@ private void DoStart(StreamSession session)
session.RdpSessionId = rdpId;
session.FreeRdpPid = freeRdpPid;
- // ── Audio: aislamiento via mute per-process de wfreerdp ───────────
- // FreeRDP /audio-mode:redirect + /sound: RDP server intercepta el audio
- // del juego dentro de la sesión RDP y lo redirige por canal a wfreerdp.exe,
- // que lo reproduce en el host (típicamente en el endpoint físico).
- // Sunshine, dentro de la sesión RDP, hace WASAPI loopback en "Remote Audio"
- // (endpoint virtual que crea Windows automáticamente cuando hay redirect),
- // capturando el SOURCE del audio antes del transporte RDP. Moonlight oye.
- // Para que el host NO oiga: mute persistente de la audio session de
- // wfreerdp.exe via ISimpleAudioVolume.SetMute. Mute es per-process por
- // endpoint, no toca volumen global ni el de otros procesos. Sunshine no
- // se ve afectado porque captura el source, no el output de wfreerdp.
+ // ── Audio: que el host no oiga la sesión ──────────────────────────
+ // Con audio redirigido, el servidor RDP saca el audio de la sesión por
+ // "Remote Audio" (donde Sunshine hace loopback: Moonlight oye) y además lo
+ // manda por el canal al cliente RDP del host. wfreerdp lo descarta
+ // (/sound:sys:fake); mstsc lo reproduciría, así que se silencia su sesión
+ // de audio (ver MuteRdpClientOnHost).
if (freeRdpPid > 0)
{
- Logger.Log($"[{session.Name}] Silenciando wfreerdp.exe (PID={freeRdpPid}) en host para evitar fuga de audio...", session.Id);
- AudioSessionMuter.StartPersistentMute((uint)freeRdpPid);
+ MuteRdpClientOnHost(session, freeRdpPid);
}
else
{
- // Sesión RDP reutilizada (no se lanzó FreeRDP). Sin PID de wfreerdp no
- // hay nada que silenciar en el host; el audio debería seguir confinado
- // al wfreerdp.exe original (si sigue vivo y muteado por su thread).
- Logger.Warning($"[{session.Name}] Sin PID de FreeRDP, no se puede aplicar mute en host esta vez.", session.Id);
+ // Sesión RDP reutilizada: el cliente original sigue conectado y, si era
+ // mstsc, su hilo de silenciado sigue activo.
+ Logger.Log($"[{session.Name}] Sesión RDP reutilizada: no se lanzó cliente RDP nuevo.", session.Id);
}
session.StartPhase = "profile";
@@ -1194,7 +1198,7 @@ private static SunshineConfigurator.SunshineConfig BuildSunshineConfig(StreamSes
OutputName: s.OutputName,
OriginWebUiAllowed: s.OriginWebUiAllowed,
// Sin audio_sink → Sunshine usa el default de la sesión RDP donde corre.
- // Con FreeRDP /audio-mode:redirect + /sound, ese default es "Remote Audio"
+ // Con el audio redirigido (mstsc, o FreeRDP /audio-mode:redirect), es "Remote Audio"
// (endpoint que crea Windows para el redirect). Loopback ahí captura el
// source del juego.
AudioSink: null,
diff --git a/Core/Helpers/RdpSessionCreator.cs b/Core/Helpers/RdpSessionCreator.cs
index db49d7e..5c1a60c 100644
--- a/Core/Helpers/RdpSessionCreator.cs
+++ b/Core/Helpers/RdpSessionCreator.cs
@@ -85,6 +85,22 @@ public sealed record RdpSessionInfo(
static string FreeRdpExe =>
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FreeRDP", "wfreerdp.exe");
+ ///
+ /// True si el PID es el wfreerdp incluido. Ese cliente descarta el audio redirigido
+ /// (/sound:sys:fake), así que, a diferencia de mstsc, no hay que silenciarlo.
+ ///
+ public static bool IsFreeRdpProcess(int pid)
+ {
+ try
+ {
+ using var process = Process.GetProcessById(pid);
+ return string.Equals(process.ProcessName, Path.GetFileNameWithoutExtension(FreeRdpExe),
+ StringComparison.OrdinalIgnoreCase);
+ }
+ catch (ArgumentException) { return false; } // ya terminó
+ catch (InvalidOperationException) { return false; }
+ }
+
///
/// Loopback alternativo: permite que el RDP local no choque con conexiones
/// RDP reales a 127.0.0.1 y que cmdkey guarde credenciales para un target
@@ -496,8 +512,11 @@ static uint CreateSessionWithFreeRdp(StreamUserConfig user, string address, int
psi.ArgumentList.Add("/network:lan");
// ── Audio ─────────────────────────────────────────────────────────────
+ // redirect: el audio de la sesión sale por "Remote Audio", donde lo captura Sunshine.
+ // El backend fake confirma cada bloque al servidor y lo descarta: el host no lo oye
+ // y no hace falta silenciar wfreerdp (mstsc sí, ver AudioSessionMuter).
psi.ArgumentList.Add("/audio-mode:redirect");
- psi.ArgumentList.Add("/sound:sys:winmm");
+ psi.ArgumentList.Add("/sound:sys:fake");
if (microphone is not null)
psi.ArgumentList.Add($"/microphone:sys:winmm,dev:{microphone}");
diff --git a/FreeRDP/freerdp-proxy.exe b/FreeRDP/freerdp-proxy.exe
deleted file mode 100644
index a7423f8..0000000
Binary files a/FreeRDP/freerdp-proxy.exe and /dev/null differ
diff --git a/FreeRDP/sdl2-freerdp.exe b/FreeRDP/sdl2-freerdp.exe
deleted file mode 100644
index cc1c15c..0000000
Binary files a/FreeRDP/sdl2-freerdp.exe and /dev/null differ
diff --git a/FreeRDP/sdl3-freerdp.exe b/FreeRDP/sdl3-freerdp.exe
deleted file mode 100644
index 2ea579a..0000000
Binary files a/FreeRDP/sdl3-freerdp.exe and /dev/null differ
diff --git a/FreeRDP/sfreerdp-server.exe b/FreeRDP/sfreerdp-server.exe
deleted file mode 100644
index 004f00c..0000000
Binary files a/FreeRDP/sfreerdp-server.exe and /dev/null differ
diff --git a/FreeRDP/wfreerdp.exe b/FreeRDP/wfreerdp.exe
index 40b1381..1815fa3 100644
Binary files a/FreeRDP/wfreerdp.exe and b/FreeRDP/wfreerdp.exe differ
diff --git a/FreeRDP/winpr-hash.exe b/FreeRDP/winpr-hash.exe
deleted file mode 100644
index e9887a0..0000000
Binary files a/FreeRDP/winpr-hash.exe and /dev/null differ
diff --git a/FreeRDP/winpr-makecert.exe b/FreeRDP/winpr-makecert.exe
deleted file mode 100644
index 3bc5ad4..0000000
Binary files a/FreeRDP/winpr-makecert.exe and /dev/null differ
diff --git a/Services/Session/AudioSessionMuter.cs b/Services/Session/AudioSessionMuter.cs
index 8e323f7..0df0e65 100644
--- a/Services/Session/AudioSessionMuter.cs
+++ b/Services/Session/AudioSessionMuter.cs
@@ -7,11 +7,12 @@ namespace OpenStreamMS.Services.Session;
/// endpoint render activo del host. Usa WASAPI IAudioSessionManager2 +
/// IAudioSessionControl2.GetProcessId + ISimpleAudioVolume.SetMute.
///
-/// Caso de uso: silenciar a wfreerdp.exe en el host. wfreerdp recibe el audio
-/// redirigido por RDP y lo reproduce en su endpoint default (físico). Mute de su
-/// audio session = host mudo. Sunshine, dentro de la sesión RDP, sigue capturando
-/// el audio en el endpoint origen ("Remote Audio") por loopback antes del transporte
-/// RDP, así que Moonlight no se ve afectado.
+/// Caso de uso: silenciar a mstsc.exe en el host. mstsc recibe el audio redirigido
+/// por RDP y lo reproduce en su endpoint default (físico). Mute de su audio session
+/// = host mudo. Sunshine, dentro de la sesión RDP, sigue capturando el audio en el
+/// endpoint origen ("Remote Audio") por loopback antes del transporte RDP, así que
+/// Moonlight no se ve afectado. wfreerdp no lo necesita: va con /sound:sys:fake
+/// y descarta el audio sin reproducirlo.
///
public static class AudioSessionMuter
{
diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md
index b999c18..d1f7484 100644
--- a/THIRD_PARTY_NOTICES.md
+++ b/THIRD_PARTY_NOTICES.md
@@ -9,8 +9,9 @@ remains under its own license; the full license text ships next to the binaries.
| Component | Version | License | License file | Source code |
|---|---|---|---|---|
-| [Sunshine](https://github.com/LizardByte/Sunshine) (LizardByte), [sunshine-webrtc](https://github.com/OpenstreamCo/Sunshine-Web-RTC) build: upstream Sunshine plus Moonlight WebRTC for Samsung TVs | 2026.1006.152353-16-gbcde822b (commit `bcde822bd8f5d750be1eb71c8ccbcb87c98f0b7a`, upstream base `0594f62d4cc6179aa055f0363043adbc8849b62b`) | GPL-3.0 | `Sunshine/LICENSE.txt` | https://github.com/OpenstreamCo/Sunshine-Web-RTC/tree/bcde822bd8f5d750be1eb71c8ccbcb87c98f0b7a |
-| [FreeRDP](https://github.com/FreeRDP/FreeRDP) | 3.32.0 | Apache-2.0 | `FreeRDP/LICENSE.txt` | https://github.com/FreeRDP/FreeRDP |
+| [Sunshine](https://github.com/LizardByte/Sunshine) (LizardByte), [sunshine-webrtc](https://github.com/mlopezsegura/Sunshine-Web-RTC) build: upstream Sunshine plus Moonlight WebRTC for Samsung TVs | 2026.1006.152353-16-gbcde822b (commit `bcde822bd8f5d750be1eb71c8ccbcb87c98f0b7a`, upstream base `0594f62d4cc6179aa055f0363043adbc8849b62b`) | GPL-3.0 | `Sunshine/LICENSE.txt` | https://github.com/mlopezsegura/Sunshine-Web-RTC/tree/bcde822bd8f5d750be1eb71c8ccbcb87c98f0b7a |
+| [FreeRDP](https://github.com/FreeRDP/FreeRDP), [SlimFreeRDP](https://github.com/mlopezsegura/SlimFreeRDP) build: `wfreerdp.exe` only, static, with the patch in `FreeRDP/patches` | 3.33.0 (SlimFreeRDP `v3.33.0-slim.1`) | Apache-2.0 | `FreeRDP/LICENSE.txt` | https://github.com/mlopezsegura/SlimFreeRDP/tree/v3.33.0-slim.1 |
+| [OpenSSL](https://www.openssl.org) (statically linked into `wfreerdp.exe`) | 3.6.5 | Apache-2.0 | `FreeRDP/LICENSE.txt` | https://github.com/openssl/openssl/tree/openssl-3.6.5 |
| [zlib](https://zlib.net) (bundled with Sunshine) | — | zlib | `Sunshine/zlib-LICENSE.txt` | https://github.com/madler/zlib |
Sunshine's web UI (`Sunshine/assets/web`) contains compiled front-end libraries that are
diff --git a/docs/puestos-locales.md b/docs/puestos-locales.md
index 5dfbf78..6f08c72 100644
--- a/docs/puestos-locales.md
+++ b/docs/puestos-locales.md
@@ -92,7 +92,7 @@ Sesión del puesto
| `Services/Seats/HandleBroker.cs` | Duplica handles NT de texturas compartidas entre procesos de distintas sesiones |
| `Services/Seats/DisplayTopology.cs` | Enumerar monitores, activarlos, colocación en esquina y sacarlos del escritorio |
| `Services/Seats/SeatAudio.cs` | Salidas de audio y micrófonos disponibles |
-| `FreeRDP/patches/0001-audin-winmm-choose-microphone.patch` | Parche de FreeRDP 3.32.0: `/microphone:sys:winmm,dev:` abre ese micrófono |
+| `FreeRDP/patches/0001-audin-winmm-choose-microphone.patch` | Parche de FreeRDP 3.33.0: `/microphone:sys:winmm,dev:` abre ese micrófono |
| `Services/Seats/SeatUsbCapture.cs` | Cambio de driver a WinUSB y vuelta (con registro en `%ProgramData%\OpenStreamMS\usb-capture.json`) y lectura de los informes USB |
| `Services/Seats/SeatHid.cs` | Descriptor de informe HID → eventos de ratón y scancodes de teclado |
| `Services/Seats/SeatInput.cs` | Respaldo: Raw Input en la consola y escudo del cursor |
@@ -152,8 +152,13 @@ restauran en su siguiente arranque.
la directiva «Permitir la redirección de grabación de audio» no esté deshabilitada
(`fDisableAudioCapture`). Un micrófono desconectado al encender el puesto se omite
(el puesto arranca sin él y queda un aviso en el log).
-- **Recompilar FreeRDP**: aplicar el parche sobre FreeRDP 3.32.0 y compilar con el
- script estático (`wfreerdp` sin dependencias); copiar `wfreerdp.exe` a `FreeRDP/`.
+- **Recompilar FreeRDP**: `wfreerdp.exe` sale de [SlimFreeRDP](https://github.com/mlopezsegura/SlimFreeRDP)
+ (FreeRDP 3.33.0 + OpenSSL 3.6.5, estático, solo los canales que se usan y con el parche
+ del micrófono). Compilar con su `build.cmd` y copiar `wfreerdp.exe` a `FreeRDP/`. No trae
+ `/gfx` ni `/rfx`, ni portapapeles ni unidades.
+- **Audio del cliente RDP en el host**: `wfreerdp` va con `/sound:sys:fake` (descarta el
+ audio redirigido; Sunshine lo captura antes, en «Remote Audio»). mstsc sí lo reproduce y
+ se silencia su sesión de audio (`AudioSessionMuter`).
- **Consola anfitriona**: recomendable un usuario técnico con autologon, sin
bloqueo, sin UAC en escritorio seguro y sin apagado de pantalla.