diff --git a/docs/configuration.md b/docs/configuration.md index cf76e366..a6e5727c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1914,6 +1914,32 @@ consent prompt for directory ACLs, so the Web UI shows setup steps for correctin +### dscp_tagging + + + + + + + + + + + + + + +
Description + Mark audio and video packets with DSCP priority tags when requested by the client. + @tip{Disabling this may help if audio crackles or stutters on Wi-Fi while the network otherwise appears + healthy. Some routers handle tagged traffic poorly.} + @note{On Windows, disabling this also removes local traffic prioritization.} +
Default@code{} + enabled + @endcode
Example@code{} + dscp_tagging = disabled + @endcode
+ ## Config Files ### file_apps diff --git a/src/config.cpp b/src/config.cpp index 1e4300ba..42ae7dae 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -844,6 +844,7 @@ namespace config { ENCRYPTION_MODE_NEVER, // lan_encryption_mode ENCRYPTION_MODE_OPPORTUNISTIC, // wan_encryption_mode 0, // packetsize + true, // dscp_tagging }; /** @@ -1811,6 +1812,7 @@ namespace config { int_between_f(vars, "lan_encryption_mode", stream.lan_encryption_mode, {0, 2}); int_between_f(vars, "wan_encryption_mode", stream.wan_encryption_mode, {0, 2}); int_between_f(vars, "packetsize", stream.packetsize, {0, PACKETSIZE_MAX}); + bool_f(vars, "dscp_tagging", stream.dscp_tagging); path_f(vars, "file_apps", stream.file_apps); #ifndef __ANDROID__ diff --git a/src/config.h b/src/config.h index d13ddfd4..80f3bd82 100644 --- a/src/config.h +++ b/src/config.h @@ -272,6 +272,8 @@ namespace config { // Limit the packetsize to avoid fragmentation on a low MTU link int packetsize; ///< Maximum payload size for network packets. + + bool dscp_tagging; ///< Allow DSCP tagging of audio and video traffic when requested by the client. }; /** diff --git a/src/stream.cpp b/src/stream.cpp index ab9125dc..28afa1c7 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -2227,7 +2227,7 @@ namespace stream { // Enable local prioritization and QoS tagging on video traffic if requested by the client auto address = session->video.peer.address(); - session->video.qos = platf::enable_socket_qos(ref->video_sock.native_handle(), address, session->video.peer.port(), platf::qos_data_type_e::video, session->config.videoQosType != 0); + session->video.qos = platf::enable_socket_qos(ref->video_sock.native_handle(), address, session->video.peer.port(), platf::qos_data_type_e::video, config::stream.dscp_tagging && session->config.videoQosType != 0); BOOST_LOG(debug) << "Start capturing Video"sv; video::capture(session->mail, session->config.monitor, session); @@ -2254,7 +2254,7 @@ namespace stream { // Enable local prioritization and QoS tagging on audio traffic if requested by the client auto address = session->audio.peer.address(); - session->audio.qos = platf::enable_socket_qos(ref->audio_sock.native_handle(), address, session->audio.peer.port(), platf::qos_data_type_e::audio, session->config.audioQosType != 0); + session->audio.qos = platf::enable_socket_qos(ref->audio_sock.native_handle(), address, session->audio.peer.port(), platf::qos_data_type_e::audio, config::stream.dscp_tagging && session->config.audioQosType != 0); BOOST_LOG(debug) << "Start capturing Audio"sv; audio::capture(session->mail, session->config.audio, session); diff --git a/src_assets/common/assets/web/configs/config_tabs.json b/src_assets/common/assets/web/configs/config_tabs.json index 71ab0211..0b842314 100644 --- a/src_assets/common/assets/web/configs/config_tabs.json +++ b/src_assets/common/assets/web/configs/config_tabs.json @@ -77,7 +77,8 @@ "lan_encryption_mode": 0, "wan_encryption_mode": 1, "ping_timeout": 10000, - "packetsize": 0 + "packetsize": 0, + "dscp_tagging": "enabled" } }, { diff --git a/src_assets/common/assets/web/configs/tabs/Network.vue b/src_assets/common/assets/web/configs/tabs/Network.vue index 9988dcc7..d09cba9d 100644 --- a/src_assets/common/assets/web/configs/tabs/Network.vue +++ b/src_assets/common/assets/web/configs/tabs/Network.vue @@ -174,5 +174,13 @@ const effectivePort = computed(() => Number(config.value?.port ?? defaultMoonlig
{{ $t('config.packetsize_desc') }}
+ + + diff --git a/src_assets/common/assets/web/public/assets/locale/en.json b/src_assets/common/assets/web/public/assets/locale/en.json index 416b274d..e631e94a 100644 --- a/src_assets/common/assets/web/public/assets/locale/en.json +++ b/src_assets/common/assets/web/public/assets/locale/en.json @@ -241,6 +241,8 @@ "dd_wa_hdr_toggle_delay": "High-contrast workaround for HDR", "ds4_back_as_touchpad_click": "Map Back/Select to Touchpad Click", "ds4_back_as_touchpad_click_desc": "When using PlayStation-style gamepad emulation, map Back/Select to Touchpad Click", + "dscp_tagging": "DSCP Tagging", + "dscp_tagging_desc": "Mark audio and video packets with DSCP priority tags when requested by the client. Disabling this may help if audio crackles or stutters on Wi-Fi, as some routers handle tagged traffic poorly.", "virtualhid_randomize_mac": "Randomize virtual controller MAC", "virtualhid_randomize_mac_desc": "Use a random MAC address for PlayStation-style virtual controllers instead of one based on the controller index. This avoids mixing per-controller settings when controllers are swapped on the client.", "encoder": "Force a Specific Encoder", diff --git a/tests/unit/test_config.cpp b/tests/unit/test_config.cpp index 562137f0..5cff436c 100644 --- a/tests/unit/test_config.cpp +++ b/tests/unit/test_config.cpp @@ -220,6 +220,21 @@ TEST_F(ConfigPersistenceTest, ParsesFp16SdrGammaEncodedOption) { EXPECT_FALSE(config::video.fp16_sdr_gamma_encoded); } +/** + * @brief Verify the DSCP tagging option parses enabled and disabled values. + */ +TEST_F(ConfigPersistenceTest, ParsesDscpTaggingOption) { + const auto apps_file = config_file().parent_path() / "sunshine_test_config_apps.json"; + ASSERT_EQ(file_handler::write_file(apps_file.string().c_str(), "{}"), 0); + config::stream.file_apps = apps_file.string(); + + config::apply_config_for_test("dscp_tagging = disabled\n"); + EXPECT_FALSE(config::stream.dscp_tagging); + + config::apply_config_for_test("dscp_tagging = enabled\n"); + EXPECT_TRUE(config::stream.dscp_tagging); +} + /** * @brief Verify mixed-case boolean values update configuration correctly. */ diff --git a/tests/web/Configuration.test.js b/tests/web/Configuration.test.js index 9cc75518..04f7a2aa 100644 --- a/tests/web/Configuration.test.js +++ b/tests/web/Configuration.test.js @@ -103,7 +103,7 @@ describe('network configuration', () => { const wrapper = mount(Network, { props: { platform: 'linux', - config: { upnp: false }, + config: { upnp: false, dscp_tagging: 'enabled' }, }, global: globalOptions, }) @@ -114,6 +114,23 @@ describe('network configuration', () => { expect(tableText).toContain('48010') expect(tableText).not.toContain('NaN') }) + + it('toggles DSCP tagging between enabled and disabled', async () => { + const config = { upnp: false, dscp_tagging: 'enabled' } + const wrapper = mount(Network, { + props: { platform: 'linux', config }, + global: { ...globalOptions, stubs: { ...globalOptions.stubs, Checkbox: false } }, + }) + const input = wrapper.get('#dscp_tagging') + + expect(input.element.checked).toBe(true) + + await input.setValue(false) + expect(config.dscp_tagging).toBe('disabled') + + await input.setValue(true) + expect(config.dscp_tagging).toBe('enabled') + }) }) describe('FEC configuration', () => {