diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 7b59a71df0..60228f7876 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -391,12 +391,41 @@ extern "C" { * concept, so it applies to a physical audio device in this case, and not an * SDL_AudioStream, nor an SDL logical audio device. * + * For Windows WASAPI audio, the following roles are supported, and map to `AUDIO_STREAM_CATEGORY`: + * + * - "Other" (default) + * - "Communications" - Real-time communications, such as VOIP or chat + * - "Game" - Game audio + * - "GameChat" - Game chat audio, similar to "Communications" except that this will not attenuate other audio streams + * - "Movie" - Music or sound with dialog + * - "Media" - Music or sound without dialog + * + * If your application applies its own echo cancellation, gain control, and noise reduction it should also set SDL_HINT_AUDIO_DEVICE_RAW_STREAM. + * * This hint should be set before an audio device is opened. * * \since This hint is available since SDL 3.2.0. */ #define SDL_HINT_AUDIO_DEVICE_STREAM_ROLE "SDL_AUDIO_DEVICE_STREAM_ROLE" +/** + * Specify whether this audio device should do audio processing. + * + * Some operating systems perform echo cancellation, gain control, and noise reduction as needed. If your application already handles these, you can set this hint to prevent the OS from doing additional audio processing. + * + * This corresponds to the WASAPI audio option `AUDCLNT_STREAMOPTIONS_RAW`. + * + * The variable can be set to the following values: + * + * - "0": audio processing can be done by the OS. (default) + * - "1": audio processing is done by the application. + * + * This hint should be set before an audio device is opened. + * + * \since This hint is available since SDL 3.4.0. + */ +#define SDL_HINT_AUDIO_DEVICE_RAW_STREAM "SDL_AUDIO_DEVICE_RAW_STREAM" + /** * Specify the input file when recording audio using the disk audio driver. * diff --git a/src/audio/wasapi/SDL_wasapi.c b/src/audio/wasapi/SDL_wasapi.c index 0a06f2c6d6..443bbd5367 100644 --- a/src/audio/wasapi/SDL_wasapi.c +++ b/src/audio/wasapi/SDL_wasapi.c @@ -54,6 +54,9 @@ static pfnAvRevertMmThreadCharacteristics pAvRevertMmThreadCharacteristics = NUL static const IID SDL_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, { 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } }; static const IID SDL_IID_IAudioCaptureClient = { 0xc8adbd64, 0xe71e, 0x48a0, { 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17 } }; static const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } }; +#ifdef __IAudioClient2_INTERFACE_DEFINED__ +static const IID SDL_IID_IAudioClient2 = { 0x726778cd, 0xf60a, 0x4EDA, { 0x82, 0xde, 0xe4, 0x76, 0x10, 0xcd, 0x78, 0xaa } }; +#endif // #ifdef __IAudioClient3_INTERFACE_DEFINED__ static const IID SDL_IID_IAudioClient3 = { 0x7ed4ee07, 0x8e67, 0x4cd4, { 0x8c, 0x1a, 0x2b, 0x7a, 0x59, 0x87, 0xad, 0x42 } }; #endif // @@ -727,6 +730,44 @@ static bool mgmtthrtask_PrepDevice(void *userdata) int new_sample_frames = 0; bool iaudioclient3_initialized = false; +#ifdef __IAudioClient2_INTERFACE_DEFINED__ + IAudioClient2 *client2 = NULL; + ret = IAudioClient_QueryInterface(client, &SDL_IID_IAudioClient2, (void **)&client2); + if (SUCCEEDED(ret)) { + AudioClientProperties audioProps; + + SDL_zero(audioProps); + audioProps.cbSize = sizeof(audioProps); + + const char *hint = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_STREAM_ROLE); + if (hint && *hint) { + if (SDL_strcasecmp(hint, "Communications") == 0) { + audioProps.eCategory = AudioCategory_Communications; + } else if (SDL_strcasecmp(hint, "Game") == 0) { + // We'll add support for GameEffects as distinct from GameMedia later when we add stream roles + audioProps.eCategory = AudioCategory_GameEffects; + } else if (SDL_strcasecmp(hint, "GameChat") == 0) { + audioProps.eCategory = AudioCategory_GameChat; + } else if (SDL_strcasecmp(hint, "Movie") == 0) { + audioProps.eCategory = AudioCategory_Movie; + } else if (SDL_strcasecmp(hint, "Media") == 0) { + audioProps.eCategory = AudioCategory_Media; + } + } + + if (SDL_GetHintBoolean(SDL_HINT_AUDIO_DEVICE_RAW_STREAM, false)) { + audioProps.Options = AUDCLNT_STREAMOPTIONS_RAW; + } + + ret = IAudioClient2_SetClientProperties(client2, &audioProps); + if (FAILED(ret)) { + // This isn't fatal, let's log it instead of failing + SDL_LogWarn(SDL_LOG_CATEGORY_AUDIO, "IAudioClient2_SetClientProperties failed: 0x%lx", ret); + } + IAudioClient2_Release(client2); + } +#endif + #ifdef __IAudioClient3_INTERFACE_DEFINED__ // Try querying IAudioClient3 if sharemode is AUDCLNT_SHAREMODE_SHARED if (sharemode == AUDCLNT_SHAREMODE_SHARED) { diff --git a/test/loopwave.c b/test/loopwave.c index 8a2d7a0ef5..e5b55e7df9 100644 --- a/test/loopwave.c +++ b/test/loopwave.c @@ -62,13 +62,17 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) consumed = SDLTest_CommonArg(state, i); if (!consumed) { - if (!filename) { + if (SDL_strcmp(argv[i], "--role") == 0 && argv[i + 1]) { + SDL_SetHint(SDL_HINT_AUDIO_DEVICE_STREAM_ROLE, argv[i + 1]); + ++i; + consumed = 1; + } else if (!filename) { filename = argv[i]; consumed = 1; } } if (consumed <= 0) { - static const char *options[] = { "[sample.wav]", NULL }; + static const char *options[] = { "[--role ROLE]", "[sample.wav]", NULL }; SDLTest_CommonLogUsage(state, argv[0], options); exit(1); }