[PATCH v2 0/2] MR11717: faudio: Initialize COM before creating the WMA decoder.
Found while testing STEINS;GATE RE:BOOT (https://store.steampowered.com/app/4012810/STEINSGATE_REBOOT/). The visual novel voiceovers were not playing and every time one was supposed to play, the following showed up in logs: `err:ole:com_get_class_object apartment not initialised` Tracing this led me to `FAudio_WMADEC_init`, which runs CoCreateInstance on the thread the application called IXAudio2::CreateSourceVoice() from, with FAudio however never initializing COM on that path. That makes combase then reject the call with the decoder never being created. I believe most games don't run into this because Media Foundation is usually initialized before this code path is hit, which leaves a process-wide MTA in place. Since the voiceovers play correctly on Windows, handling it in FAudio seemed like the right call. -- v2: faudio: Initialize MTA in FAudio_PlatformAddRef(). xaudio2/tests: Test creating an xWMA source voice without COM. https://gitlab.winehq.org/wine/wine/-/merge_requests/11717
From: Nello De Gregoris <bluechxindv@gmail.com> --- dlls/xaudio2_7/tests/xaudio2.c | 54 ++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/dlls/xaudio2_7/tests/xaudio2.c b/dlls/xaudio2_7/tests/xaudio2.c index 115616cf764..3c0702a1bd4 100644 --- a/dlls/xaudio2_7/tests/xaudio2.c +++ b/dlls/xaudio2_7/tests/xaudio2.c @@ -37,6 +37,8 @@ #include "ks.h" #include "ksmedia.h" +static const GUID KSDATAFORMAT_SUBTYPE_WMAUDIO2 = {0x00000161, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; + static const GUID IID_IXAudio27 = {0x8bcf1f58, 0x9fe7, 0x4583, {0x8a, 0xc6, 0xe2, 0xad, 0xc4, 0x65, 0xc8, 0xbb}}; static const GUID IID_IXAudio28 = {0x60d8dac8, 0x5aa1, 0x4e8e, {0xb5, 0x97, 0x2f, 0x5e, 0x28, 0x83, 0xd4, 0x84}}; static const GUID IID_IXAudio29 = {0x2b02e3cf, 0x2e0b, 0x4ec3, {0xbe, 0x45, 0x1b, 0x2a, 0x3f, 0xe7, 0x21, 0x0d}}; @@ -1463,6 +1465,57 @@ static void test_XAudio2CreateWithVersionInfo(void) } #endif +struct wma_voice_params +{ + IXAudio2 *audio; + HRESULT hr; +}; + +static DWORD WINAPI wma_voice_thread(void *arg) +{ + struct wma_voice_params *params = arg; + IXAudio2SourceVoice *voice = NULL; + WAVEFORMATEXTENSIBLE fmt; + + memset(&fmt, 0, sizeof(fmt)); + fmt.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + fmt.Format.nChannels = 2; + fmt.Format.nSamplesPerSec = 44100; + fmt.Format.nAvgBytesPerSec = 20000; + fmt.Format.nBlockAlign = 2230; + fmt.Format.wBitsPerSample = 16; + fmt.Format.cbSize = sizeof(fmt) - sizeof(WAVEFORMATEX); + fmt.Samples.wValidBitsPerSample = 16; + fmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; + fmt.SubFormat = KSDATAFORMAT_SUBTYPE_WMAUDIO2; + + params->hr = IXAudio2_CreateSourceVoice(params->audio, &voice, + (const WAVEFORMATEX *)&fmt, 0, 1.0f, NULL, NULL, NULL); + if (SUCCEEDED(params->hr)) + IXAudio2SourceVoice_DestroyVoice(voice); + return 0; +} + +static void test_wma_voice_without_com(IXAudio2 *audio) +{ + struct wma_voice_params params = { audio, E_FAIL }; + IXAudio2MasteringVoice *master; + HANDLE thread; + HRESULT hr; + + hr = create_mastering_voice(audio, 2, &master); + ok(hr == S_OK, "CreateMasteringVoice failed: %08lx\n", hr); + + thread = CreateThread(NULL, 0, wma_voice_thread, ¶ms, 0, NULL); + ok(thread != NULL, "CreateThread failed: %lu\n", GetLastError()); + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + + todo_wine ok(params.hr == S_OK, "got %#lx\n", params.hr); + + IXAudio2MasteringVoice_DestroyVoice(master); +} + static UINT32 check_has_devices(IXAudio2 *xa) { HRESULT hr; @@ -1504,6 +1557,7 @@ START_TEST(xaudio2) test_submix(audio); test_flush(audio); test_setchannelvolumes(audio); + test_wma_voice_without_com(audio); } ref = IXAudio2_Release(audio); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11717
From: Nello De Gregoris <bluechxindv@gmail.com> --- dlls/xaudio2_7/tests/xaudio2.c | 2 +- libs/faudio/src/FAudio_platform_win32.c | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/dlls/xaudio2_7/tests/xaudio2.c b/dlls/xaudio2_7/tests/xaudio2.c index 3c0702a1bd4..2c7a58cd7e7 100644 --- a/dlls/xaudio2_7/tests/xaudio2.c +++ b/dlls/xaudio2_7/tests/xaudio2.c @@ -1511,7 +1511,7 @@ static void test_wma_voice_without_com(IXAudio2 *audio) WaitForSingleObject(thread, INFINITE); CloseHandle(thread); - todo_wine ok(params.hr == S_OK, "got %#lx\n", params.hr); + ok(params.hr == S_OK, "got %#lx\n", params.hr); IXAudio2MasteringVoice_DestroyVoice(master); } diff --git a/libs/faudio/src/FAudio_platform_win32.c b/libs/faudio/src/FAudio_platform_win32.c index 714d36c66d7..77a1e5cbc4b 100644 --- a/libs/faudio/src/FAudio_platform_win32.c +++ b/libs/faudio/src/FAudio_platform_win32.c @@ -45,7 +45,7 @@ static CRITICAL_SECTION faudio_cs = { NULL, -1, 0, 0, 0, 0 }; static IMMDeviceEnumerator *device_enumerator; -static HRESULT init_hr; +static CO_MTA_USAGE_COOKIE mta_cookie; struct FAudioWin32PlatformData { @@ -483,7 +483,7 @@ void FAudio_PlatformAddRef() EnterCriticalSection(&faudio_cs); if (!device_enumerator) { - init_hr = CoInitialize(NULL); + CoIncrementMTAUsage(&mta_cookie); hr = CoCreateInstance( &CLSID_MMDeviceEnumerator, NULL, @@ -503,7 +503,11 @@ void FAudio_PlatformRelease() if (!IMMDeviceEnumerator_Release(device_enumerator)) { device_enumerator = NULL; - if (SUCCEEDED(init_hr)) CoUninitialize(); + if (mta_cookie) + { + CoDecrementMTAUsage(mta_cookie); + mta_cookie = NULL; + } } LeaveCriticalSection(&faudio_cs); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11717
Turns out I was completely wrong (again): xaudio2 holds no reference of its own and the WMA path doesn't depend on COM at all. Looking at a +ole log again, the game actually calls CoUninitialize() before it gets to creating the WMA voice: it initializes COM on its audio thread, sets up audio, drops COM again which destroys the apartment, and only about three seconds later creates the xWMA voice on that same thread, which is where we fail, while on Windows CreateSourceVoice() with a WMA format works fine from a thread that never initialized COM. The simplest solution here seemed like replacing the CoInitialize(NULL) FAudio already had in FAudio_PlatformAddRef() with CoIncrementMTAUsage(), so an MTA is around for as long as the platform device is, without changing the apartment of any application thread. I also found afterwards that dlls/dsound does the same thing in DirectSoundDevice_Initialize. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11717#note_149547
On Fri Aug 21 19:10:25 2026 +0000, Nello De Gregoris wrote:
Turns out I was completely wrong (again): xaudio2 holds no reference of its own and the WMA path doesn't depend on COM at all. Looking at a +ole log again, the game actually calls CoUninitialize() before it gets to creating the WMA voice: it initializes COM on its audio thread, sets up audio, drops COM again which destroys the apartment, and only about three seconds later creates the xWMA voice on that same thread, which is where we fail, while on Windows CreateSourceVoice() with a WMA format works fine from a thread that never initialized COM. The simplest solution here seemed like replacing the CoInitialize(NULL) FAudio already had in FAudio_PlatformAddRef() with CoIncrementMTAUsage(), so an MTA is around for as long as the platform device is, without changing the apartment of any application thread. I also found afterwards that dlls/dsound does the same thing in DirectSoundDevice_Initialize. I think this should be explored further and fixed by FAudio project.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11717#note_149558
On Fri Aug 21 19:10:25 2026 +0000, Nikolay Sivov wrote:
I think this should be explored further and fixed by FAudio project. I don't think it's possible, the WMA decoding feature using an MF transform was added specifically for better Wine integration and because it's not really possible to embed a WMA decoder directly in FAudio for various reasons.
While FAudio probably doesn't care very much about this not working for one game (they are basically more interested in easing the porting of games, which usually includes converting the WMA to a different free codec), we could simply have a simple diff in Wine that uses a non-COM interface to instantiate this decoder, the same way we do in ir50_32. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11717#note_149561
On Fri Aug 21 20:12:39 2026 +0000, Rémi Bernon wrote:
I don't think it's possible, the WMA decoding feature using an MF transform was added specifically for better Wine integration and because it's not really possible to embed a WMA decoder directly in FAudio for various reasons. While FAudio probably doesn't care very much about this not working for one game (they are basically more interested in easing the porting of games, which usually includes converting the WMA to a different free codec), we could simply have a simple diff in Wine that uses a non-COM interface to instantiate this decoder, the same way we do in ir50_32. Looked into ir50_32, so the idea would be exporting something like winegstreamer_create_wma_decoder next to winegstreamer_create_video_decoder and calling that instead of CoCreateInstance?
Though one thing I'm unsure about is how to reach the export without importing winegstreamer in every dll using FAudio (since there's a lot of them). Would GetProcAddress be acceptable? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11717#note_149622
On Sun Aug 23 13:30:31 2026 +0000, Nello De Gregoris wrote:
Looked into ir50_32, so the idea would be exporting something like winegstreamer_create_wma_decoder next to winegstreamer_create_video_decoder and calling that instead of CoCreateInstance? Though one thing I'm unsure about is how to reach the export without importing winegstreamer in every dll using FAudio (since there's a lot of them). Would GetProcAddress be acceptable? If we're loading the library dynamically maybe we could call wmadmod DllGetClassObject instead, and change wmadmod so it itself calls winegstreamer custom export instead of CoCreateInstance, this would ensure it would be easier to change it in the future if we want to.
That change could perhaps go upstream FAudio after all, if that's not considered too ugly and if it works on Windows too? -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11717#note_149623
participants (4)
-
Nello De Gregoris -
Nello De Gregoris (@bluechxin) -
Nikolay Sivov (@nsivov) -
Rémi Bernon (@rbernon)