[PATCH v3 0/4] MR11717: faudio: Initialize MTA in FAudio_PlatformAddRef().
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. -- v3: xaudio2/tests: Test creating an xWMA source voice without COM. faudio: Create the WMA decoder without using COM. wmadmod: Create the decoder without using COM. winegstreamer: Add winegstreamer_create_wma_decoder(). https://gitlab.winehq.org/wine/wine/-/merge_requests/11717
From: Nello De Gregoris <bluechxindv@gmail.com> --- dlls/winegstreamer/winegstreamer.spec | 1 + dlls/winegstreamer/wma_decoder.c | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/dlls/winegstreamer/winegstreamer.spec b/dlls/winegstreamer/winegstreamer.spec index 095f75a0865..40a95e491b7 100644 --- a/dlls/winegstreamer/winegstreamer.spec +++ b/dlls/winegstreamer/winegstreamer.spec @@ -4,3 +4,4 @@ @ stdcall -private DllUnregisterServer() @ stdcall winegstreamer_create_wm_sync_reader(ptr ptr) @ stdcall winegstreamer_create_video_decoder(ptr) +@ stdcall winegstreamer_create_wma_decoder(ptr ptr) diff --git a/dlls/winegstreamer/wma_decoder.c b/dlls/winegstreamer/wma_decoder.c index e5e6cc616d1..2f446f8baa2 100644 --- a/dlls/winegstreamer/wma_decoder.c +++ b/dlls/winegstreamer/wma_decoder.c @@ -1105,3 +1105,14 @@ HRESULT wma_decoder_create(IUnknown *outer, IUnknown **out) TRACE("Created decoder %p\n", *out); return S_OK; } + +HRESULT WINAPI winegstreamer_create_wma_decoder(IUnknown *outer, IUnknown **out) +{ + TRACE("outer %p, out %p\n", outer, out); + + if (!init_gstreamer()) + return E_FAIL; + + return wma_decoder_create(outer, out); +} + -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11717
From: Nello De Gregoris <bluechxindv@gmail.com> --- dlls/wmadmod/Makefile.in | 2 +- dlls/wmadmod/wmadmod.c | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/dlls/wmadmod/Makefile.in b/dlls/wmadmod/Makefile.in index bd22cb82b61..3828e565b0d 100644 --- a/dlls/wmadmod/Makefile.in +++ b/dlls/wmadmod/Makefile.in @@ -1,5 +1,5 @@ MODULE = wmadmod.dll -IMPORTS = combase mfplat msdmo mfuuid dmoguids strmiids wmcodecdspuuid uuid +IMPORTS = combase mfplat msdmo mfuuid dmoguids strmiids wmcodecdspuuid uuid winegstreamer SOURCES = \ wmadmod.c \ diff --git a/dlls/wmadmod/wmadmod.c b/dlls/wmadmod/wmadmod.c index 3d619d93a35..c3c6459c080 100644 --- a/dlls/wmadmod/wmadmod.c +++ b/dlls/wmadmod/wmadmod.c @@ -34,11 +34,24 @@ WINE_DEFAULT_DEBUG_CHANNEL(dmo); +extern HRESULT WINAPI winegstreamer_create_wma_decoder(IUnknown *outer, IUnknown **out); + static HRESULT WINAPI wma_decoder_factory_CreateInstance(IClassFactory *iface, IUnknown *outer, REFIID riid, void **out) { - static const GUID CLSID_wg_wma_decoder = {0x5b4d4e54,0x0620,0x4cf9,{0x94,0xae,0x78,0x23,0x96,0x5c,0x28,0xb6}}; - return CoCreateInstance(&CLSID_wg_wma_decoder, outer, CLSCTX_INPROC_SERVER, riid, out); + IUnknown *unk; + HRESULT hr; + + if (outer && !IsEqualGUID(riid, &IID_IUnknown)) + return E_NOINTERFACE; + + *out = NULL; + if (FAILED(hr = winegstreamer_create_wma_decoder(outer, &unk))) + return hr; + + hr = IUnknown_QueryInterface(unk, riid, out); + IUnknown_Release(unk); + return hr; } static HRESULT WINAPI class_factory_QueryInterface(IClassFactory *iface, REFIID riid, void **out) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11717
From: Nello De Gregoris <bluechxindv@gmail.com> --- .../faudio/src/FAudio_platform_win32_wmadec.c | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/libs/faudio/src/FAudio_platform_win32_wmadec.c b/libs/faudio/src/FAudio_platform_win32_wmadec.c index 8d7b9185a94..e3dfec7e170 100644 --- a/libs/faudio/src/FAudio_platform_win32_wmadec.c +++ b/libs/faudio/src/FAudio_platform_win32_wmadec.c @@ -247,6 +247,34 @@ error: LOG_FUNC_EXIT(voice->audio) } +static HRESULT create_wma_decoder(IMFTransform **out) +{ + static HRESULT (WINAPI *pDllGetClassObject)(REFCLSID, REFIID, void **); + IClassFactory *factory; + HMODULE wmadmod; + HRESULT hr; + + if (!pDllGetClassObject) + { + if (!(wmadmod = LoadLibraryW(L"wmadmod.dll"))) + return E_FAIL; + + pDllGetClassObject = (HRESULT (WINAPI *)(REFCLSID, REFIID, void **)) GetProcAddress(wmadmod, "DllGetClassObject"); + + if (!pDllGetClassObject) + return E_FAIL; + } + + hr = pDllGetClassObject(&CLSID_CWMADecMediaObject, &IID_IClassFactory, (void **)&factory); + if (FAILED(hr)) + return hr; + + hr = IClassFactory_CreateInstance(factory, NULL, &IID_IMFTransform, (void **)out); + IClassFactory_Release(factory); + + return hr; +} + uint32_t FAudio_WMADEC_init(FAudioSourceVoice *voice, uint32_t type) { static const uint8_t fake_codec_data[16] = {0, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; @@ -266,13 +294,7 @@ uint32_t FAudio_WMADEC_init(FAudioSourceVoice *voice, uint32_t type) if (!(impl = voice->audio->pMalloc(sizeof(*impl)))) return -1; FAudio_memset(impl, 0, sizeof(*impl)); - hr = CoCreateInstance( - &CLSID_CWMADecMediaObject, - 0, - CLSCTX_INPROC_SERVER, - &IID_IMFTransform, - (void **)&decoder - ); + hr = create_wma_decoder(&decoder); if (FAILED(hr)) { voice->audio->pFree(impl->output_buf); -- GitLab 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..2c7a58cd7e7 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); + + 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
On Sun Aug 23 13:47:08 2026 +0000, Rémi Bernon wrote:
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? Thanks a lot for the advice! Took me a few days but this should be working, both the test and the game are working now.
FAudio going through wmadmod and DllGetClassObject works on Windows as well. If the changes look good here I'll send that part upstream. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11717#note_149837
participants (2)
-
Nello De Gregoris -
Nello De Gregoris (@bluechxin)