From: Shaun Ren sren@codeweavers.com
--- dlls/sapi/Makefile.in | 1 + dlls/sapi/main.c | 3 + dlls/sapi/mmaudio.c | 633 ++++++++++++++++++++++++++++++++++++ dlls/sapi/sapi_classes.idl | 15 + dlls/sapi/sapi_private.h | 1 + dlls/sapi/tests/Makefile.in | 1 + dlls/sapi/tests/mmaudio.c | 68 ++++ 7 files changed, 722 insertions(+) create mode 100644 dlls/sapi/mmaudio.c create mode 100644 dlls/sapi/tests/mmaudio.c
diff --git a/dlls/sapi/Makefile.in b/dlls/sapi/Makefile.in index e0f9a8cdd07..ca64edec6dd 100644 --- a/dlls/sapi/Makefile.in +++ b/dlls/sapi/Makefile.in @@ -4,6 +4,7 @@ IMPORTS = uuid ole32 user32 advapi32 C_SRCS = \ automation.c \ main.c \ + mmaudio.c \ resource.c \ stream.c \ token.c \ diff --git a/dlls/sapi/main.c b/dlls/sapi/main.c index d83d2257186..108db7d13d8 100644 --- a/dlls/sapi/main.c +++ b/dlls/sapi/main.c @@ -105,6 +105,7 @@ static const struct IClassFactoryVtbl class_factory_vtbl =
static struct class_factory data_key_cf = { { &class_factory_vtbl }, data_key_create }; static struct class_factory file_stream_cf = { { &class_factory_vtbl }, file_stream_create }; +static struct class_factory mmaudio_out_cf = { { &class_factory_vtbl }, mmaudio_out_create }; static struct class_factory resource_mgr_cf = { { &class_factory_vtbl }, resource_manager_create }; static struct class_factory speech_stream_cf = { { &class_factory_vtbl }, speech_stream_create }; static struct class_factory speech_voice_cf = { { &class_factory_vtbl }, speech_voice_create }; @@ -125,6 +126,8 @@ HRESULT WINAPI DllGetClassObject( REFCLSID clsid, REFIID iid, void **obj ) cf = &data_key_cf.IClassFactory_iface; else if (IsEqualCLSID( clsid, &CLSID_SpFileStream )) cf = &file_stream_cf.IClassFactory_iface; + else if (IsEqualCLSID( clsid, &CLSID_SpMMAudioOut )) + cf = &mmaudio_out_cf.IClassFactory_iface; else if (IsEqualCLSID( clsid, &CLSID_SpObjectTokenCategory )) cf = &token_category_cf.IClassFactory_iface; else if (IsEqualCLSID( clsid, &CLSID_SpObjectTokenEnum )) diff --git a/dlls/sapi/mmaudio.c b/dlls/sapi/mmaudio.c new file mode 100644 index 00000000000..684476f5c56 --- /dev/null +++ b/dlls/sapi/mmaudio.c @@ -0,0 +1,633 @@ +/* + * Speech API (SAPI) winmm audio implementation. + * + * Copyright 2023 Shaun Ren for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <stdarg.h> + +#define COBJMACROS + +#include "windef.h" +#include "winbase.h" +#include "objbase.h" + +#include "sapiddk.h" +#include "sperror.h" + +#include "wine/debug.h" + +#include "sapi_private.h" + +WINE_DEFAULT_DEBUG_CHANNEL(sapi); + +enum flow_type { FLOW_IN, FLOW_OUT }; + +struct mmaudio +{ + ISpEventSource ISpEventSource_iface; + ISpEventSink ISpEventSink_iface; + ISpObjectWithToken ISpObjectWithToken_iface; + ISpMMSysAudio ISpMMSysAudio_iface; + LONG ref; + + enum flow_type flow; + ISpObjectToken *token; +}; + +static inline struct mmaudio *impl_from_ISpEventSource(ISpEventSource *iface) +{ + return CONTAINING_RECORD(iface, struct mmaudio, ISpEventSource_iface); +} + +static inline struct mmaudio *impl_from_ISpEventSink(ISpEventSink *iface) +{ + return CONTAINING_RECORD(iface, struct mmaudio, ISpEventSink_iface); +} + +static inline struct mmaudio *impl_from_ISpObjectWithToken(ISpObjectWithToken *iface) +{ + return CONTAINING_RECORD(iface, struct mmaudio, ISpObjectWithToken_iface); +} + +static inline struct mmaudio *impl_from_ISpMMSysAudio(ISpMMSysAudio *iface) +{ + return CONTAINING_RECORD(iface, struct mmaudio, ISpMMSysAudio_iface); +} + +static HRESULT WINAPI event_source_QueryInterface(ISpEventSource *iface, REFIID iid, void **obj) +{ + struct mmaudio *This = impl_from_ISpEventSource(iface); + + TRACE("(%p, %s, %p).\n", iface, debugstr_guid(iid), obj); + + return ISpMMSysAudio_QueryInterface(&This->ISpMMSysAudio_iface, iid, obj); +} + +static ULONG WINAPI event_source_AddRef(ISpEventSource *iface) +{ + struct mmaudio *This = impl_from_ISpEventSource(iface); + + TRACE("(%p).\n", iface); + + return ISpMMSysAudio_AddRef(&This->ISpMMSysAudio_iface); +} + +static ULONG WINAPI event_source_Release(ISpEventSource *iface) +{ + struct mmaudio *This = impl_from_ISpEventSource(iface); + + TRACE("(%p).\n", iface); + + return ISpMMSysAudio_Release(&This->ISpMMSysAudio_iface); +} + +static HRESULT WINAPI event_source_SetNotifySink(ISpEventSource *iface, ISpNotifySink *sink) +{ + FIXME("(%p, %p): stub.\n", iface, sink); + + return E_NOTIMPL; +} + +static HRESULT WINAPI event_source_SetNotifyWindowMessage(ISpEventSource *iface, HWND hwnd, + UINT msg, WPARAM wparam, LPARAM lparam) +{ + FIXME("(%p, %p, %u, %Ix, %Ix): stub.\n", iface, hwnd, msg, wparam, lparam); + + return E_NOTIMPL; +} + +static HRESULT WINAPI event_source_SetNotifyCallbackFunction(ISpEventSource *iface, SPNOTIFYCALLBACK *callback, + WPARAM wparam, LPARAM lparam) +{ + FIXME("(%p, %p, %Ix, %Ix): stub.\n", iface, callback, wparam, lparam); + + return E_NOTIMPL; +} + +static HRESULT WINAPI event_source_SetNotifyCallbackInterface(ISpEventSource *iface, ISpNotifyCallback *callback, + WPARAM wparam, LPARAM lparam) +{ + FIXME("(%p, %p, %Ix, %Ix): stub.\n", iface, callback, wparam, lparam); + + return E_NOTIMPL; +} + +static HRESULT WINAPI event_source_SetNotifyWin32Event(ISpEventSource *iface) +{ + FIXME("(%p): stub.\n", iface); + + return E_NOTIMPL; +} + +static HRESULT WINAPI event_source_WaitForNotifyEvent(ISpEventSource *iface, DWORD milliseconds) +{ + FIXME("(%p, %ld): stub.\n", iface, milliseconds); + + return E_NOTIMPL; +} + +static HANDLE WINAPI event_source_GetNotifyEventHandle(ISpEventSource *iface) +{ + FIXME("(%p): stub.\n", iface); + + return NULL; +} + +static HRESULT WINAPI event_source_SetInterest(ISpEventSource *iface, ULONGLONG event, ULONGLONG queued) +{ + FIXME("(%p, %s, %s): stub.\n", iface, wine_dbgstr_longlong(event), wine_dbgstr_longlong(queued)); + + return E_NOTIMPL; +} + +static HRESULT WINAPI event_source_GetEvents(ISpEventSource *iface, ULONG count, SPEVENT *array, ULONG *fetched) +{ + FIXME("(%p, %lu, %p, %p): stub.\n", iface, count, array, fetched); + + return E_NOTIMPL; +} + +static HRESULT WINAPI event_source_GetInfo(ISpEventSource *iface, SPEVENTSOURCEINFO *info) +{ + FIXME("(%p, %p): stub.\n", iface, info); + + return E_NOTIMPL; +} + +static const ISpEventSourceVtbl event_source_vtbl = +{ + event_source_QueryInterface, + event_source_AddRef, + event_source_Release, + event_source_SetNotifySink, + event_source_SetNotifyWindowMessage, + event_source_SetNotifyCallbackFunction, + event_source_SetNotifyCallbackInterface, + event_source_SetNotifyWin32Event, + event_source_WaitForNotifyEvent, + event_source_GetNotifyEventHandle, + event_source_SetInterest, + event_source_GetEvents, + event_source_GetInfo +}; + +static HRESULT WINAPI event_sink_QueryInterface(ISpEventSink *iface, REFIID iid, void **obj) +{ + struct mmaudio *This = impl_from_ISpEventSink(iface); + + TRACE("(%p, %s, %p).\n", iface, debugstr_guid(iid), obj); + + return ISpMMSysAudio_QueryInterface(&This->ISpMMSysAudio_iface, iid, obj); +} + +static ULONG WINAPI event_sink_AddRef(ISpEventSink *iface) +{ + struct mmaudio *This = impl_from_ISpEventSink(iface); + + TRACE("(%p).\n", iface); + + return ISpMMSysAudio_AddRef(&This->ISpMMSysAudio_iface); +} + +static ULONG WINAPI event_sink_Release(ISpEventSink *iface) +{ + struct mmaudio *This = impl_from_ISpEventSink(iface); + + TRACE("(%p).\n", iface); + + return ISpMMSysAudio_Release(&This->ISpMMSysAudio_iface); +} + +static HRESULT WINAPI event_sink_AddEvents(ISpEventSink *iface, const SPEVENT *events, ULONG count) +{ + FIXME("(%p, %p, %lu).\n", iface, events, count); + + return E_NOTIMPL; +} + +static HRESULT WINAPI event_sink_GetEventInterest(ISpEventSink *iface, ULONGLONG *interest) +{ + FIXME("(%p, %p).\n", iface, interest); + + return E_NOTIMPL; +} + +static const ISpEventSinkVtbl event_sink_vtbl = +{ + event_sink_QueryInterface, + event_sink_AddRef, + event_sink_Release, + event_sink_AddEvents, + event_sink_GetEventInterest +}; + +static HRESULT WINAPI objwithtoken_QueryInterface(ISpObjectWithToken *iface, REFIID iid, void **obj) +{ + struct mmaudio *This = impl_from_ISpObjectWithToken(iface); + + TRACE("(%p, %s, %p).\n", iface, debugstr_guid(iid), obj); + + return ISpMMSysAudio_QueryInterface(&This->ISpMMSysAudio_iface, iid, obj); +} + +static ULONG WINAPI objwithtoken_AddRef(ISpObjectWithToken *iface) +{ + struct mmaudio *This = impl_from_ISpObjectWithToken(iface); + + TRACE("(%p).\n", iface); + + return ISpMMSysAudio_AddRef(&This->ISpMMSysAudio_iface); +} + +static ULONG WINAPI objwithtoken_Release(ISpObjectWithToken *iface) +{ + struct mmaudio *This = impl_from_ISpObjectWithToken(iface); + + TRACE("(%p).\n", iface); + + return ISpMMSysAudio_Release(&This->ISpMMSysAudio_iface); +} + +static HRESULT WINAPI objwithtoken_SetObjectToken(ISpObjectWithToken *iface, ISpObjectToken *token) +{ + struct mmaudio *This = impl_from_ISpObjectWithToken(iface); + + FIXME("(%p, %p): semi-stub.\n", iface, token); + + if (!token) + return E_INVALIDARG; + if (This->token) + return SPERR_ALREADY_INITIALIZED; + + This->token = token; + return S_OK; +} + +static HRESULT WINAPI objwithtoken_GetObjectToken(ISpObjectWithToken *iface, ISpObjectToken **token) +{ + struct mmaudio *This = impl_from_ISpObjectWithToken(iface); + + TRACE("(%p, %p).\n", iface, token); + + if (!token) + return E_POINTER; + + *token = This->token; + if (*token) + { + ISpObjectToken_AddRef(*token); + return S_OK; + } + else + return S_FALSE; +} + +static const ISpObjectWithTokenVtbl objwithtoken_vtbl = +{ + objwithtoken_QueryInterface, + objwithtoken_AddRef, + objwithtoken_Release, + objwithtoken_SetObjectToken, + objwithtoken_GetObjectToken +}; + +static HRESULT WINAPI mmsysaudio_QueryInterface(ISpMMSysAudio *iface, REFIID iid, void **obj) +{ + struct mmaudio *This = impl_from_ISpMMSysAudio(iface); + + TRACE("(%p, %s, %p).\n", iface, debugstr_guid(iid), obj); + + if (IsEqualIID(iid, &IID_IUnknown) || + IsEqualIID(iid, &IID_ISequentialStream) || + IsEqualIID(iid, &IID_IStream) || + IsEqualIID(iid, &IID_ISpStreamFormat) || + IsEqualIID(iid, &IID_ISpAudio) || + IsEqualIID(iid, &IID_ISpMMSysAudio)) + *obj = &This->ISpMMSysAudio_iface; + else if (IsEqualIID(iid, &IID_ISpEventSource)) + *obj = &This->ISpEventSource_iface; + else if (IsEqualIID(iid, &IID_ISpEventSink)) + *obj = &This->ISpEventSink_iface; + else if (IsEqualIID(iid, &IID_ISpObjectWithToken)) + *obj = &This->ISpObjectWithToken_iface; + else + { + *obj = NULL; + FIXME("interface %s not implemented.\n", debugstr_guid(iid)); + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown *)*obj); + return S_OK; +} + +static ULONG WINAPI mmsysaudio_AddRef(ISpMMSysAudio *iface) +{ + struct mmaudio *This = impl_from_ISpMMSysAudio(iface); + ULONG ref = InterlockedIncrement(&This->ref); + + TRACE("(%p): ref=%lu\n", iface, ref); + + return ref; +} + +static ULONG WINAPI mmsysaudio_Release(ISpMMSysAudio *iface) +{ + struct mmaudio *This = impl_from_ISpMMSysAudio(iface); + ULONG ref = InterlockedDecrement(&This->ref); + + TRACE("(%p): ref=%lu\n", iface, ref); + + if (!ref) + { + if (This->token) ISpObjectToken_Release(This->token); + + heap_free(This); + } + + return ref; +} + +static HRESULT WINAPI mmsysaudio_Read(ISpMMSysAudio *iface, void *pv, ULONG cb, ULONG *cb_read) +{ + FIXME("(%p, %p, %lu, %p): stub.\n", iface, pv, cb, cb_read); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_Write(ISpMMSysAudio *iface, const void *pv, ULONG cb, ULONG *cb_written) +{ + FIXME("(%p, %p, %lu, %p): stub.\n", iface, pv, cb, cb_written); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_Seek(ISpMMSysAudio *iface, LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER *new_pos) +{ + FIXME("(%p, %s, %lu, %p): stub.\n", iface, wine_dbgstr_longlong(move.QuadPart), origin, new_pos); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_SetSize(ISpMMSysAudio *iface, ULARGE_INTEGER new_size) +{ + FIXME("(%p, %s): stub.\n", iface, wine_dbgstr_longlong(new_size.QuadPart)); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_CopyTo(ISpMMSysAudio *iface, IStream *stream, ULARGE_INTEGER cb, + ULARGE_INTEGER *cb_read, ULARGE_INTEGER *cb_written) +{ + FIXME("(%p, %p, %s, %p, %p): stub.\n", iface, stream, wine_dbgstr_longlong(cb.QuadPart), cb_read, cb_written); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_Commit(ISpMMSysAudio *iface, DWORD flags) +{ + FIXME("(%p, %#lx): stub.\n", iface, flags); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_Revert(ISpMMSysAudio *iface) +{ + FIXME("(%p).\n", iface); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_LockRegion(ISpMMSysAudio *iface, ULARGE_INTEGER offset, ULARGE_INTEGER cb, + DWORD lock_type) +{ + FIXME("(%p, %s, %s, %#lx): stub.\n", iface, wine_dbgstr_longlong(offset.QuadPart), + wine_dbgstr_longlong(cb.QuadPart), lock_type); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_UnlockRegion(ISpMMSysAudio *iface, ULARGE_INTEGER offset, ULARGE_INTEGER cb, + DWORD lock_type) +{ + FIXME("(%p, %s, %s, %#lx): stub.\n", iface, wine_dbgstr_longlong(offset.QuadPart), + wine_dbgstr_longlong(cb.QuadPart), lock_type); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_Stat(ISpMMSysAudio *iface, STATSTG *statstg, DWORD flags) +{ + FIXME("(%p, %p, %#lx): stub.\n", iface, statstg, flags); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_Clone(ISpMMSysAudio *iface, IStream **stream) +{ + FIXME("(%p, %p): stub.\n", iface, stream); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_GetFormat(ISpMMSysAudio *iface, GUID *format, WAVEFORMATEX **wfx) +{ + FIXME("(%p, %p, %p): stub.\n", iface, format, wfx); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_SetState(ISpMMSysAudio *iface, SPAUDIOSTATE state, ULONGLONG reserved) +{ + FIXME("(%p, %u, %s): stub.\n", iface, state, wine_dbgstr_longlong(reserved)); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_SetFormat(ISpMMSysAudio *iface, const GUID *guid, const WAVEFORMATEX *wfx) +{ + FIXME("(%p, %s, %p): stub.\n", iface, debugstr_guid(guid), wfx); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_GetStatus(ISpMMSysAudio *iface, SPAUDIOSTATUS *status) +{ + FIXME("(%p, %p): stub.\n", iface, status); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_SetBufferInfo(ISpMMSysAudio *iface, const SPAUDIOBUFFERINFO *info) +{ + FIXME("(%p, %p): stub.\n", iface, info); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_GetBufferInfo(ISpMMSysAudio *iface, SPAUDIOBUFFERINFO *info) +{ + FIXME("(%p, %p): stub.\n", iface, info); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_GetDefaultFormat(ISpMMSysAudio *iface, GUID *guid, WAVEFORMATEX **wfx) +{ + FIXME("(%p, %p, %p): stub.\n", iface, guid, wfx); + + return E_NOTIMPL; +} + +static HANDLE WINAPI mmsysaudio_EventHandle(ISpMMSysAudio *iface) +{ + FIXME("(%p): stub.\n", iface); + + return NULL; +} + +static HRESULT WINAPI mmsysaudio_GetVolumeLevel(ISpMMSysAudio *iface, ULONG *level) +{ + FIXME("(%p, %p): stub.\n", iface, level); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_SetVolumeLevel(ISpMMSysAudio *iface, ULONG level) +{ + FIXME("(%p, %lu): stub.\n", iface, level); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_GetBufferNotifySize(ISpMMSysAudio *iface, ULONG *size) +{ + FIXME("(%p, %p): stub.\n", iface, size); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_SetBufferNotifySize(ISpMMSysAudio *iface, ULONG size) +{ + FIXME("(%p, %lu): stub.\n", iface, size); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_GetDeviceId(ISpMMSysAudio *iface, UINT *id) +{ + FIXME("(%p, %p): stub.\n", iface, id); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_SetDeviceId(ISpMMSysAudio *iface, UINT id) +{ + FIXME("(%p, %u): stub.\n", iface, id); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_GetMMHandle(ISpMMSysAudio *iface, void **handle) +{ + FIXME("(%p, %p): stub.\n", iface, handle); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_GetLineId(ISpMMSysAudio *iface, UINT *id) +{ + FIXME("(%p, %p): stub.\n", iface, id); + + return E_NOTIMPL; +} + +static HRESULT WINAPI mmsysaudio_SetLineId(ISpMMSysAudio *iface, UINT id) +{ + FIXME("(%p, %u): stub.\n", iface, id); + + return E_NOTIMPL; +} + +static const ISpMMSysAudioVtbl mmsysaudio_vtbl = +{ + mmsysaudio_QueryInterface, + mmsysaudio_AddRef, + mmsysaudio_Release, + mmsysaudio_Read, + mmsysaudio_Write, + mmsysaudio_Seek, + mmsysaudio_SetSize, + mmsysaudio_CopyTo, + mmsysaudio_Commit, + mmsysaudio_Revert, + mmsysaudio_LockRegion, + mmsysaudio_UnlockRegion, + mmsysaudio_Stat, + mmsysaudio_Clone, + mmsysaudio_GetFormat, + mmsysaudio_SetState, + mmsysaudio_SetFormat, + mmsysaudio_GetStatus, + mmsysaudio_SetBufferInfo, + mmsysaudio_GetBufferInfo, + mmsysaudio_GetDefaultFormat, + mmsysaudio_EventHandle, + mmsysaudio_GetVolumeLevel, + mmsysaudio_SetVolumeLevel, + mmsysaudio_GetBufferNotifySize, + mmsysaudio_SetBufferNotifySize, + mmsysaudio_GetDeviceId, + mmsysaudio_SetDeviceId, + mmsysaudio_GetMMHandle, + mmsysaudio_GetLineId, + mmsysaudio_SetLineId +}; + +static HRESULT mmaudio_create(IUnknown *outer, REFIID iid, void **obj, enum flow_type flow) +{ + struct mmaudio *This; + HRESULT hr; + + if (flow != FLOW_OUT) + { + FIXME("flow %d not implemented.\n", flow); + return E_NOTIMPL; + } + + if (!(This = heap_alloc_zero(sizeof(*This)))) + return E_OUTOFMEMORY; + This->ISpEventSource_iface.lpVtbl = &event_source_vtbl; + This->ISpEventSink_iface.lpVtbl = &event_sink_vtbl; + This->ISpObjectWithToken_iface.lpVtbl = &objwithtoken_vtbl; + This->ISpMMSysAudio_iface.lpVtbl = &mmsysaudio_vtbl; + This->ref = 1; + + This->flow = flow; + This->token = NULL; + + hr = ISpMMSysAudio_QueryInterface(&This->ISpMMSysAudio_iface, iid, obj); + + ISpMMSysAudio_Release(&This->ISpMMSysAudio_iface); + return hr; +} + +HRESULT mmaudio_out_create(IUnknown *outer, REFIID iid, void **obj) +{ + return mmaudio_create(outer, iid, obj, FLOW_OUT); +} diff --git a/dlls/sapi/sapi_classes.idl b/dlls/sapi/sapi_classes.idl index bb580dde18e..14f24aa9c02 100644 --- a/dlls/sapi/sapi_classes.idl +++ b/dlls/sapi/sapi_classes.idl @@ -103,3 +103,18 @@ coclass SpFileStream interface ISpStream; [default] interface ISpeechFileStream; } + +[ + uuid(a8c680eb-3d32-11d2-9ee7-00c04f797396), + helpstring("SpMMAudioOut"), + progid("SAPI.SpMMAudioOut.1"), + vi_progid("SAPI.SpMMAudioOut"), + threading(both) +] +coclass SpMMAudioOut +{ + interface ISpEventSource; + interface ISpEventSink; + interface ISpObjectWithToken; + interface ISpMMSysAudio; +} diff --git a/dlls/sapi/sapi_private.h b/dlls/sapi/sapi_private.h index fcecafb450e..88b1a27516f 100644 --- a/dlls/sapi/sapi_private.h +++ b/dlls/sapi/sapi_private.h @@ -25,6 +25,7 @@ HRESULT file_stream_create( IUnknown *outer, REFIID iid, void **obj ) DECLSPEC_H HRESULT resource_manager_create( IUnknown *outer, REFIID iid, void **obj ) DECLSPEC_HIDDEN; HRESULT speech_stream_create( IUnknown *outer, REFIID iid, void **obj ) DECLSPEC_HIDDEN; HRESULT speech_voice_create( IUnknown *outer, REFIID iid, void **obj ) DECLSPEC_HIDDEN; +HRESULT mmaudio_out_create( IUnknown *outer, REFIID iid, void **obj ) DECLSPEC_HIDDEN; HRESULT token_category_create( IUnknown *outer, REFIID iid, void **obj ) DECLSPEC_HIDDEN; HRESULT token_enum_create( IUnknown *outer, REFIID iid, void **obj ) DECLSPEC_HIDDEN; HRESULT token_create( IUnknown *outer, REFIID iid, void **obj ) DECLSPEC_HIDDEN; diff --git a/dlls/sapi/tests/Makefile.in b/dlls/sapi/tests/Makefile.in index 75c70d072d8..981bb828b8b 100644 --- a/dlls/sapi/tests/Makefile.in +++ b/dlls/sapi/tests/Makefile.in @@ -3,6 +3,7 @@ IMPORTS = ole32 user32 advapi32
C_SRCS = \ automation.c \ + mmaudio.c \ resource.c \ stream.c \ token.c \ diff --git a/dlls/sapi/tests/mmaudio.c b/dlls/sapi/tests/mmaudio.c new file mode 100644 index 00000000000..5dd0d91ab1d --- /dev/null +++ b/dlls/sapi/tests/mmaudio.c @@ -0,0 +1,68 @@ +/* + * Speech API (SAPI) winmm audio tests. + * + * Copyright 2023 Shaun Ren for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#define COBJMACROS + +#include "sapiddk.h" +#include "sperror.h" + +#include "wine/test.h" + +static void test_interfaces(void) +{ + ISpMMSysAudio *mmaudio; + IUnknown *unk; + ISpEventSource *source; + ISpEventSink *sink; + ISpObjectWithToken *obj_with_token; + HRESULT hr; + + hr = CoCreateInstance(&CLSID_SpMMAudioOut, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpMMSysAudio, (void **)&mmaudio); + ok(hr == S_OK, "Failed to create ISpMMSysAudio interface: %#lx.\n", hr); + ISpMMSysAudio_Release(mmaudio); + + hr = CoCreateInstance(&CLSID_SpMMAudioOut, NULL, CLSCTX_INPROC_SERVER, + &IID_IUnknown, (void **)&unk); + ok(hr == S_OK, "Failed to create IUnknown interface: %#lx.\n", hr); + IUnknown_Release(unk); + + hr = CoCreateInstance(&CLSID_SpMMAudioOut, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpEventSource, (void **)&source); + ok(hr == S_OK, "Failed to create ISpEventSource interface: %#lx.\n", hr); + ISpEventSource_Release(source); + + hr = CoCreateInstance(&CLSID_SpMMAudioOut, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpEventSink, (void **)&sink); + ok(hr == S_OK, "Failed to create ISpEventSink interface: %#lx.\n", hr); + ISpEventSink_Release(sink); + + hr = CoCreateInstance(&CLSID_SpMMAudioOut, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpObjectWithToken, (void **)&obj_with_token); + ok(hr == S_OK, "Failed to create ISpObjectWithToken interface: %#lx.\n", hr); + ISpObjectWithToken_Release(obj_with_token); +} + +START_TEST(mmaudio) +{ + CoInitialize(NULL); + test_interfaces(); + CoUninitialize(); +}
From: Shaun Ren sren@codeweavers.com
--- dlls/sapi/Makefile.in | 1 + dlls/sapi/mmaudio.c | 32 ++++++++++++++++++++++++----- dlls/sapi/tests/Makefile.in | 2 +- dlls/sapi/tests/mmaudio.c | 40 +++++++++++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 6 deletions(-)
diff --git a/dlls/sapi/Makefile.in b/dlls/sapi/Makefile.in index ca64edec6dd..8a1ead0b78b 100644 --- a/dlls/sapi/Makefile.in +++ b/dlls/sapi/Makefile.in @@ -1,5 +1,6 @@ MODULE = sapi.dll IMPORTS = uuid ole32 user32 advapi32 +DELAYIMPORTS = winmm
C_SRCS = \ automation.c \ diff --git a/dlls/sapi/mmaudio.c b/dlls/sapi/mmaudio.c index 684476f5c56..ec9c02cb210 100644 --- a/dlls/sapi/mmaudio.c +++ b/dlls/sapi/mmaudio.c @@ -47,6 +47,8 @@ struct mmaudio
enum flow_type flow; ISpObjectToken *token; + UINT device_id; + CRITICAL_SECTION cs; };
static inline struct mmaudio *impl_from_ISpEventSource(ISpEventSource *iface) @@ -356,6 +358,7 @@ static ULONG WINAPI mmsysaudio_Release(ISpMMSysAudio *iface) if (!ref) { if (This->token) ISpObjectToken_Release(This->token); + DeleteCriticalSection(&This->cs);
heap_free(This); } @@ -531,16 +534,33 @@ static HRESULT WINAPI mmsysaudio_SetBufferNotifySize(ISpMMSysAudio *iface, ULONG
static HRESULT WINAPI mmsysaudio_GetDeviceId(ISpMMSysAudio *iface, UINT *id) { - FIXME("(%p, %p): stub.\n", iface, id); + struct mmaudio *This = impl_from_ISpMMSysAudio(iface);
- return E_NOTIMPL; + TRACE("(%p, %p).\n", iface, id); + + if (!id) return E_POINTER; + + EnterCriticalSection(&This->cs); + *id = This->device_id; + LeaveCriticalSection(&This->cs); + + return S_OK; }
static HRESULT WINAPI mmsysaudio_SetDeviceId(ISpMMSysAudio *iface, UINT id) { - FIXME("(%p, %u): stub.\n", iface, id); + struct mmaudio *This = impl_from_ISpMMSysAudio(iface);
- return E_NOTIMPL; + TRACE("(%p, %u).\n", iface, id); + + if (id != WAVE_MAPPER && id >= waveOutGetNumDevs()) + return E_INVALIDARG; + + EnterCriticalSection(&This->cs); + This->device_id = id; + LeaveCriticalSection(&This->cs); + + return S_OK; }
static HRESULT WINAPI mmsysaudio_GetMMHandle(ISpMMSysAudio *iface, void **handle) @@ -620,9 +640,11 @@ static HRESULT mmaudio_create(IUnknown *outer, REFIID iid, void **obj, enum flow
This->flow = flow; This->token = NULL; + This->device_id = WAVE_MAPPER;
- hr = ISpMMSysAudio_QueryInterface(&This->ISpMMSysAudio_iface, iid, obj); + InitializeCriticalSection(&This->cs);
+ hr = ISpMMSysAudio_QueryInterface(&This->ISpMMSysAudio_iface, iid, obj); ISpMMSysAudio_Release(&This->ISpMMSysAudio_iface); return hr; } diff --git a/dlls/sapi/tests/Makefile.in b/dlls/sapi/tests/Makefile.in index 981bb828b8b..ea14710194f 100644 --- a/dlls/sapi/tests/Makefile.in +++ b/dlls/sapi/tests/Makefile.in @@ -1,5 +1,5 @@ TESTDLL = sapi.dll -IMPORTS = ole32 user32 advapi32 +IMPORTS = ole32 user32 advapi32 winmm
C_SRCS = \ automation.c \ diff --git a/dlls/sapi/tests/mmaudio.c b/dlls/sapi/tests/mmaudio.c index 5dd0d91ab1d..3816c0caf9c 100644 --- a/dlls/sapi/tests/mmaudio.c +++ b/dlls/sapi/tests/mmaudio.c @@ -60,9 +60,49 @@ static void test_interfaces(void) ISpObjectWithToken_Release(obj_with_token); }
+static void test_device_id(void) +{ + ISpMMSysAudio *mmaudio; + UINT id, num_devs; + HRESULT hr; + + hr = CoCreateInstance(&CLSID_SpMMAudioOut, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpMMSysAudio, (void **)&mmaudio); + ok(hr == S_OK, "failed to create SpMMAudioOut instance: %#lx.\n", hr); + + id = 0xdeadbeef; + hr = ISpMMSysAudio_GetDeviceId(mmaudio, &id); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(id == WAVE_MAPPER, "got %#x.\n", id); + + hr = ISpMMSysAudio_SetDeviceId(mmaudio, WAVE_MAPPER); + ok(hr == S_OK, "got %#lx.\n", hr); + + num_devs = waveOutGetNumDevs(); + if (num_devs == 0) { + skip("no wave out devices.\n"); + ISpMMSysAudio_Release(mmaudio); + return; + } + + hr = ISpMMSysAudio_SetDeviceId(mmaudio, num_devs); + ok(hr == E_INVALIDARG, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetDeviceId(mmaudio, 0); + ok(hr == S_OK || broken(hr == S_FALSE) /* Windows */, "got %#lx.\n", hr); + + id = 0xdeadbeef; + hr = ISpMMSysAudio_GetDeviceId(mmaudio, &id); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(id == 0, "got %u.\n", id); + + ISpMMSysAudio_Release(mmaudio); +} + START_TEST(mmaudio) { CoInitialize(NULL); test_interfaces(); + test_device_id(); CoUninitialize(); }
From: Shaun Ren sren@codeweavers.com
--- include/sapi.idl | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/include/sapi.idl b/include/sapi.idl index 92a2881501c..1d692665029 100644 --- a/include/sapi.idl +++ b/include/sapi.idl @@ -426,6 +426,9 @@ typedef [hidden] enum SPSTREAMFORMAT SPSF_NUM_FORMATS } SPSTREAMFORMAT;
+cpp_quote("DEFINE_GUID(SPDFID_Text, 0x7ceef9f9, 0x3d13, 0x11d2, 0x9e, 0xe7, 0x00, 0xc0, 0x4f, 0x79, 0x73, 0x96);") +cpp_quote("DEFINE_GUID(SPDFID_WaveFormatEx, 0xc31adbae, 0x527f, 0x4ff5, 0xa2, 0x30, 0xf6, 0x2b, 0xb6, 0x1f, 0xf7, 0x0c);") + typedef unsigned short SPPHONEID;
typedef [restricted, hidden] struct SPPHRASEELEMENT
From: Shaun Ren sren@codeweavers.com
--- include/sperror.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/include/sperror.h b/include/sperror.h index cd8ed9b948d..0e37ac91c87 100644 --- a/include/sperror.h +++ b/include/sperror.h @@ -47,6 +47,7 @@
#define SPERR_UNINITIALIZED 0x80045001 #define SPERR_ALREADY_INITIALIZED 0x80045002 +#define SPERR_UNSUPPORTED_FORMAT 0x80045003 #define SPERR_INVALID_FLAGS 0x80045004 #define SPERR_DEVICE_BUSY 0x80045006 #define SPERR_DEVICE_NOT_SUPPORTED 0x80045007
From: Shaun Ren sren@codeweavers.com
--- dlls/sapi/mmaudio.c | 68 ++++++++++++++++++++++++++++++++++++--- dlls/sapi/tests/mmaudio.c | 56 ++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 4 deletions(-)
diff --git a/dlls/sapi/mmaudio.c b/dlls/sapi/mmaudio.c index ec9c02cb210..54b4ee12807 100644 --- a/dlls/sapi/mmaudio.c +++ b/dlls/sapi/mmaudio.c @@ -48,6 +48,7 @@ struct mmaudio enum flow_type flow; ISpObjectToken *token; UINT device_id; + WAVEFORMATEX *wfx; CRITICAL_SECTION cs; };
@@ -358,6 +359,7 @@ static ULONG WINAPI mmsysaudio_Release(ISpMMSysAudio *iface) if (!ref) { if (This->token) ISpObjectToken_Release(This->token); + heap_free(This->wfx); DeleteCriticalSection(&This->cs);
heap_free(This); @@ -450,9 +452,26 @@ static HRESULT WINAPI mmsysaudio_Clone(ISpMMSysAudio *iface, IStream **stream)
static HRESULT WINAPI mmsysaudio_GetFormat(ISpMMSysAudio *iface, GUID *format, WAVEFORMATEX **wfx) { - FIXME("(%p, %p, %p): stub.\n", iface, format, wfx); + struct mmaudio *This = impl_from_ISpMMSysAudio(iface);
- return E_NOTIMPL; + TRACE("(%p, %p, %p).\n", iface, format, wfx); + + if (!format || !wfx) + return E_POINTER; + + EnterCriticalSection(&This->cs); + + if (!(*wfx = CoTaskMemAlloc(sizeof(WAVEFORMATEX) + This->wfx->cbSize))) + { + LeaveCriticalSection(&This->cs); + return E_OUTOFMEMORY; + } + *format = SPDFID_WaveFormatEx; + memcpy(*wfx, This->wfx, sizeof(WAVEFORMATEX) + This->wfx->cbSize); + + LeaveCriticalSection(&This->cs); + + return S_OK; }
static HRESULT WINAPI mmsysaudio_SetState(ISpMMSysAudio *iface, SPAUDIOSTATE state, ULONGLONG reserved) @@ -464,9 +483,37 @@ static HRESULT WINAPI mmsysaudio_SetState(ISpMMSysAudio *iface, SPAUDIOSTATE sta
static HRESULT WINAPI mmsysaudio_SetFormat(ISpMMSysAudio *iface, const GUID *guid, const WAVEFORMATEX *wfx) { - FIXME("(%p, %s, %p): stub.\n", iface, debugstr_guid(guid), wfx); + struct mmaudio *This = impl_from_ISpMMSysAudio(iface); + MMRESULT res; + WAVEFORMATEX *new_wfx;
- return E_NOTIMPL; + TRACE("(%p, %s, %p).\n", iface, debugstr_guid(guid), wfx); + + if (!guid || !wfx || !IsEqualGUID(guid, &SPDFID_WaveFormatEx)) + return E_INVALIDARG; + + EnterCriticalSection(&This->cs); + + /* Determine whether the device supports the requested format. */ + res = waveOutOpen(NULL, This->device_id, wfx, 0, 0, WAVE_FORMAT_QUERY); + if (res != MMSYSERR_NOERROR) + { + LeaveCriticalSection(&This->cs); + return res == WAVERR_BADFORMAT ? SPERR_UNSUPPORTED_FORMAT : SPERR_GENERIC_MMSYS_ERROR; + } + + if (!(new_wfx = heap_alloc(sizeof(*wfx) + wfx->cbSize))) + { + LeaveCriticalSection(&This->cs); + return E_OUTOFMEMORY; + } + memcpy(new_wfx, wfx, sizeof(*wfx) + wfx->cbSize); + heap_free(This->wfx); + This->wfx = new_wfx; + + LeaveCriticalSection(&This->cs); + + return S_OK; }
static HRESULT WINAPI mmsysaudio_GetStatus(ISpMMSysAudio *iface, SPAUDIOSTATUS *status) @@ -642,6 +689,19 @@ static HRESULT mmaudio_create(IUnknown *outer, REFIID iid, void **obj, enum flow This->token = NULL; This->device_id = WAVE_MAPPER;
+ if (!(This->wfx = heap_alloc(sizeof(*This->wfx)))) + { + heap_free(This); + return E_OUTOFMEMORY; + } + This->wfx->wFormatTag = WAVE_FORMAT_PCM; + This->wfx->nChannels = 1; + This->wfx->nSamplesPerSec = 22050; + This->wfx->nAvgBytesPerSec = 22050 * 2; + This->wfx->nBlockAlign = 2; + This->wfx->wBitsPerSample = 16; + This->wfx->cbSize = 0; + InitializeCriticalSection(&This->cs);
hr = ISpMMSysAudio_QueryInterface(&This->ISpMMSysAudio_iface, iid, obj); diff --git a/dlls/sapi/tests/mmaudio.c b/dlls/sapi/tests/mmaudio.c index 3816c0caf9c..dc99cf53586 100644 --- a/dlls/sapi/tests/mmaudio.c +++ b/dlls/sapi/tests/mmaudio.c @@ -99,10 +99,66 @@ static void test_device_id(void) ISpMMSysAudio_Release(mmaudio); }
+static void test_formats(void) +{ + ISpMMSysAudio *mmaudio; + GUID fmtid; + WAVEFORMATEX *wfx; + WAVEFORMATEX wfx2; + HRESULT hr; + + hr = CoCreateInstance(&CLSID_SpMMAudioOut, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpMMSysAudio, (void **)&mmaudio); + ok(hr == S_OK, "failed to create SpMMAudioOut instance: %#lx.\n", hr); + + wfx = NULL; + hr = ISpMMSysAudio_GetFormat(mmaudio, &fmtid, &wfx); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(IsEqualGUID(&fmtid, &SPDFID_WaveFormatEx), "got %s.\n", wine_dbgstr_guid(&fmtid)); + ok(wfx != NULL, "wfx == NULL.\n"); + ok(wfx->wFormatTag == WAVE_FORMAT_PCM, "got %u.\n", wfx->wFormatTag); + ok(wfx->nChannels == 1, "got %u.\n", wfx->nChannels); + ok(wfx->nSamplesPerSec == 22050, "got %lu.\n", wfx->nSamplesPerSec); + ok(wfx->nAvgBytesPerSec == 22050 * 2, "got %lu.\n", wfx->nAvgBytesPerSec); + ok(wfx->nBlockAlign == 2, "got %u.\n", wfx->nBlockAlign); + ok(wfx->wBitsPerSample == 16, "got %u.\n", wfx->wBitsPerSample); + ok(wfx->cbSize == 0, "got %u.\n", wfx->cbSize); + CoTaskMemFree(wfx); + + hr = ISpMMSysAudio_SetFormat(mmaudio, NULL, NULL); + ok(hr == E_INVALIDARG, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetFormat(mmaudio, &SPDFID_Text, NULL); + ok(hr == E_INVALIDARG, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetFormat(mmaudio, &SPDFID_WaveFormatEx, NULL); + ok(hr == E_INVALIDARG, "got %#lx.\n", hr); + + if (waveOutGetNumDevs() == 0) { + skip("no wave out devices.\n"); + ISpMMSysAudio_Release(mmaudio); + return; + } + + wfx2.wFormatTag = WAVE_FORMAT_PCM; + wfx2.nChannels = 2; + wfx2.nSamplesPerSec = 16000; + wfx2.nAvgBytesPerSec = 16000 * 2 * 2; + wfx2.nBlockAlign = 2 * 2; + wfx2.wBitsPerSample = 16; + wfx2.cbSize = 0; + + hr = ISpMMSysAudio_SetFormat(mmaudio, &SPDFID_WaveFormatEx, &wfx2); + ok(hr == S_OK, "got %#lx.\n", hr); + + ISpMMSysAudio_Release(mmaudio); +} + START_TEST(mmaudio) { CoInitialize(NULL); test_interfaces(); test_device_id(); + test_formats(); CoUninitialize(); }
From: Shaun Ren sren@codeweavers.com
--- dlls/sapi/mmaudio.c | 73 +++++++++++++++++++++++++++++++++++++-- dlls/sapi/tests/mmaudio.c | 62 +++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 2 deletions(-)
diff --git a/dlls/sapi/mmaudio.c b/dlls/sapi/mmaudio.c index 54b4ee12807..496e9b66bc3 100644 --- a/dlls/sapi/mmaudio.c +++ b/dlls/sapi/mmaudio.c @@ -48,7 +48,13 @@ struct mmaudio enum flow_type flow; ISpObjectToken *token; UINT device_id; + SPAUDIOSTATE state; WAVEFORMATEX *wfx; + union + { + HWAVEIN in; + HWAVEOUT out; + } hwave; CRITICAL_SECTION cs; };
@@ -358,6 +364,8 @@ static ULONG WINAPI mmsysaudio_Release(ISpMMSysAudio *iface)
if (!ref) { + ISpMMSysAudio_SetState(iface, SPAS_CLOSED, 0); + if (This->token) ISpObjectToken_Release(This->token); heap_free(This->wfx); DeleteCriticalSection(&This->cs); @@ -476,9 +484,45 @@ static HRESULT WINAPI mmsysaudio_GetFormat(ISpMMSysAudio *iface, GUID *format, W
static HRESULT WINAPI mmsysaudio_SetState(ISpMMSysAudio *iface, SPAUDIOSTATE state, ULONGLONG reserved) { - FIXME("(%p, %u, %s): stub.\n", iface, state, wine_dbgstr_longlong(reserved)); + struct mmaudio *This = impl_from_ISpMMSysAudio(iface); + HRESULT hr = S_OK;
- return E_NOTIMPL; + TRACE("(%p, %u, %s).\n", iface, state, wine_dbgstr_longlong(reserved)); + + if (state != SPAS_CLOSED && state != SPAS_RUN) + { + FIXME("state %#x not implemented.\n", state); + return E_NOTIMPL; + } + + EnterCriticalSection(&This->cs); + + if (This->state == state) + goto done; + + if (This->state == SPAS_CLOSED) + { + if (waveOutOpen(&This->hwave.out, This->device_id, This->wfx, 0, 0, 0) != MMSYSERR_NOERROR) + { + hr = SPERR_GENERIC_MMSYS_ERROR; + goto done; + } + } + + if (state == SPAS_CLOSED && This->state != SPAS_CLOSED) + { + if (waveOutClose(This->hwave.out) != MMSYSERR_NOERROR) + { + hr = SPERR_GENERIC_MMSYS_ERROR; + goto done; + } + } + + This->state = state; + +done: + LeaveCriticalSection(&This->cs); + return hr; }
static HRESULT WINAPI mmsysaudio_SetFormat(ISpMMSysAudio *iface, const GUID *guid, const WAVEFORMATEX *wfx) @@ -494,6 +538,18 @@ static HRESULT WINAPI mmsysaudio_SetFormat(ISpMMSysAudio *iface, const GUID *gui
EnterCriticalSection(&This->cs);
+ if (!memcmp(wfx, This->wfx, sizeof(*wfx)) && !memcmp(wfx + 1, This->wfx + 1, wfx->cbSize)) + { + LeaveCriticalSection(&This->cs); + return S_OK; + } + + if (This->state != SPAS_CLOSED) + { + LeaveCriticalSection(&This->cs); + return SPERR_DEVICE_BUSY; + } + /* Determine whether the device supports the requested format. */ res = waveOutOpen(NULL, This->device_id, wfx, 0, 0, WAVE_FORMAT_QUERY); if (res != MMSYSERR_NOERROR) @@ -604,7 +660,19 @@ static HRESULT WINAPI mmsysaudio_SetDeviceId(ISpMMSysAudio *iface, UINT id) return E_INVALIDARG;
EnterCriticalSection(&This->cs); + + if (id == This->device_id) + { + LeaveCriticalSection(&This->cs); + return S_OK; + } + if (This->state != SPAS_CLOSED) + { + LeaveCriticalSection(&This->cs); + return SPERR_DEVICE_BUSY; + } This->device_id = id; + LeaveCriticalSection(&This->cs);
return S_OK; @@ -688,6 +756,7 @@ static HRESULT mmaudio_create(IUnknown *outer, REFIID iid, void **obj, enum flow This->flow = flow; This->token = NULL; This->device_id = WAVE_MAPPER; + This->state = SPAS_CLOSED;
if (!(This->wfx = heap_alloc(sizeof(*This->wfx)))) { diff --git a/dlls/sapi/tests/mmaudio.c b/dlls/sapi/tests/mmaudio.c index dc99cf53586..029addbf5e1 100644 --- a/dlls/sapi/tests/mmaudio.c +++ b/dlls/sapi/tests/mmaudio.c @@ -154,11 +154,73 @@ static void test_formats(void) ISpMMSysAudio_Release(mmaudio); }
+static void test_audio_out(void) +{ + ISpMMSysAudio *mmaudio; + GUID fmtid; + WAVEFORMATEX *wfx = NULL; + UINT devid; + HRESULT hr; + + if (waveOutGetNumDevs() == 0) { + skip("no wave out devices.\n"); + return; + } + + hr = CoCreateInstance(&CLSID_SpMMAudioOut, NULL, CLSCTX_INPROC_SERVER, + &IID_ISpMMSysAudio, (void **)&mmaudio); + ok(hr == S_OK, "failed to create SPMMAudioOut instance: %#lx.\n", hr); + + hr = ISpMMSysAudio_SetState(mmaudio, SPAS_CLOSED, 0); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_GetFormat(mmaudio, &fmtid, &wfx); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(IsEqualGUID(&fmtid, &SPDFID_WaveFormatEx), "got %s.\n", wine_dbgstr_guid(&fmtid)); + ok(wfx != NULL, "wfx == NULL.\n"); + + hr = ISpMMSysAudio_SetFormat(mmaudio, &fmtid, wfx); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetDeviceId(mmaudio, WAVE_MAPPER); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetState(mmaudio, SPAS_RUN, 0); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetDeviceId(mmaudio, WAVE_MAPPER); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetDeviceId(mmaudio, 0); + ok(hr == SPERR_DEVICE_BUSY, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetFormat(mmaudio, &fmtid, wfx); + ok(hr == S_OK, "got %#lx.\n", hr); + + wfx->nChannels = wfx->nChannels == 1 ? 2 : 1; + wfx->nAvgBytesPerSec = wfx->nSamplesPerSec * wfx->nChannels * wfx->wBitsPerSample / 8; + wfx->nBlockAlign = wfx->nChannels * wfx->wBitsPerSample / 8; + hr = ISpMMSysAudio_SetFormat(mmaudio, &fmtid, wfx); + ok(hr == SPERR_DEVICE_BUSY, "got %#lx.\n", hr); + + devid = 0xdeadbeef; + hr = ISpMMSysAudio_GetDeviceId(mmaudio, &devid); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(devid == WAVE_MAPPER, "got %#x.\n", devid); + + hr = ISpMMSysAudio_SetState(mmaudio, SPAS_CLOSED, 0); + ok(hr == S_OK, "got %#lx.\n", hr); + + CoTaskMemFree(wfx); + ISpMMSysAudio_Release(mmaudio); +} + START_TEST(mmaudio) { CoInitialize(NULL); test_interfaces(); test_device_id(); test_formats(); + test_audio_out(); CoUninitialize(); }
From: Shaun Ren sren@codeweavers.com
The buffers cannot be freed directly in wave_out_proc, because calling waveOut related functions in the callback could cause a deadlock. --- dlls/sapi/Makefile.in | 1 + dlls/sapi/async.c | 172 +++++++++++++++++++++++++++++++++++++++ dlls/sapi/mmaudio.c | 78 +++++++++++++++++- dlls/sapi/sapi_private.h | 24 ++++++ 4 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 dlls/sapi/async.c
diff --git a/dlls/sapi/Makefile.in b/dlls/sapi/Makefile.in index 8a1ead0b78b..4229320e473 100644 --- a/dlls/sapi/Makefile.in +++ b/dlls/sapi/Makefile.in @@ -3,6 +3,7 @@ IMPORTS = uuid ole32 user32 advapi32 DELAYIMPORTS = winmm
C_SRCS = \ + async.c \ automation.c \ main.c \ mmaudio.c \ diff --git a/dlls/sapi/async.c b/dlls/sapi/async.c new file mode 100644 index 00000000000..c0fd716c234 --- /dev/null +++ b/dlls/sapi/async.c @@ -0,0 +1,172 @@ +/* + * Speech API (SAPI) async helper implementation. + * + * Copyright 2023 Shaun Ren for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <stdarg.h> + +#include "windef.h" +#include "winbase.h" +#include "objbase.h" + +#include "wine/heap.h" +#include "wine/list.h" +#include "wine/debug.h" + +#include "sapi_private.h" + +WINE_DEFAULT_DEBUG_CHANNEL(sapi); + +static struct async_task *async_dequeue_task(struct async_queue *queue) +{ + struct async_task *task; + + EnterCriticalSection(&queue->cs); + task = LIST_ENTRY(list_head(&queue->tasks), struct async_task, entry); + if (task) list_remove(&task->entry); + LeaveCriticalSection(&queue->cs); + + return task; +} + +void async_empty_queue(struct async_queue *queue) +{ + struct list *ptr; + + EnterCriticalSection(&queue->cs); + while ((ptr = list_head(&queue->tasks))) + { + struct async_task *task = LIST_ENTRY(ptr, struct async_task, entry); + list_remove(&task->entry); + heap_free(task); + } + LeaveCriticalSection(&queue->cs); + + SetEvent(queue->empty); +} + +static void CALLBACK async_worker(TP_CALLBACK_INSTANCE *instance, void *ctx) +{ + struct async_queue *queue = ctx; + HANDLE handles[2] = { queue->cancel, queue->wait }; + DWORD ret; + + SetEvent(queue->ready); + + for (;;) + { + ret = WaitForMultipleObjects(2, handles, FALSE, INFINITE); + if (ret == WAIT_OBJECT_0) + goto cancel; + else if (ret == WAIT_OBJECT_0 + 1) + { + struct async_task *task; + + while ((task = async_dequeue_task(queue))) + { + ResetEvent(queue->empty); + task->proc(task); + heap_free(task); + if (WaitForSingleObject(queue->cancel, 0) == WAIT_OBJECT_0) + goto cancel; + } + + SetEvent(queue->empty); + } + else + ERR("WaitForMultipleObjects failed: %#lx.\n", ret); + } + +cancel: + async_empty_queue(queue); + TRACE("cancelled.\n"); + SetEvent(queue->ready); +} + +HRESULT async_start_queue(struct async_queue *queue) +{ + HRESULT hr; + + if (queue->init) + return S_OK; + + InitializeCriticalSection(&queue->cs); + list_init(&queue->tasks); + + if (!(queue->wait = CreateEventW(NULL, FALSE, FALSE, NULL)) || + !(queue->ready = CreateEventW(NULL, FALSE, FALSE, NULL)) || + !(queue->cancel = CreateEventW(NULL, FALSE, FALSE, NULL)) || + !(queue->empty = CreateEventW(NULL, TRUE, TRUE, NULL))) + goto fail; + + queue->init = TRUE; + + if (!TrySubmitThreadpoolCallback(async_worker, queue, NULL)) + goto fail; + + WaitForSingleObject(queue->ready, INFINITE); + return S_OK; + +fail: + hr = HRESULT_FROM_WIN32(GetLastError()); + DeleteCriticalSection(&queue->cs); + if (queue->wait) CloseHandle(queue->wait); + if (queue->ready) CloseHandle(queue->ready); + if (queue->cancel) CloseHandle(queue->cancel); + if (queue->empty) CloseHandle(queue->empty); + memset(queue, 0, sizeof(*queue)); + return hr; +} + +void async_cancel_queue(struct async_queue *queue) +{ + if (!queue->init) return; + + SetEvent(queue->cancel); + WaitForSingleObject(queue->ready, INFINITE); + + DeleteCriticalSection(&queue->cs); + CloseHandle(queue->wait); + CloseHandle(queue->ready); + CloseHandle(queue->cancel); + CloseHandle(queue->empty); + + memset(queue, 0, sizeof(*queue)); +} + +HRESULT async_queue_task(struct async_queue *queue, struct async_task *task) +{ + HRESULT hr; + + if (FAILED(hr = async_start_queue(queue))) + return hr; + + EnterCriticalSection(&queue->cs); + list_add_tail(&queue->tasks, &task->entry); + LeaveCriticalSection(&queue->cs); + + SetEvent(queue->wait); + + return S_OK; +} + +void async_wait_queue_empty(struct async_queue *queue, DWORD timeout) +{ + if (!queue->init) return; + WaitForSingleObject(queue->empty, timeout); +} diff --git a/dlls/sapi/mmaudio.c b/dlls/sapi/mmaudio.c index 496e9b66bc3..2f84bb7eac0 100644 --- a/dlls/sapi/mmaudio.c +++ b/dlls/sapi/mmaudio.c @@ -55,7 +55,12 @@ struct mmaudio HWAVEIN in; HWAVEOUT out; } hwave; + HANDLE event; + struct async_queue queue; CRITICAL_SECTION cs; + + size_t pending_buf_count; + CRITICAL_SECTION pending_cs; };
static inline struct mmaudio *impl_from_ISpEventSource(ISpEventSource *iface) @@ -366,8 +371,13 @@ static ULONG WINAPI mmsysaudio_Release(ISpMMSysAudio *iface) { ISpMMSysAudio_SetState(iface, SPAS_CLOSED, 0);
+ async_wait_queue_empty(&This->queue, INFINITE); + async_cancel_queue(&This->queue); + if (This->token) ISpObjectToken_Release(This->token); heap_free(This->wfx); + CloseHandle(This->event); + DeleteCriticalSection(&This->pending_cs); DeleteCriticalSection(&This->cs);
heap_free(This); @@ -482,6 +492,57 @@ static HRESULT WINAPI mmsysaudio_GetFormat(ISpMMSysAudio *iface, GUID *format, W return S_OK; }
+struct free_buf_task +{ + struct async_task task; + struct mmaudio *audio; + WAVEHDR *buf; +}; + +static void free_out_buf_proc(struct async_task *task) +{ + struct free_buf_task *fbt = (struct free_buf_task *)task; + size_t buf_count; + + TRACE("(%p).\n", task); + + waveOutUnprepareHeader(fbt->audio->hwave.out, fbt->buf, sizeof(WAVEHDR)); + heap_free(fbt->buf); + + EnterCriticalSection(&fbt->audio->pending_cs); + buf_count = --fbt->audio->pending_buf_count; + LeaveCriticalSection(&fbt->audio->pending_cs); + if (!buf_count) + SetEvent(fbt->audio->event); + TRACE("pending_buf_count = %Iu.\n", buf_count); +} + +static void CALLBACK wave_out_proc(HWAVEOUT hwo, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) +{ + struct mmaudio *This = (struct mmaudio *)instance; + struct free_buf_task *task; + + TRACE("(%p, %#x, %08Ix, %08Ix, %08Ix).\n", hwo, msg, instance, param1, param2); + + switch (msg) + { + case WOM_DONE: + if (!(task = heap_alloc(sizeof(*task)))) + { + ERR("failed to allocate free_buf_task.\n"); + break; + } + task->task.proc = free_out_buf_proc; + task->audio = This; + task->buf = (WAVEHDR *)param1; + async_queue_task(&This->queue, (struct async_task *)task); + break; + + default: + break; + } +} + static HRESULT WINAPI mmsysaudio_SetState(ISpMMSysAudio *iface, SPAUDIOSTATE state, ULONGLONG reserved) { struct mmaudio *This = impl_from_ISpMMSysAudio(iface); @@ -502,7 +563,14 @@ static HRESULT WINAPI mmsysaudio_SetState(ISpMMSysAudio *iface, SPAUDIOSTATE sta
if (This->state == SPAS_CLOSED) { - if (waveOutOpen(&This->hwave.out, This->device_id, This->wfx, 0, 0, 0) != MMSYSERR_NOERROR) + if (FAILED(hr = async_start_queue(&This->queue))) + { + ERR("Failed to start async queue: %#lx.\n", hr); + goto done; + } + + if (waveOutOpen(&This->hwave.out, This->device_id, This->wfx, (DWORD_PTR)wave_out_proc, + (DWORD_PTR)This, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) { hr = SPERR_GENERIC_MMSYS_ERROR; goto done; @@ -511,6 +579,10 @@ static HRESULT WINAPI mmsysaudio_SetState(ISpMMSysAudio *iface, SPAUDIOSTATE sta
if (state == SPAS_CLOSED && This->state != SPAS_CLOSED) { + waveOutReset(This->hwave.out); + /* Wait until all buffers are freed. */ + WaitForSingleObject(This->event, INFINITE); + if (waveOutClose(This->hwave.out) != MMSYSERR_NOERROR) { hr = SPERR_GENERIC_MMSYS_ERROR; @@ -771,7 +843,11 @@ static HRESULT mmaudio_create(IUnknown *outer, REFIID iid, void **obj, enum flow This->wfx->wBitsPerSample = 16; This->wfx->cbSize = 0;
+ This->pending_buf_count = 0; + This->event = CreateEventW(NULL, TRUE, TRUE, NULL); + InitializeCriticalSection(&This->cs); + InitializeCriticalSection(&This->pending_cs);
hr = ISpMMSysAudio_QueryInterface(&This->ISpMMSysAudio_iface, iid, obj); ISpMMSysAudio_Release(&This->ISpMMSysAudio_iface); diff --git a/dlls/sapi/sapi_private.h b/dlls/sapi/sapi_private.h index 88b1a27516f..d0cd7669623 100644 --- a/dlls/sapi/sapi_private.h +++ b/dlls/sapi/sapi_private.h @@ -19,6 +19,30 @@ */
#include "wine/heap.h" +#include "wine/list.h" + +struct async_task +{ + struct list entry; + void (*proc)(struct async_task *); +}; + +struct async_queue +{ + BOOL init; + HANDLE wait; + HANDLE ready; + HANDLE empty; + HANDLE cancel; + struct list tasks; + CRITICAL_SECTION cs; +}; + +HRESULT async_start_queue(struct async_queue *queue); +void async_empty_queue(struct async_queue *queue); +void async_cancel_queue(struct async_queue *queue); +HRESULT async_queue_task(struct async_queue *queue, struct async_task *task); +void async_wait_queue_empty(struct async_queue *queue, DWORD timeout);
HRESULT data_key_create( IUnknown *outer, REFIID iid, void **obj ) DECLSPEC_HIDDEN; HRESULT file_stream_create( IUnknown *outer, REFIID iid, void **obj ) DECLSPEC_HIDDEN;
From: Shaun Ren sren@codeweavers.com
--- dlls/sapi/mmaudio.c | 53 +++++++++++++++++++++++++++++++++++-- dlls/sapi/tests/mmaudio.c | 55 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 102 insertions(+), 6 deletions(-)
diff --git a/dlls/sapi/mmaudio.c b/dlls/sapi/mmaudio.c index 2f84bb7eac0..f0331259c7b 100644 --- a/dlls/sapi/mmaudio.c +++ b/dlls/sapi/mmaudio.c @@ -395,9 +395,58 @@ static HRESULT WINAPI mmsysaudio_Read(ISpMMSysAudio *iface, void *pv, ULONG cb,
static HRESULT WINAPI mmsysaudio_Write(ISpMMSysAudio *iface, const void *pv, ULONG cb, ULONG *cb_written) { - FIXME("(%p, %p, %lu, %p): stub.\n", iface, pv, cb, cb_written); + struct mmaudio *This = impl_from_ISpMMSysAudio(iface); + HRESULT hr = S_OK; + WAVEHDR *buf;
- return E_NOTIMPL; + TRACE("(%p, %p, %lu, %p).\n", iface, pv, cb, cb_written); + + if (This->flow != FLOW_OUT) + return STG_E_ACCESSDENIED; + + if (cb_written) + *cb_written = 0; + + EnterCriticalSection(&This->cs); + + if (This->state == SPAS_CLOSED || This->state == SPAS_STOP) + { + LeaveCriticalSection(&This->cs); + return SP_AUDIO_STOPPED; + } + + if (!(buf = heap_alloc(sizeof(WAVEHDR) + cb))) + { + LeaveCriticalSection(&This->cs); + return E_OUTOFMEMORY; + } + memcpy((char *)(buf + 1), pv, cb); + buf->lpData = (char *)(buf + 1); + buf->dwBufferLength = cb; + buf->dwFlags = 0; + + if (waveOutPrepareHeader(This->hwave.out, buf, sizeof(WAVEHDR)) != MMSYSERR_NOERROR) + { + LeaveCriticalSection(&This->cs); + heap_free(buf); + return E_FAIL; + } + + waveOutWrite(This->hwave.out, buf, sizeof(WAVEHDR)); + + EnterCriticalSection(&This->pending_cs); + ++This->pending_buf_count; + TRACE("pending_buf_count = %Iu\n", This->pending_buf_count); + LeaveCriticalSection(&This->pending_cs); + + ResetEvent(This->event); + + LeaveCriticalSection(&This->cs); + + if (cb_written) + *cb_written = cb; + + return hr; }
static HRESULT WINAPI mmsysaudio_Seek(ISpMMSysAudio *iface, LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER *new_pos) diff --git a/dlls/sapi/tests/mmaudio.c b/dlls/sapi/tests/mmaudio.c index 029addbf5e1..2e1c18442b8 100644 --- a/dlls/sapi/tests/mmaudio.c +++ b/dlls/sapi/tests/mmaudio.c @@ -159,7 +159,10 @@ static void test_audio_out(void) ISpMMSysAudio *mmaudio; GUID fmtid; WAVEFORMATEX *wfx = NULL; + WAVEFORMATEX wfx2; UINT devid; + char *buf = NULL; + ULONG written; HRESULT hr;
if (waveOutGetNumDevs() == 0) { @@ -178,6 +181,8 @@ static void test_audio_out(void) ok(hr == S_OK, "got %#lx.\n", hr); ok(IsEqualGUID(&fmtid, &SPDFID_WaveFormatEx), "got %s.\n", wine_dbgstr_guid(&fmtid)); ok(wfx != NULL, "wfx == NULL.\n"); + ok(wfx->wFormatTag == WAVE_FORMAT_PCM, "got %u.\n", wfx->wFormatTag); + ok(wfx->cbSize == 0, "got %u.\n", wfx->cbSize);
hr = ISpMMSysAudio_SetFormat(mmaudio, &fmtid, wfx); ok(hr == S_OK, "got %#lx.\n", hr); @@ -197,10 +202,12 @@ static void test_audio_out(void) hr = ISpMMSysAudio_SetFormat(mmaudio, &fmtid, wfx); ok(hr == S_OK, "got %#lx.\n", hr);
- wfx->nChannels = wfx->nChannels == 1 ? 2 : 1; - wfx->nAvgBytesPerSec = wfx->nSamplesPerSec * wfx->nChannels * wfx->wBitsPerSample / 8; - wfx->nBlockAlign = wfx->nChannels * wfx->wBitsPerSample / 8; - hr = ISpMMSysAudio_SetFormat(mmaudio, &fmtid, wfx); + memcpy(&wfx2, wfx, sizeof(wfx2)); + wfx2.nChannels = wfx->nChannels == 1 ? 2 : 1; + wfx2.nAvgBytesPerSec = wfx2.nSamplesPerSec * wfx2.nChannels * wfx2.wBitsPerSample / 8; + wfx2.nBlockAlign = wfx2.nChannels * wfx2.wBitsPerSample / 8; + + hr = ISpMMSysAudio_SetFormat(mmaudio, &fmtid, &wfx2); ok(hr == SPERR_DEVICE_BUSY, "got %#lx.\n", hr);
devid = 0xdeadbeef; @@ -211,7 +218,47 @@ static void test_audio_out(void) hr = ISpMMSysAudio_SetState(mmaudio, SPAS_CLOSED, 0); ok(hr == S_OK, "got %#lx.\n", hr);
+ buf = calloc(1, wfx->nAvgBytesPerSec); + ok(buf != NULL, "failed to allocate buffer.\n"); + + hr = ISpMMSysAudio_Write(mmaudio, buf, wfx->nAvgBytesPerSec, NULL); + ok(hr == SP_AUDIO_STOPPED, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetState(mmaudio, SPAS_STOP, 0); + todo_wine ok(hr == S_OK, "got %#lx.\n", hr); + if (hr == S_OK) + { + hr = ISpMMSysAudio_Write(mmaudio, buf, wfx->nAvgBytesPerSec, NULL); + ok(hr == SP_AUDIO_STOPPED, "got %#lx.\n", hr); + } + + hr = ISpMMSysAudio_SetState(mmaudio, SPAS_CLOSED, 0); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetState(mmaudio, SPAS_RUN, 0); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_Write(mmaudio, buf, wfx->nAvgBytesPerSec, NULL); + ok(hr == S_OK, "got %#lx.\n", hr); + + Sleep(200); + + hr = ISpMMSysAudio_SetState(mmaudio, SPAS_CLOSED, 0); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = ISpMMSysAudio_SetState(mmaudio, SPAS_RUN, 0); + ok(hr == S_OK, "got %#lx.\n", hr); + + written = 0xdeadbeef; + hr = ISpMMSysAudio_Write(mmaudio, buf, wfx->nAvgBytesPerSec * 200 / 1000, &written); + ok(hr == S_OK, "got %#lx.\n", hr); + ok(written == wfx->nAvgBytesPerSec * 200 / 1000, "got %lu.\n", written); + + hr = ISpMMSysAudio_SetState(mmaudio, SPAS_CLOSED, 0); + ok(hr == S_OK, "got %#lx.\n", hr); + CoTaskMemFree(wfx); + free(buf); ISpMMSysAudio_Release(mmaudio); }
From: Shaun Ren sren@codeweavers.com
--- dlls/sapi/mmaudio.c | 6 ++++-- dlls/sapi/tests/mmaudio.c | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/dlls/sapi/mmaudio.c b/dlls/sapi/mmaudio.c index f0331259c7b..ccaa849c923 100644 --- a/dlls/sapi/mmaudio.c +++ b/dlls/sapi/mmaudio.c @@ -723,9 +723,11 @@ static HRESULT WINAPI mmsysaudio_GetDefaultFormat(ISpMMSysAudio *iface, GUID *gu
static HANDLE WINAPI mmsysaudio_EventHandle(ISpMMSysAudio *iface) { - FIXME("(%p): stub.\n", iface); + struct mmaudio *This = impl_from_ISpMMSysAudio(iface);
- return NULL; + TRACE("(%p).\n", iface); + + return This->event; }
static HRESULT WINAPI mmsysaudio_GetVolumeLevel(ISpMMSysAudio *iface, ULONG *level) diff --git a/dlls/sapi/tests/mmaudio.c b/dlls/sapi/tests/mmaudio.c index 2e1c18442b8..c5f83fc954f 100644 --- a/dlls/sapi/tests/mmaudio.c +++ b/dlls/sapi/tests/mmaudio.c @@ -163,6 +163,8 @@ static void test_audio_out(void) UINT devid; char *buf = NULL; ULONG written; + DWORD start, end; + HANDLE event = NULL; HRESULT hr;
if (waveOutGetNumDevs() == 0) { @@ -254,6 +256,20 @@ static void test_audio_out(void) ok(hr == S_OK, "got %#lx.\n", hr); ok(written == wfx->nAvgBytesPerSec * 200 / 1000, "got %lu.\n", written);
+ hr = ISpMMSysAudio_Write(mmaudio, buf, wfx->nAvgBytesPerSec * 200 / 1000, NULL); + ok(hr == S_OK, "got %#lx.\n", hr); + + start = GetTickCount(); + + event = ISpMMSysAudio_EventHandle(mmaudio); + ok(event != NULL, "event == NULL.\n"); + + hr = WaitForSingleObject(event, 1000); + ok(hr == WAIT_OBJECT_0, "got %#lx.\n", hr); + + end = GetTickCount(); + ok(end - start <= 500, "waited for %lu ms.\n", end - start); + hr = ISpMMSysAudio_SetState(mmaudio, SPAS_CLOSED, 0); ok(hr == S_OK, "got %#lx.\n", hr);
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=133272
Your paranoid android.
=== debian11 (32 bit report) ===
user32: win.c:10983: Test failed: GetForegroundWindow() = 016E0070
Could you split this MR into two or three smaller ones? - just force push the first few commits to this one, then, once this is merged, send the next one.
...and, if at all possible, split the actual commits into smaller changes.