wine/dlls/winmm/waveform.c

4408 lines
129 KiB
C

/*
* Copyright 1993 Martin Ayotte
* 1998-2002 Eric Pouech
* 2011 Andrew Eikum 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 <stdio.h>
#include <stdarg.h>
#include <string.h>
#define NONAMELESSUNION
#define NONAMELESSSTRUCT
#define COBJMACROS
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "mmsystem.h"
#include "mmreg.h"
#include "msacm.h"
#include "winuser.h"
#include "winnls.h"
#include "winternl.h"
#include "winemm.h"
#include "ole2.h"
#include "initguid.h"
#include "devpkey.h"
#include "mmdeviceapi.h"
#include "audioclient.h"
#include "audiopolicy.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(winmm);
/* FIXME: Should be localized */
static const WCHAR volumeW[] = {'V','o','l','u','m','e',0};
static const WCHAR mastervolumeW[] = {'M','a','s','t','e','r',' ','V','o','l',
'u','m','e',0};
static const WCHAR muteW[] = {'M','u','t','e',0};
/* HWAVE (and HMIXER) format:
*
* XXXX... 1FDD DDDD IIII IIII
* X = unused (must be 0)
* 1 = the bit is set to 1, to avoid all-zero HWAVEs
* F = flow direction (0 = IN, 1 = OUT)
* D = index into g_out_mmdevices, or all 1s for the MAPPER device
* I = index in the mmdevice's devices array
*
* Two reasons that we don't just use pointers:
* - HWAVEs must fit into 16 bits for compatibility with old applications.
* - We must be able to identify bad devices without crashing.
*/
/* buffer size = 10 * 100000 (100 ns) = 0.1 seconds */
#define AC_BUFLEN (10 * 100000)
#define MAX_DEVICES 256
#define MAPPER_INDEX 0x3F
typedef struct _WINMM_CBInfo {
DWORD_PTR callback;
DWORD_PTR user;
DWORD flags;
HWAVE hwave;
} WINMM_CBInfo;
struct _WINMM_MMDevice;
typedef struct _WINMM_MMDevice WINMM_MMDevice;
typedef struct _WINMM_Device {
WINMM_CBInfo cb_info;
HWAVE handle;
BOOL open;
IMMDevice *device;
IAudioClient *client;
IAudioRenderClient *render;
IAudioCaptureClient *capture;
IAudioClock *clock;
IAudioStreamVolume *volume;
WAVEFORMATEX *orig_fmt;
HACMSTREAM acm_handle;
ACMSTREAMHEADER acm_hdr;
UINT32 acm_offs;
WAVEHDR *first, *last, *playing, *loop_start;
BOOL stopped;
DWORD loop_counter;
UINT32 bytes_per_frame, samples_per_sec, ofs_bytes, played_frames;
UINT32 remainder_frames; /* header chunk frames already played when a device switch occurred */
/* stored in frames of sample rate, *not* AC::GetFrequency */
UINT64 last_clock_pos;
HANDLE event;
CRITICAL_SECTION lock;
WINMM_MMDevice *parent;
} WINMM_Device;
struct _WINMM_MMDevice {
WAVEOUTCAPSW out_caps; /* must not be modified outside of WINMM_InitMMDevices*/
WAVEINCAPSW in_caps; /* must not be modified outside of WINMM_InitMMDevices*/
WCHAR *dev_id;
ISimpleAudioVolume *volume;
GUID session;
UINT index;
/* HMIXER format is the same as the HWAVE format, but the I bits are
* replaced by the value of this counter, to keep each HMIXER unique */
UINT mixer_count;
CRITICAL_SECTION lock;
WINMM_Device *devices[MAX_DEVICES];
};
static WINMM_MMDevice *g_out_mmdevices;
static WINMM_MMDevice **g_out_map;
static UINT g_outmmdevices_count;
static WINMM_Device *g_out_mapper_devices[MAX_DEVICES];
static WINMM_MMDevice *g_in_mmdevices;
static WINMM_MMDevice **g_in_map;
static UINT g_inmmdevices_count;
static WINMM_Device *g_in_mapper_devices[MAX_DEVICES];
static IMMDeviceEnumerator *g_devenum;
static CRITICAL_SECTION g_devthread_lock;
static CRITICAL_SECTION_DEBUG g_devthread_lock_debug =
{
0, 0, &g_devthread_lock,
{ &g_devthread_lock_debug.ProcessLocksList, &g_devthread_lock_debug.ProcessLocksList },
0, 0, { (DWORD_PTR)(__FILE__ ": g_devthread_lock") }
};
static CRITICAL_SECTION g_devthread_lock = { &g_devthread_lock_debug, -1, 0, 0, 0, 0 };
static LONG g_devthread_token;
static HANDLE g_devices_thread;
static HWND g_devices_hwnd;
static HMODULE g_devthread_module;
static UINT g_devhandle_count;
static HANDLE *g_device_handles;
static WINMM_Device **g_handle_devices;
typedef struct _WINMM_OpenInfo {
HWAVE handle;
UINT req_device;
WAVEFORMATEX *format;
DWORD_PTR callback;
DWORD_PTR cb_user;
DWORD flags;
BOOL reset;
} WINMM_OpenInfo;
typedef struct _WINMM_ControlDetails {
HMIXEROBJ hmix;
MIXERCONTROLDETAILS *details;
DWORD flags;
} WINMM_ControlDetails;
typedef struct _WINMM_QueryInterfaceInfo {
BOOL is_out;
UINT index;
WCHAR *str;
UINT *len_bytes;
} WINMM_QueryInterfaceInfo;
static LRESULT WOD_Open(WINMM_OpenInfo *info);
static LRESULT WOD_Close(HWAVEOUT hwave);
static LRESULT WID_Open(WINMM_OpenInfo *info);
static LRESULT WID_Close(HWAVEIN hwave);
static MMRESULT WINMM_BeginPlaying(WINMM_Device *device);
void WINMM_DeleteWaveform(void)
{
UINT i, j;
if(g_devices_thread)
CloseHandle(g_devices_thread);
for(i = 0; i < g_outmmdevices_count; ++i){
WINMM_MMDevice *mmdevice = &g_out_mmdevices[i];
for(j = 0; j < MAX_DEVICES && mmdevice->devices[j]; ++j){
WINMM_Device *device = mmdevice->devices[j];
if(device->handle)
CloseHandle(device->handle);
DeleteCriticalSection(&device->lock);
}
if(mmdevice->volume)
ISimpleAudioVolume_Release(mmdevice->volume);
CoTaskMemFree(mmdevice->dev_id);
DeleteCriticalSection(&mmdevice->lock);
}
for(i = 0; i < g_inmmdevices_count; ++i){
WINMM_MMDevice *mmdevice = &g_in_mmdevices[i];
for(j = 0; j < MAX_DEVICES && mmdevice->devices[j]; ++j){
WINMM_Device *device = mmdevice->devices[j];
if(device->handle)
CloseHandle(device->handle);
DeleteCriticalSection(&device->lock);
}
if(mmdevice->volume)
ISimpleAudioVolume_Release(mmdevice->volume);
CoTaskMemFree(mmdevice->dev_id);
DeleteCriticalSection(&mmdevice->lock);
}
HeapFree(GetProcessHeap(), 0, g_out_mmdevices);
HeapFree(GetProcessHeap(), 0, g_in_mmdevices);
HeapFree(GetProcessHeap(), 0, g_device_handles);
HeapFree(GetProcessHeap(), 0, g_handle_devices);
DeleteCriticalSection(&g_devthread_lock);
}
static inline HWAVE WINMM_MakeHWAVE(UINT mmdevice, BOOL is_out, UINT device)
{
return ULongToHandle((1 << 15) | ((!!is_out) << 14) |
(mmdevice << 8) | device);
}
static inline void WINMM_DecomposeHWAVE(HWAVE hwave, UINT *mmdevice_index,
BOOL *is_out, UINT *device_index, UINT *junk)
{
ULONG32 l = HandleToULong(hwave);
*device_index = l & 0xFF;
*mmdevice_index = (l >> 8) & 0x3F;
*is_out = (l >> 14) & 0x1;
*junk = l >> 15;
}
static void WINMM_InitDevice(WINMM_Device *device)
{
InitializeCriticalSection(&device->lock);
device->lock.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": WINMM_Device.lock");
}
static inline WINMM_MMDevice *read_map(WINMM_MMDevice **map, UINT index)
{
WINMM_MMDevice *ret;
EnterCriticalSection(&g_devthread_lock);
ret = map[index];
LeaveCriticalSection(&g_devthread_lock);
return ret;
}
/* finds the first unused Device, marks it as "open", and returns
* a pointer to the device
*
* IMPORTANT: it is the caller's responsibility to release the device's lock
* on success
*/
static WINMM_Device *WINMM_FindUnusedDevice(WINMM_Device **devices,
WINMM_MMDevice *parent, UINT internal_index, BOOL is_out)
{
UINT i;
for(i = 0; i < MAX_DEVICES; ++i){
WINMM_Device *device = devices[i];
if(!device){
device = devices[i] = HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY, sizeof(WINMM_Device));
if(!device)
return NULL;
WINMM_InitDevice(device);
EnterCriticalSection(&device->lock);
}else
EnterCriticalSection(&device->lock);
if(!device->open){
device->handle = WINMM_MakeHWAVE(internal_index, is_out, i);
device->parent = parent;
device->open = TRUE;
return device;
}
LeaveCriticalSection(&device->lock);
}
TRACE("All devices in use: mmdevice: %u\n", internal_index);
return NULL;
}
static inline BOOL WINMM_ValidateAndLock(WINMM_Device *device)
{
if(!device)
return FALSE;
EnterCriticalSection(&device->lock);
if(!device->open){
LeaveCriticalSection(&device->lock);
return FALSE;
}
return TRUE;
}
static WINMM_Device *WINMM_GetDeviceFromHWAVE(HWAVE hwave)
{
WINMM_MMDevice *mmdevice;
WINMM_Device *device;
UINT mmdevice_index, device_index, junk;
BOOL is_out;
WINMM_DecomposeHWAVE(hwave, &mmdevice_index, &is_out, &device_index, &junk);
if(junk != 0x1)
return NULL;
if(mmdevice_index == MAPPER_INDEX){
EnterCriticalSection(&g_devthread_lock);
if(is_out)
device = g_out_mapper_devices[device_index];
else
device = g_in_mapper_devices[device_index];
LeaveCriticalSection(&g_devthread_lock);
return device;
}
if(mmdevice_index >= (is_out ? g_outmmdevices_count : g_inmmdevices_count))
return NULL;
if(is_out)
mmdevice = &g_out_mmdevices[mmdevice_index];
else
mmdevice = &g_in_mmdevices[mmdevice_index];
EnterCriticalSection(&mmdevice->lock);
device = mmdevice->devices[device_index];
LeaveCriticalSection(&mmdevice->lock);
return device;
}
/* Note: NotifyClient should never be called while holding the device lock
* since the client may call wave* functions from within the callback. */
static inline void WINMM_NotifyClient(WINMM_CBInfo *info, WORD msg, DWORD_PTR param1,
DWORD_PTR param2)
{
DriverCallback(info->callback, info->flags, (HDRVR)info->hwave,
msg, info->user, param1, param2);
}
static MMRESULT hr2mmr(HRESULT hr)
{
switch(hr){
case S_OK:
case AUDCLNT_E_NOT_STOPPED:
return MMSYSERR_NOERROR;
case AUDCLNT_E_UNSUPPORTED_FORMAT:
return WAVERR_BADFORMAT;
case AUDCLNT_E_DEVICE_IN_USE:
return MMSYSERR_ALLOCATED;
case AUDCLNT_E_ENDPOINT_CREATE_FAILED:
return MMSYSERR_NOTENABLED;
case E_OUTOFMEMORY:
return MMSYSERR_NOMEM;
case E_POINTER:
case E_INVALIDARG:
return MMSYSERR_INVALPARAM;
case AUDCLNT_E_DEVICE_INVALIDATED: /* DSERR_BUFFERLOST */
default:
return FAILED(hr) ? MMSYSERR_ERROR : MMSYSERR_NOERROR;
}
}
static HRESULT WINMM_GetFriendlyName(IMMDevice *device, WCHAR *out,
UINT outlen)
{
IPropertyStore *ps;
PROPVARIANT var;
HRESULT hr;
hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &ps);
if(FAILED(hr))
return hr;
PropVariantInit(&var);
hr = IPropertyStore_GetValue(ps,
(PROPERTYKEY*)&DEVPKEY_Device_FriendlyName, &var);
if(FAILED(hr)){
IPropertyStore_Release(ps);
return hr;
}
lstrcpynW(out, var.u.pwszVal, outlen);
PropVariantClear(&var);
IPropertyStore_Release(ps);
return S_OK;
}
static HRESULT WINMM_TestFormat(IAudioClient *client, DWORD rate, DWORD depth,
WORD channels)
{
WAVEFORMATEX fmt, *junk;
HRESULT hr;
fmt.wFormatTag = WAVE_FORMAT_PCM;
fmt.nChannels = channels;
fmt.nSamplesPerSec = rate;
fmt.wBitsPerSample = depth;
fmt.nBlockAlign = (channels * depth) / 8;
fmt.nAvgBytesPerSec = rate * fmt.nBlockAlign;
fmt.cbSize = 0;
hr = IAudioClient_IsFormatSupported(client, AUDCLNT_SHAREMODE_SHARED,
&fmt, &junk);
if(SUCCEEDED(hr))
CoTaskMemFree(junk);
return hr;
}
static struct _TestFormat {
DWORD flag;
DWORD rate;
DWORD depth;
WORD channels;
} formats_to_test[] = {
{ WAVE_FORMAT_1M08, 11025, 8, 1 },
{ WAVE_FORMAT_1M16, 11025, 16, 1 },
{ WAVE_FORMAT_1S08, 11025, 8, 2 },
{ WAVE_FORMAT_1S16, 11025, 16, 2 },
{ WAVE_FORMAT_2M08, 22050, 8, 1 },
{ WAVE_FORMAT_2M16, 22050, 16, 1 },
{ WAVE_FORMAT_2S08, 22050, 8, 2 },
{ WAVE_FORMAT_2S16, 22050, 16, 2 },
{ WAVE_FORMAT_4M08, 44100, 8, 1 },
{ WAVE_FORMAT_4M16, 44100, 16, 1 },
{ WAVE_FORMAT_4S08, 44100, 8, 2 },
{ WAVE_FORMAT_4S16, 44100, 16, 2 },
{ WAVE_FORMAT_48M08, 48000, 8, 1 },
{ WAVE_FORMAT_48M16, 48000, 16, 1 },
{ WAVE_FORMAT_48S08, 48000, 8, 2 },
{ WAVE_FORMAT_48S16, 48000, 16, 2 },
{ WAVE_FORMAT_96M08, 96000, 8, 1 },
{ WAVE_FORMAT_96M16, 96000, 16, 1 },
{ WAVE_FORMAT_96S08, 96000, 8, 2 },
{ WAVE_FORMAT_96S16, 96000, 16, 2 },
{0}
};
static DWORD WINMM_GetSupportedFormats(IMMDevice *device)
{
DWORD flags = 0;
HRESULT hr;
struct _TestFormat *fmt;
IAudioClient *client;
hr = IMMDevice_Activate(device, &IID_IAudioClient,
CLSCTX_INPROC_SERVER, NULL, (void**)&client);
if(FAILED(hr))
return 0;
for(fmt = formats_to_test; fmt->flag; ++fmt){
hr = WINMM_TestFormat(client, fmt->rate, fmt->depth, fmt->channels);
if(hr == S_OK)
flags |= fmt->flag;
}
IAudioClient_Release(client);
return flags;
}
static HRESULT WINMM_InitMMDevice(EDataFlow flow, IMMDevice *device,
WINMM_MMDevice *dev, UINT index)
{
HRESULT hr;
if(flow == eRender){
dev->out_caps.wMid = 0xFF;
dev->out_caps.wPid = 0xFF;
dev->out_caps.vDriverVersion = 0x00010001;
dev->out_caps.dwFormats = WINMM_GetSupportedFormats(device);
dev->out_caps.wReserved1 = 0;
dev->out_caps.dwSupport = WAVECAPS_LRVOLUME | WAVECAPS_VOLUME |
WAVECAPS_SAMPLEACCURATE;
dev->out_caps.wChannels = 2;
dev->out_caps.szPname[0] = '\0';
hr = WINMM_GetFriendlyName(device, dev->out_caps.szPname,
sizeof(dev->out_caps.szPname) /
sizeof(*dev->out_caps.szPname));
if(FAILED(hr))
return hr;
}else{
dev->in_caps.wMid = 0xFF;
dev->in_caps.wPid = 0xFF;
dev->in_caps.vDriverVersion = 0x00010001;
dev->in_caps.dwFormats = WINMM_GetSupportedFormats(device);
dev->in_caps.wReserved1 = 0;
dev->in_caps.wChannels = 2;
dev->in_caps.szPname[0] = '\0';
hr = WINMM_GetFriendlyName(device, dev->in_caps.szPname,
sizeof(dev->in_caps.szPname) /
sizeof(*dev->in_caps.szPname));
if(FAILED(hr))
return hr;
}
hr = IMMDevice_GetId(device, &dev->dev_id);
if(FAILED(hr))
return hr;
CoCreateGuid(&dev->session);
dev->index = index;
InitializeCriticalSection(&dev->lock);
dev->lock.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": WINMM_Device.lock");
return S_OK;
}
static HRESULT WINMM_EnumDevices(WINMM_MMDevice **devices,
WINMM_MMDevice ***map, UINT *devcount, EDataFlow flow,
IMMDeviceEnumerator *devenum)
{
IMMDeviceCollection *devcoll;
HRESULT hr;
hr = IMMDeviceEnumerator_EnumAudioEndpoints(devenum, flow,
DEVICE_STATE_ACTIVE, &devcoll);
if(FAILED(hr))
return hr;
hr = IMMDeviceCollection_GetCount(devcoll, devcount);
if(FAILED(hr)){
IMMDeviceCollection_Release(devcoll);
return hr;
}
if(*devcount > 0){
UINT n, count = 1;
IMMDevice *def_dev = NULL;
*devices = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
sizeof(WINMM_MMDevice) * (*devcount));
if(!*devices){
IMMDeviceCollection_Release(devcoll);
return E_OUTOFMEMORY;
}
*map = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
sizeof(WINMM_MMDevice *) * (*devcount));
if(!*map){
IMMDeviceCollection_Release(devcoll);
HeapFree(GetProcessHeap(), 0, *devices);
return E_OUTOFMEMORY;
}
/* make sure that device 0 is the default device */
IMMDeviceEnumerator_GetDefaultAudioEndpoint(devenum,
flow, eConsole, &def_dev);
for(n = 0; n < *devcount; ++n){
IMMDevice *device;
hr = IMMDeviceCollection_Item(devcoll, n, &device);
if(SUCCEEDED(hr)){
WINMM_InitMMDevice(flow, device, &(*devices)[n], n);
if(device == def_dev)
(*map)[0] = &(*devices)[n];
else{
(*map)[count] = &(*devices)[n];
++count;
}
IMMDevice_Release(device);
}
}
IMMDevice_Release(def_dev);
*devcount = count;
}
IMMDeviceCollection_Release(devcoll);
return S_OK;
}
static HRESULT WINAPI notif_QueryInterface(IMMNotificationClient *iface,
const GUID *riid, void **obj)
{
ERR("Unexpected QueryInterface call: %s\n", wine_dbgstr_guid(riid));
return E_NOINTERFACE;
}
static ULONG WINAPI notif_AddRef(IMMNotificationClient *iface)
{
return 2;
}
static ULONG WINAPI notif_Release(IMMNotificationClient *iface)
{
return 1;
}
static HRESULT WINAPI notif_OnDeviceStateChanged(IMMNotificationClient *iface,
const WCHAR *device_id, DWORD new_state)
{
TRACE("Ignoring OnDeviceStateChanged callback\n");
return S_OK;
}
static HRESULT WINAPI notif_OnDeviceAdded(IMMNotificationClient *iface,
const WCHAR *device_id)
{
TRACE("Ignoring OnDeviceAdded callback\n");
return S_OK;
}
static HRESULT WINAPI notif_OnDeviceRemoved(IMMNotificationClient *iface,
const WCHAR *device_id)
{
TRACE("Ignoring OnDeviceRemoved callback\n");
return S_OK;
}
static HRESULT update_mapping(WINMM_MMDevice ***map, UINT count,
const WCHAR *default_id)
{
WINMM_MMDevice *prev;
UINT i;
prev = (*map)[0];
for(i = 0; i < count; ++i){
WINMM_MMDevice *tmp;
if(!lstrcmpW((*map)[i]->dev_id, default_id)){
(*map)[0] = (*map)[i];
(*map)[i] = prev;
return S_OK;
}
tmp = (*map)[i];
(*map)[i] = prev;
prev = tmp;
}
WARN("Couldn't find new default device! Rearranged map for no reason.\n");
(*map)[0] = prev;
return S_FALSE;
}
static HRESULT reroute_mapper_device(WINMM_Device *device, BOOL is_out)
{
WINMM_OpenInfo info;
BOOL stopped;
MMRESULT mr;
HRESULT hr;
UINT64 clock_freq, clock_pos;
TRACE("rerouting device %p\n", device->handle);
EnterCriticalSection(&device->lock);
if(!device->open || device->acm_handle){
/* Windows 7 doesn't re-route ACM devices, so we don't either.
* Seems to be because of the data waveXxxPrepareHeader allocates. */
LeaveCriticalSection(&device->lock);
return S_FALSE;
}
stopped = device->stopped;
info.handle = 0;
info.req_device = WAVE_MAPPER;
info.format = device->orig_fmt;
info.callback = device->cb_info.callback;
info.cb_user = device->cb_info.user;
/* We have to use direct here so that we don't suddenly introduce ACM
* into a playing stream that hasn't been Prepared for it */
info.flags = (device->cb_info.flags << 16) | WAVE_FORMAT_DIRECT_QUERY;
info.reset = FALSE;
if(is_out)
mr = WOD_Open(&info);
else
mr = WID_Open(&info);
if(mr != MMSYSERR_NOERROR){
TRACE("New default device doesn't support this stream: %p\n", device->handle);
LeaveCriticalSection(&device->lock);
return S_FALSE;
}
hr = IAudioClient_Stop(device->client);
if(FAILED(hr))
WARN("Stop failed: %08x\n", hr);
hr = IAudioClock_GetFrequency(device->clock, &clock_freq);
if(FAILED(hr)){
WARN("GetFrequency failed: %08x\n", hr);
LeaveCriticalSection(&device->lock);
return hr;
}
hr = IAudioClock_GetPosition(device->clock, &clock_pos, NULL);
if(FAILED(hr)){
WARN("GetPosition failed: %08x\n", hr);
LeaveCriticalSection(&device->lock);
return hr;
}
device->remainder_frames = MulDiv(clock_pos, device->samples_per_sec, clock_freq) - device->last_clock_pos;
info.handle = device->handle;
info.flags = (device->cb_info.flags << 16) | WAVE_FORMAT_DIRECT;
if(is_out){
WOD_Close((HWAVEOUT)device->handle);
device->parent = read_map(g_out_map, 0);
mr = WOD_Open(&info);
}else{
WID_Close((HWAVEIN)device->handle);
device->parent = read_map(g_in_map, 0);
mr = WID_Open(&info);
}
if(mr != MMSYSERR_NOERROR){
ERR("Opening new default device failed! %u\n", mr);
LeaveCriticalSection(&device->lock);
return E_FAIL;
}
HeapFree(GetProcessHeap(), 0, info.format);
if(!stopped)
WINMM_BeginPlaying(device);
LeaveCriticalSection(&device->lock);
return S_OK;
}
static HRESULT WINAPI notif_OnDefaultDeviceChanged(IMMNotificationClient *iface,
EDataFlow flow, ERole role, const WCHAR *device_id)
{
UINT i;
TRACE("%u %u %s\n", flow, role, wine_dbgstr_w(device_id));
if(role != eConsole)
return S_OK;
EnterCriticalSection(&g_devthread_lock);
if(flow == eRender)
update_mapping(&g_out_map, g_outmmdevices_count, device_id);
else
update_mapping(&g_in_map, g_inmmdevices_count, device_id);
for(i = 0; i < MAX_DEVICES && g_out_mapper_devices[i]; ++i)
reroute_mapper_device(g_out_mapper_devices[i], TRUE);
for(i = 0; i < MAX_DEVICES && g_in_mapper_devices[i]; ++i)
reroute_mapper_device(g_in_mapper_devices[i], FALSE);
LeaveCriticalSection(&g_devthread_lock);
return S_OK;
}
static HRESULT WINAPI notif_OnPropertyValueChanged(IMMNotificationClient *iface,
const WCHAR *device_id, const PROPERTYKEY key)
{
TRACE("Ignoring OnPropertyValueChanged callback\n");
return S_OK;
}
static IMMNotificationClientVtbl g_notif_vtbl = {
notif_QueryInterface,
notif_AddRef,
notif_Release,
notif_OnDeviceStateChanged,
notif_OnDeviceAdded,
notif_OnDeviceRemoved,
notif_OnDefaultDeviceChanged,
notif_OnPropertyValueChanged
};
static IMMNotificationClient g_notif = { &g_notif_vtbl };
static HRESULT WINMM_InitMMDevices(void)
{
HRESULT hr, init_hr;
IMMDeviceEnumerator *devenum = NULL;
if(g_outmmdevices_count || g_inmmdevices_count)
return S_FALSE;
init_hr = CoInitialize(NULL);
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL,
CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&devenum);
if(FAILED(hr))
goto exit;
hr = IMMDeviceEnumerator_RegisterEndpointNotificationCallback(devenum, &g_notif);
if(FAILED(hr))
WARN("RegisterEndpointNotificationCallback failed: %08x\n", hr);
hr = WINMM_EnumDevices(&g_out_mmdevices, &g_out_map, &g_outmmdevices_count,
eRender, devenum);
if(FAILED(hr)){
g_outmmdevices_count = 0;
g_inmmdevices_count = 0;
goto exit;
}
hr = WINMM_EnumDevices(&g_in_mmdevices, &g_in_map, &g_inmmdevices_count,
eCapture, devenum);
if(FAILED(hr)){
g_inmmdevices_count = 0;
goto exit;
}
exit:
if(devenum)
IMMDeviceEnumerator_Release(devenum);
if(SUCCEEDED(init_hr))
CoUninitialize();
return hr;
}
static inline BOOL WINMM_IsMapper(UINT device)
{
return (device == WAVE_MAPPER || device == (UINT16)WAVE_MAPPER);
}
static MMRESULT WINMM_TryDeviceMapping(WINMM_Device *device, WAVEFORMATEX *fmt,
WORD channels, DWORD freq, DWORD bits_per_samp, BOOL is_out)
{
WAVEFORMATEX target, *closer_fmt = NULL;
HRESULT hr;
MMRESULT mr;
TRACE("format: %u, channels: %u, sample rate: %u, bit depth: %u\n",
WAVE_FORMAT_PCM, channels, freq, bits_per_samp);
target.wFormatTag = WAVE_FORMAT_PCM;
target.nChannels = channels;
target.nSamplesPerSec = freq;
target.wBitsPerSample = bits_per_samp;
target.nBlockAlign = (target.nChannels * target.wBitsPerSample) / 8;
target.nAvgBytesPerSec = target.nSamplesPerSec * target.nBlockAlign;
target.cbSize = 0;
hr = IAudioClient_IsFormatSupported(device->client,
AUDCLNT_SHAREMODE_SHARED, &target, &closer_fmt);
if(closer_fmt)
CoTaskMemFree(closer_fmt);
if(hr != S_OK)
return WAVERR_BADFORMAT;
/* device supports our target format, so see if MSACM can
* do the conversion */
if(is_out)
mr = acmStreamOpen(&device->acm_handle, NULL, fmt, &target, NULL,
0, 0, 0);
else
mr = acmStreamOpen(&device->acm_handle, NULL, &target, fmt, NULL,
0, 0, 0);
if(mr != MMSYSERR_NOERROR)
return mr;
/* yes it can. initialize the audioclient and return success */
hr = IAudioClient_Initialize(device->client, AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST,
AC_BUFLEN, 0, &target, &device->parent->session);
if(hr != S_OK){
WARN("Initialize failed: %08x\n", hr);
acmStreamClose(device->acm_handle, 0);
device->acm_handle = NULL;
return MMSYSERR_ERROR;
}
device->bytes_per_frame = target.nBlockAlign;
device->samples_per_sec = target.nSamplesPerSec;
TRACE("Success!\n");
return MMSYSERR_NOERROR;
}
static MMRESULT WINMM_MapDevice(WINMM_Device *device, BOOL is_out)
{
MMRESULT mr;
WAVEFORMATEXTENSIBLE *fmtex = (WAVEFORMATEXTENSIBLE*)device->orig_fmt;
TRACE("(%p, %u)\n", device, is_out);
/* set up the ACM stream */
if(device->orig_fmt->wFormatTag != WAVE_FORMAT_PCM &&
!(device->orig_fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
IsEqualGUID(&fmtex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))){
/* convert to PCM format if it's not already */
mr = WINMM_TryDeviceMapping(device, device->orig_fmt,
device->orig_fmt->nChannels, device->orig_fmt->nSamplesPerSec,
16, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt,
device->orig_fmt->nChannels, device->orig_fmt->nSamplesPerSec,
8, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
}else{
WORD channels;
/* first try just changing bit depth and channels */
channels = device->orig_fmt->nChannels;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels,
device->orig_fmt->nSamplesPerSec, 16, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels,
device->orig_fmt->nSamplesPerSec, 8, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
channels = (channels == 2) ? 1 : 2;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels,
device->orig_fmt->nSamplesPerSec, 16, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels,
device->orig_fmt->nSamplesPerSec, 8, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
/* that didn't work, so now try different sample rates */
channels = device->orig_fmt->nChannels;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels, 96000, 16, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels, 48000, 16, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels, 44100, 16, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels, 22050, 16, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels, 11025, 16, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
channels = (channels == 2) ? 1 : 2;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels, 96000, 16, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels, 48000, 16, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels, 44100, 16, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels, 22050, 16, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels, 11025, 16, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
channels = device->orig_fmt->nChannels;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels, 96000, 8, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels, 48000, 8, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels, 44100, 8, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels, 22050, 8, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels, 11025, 8, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
channels = (channels == 2) ? 1 : 2;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels, 96000, 8, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels, 48000, 8, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels, 44100, 8, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels, 22050, 8, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
mr = WINMM_TryDeviceMapping(device, device->orig_fmt, channels, 11025, 8, is_out);
if(mr == MMSYSERR_NOERROR)
return mr;
}
WARN("Unable to find compatible device!\n");
return WAVERR_BADFORMAT;
}
static LRESULT WINMM_OpenDevice(WINMM_Device *device, WINMM_OpenInfo *info,
BOOL is_out)
{
LRESULT ret = MMSYSERR_NOMEM;
HRESULT hr;
hr = IMMDeviceEnumerator_GetDevice(g_devenum, device->parent->dev_id,
&device->device);
if(FAILED(hr)){
WARN("Device %s (%s) unavailable: %08x\n",
wine_dbgstr_w(device->parent->dev_id),
wine_dbgstr_w(device->parent->out_caps.szPname), hr);
ret = MMSYSERR_NODRIVER;
goto error;
}
/* this is where winexyz.drv opens the audio device */
hr = IMMDevice_Activate(device->device, &IID_IAudioClient,
CLSCTX_INPROC_SERVER, NULL, (void**)&device->client);
if(FAILED(hr)){
WARN("Activate failed: %08x\n", hr);
ret = hr2mmr(hr);
if(ret == MMSYSERR_ERROR)
ret = MMSYSERR_NOTENABLED;
goto error;
}
if(info->format->wFormatTag == WAVE_FORMAT_PCM){
/* we aren't guaranteed that the struct in lpFormat is a full
* WAVEFORMATEX struct, which IAC::IsFormatSupported requires */
device->orig_fmt = HeapAlloc(GetProcessHeap(), 0, sizeof(WAVEFORMATEX));
memcpy(device->orig_fmt, info->format, sizeof(PCMWAVEFORMAT));
device->orig_fmt->cbSize = 0;
if(device->orig_fmt->wBitsPerSample % 8 != 0){
WARN("Fixing bad wBitsPerSample (%u)\n", device->orig_fmt->wBitsPerSample);
device->orig_fmt->wBitsPerSample = (device->orig_fmt->wBitsPerSample + 7) & ~7;
}
/* winmm ignores broken blockalign and avgbytes */
if(device->orig_fmt->nBlockAlign != device->orig_fmt->nChannels * device->orig_fmt->wBitsPerSample/8){
WARN("Fixing bad nBlockAlign (%u)\n", device->orig_fmt->nBlockAlign);
device->orig_fmt->nBlockAlign = device->orig_fmt->nChannels * device->orig_fmt->wBitsPerSample/8;
}
if (device->orig_fmt->nAvgBytesPerSec != device->orig_fmt->nSamplesPerSec * device->orig_fmt->nBlockAlign) {
WARN("Fixing bad nAvgBytesPerSec (%u)\n", device->orig_fmt->nAvgBytesPerSec);
device->orig_fmt->nAvgBytesPerSec = device->orig_fmt->nSamplesPerSec * device->orig_fmt->nBlockAlign;
}
}else{
device->orig_fmt = HeapAlloc(GetProcessHeap(), 0,
sizeof(WAVEFORMATEX) + info->format->cbSize);
memcpy(device->orig_fmt, info->format,
sizeof(WAVEFORMATEX) + info->format->cbSize);
}
if(info->flags & WAVE_FORMAT_QUERY){
WAVEFORMATEX *closer_fmt = NULL;
hr = IAudioClient_IsFormatSupported(device->client,
AUDCLNT_SHAREMODE_SHARED, device->orig_fmt, &closer_fmt);
if(closer_fmt)
CoTaskMemFree(closer_fmt);
ret = hr == S_FALSE ? WAVERR_BADFORMAT : hr2mmr(hr);
goto error;
}
hr = IAudioClient_Initialize(device->client, AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST,
AC_BUFLEN, 0, device->orig_fmt, &device->parent->session);
if(FAILED(hr)){
if(hr == AUDCLNT_E_UNSUPPORTED_FORMAT && !(info->flags & WAVE_FORMAT_DIRECT)){
ret = WINMM_MapDevice(device, is_out);
if(ret != MMSYSERR_NOERROR || info->flags & WAVE_FORMAT_QUERY)
goto error;
}else{
WARN("Initialize failed: %08x\n", hr);
ret = hr2mmr(hr);
goto error;
}
}else{
device->bytes_per_frame = device->orig_fmt->nBlockAlign;
device->samples_per_sec = device->orig_fmt->nSamplesPerSec;
}
hr = IAudioClient_GetService(device->client, &IID_IAudioClock,
(void**)&device->clock);
if(FAILED(hr)){
WARN("GetService failed: %08x\n", hr);
goto error;
}
if(!device->event){
device->event = CreateEventW(NULL, FALSE, FALSE, NULL);
if(!device->event){
WARN("CreateEvent failed: %08x\n", hr);
goto error;
}
/* As the devices thread is waiting on g_device_handles, it can
* only be modified from within this same thread. */
if(g_device_handles){
g_device_handles = HeapReAlloc(GetProcessHeap(), 0, g_device_handles,
sizeof(HANDLE) * (g_devhandle_count + 1));
g_handle_devices = HeapReAlloc(GetProcessHeap(), 0, g_handle_devices,
sizeof(WINMM_Device *) * (g_devhandle_count + 1));
}else{
g_device_handles = HeapAlloc(GetProcessHeap(), 0, sizeof(HANDLE));
g_handle_devices = HeapAlloc(GetProcessHeap(), 0,
sizeof(WINMM_Device *));
}
g_device_handles[g_devhandle_count] = device->event;
g_handle_devices[g_devhandle_count] = device;
++g_devhandle_count;
}
hr = IAudioClient_SetEventHandle(device->client, device->event);
if(FAILED(hr)){
WARN("SetEventHandle failed: %08x\n", hr);
goto error;
}
if(info->reset){
device->played_frames = 0;
device->ofs_bytes = 0;
device->loop_counter = 0;
device->first = device->last = device->playing = device->loop_start = NULL;
}
device->stopped = TRUE;
device->last_clock_pos = 0;
device->cb_info.flags = HIWORD(info->flags & CALLBACK_TYPEMASK);
device->cb_info.callback = info->callback;
device->cb_info.user = info->cb_user;
device->cb_info.hwave = device->handle;
info->handle = device->handle;
return MMSYSERR_NOERROR;
error:
if(device->client){
IAudioClient_Release(device->client);
device->client = NULL;
}
if(device->device){
IMMDevice_Release(device->device);
device->device = NULL;
}
return ret;
}
static LRESULT WOD_Open(WINMM_OpenInfo *info)
{
WINMM_Device *device;
LRESULT ret = MMSYSERR_ERROR;
HRESULT hr;
if(info->handle != 0){
device = WINMM_GetDeviceFromHWAVE(info->handle);
if(!device){
WARN("Unexpected! Invalid info->handle given: %p\n", info->handle);
return MMSYSERR_ERROR;
}
EnterCriticalSection(&device->lock);
device->open = TRUE;
}else{
CRITICAL_SECTION *lock;
UINT internal_index;
WINMM_Device **devices;
WINMM_MMDevice *mmdevice;
if(WINMM_IsMapper(info->req_device)){
devices = g_out_mapper_devices;
mmdevice = read_map(g_out_map, 0);
lock = &g_devthread_lock;
internal_index = MAPPER_INDEX;
}else{
if(info->req_device >= g_outmmdevices_count)
return MMSYSERR_BADDEVICEID;
mmdevice = read_map(g_out_map, info->req_device);
if(!mmdevice->out_caps.szPname[0])
return MMSYSERR_NOTENABLED;
devices = mmdevice->devices;
lock = &mmdevice->lock;
internal_index = mmdevice->index;
}
EnterCriticalSection(lock);
device = WINMM_FindUnusedDevice(devices, mmdevice,
internal_index, TRUE);
if(!device){
LeaveCriticalSection(lock);
return MMSYSERR_ALLOCATED;
}
LeaveCriticalSection(lock);
}
ret = WINMM_OpenDevice(device, info, TRUE);
if((info->flags & WAVE_FORMAT_QUERY) || ret != MMSYSERR_NOERROR)
goto error;
ret = MMSYSERR_ERROR;
hr = IAudioClient_GetService(device->client, &IID_IAudioRenderClient,
(void**)&device->render);
if(FAILED(hr)){
ERR("GetService failed: %08x\n", hr);
goto error;
}
hr = IAudioClient_GetService(device->client, &IID_IAudioStreamVolume,
(void**)&device->volume);
if(FAILED(hr)){
ERR("GetService failed: %08x\n", hr);
goto error;
}
LeaveCriticalSection(&device->lock);
return MMSYSERR_NOERROR;
error:
if(device->device){
IMMDevice_Release(device->device);
device->device = NULL;
}
if(device->client){
IAudioClient_Release(device->client);
device->client = NULL;
}
if(device->render){
IAudioRenderClient_Release(device->render);
device->render = NULL;
}
if(device->volume){
IAudioStreamVolume_Release(device->volume);
device->volume = NULL;
}
if(device->clock){
IAudioClock_Release(device->clock);
device->clock = NULL;
}
device->open = FALSE;
LeaveCriticalSection(&device->lock);
return ret;
}
static LRESULT WID_Open(WINMM_OpenInfo *info)
{
WINMM_Device *device, **devices;
WINMM_MMDevice *mmdevice;
UINT internal_index;
CRITICAL_SECTION *lock;
LRESULT ret = MMSYSERR_ERROR;
HRESULT hr;
if(WINMM_IsMapper(info->req_device)){
devices = g_in_mapper_devices;
mmdevice = read_map(g_in_map, 0);
lock = &g_devthread_lock;
internal_index = MAPPER_INDEX;
}else{
if(info->req_device >= g_inmmdevices_count)
return MMSYSERR_BADDEVICEID;
mmdevice = read_map(g_in_map, info->req_device);
if(!mmdevice->in_caps.szPname[0])
return MMSYSERR_NOTENABLED;
devices = mmdevice->devices;
lock = &mmdevice->lock;
internal_index = mmdevice->index;
}
EnterCriticalSection(lock);
device = WINMM_FindUnusedDevice(devices, mmdevice, internal_index, FALSE);
if(!device){
LeaveCriticalSection(lock);
return MMSYSERR_ALLOCATED;
}
LeaveCriticalSection(lock);
ret = WINMM_OpenDevice(device, info, FALSE);
if((info->flags & WAVE_FORMAT_QUERY) || ret != MMSYSERR_NOERROR)
goto error;
ret = MMSYSERR_ERROR;
hr = IAudioClient_GetService(device->client, &IID_IAudioCaptureClient,
(void**)&device->capture);
if(FAILED(hr)){
WARN("GetService failed: %08x\n", hr);
goto error;
}
LeaveCriticalSection(&device->lock);
return MMSYSERR_NOERROR;
error:
if(device->device){
IMMDevice_Release(device->device);
device->device = NULL;
}
if(device->client){
IAudioClient_Release(device->client);
device->client = NULL;
}
if(device->capture){
IAudioCaptureClient_Release(device->capture);
device->capture = NULL;
}
if(device->clock){
IAudioClock_Release(device->clock);
device->clock = NULL;
}
device->open = FALSE;
LeaveCriticalSection(&device->lock);
return ret;
}
static HRESULT WINMM_CloseDevice(WINMM_Device *device)
{
device->open = FALSE;
if(!device->stopped){
IAudioClient_Stop(device->client);
device->stopped = TRUE;
}
if(device->acm_handle){
acmStreamClose(device->acm_handle, 0);
device->acm_handle = NULL;
}
IMMDevice_Release(device->device);
device->device = NULL;
IAudioClient_Release(device->client);
device->client = NULL;
IAudioClock_Release(device->clock);
device->clock = NULL;
return S_OK;
}
static LRESULT WOD_Close(HWAVEOUT hwave)
{
WINMM_Device *device = WINMM_GetDeviceFromHWAVE((HWAVE)hwave);
TRACE("(%p)\n", hwave);
if(!WINMM_ValidateAndLock(device))
return MMSYSERR_INVALHANDLE;
WINMM_CloseDevice(device);
IAudioRenderClient_Release(device->render);
device->render = NULL;
IAudioStreamVolume_Release(device->volume);
device->volume = NULL;
LeaveCriticalSection(&device->lock);
return MMSYSERR_NOERROR;
}
static LRESULT WID_Close(HWAVEIN hwave)
{
WINMM_Device *device = WINMM_GetDeviceFromHWAVE((HWAVE)hwave);
TRACE("(%p)\n", hwave);
if(!WINMM_ValidateAndLock(device))
return MMSYSERR_INVALHANDLE;
WINMM_CloseDevice(device);
IAudioCaptureClient_Release(device->capture);
device->capture = NULL;
LeaveCriticalSection(&device->lock);
return MMSYSERR_NOERROR;
}
static DWORD WINMM_FixedBufferLen(DWORD length, WINMM_Device *device)
{
return length - length % device->bytes_per_frame;
}
static LRESULT WINMM_PrepareHeader(HWAVE hwave, WAVEHDR *header)
{
WINMM_Device *device = WINMM_GetDeviceFromHWAVE(hwave);
TRACE("(%p, %p)\n", hwave, header);
if(!WINMM_ValidateAndLock(device))
return MMSYSERR_INVALHANDLE;
if(device->render && device->acm_handle){
ACMSTREAMHEADER *ash;
DWORD size;
MMRESULT mr;
mr = acmStreamSize(device->acm_handle, header->dwBufferLength, &size,
ACM_STREAMSIZEF_SOURCE);
if(mr != MMSYSERR_NOERROR){
LeaveCriticalSection(&device->lock);
return mr;
}
ash = HeapAlloc(GetProcessHeap(), 0, sizeof(ACMSTREAMHEADER) + size);
if(!ash){
LeaveCriticalSection(&device->lock);
return MMSYSERR_NOMEM;
}
ash->cbStruct = sizeof(*ash);
ash->fdwStatus = 0;
ash->dwUser = (DWORD_PTR)header;
ash->pbSrc = (BYTE*)header->lpData;
ash->cbSrcLength = header->dwBufferLength;
ash->dwSrcUser = header->dwUser;
ash->pbDst = (BYTE*)ash + sizeof(ACMSTREAMHEADER);
ash->cbDstLength = size;
ash->dwDstUser = 0;
mr = acmStreamPrepareHeader(device->acm_handle, ash, 0);
if(mr != MMSYSERR_NOERROR){
HeapFree(GetProcessHeap(), 0, ash);
LeaveCriticalSection(&device->lock);
return mr;
}
header->reserved = (DWORD_PTR)ash;
}
LeaveCriticalSection(&device->lock);
header->dwFlags |= WHDR_PREPARED;
header->dwFlags &= ~(WHDR_DONE|WHDR_INQUEUE); /* flags cleared since w2k */
return MMSYSERR_NOERROR;
}
static LRESULT WINMM_UnprepareHeader(HWAVE hwave, WAVEHDR *header)
{
WINMM_Device *device = WINMM_GetDeviceFromHWAVE(hwave);
TRACE("(%p, %p)\n", hwave, header);
if(!WINMM_ValidateAndLock(device))
return MMSYSERR_INVALHANDLE;
if(device->render && device->acm_handle){
ACMSTREAMHEADER *ash = (ACMSTREAMHEADER*)header->reserved;
acmStreamUnprepareHeader(device->acm_handle, ash, 0);
HeapFree(GetProcessHeap(), 0, ash);
}
LeaveCriticalSection(&device->lock);
header->dwFlags &= ~WHDR_PREPARED;
return MMSYSERR_NOERROR;
}
static UINT32 WINMM_HeaderLenBytes(WINMM_Device *device, WAVEHDR *header)
{
if(device->acm_handle){
ACMSTREAMHEADER *ash = (ACMSTREAMHEADER*)header->reserved;
return WINMM_FixedBufferLen(ash->cbDstLengthUsed, device);
}
return WINMM_FixedBufferLen(header->dwBufferLength, device);
}
static UINT32 WINMM_HeaderLenFrames(WINMM_Device *device, WAVEHDR *header)
{
return WINMM_HeaderLenBytes(device, header) / device->bytes_per_frame;
}
static WAVEHDR *WOD_MarkDoneHeaders(WINMM_Device *device)
{
HRESULT hr;
WAVEHDR *first = device->first, *queue = first, *last = NULL;
UINT64 clock_freq, clock_pos, clock_frames;
UINT32 nloops, queue_frames = 0;
hr = IAudioClock_GetFrequency(device->clock, &clock_freq);
if(FAILED(hr)){
WARN("GetFrequency failed: %08x\n", hr);
return NULL;
}
hr = IAudioClock_GetPosition(device->clock, &clock_pos, NULL);
if(FAILED(hr)){
WARN("GetPosition failed: %08x\n", hr);
return NULL;
}
clock_frames = (clock_pos * device->samples_per_sec) / clock_freq;
nloops = device->loop_counter;
while(queue &&
(queue_frames += WINMM_HeaderLenFrames(device, queue)) <=
clock_frames - device->last_clock_pos + device->remainder_frames){
if(!nloops){
last = queue;
device->last_clock_pos += queue_frames;
device->remainder_frames = 0;
queue_frames = 0;
queue = device->first = queue->lpNext;
}else{
if(queue->dwFlags & WHDR_BEGINLOOP){
if(queue->dwFlags & WHDR_ENDLOOP)
--nloops;
else
queue = queue->lpNext;
}else if(queue->dwFlags & WHDR_ENDLOOP){
queue = device->loop_start;
--nloops;
}
}
}
if(last){
last->lpNext = NULL;
return first;
}else
return NULL;
}
static void WOD_PushData(WINMM_Device *device)
{
WINMM_CBInfo cb_info;
HRESULT hr;
UINT32 pad, bufsize, avail_frames, queue_frames, written, ofs;
UINT32 queue_bytes, nloops;
BYTE *data;
WAVEHDR *queue, *first = NULL;
TRACE("(%p)\n", device->handle);
EnterCriticalSection(&device->lock);
if(!device->device)
goto exit;
if(!device->first){
if (device->stopped)
goto exit;
device->stopped = TRUE;
device->last_clock_pos = 0;
IAudioClient_Stop(device->client);
IAudioClient_Reset(device->client);
goto exit;
}
hr = IAudioClient_GetBufferSize(device->client, &bufsize);
if(FAILED(hr)){
WARN("GetBufferSize failed: %08x\n", hr);
goto exit;
}
hr = IAudioClient_GetCurrentPadding(device->client, &pad);
if(FAILED(hr)){
WARN("GetCurrentPadding failed: %08x\n", hr);
goto exit;
}
first = WOD_MarkDoneHeaders(device);
/* determine which is larger between the available buffer size and
* the amount of data left in the queue */
avail_frames = bufsize - pad;
queue = device->playing;
ofs = device->ofs_bytes;
queue_frames = 0;
nloops = 0;
while(queue && queue_frames < avail_frames){
queue_bytes = WINMM_HeaderLenBytes(device, queue);
queue_frames += (queue_bytes - ofs) / device->bytes_per_frame;
ofs = 0;
if(queue->dwFlags & WHDR_ENDLOOP && nloops < device->loop_counter){
queue = device->loop_start;
++nloops;
}else
queue = queue->lpNext;
}
if(queue_frames < avail_frames)
avail_frames = queue_frames;
if(avail_frames == 0)
goto exit;
hr = IAudioRenderClient_GetBuffer(device->render, avail_frames, &data);
if(FAILED(hr)){
WARN("GetBuffer failed: %08x\n", hr);
goto exit;
}
written = 0;
while(device->playing && written < avail_frames){
UINT32 copy_frames, copy_bytes;
BYTE *queue_data;
queue = device->playing;
queue_bytes = WINMM_HeaderLenBytes(device, queue);
if(device->acm_handle)
queue_data = ((ACMSTREAMHEADER*)queue->reserved)->pbDst;
else
queue_data = (BYTE*)queue->lpData;
queue_frames = (queue_bytes - device->ofs_bytes) /
device->bytes_per_frame;
copy_frames = queue_frames < (avail_frames - written) ?
queue_frames : avail_frames - written;
copy_bytes = copy_frames * device->bytes_per_frame;
memcpy(data, queue_data + device->ofs_bytes, copy_bytes);
data += copy_bytes;
written += copy_frames;
device->ofs_bytes += copy_bytes;
if(device->ofs_bytes >= queue_bytes){
device->ofs_bytes = 0;
if(!(queue->dwFlags & (WHDR_BEGINLOOP | WHDR_ENDLOOP)))
device->playing = queue->lpNext;
else{
if(queue->dwFlags & WHDR_BEGINLOOP){
device->loop_start = device->playing;
device->playing = queue->lpNext;
device->loop_counter = queue->dwLoops;
}
if(queue->dwFlags & WHDR_ENDLOOP){
--device->loop_counter;
if(device->loop_counter)
device->playing = device->loop_start;
else
device->loop_start = device->playing = queue->lpNext;
}
}
}
}
hr = IAudioRenderClient_ReleaseBuffer(device->render, avail_frames, 0);
if(FAILED(hr)){
WARN("ReleaseBuffer failed: %08x\n", hr);
goto exit;
}
if(device->orig_fmt->nSamplesPerSec != device->samples_per_sec)
device->played_frames += MulDiv(avail_frames, device->orig_fmt->nSamplesPerSec, device->samples_per_sec);
else
device->played_frames += avail_frames;
exit:
cb_info = device->cb_info;
LeaveCriticalSection(&device->lock);
while(first){
WAVEHDR *next = first->lpNext;
first->dwFlags &= ~WHDR_INQUEUE;
first->dwFlags |= WHDR_DONE;
WINMM_NotifyClient(&cb_info, WOM_DONE, (DWORD_PTR)first, 0);
first = next;
}
}
static void WID_PullACMData(WINMM_Device *device)
{
UINT32 packet, packet_bytes;
DWORD flags;
BYTE *data;
WAVEHDR *queue;
HRESULT hr;
MMRESULT mr;
if(device->acm_hdr.cbDstLength == 0){
hr = IAudioCaptureClient_GetBuffer(device->capture, &data, &packet,
&flags, NULL, NULL);
if(hr != S_OK){
if(FAILED(hr))
WARN("GetBuffer failed: %08x\n", hr);
return;
}
acmStreamSize(device->acm_handle, packet * device->bytes_per_frame,
&packet_bytes, ACM_STREAMSIZEF_SOURCE);
device->acm_offs = 0;
device->acm_hdr.cbStruct = sizeof(device->acm_hdr);
device->acm_hdr.fdwStatus = 0;
device->acm_hdr.dwUser = 0;
device->acm_hdr.pbSrc = data;
device->acm_hdr.cbSrcLength = packet * device->bytes_per_frame;
device->acm_hdr.cbSrcLengthUsed = 0;
device->acm_hdr.dwSrcUser = 0;
device->acm_hdr.pbDst = HeapAlloc(GetProcessHeap(), 0, packet_bytes);
device->acm_hdr.cbDstLength = packet_bytes;
device->acm_hdr.cbDstLengthUsed = 0;
device->acm_hdr.dwDstUser = 0;
mr = acmStreamPrepareHeader(device->acm_handle, &device->acm_hdr, 0);
if(mr != MMSYSERR_NOERROR){
WARN("acmStreamPrepareHeader failed: %d\n", mr);
return;
}
mr = acmStreamConvert(device->acm_handle, &device->acm_hdr, 0);
if(mr != MMSYSERR_NOERROR){
WARN("acmStreamConvert failed: %d\n", mr);
return;
}
hr = IAudioCaptureClient_ReleaseBuffer(device->capture, packet);
if(FAILED(hr))
WARN("ReleaseBuffer failed: %08x\n", hr);
device->played_frames += packet;
}
queue = device->first;
while(queue){
UINT32 to_copy_bytes;
to_copy_bytes = min(WINMM_FixedBufferLen(queue->dwBufferLength, device) - queue->dwBytesRecorded,
WINMM_FixedBufferLen(device->acm_hdr.cbDstLengthUsed, device) - device->acm_offs);
memcpy(queue->lpData + queue->dwBytesRecorded,
device->acm_hdr.pbDst + device->acm_offs, to_copy_bytes);
queue->dwBytesRecorded += to_copy_bytes;
device->acm_offs += to_copy_bytes;
if(queue->dwBufferLength - queue->dwBytesRecorded <
device->bytes_per_frame){
queue->dwFlags &= ~WHDR_INQUEUE;
queue->dwFlags |= WHDR_DONE;
device->first = queue = queue->lpNext;
}
if(device->acm_offs >= WINMM_FixedBufferLen(device->acm_hdr.cbDstLengthUsed, device)){
acmStreamUnprepareHeader(device->acm_handle, &device->acm_hdr, 0);
HeapFree(GetProcessHeap(), 0, device->acm_hdr.pbDst);
device->acm_hdr.cbDstLength = 0;
device->acm_hdr.cbDstLengthUsed = 0;
/* done with this ACM Header, so try to pull more data */
WID_PullACMData(device);
return;
}
}
/* out of WAVEHDRs to write into, so toss the rest of this packet */
acmStreamUnprepareHeader(device->acm_handle, &device->acm_hdr, 0);
HeapFree(GetProcessHeap(), 0, device->acm_hdr.pbDst);
device->acm_hdr.cbDstLength = 0;
device->acm_hdr.cbDstLengthUsed = 0;
}
static void WID_PullData(WINMM_Device *device)
{
WINMM_CBInfo cb_info;
WAVEHDR *queue, *first = NULL, *last = NULL;
HRESULT hr;
TRACE("(%p)\n", device->handle);
EnterCriticalSection(&device->lock);
if(!device->device || !device->first)
goto exit;
first = device->first;
if(device->acm_handle){
WID_PullACMData(device);
goto exit;
}
while(device->first){
BYTE *data;
UINT32 packet_len, packet;
DWORD flags;
hr = IAudioCaptureClient_GetBuffer(device->capture, &data, &packet_len,
&flags, NULL, NULL);
if(hr != S_OK){
if(FAILED(hr))
WARN("GetBuffer failed: %08x\n", hr);
else /* AUDCLNT_S_BUFFER_EMPTY success code */
IAudioCaptureClient_ReleaseBuffer(device->capture, 0);
goto exit;
}
packet = packet_len;
queue = device->first;
while(queue && packet > 0){
UINT32 to_copy_bytes;
to_copy_bytes = min(packet * device->bytes_per_frame,
WINMM_FixedBufferLen(queue->dwBufferLength, device) - queue->dwBytesRecorded);
memcpy(queue->lpData + queue->dwBytesRecorded,
data + (packet_len - packet) * device->bytes_per_frame,
to_copy_bytes);
queue->dwBytesRecorded += to_copy_bytes;
if(queue->dwBufferLength - queue->dwBytesRecorded <
device->bytes_per_frame){
last = queue;
device->first = queue = queue->lpNext;
}
packet -= to_copy_bytes / device->bytes_per_frame;
}
hr = IAudioCaptureClient_ReleaseBuffer(device->capture, packet_len);
if(FAILED(hr))
WARN("ReleaseBuffer failed: %08x\n", hr);
if(packet > 0)
WARN("losing %u frames\n", packet);
device->played_frames += packet_len;
}
exit:
cb_info = device->cb_info;
LeaveCriticalSection(&device->lock);
if(last){
last->lpNext = NULL;
while(first){
WAVEHDR *next = first->lpNext;
first->dwFlags &= ~WHDR_INQUEUE;
first->dwFlags |= WHDR_DONE;
WINMM_NotifyClient(&cb_info, WIM_DATA, (DWORD_PTR)first, 0);
first = next;
}
}
}
static MMRESULT WINMM_BeginPlaying(WINMM_Device *device)
{
HRESULT hr;
TRACE("(%p)\n", device->handle);
if(device->render)
/* prebuffer data before starting */
WOD_PushData(device);
if(device->stopped){
device->stopped = FALSE;
hr = IAudioClient_Start(device->client);
if(FAILED(hr) && hr != AUDCLNT_E_NOT_STOPPED){
device->stopped = TRUE;
WARN("Start failed: %08x\n", hr);
return MMSYSERR_ERROR;
}
}
return MMSYSERR_NOERROR;
}
static LRESULT WINMM_Pause(HWAVE hwave)
{
WINMM_Device *device = WINMM_GetDeviceFromHWAVE(hwave);
HRESULT hr;
TRACE("(%p)\n", hwave);
if(!WINMM_ValidateAndLock(device))
return MMSYSERR_INVALHANDLE;
hr = IAudioClient_Stop(device->client);
if(FAILED(hr)){
LeaveCriticalSection(&device->lock);
WARN("Stop failed: %08x\n", hr);
return MMSYSERR_ERROR;
}
device->stopped = FALSE;
LeaveCriticalSection(&device->lock);
return MMSYSERR_NOERROR;
}
static LRESULT WINMM_Reset(HWAVE hwave)
{
WINMM_CBInfo cb_info;
WINMM_Device *device = WINMM_GetDeviceFromHWAVE(hwave);
BOOL is_out;
WAVEHDR *first;
HRESULT hr;
TRACE("(%p)\n", hwave);
if(!WINMM_ValidateAndLock(device))
return MMSYSERR_INVALHANDLE;
hr = IAudioClient_Stop(device->client);
if(FAILED(hr)){
LeaveCriticalSection(&device->lock);
WARN("Stop failed: %08x\n", hr);
return MMSYSERR_ERROR;
}
device->stopped = TRUE;
first = device->first;
device->first = device->last = device->playing = NULL;
device->ofs_bytes = 0;
device->played_frames = 0;
device->loop_counter = 0;
device->last_clock_pos = 0;
IAudioClient_Reset(device->client);
cb_info = device->cb_info;
is_out = device->render != NULL;
LeaveCriticalSection(&device->lock);
while(first){
WAVEHDR *next = first->lpNext;
first->dwFlags &= ~WHDR_INQUEUE;
first->dwFlags |= WHDR_DONE;
if(is_out)
WINMM_NotifyClient(&cb_info, WOM_DONE, (DWORD_PTR)first, 0);
else
WINMM_NotifyClient(&cb_info, WIM_DATA, (DWORD_PTR)first, 0);
first = next;
}
return MMSYSERR_NOERROR;
}
static MMRESULT WINMM_FramesToMMTime(MMTIME *time, UINT32 played_frames,
UINT32 sample_rate, UINT32 bytes_per_sec)
{
switch(time->wType){
case TIME_SAMPLES:
time->u.sample = played_frames;
return MMSYSERR_NOERROR;
case TIME_MS:
time->u.ms = (UINT64)played_frames * 1000 / sample_rate;
return MMSYSERR_NOERROR;
case TIME_SMPTE:
time->u.smpte.fps = 30;
played_frames += sample_rate / time->u.smpte.fps - 1; /* round up */
time->u.smpte.frame = (played_frames % sample_rate) * time->u.smpte.fps / sample_rate;
played_frames /= sample_rate; /* yields seconds */
time->u.smpte.sec = played_frames % 60;
played_frames /= 60;
time->u.smpte.min = played_frames % 60;
time->u.smpte.hour= played_frames / 60;
return MMSYSERR_NOERROR;
default:
time->wType = TIME_BYTES;
/* fall through */
case TIME_BYTES:
time->u.cb = MulDiv(played_frames, bytes_per_sec, sample_rate);
return MMSYSERR_NOERROR;
}
}
static LRESULT WINMM_GetPosition(HWAVE hwave, MMTIME *time)
{
WINMM_Device *device = WINMM_GetDeviceFromHWAVE(hwave);
UINT32 played_frames, sample_rate, bytes_per_sec;
TRACE("(%p, %p)\n", hwave, time);
if(!WINMM_ValidateAndLock(device))
return MMSYSERR_INVALHANDLE;
played_frames = device->played_frames;
sample_rate = device->orig_fmt->nSamplesPerSec;
bytes_per_sec = device->orig_fmt->nAvgBytesPerSec;
LeaveCriticalSection(&device->lock);
return WINMM_FramesToMMTime(time, played_frames, sample_rate, bytes_per_sec);
}
static WINMM_MMDevice *WINMM_GetMixerMMDevice(HMIXEROBJ hmix, DWORD flags,
UINT *mmdev_index)
{
UINT mmdev, dev, junk, *out;
BOOL is_out;
if(!mmdev_index)
out = &mmdev;
else
out = mmdev_index;
switch(flags & 0xF0000000){
case MIXER_OBJECTF_MIXER: /* == 0 */
*out = HandleToULong(hmix);
if(*out < g_outmmdevices_count)
return read_map(g_out_map, *out);
if(*out - g_outmmdevices_count < g_inmmdevices_count){
*out -= g_outmmdevices_count;
return read_map(g_in_map, *out);
}
/* fall through -- if it's not a valid mixer device, then
* it could be a valid mixer handle. windows seems to do
* this as well. */
case MIXER_OBJECTF_HMIXER:
case MIXER_OBJECTF_HWAVEOUT:
case MIXER_OBJECTF_HWAVEIN:
WINMM_DecomposeHWAVE((HWAVE)hmix, out, &is_out, &dev, &junk);
if(junk != 0x1 || (is_out && *out >= g_outmmdevices_count) ||
(!is_out && *out >= g_inmmdevices_count))
return NULL;
if(is_out)
return read_map(g_out_map, *out);
return read_map(g_in_map, *out);
case MIXER_OBJECTF_WAVEOUT:
*out = HandleToULong(hmix);
if(*out < g_outmmdevices_count)
return read_map(g_out_map, *out);
return NULL;
case MIXER_OBJECTF_WAVEIN:
*out = HandleToULong(hmix);
if(*out < g_inmmdevices_count)
return read_map(g_in_map, *out);
return NULL;
}
return NULL;
}
static MMRESULT WINMM_SetupMMDeviceVolume(WINMM_MMDevice *mmdevice)
{
IAudioSessionManager *sesman;
IMMDevice *device;
HRESULT hr;
hr = IMMDeviceEnumerator_GetDevice(g_devenum, mmdevice->dev_id, &device);
if(FAILED(hr)){
WARN("Device %s (%s) unavailable: %08x\n",
wine_dbgstr_w(mmdevice->dev_id),
wine_dbgstr_w(mmdevice->out_caps.szPname), hr);
return MMSYSERR_ERROR;
}
hr = IMMDevice_Activate(device, &IID_IAudioSessionManager,
CLSCTX_INPROC_SERVER, NULL, (void**)&sesman);
if(FAILED(hr)){
WARN("Activate failed: %08x\n", hr);
IMMDevice_Release(device);
return MMSYSERR_ERROR;
}
IMMDevice_Release(device);
hr = IAudioSessionManager_GetSimpleAudioVolume(sesman, &mmdevice->session,
FALSE, &mmdevice->volume);
IAudioSessionManager_Release(sesman);
if(FAILED(hr)){
WARN("GetSimpleAudioVolume failed: %08x\n", hr);
return MMSYSERR_ERROR;
}
return MMSYSERR_NOERROR;
}
static LRESULT MXD_GetControlDetails(WINMM_ControlDetails *details)
{
WINMM_MMDevice *mmdevice;
MIXERCONTROLDETAILS *control = details->details;
HRESULT hr;
TRACE("(%p)\n", details->hmix);
mmdevice = WINMM_GetMixerMMDevice(details->hmix, details->flags, NULL);
if(!mmdevice)
return MMSYSERR_INVALHANDLE;
EnterCriticalSection(&mmdevice->lock);
if(!mmdevice->volume){
MMRESULT mr;
mr = WINMM_SetupMMDeviceVolume(mmdevice);
if(mr != MMSYSERR_NOERROR){
LeaveCriticalSection(&mmdevice->lock);
return mr;
}
}
if(control->dwControlID == 0){
float vol;
MIXERCONTROLDETAILS_UNSIGNED *udet;
if(!control->paDetails ||
control->cbDetails < sizeof(MIXERCONTROLDETAILS_UNSIGNED)){
LeaveCriticalSection(&mmdevice->lock);
return MMSYSERR_INVALPARAM;
}
hr = ISimpleAudioVolume_GetMasterVolume(mmdevice->volume, &vol);
if(FAILED(hr)){
WARN("GetMasterVolume failed: %08x\n", hr);
LeaveCriticalSection(&mmdevice->lock);
return MMSYSERR_ERROR;
}
udet = (MIXERCONTROLDETAILS_UNSIGNED*)control->paDetails;
udet->dwValue = vol * ((unsigned int)0xFFFF);
}else if(control->dwControlID == 1){
BOOL mute;
MIXERCONTROLDETAILS_BOOLEAN *bdet;
if(!control->paDetails ||
control->cbDetails < sizeof(MIXERCONTROLDETAILS_BOOLEAN)){
LeaveCriticalSection(&mmdevice->lock);
return MMSYSERR_INVALPARAM;
}
hr = ISimpleAudioVolume_GetMute(mmdevice->volume, &mute);
if(FAILED(hr)){
WARN("GetMute failed: %08x\n", hr);
LeaveCriticalSection(&mmdevice->lock);
return MMSYSERR_ERROR;
}
bdet = (MIXERCONTROLDETAILS_BOOLEAN*)control->paDetails;
bdet->fValue = mute;
}else if(control->dwControlID == 2 || control->dwControlID == 3){
FIXME("What should the sw-side mixer controls map to?\n");
}else{
LeaveCriticalSection(&mmdevice->lock);
return MIXERR_INVALCONTROL;
}
LeaveCriticalSection(&mmdevice->lock);
return MMSYSERR_NOERROR;
}
static LRESULT MXD_SetControlDetails(WINMM_ControlDetails *details)
{
WINMM_MMDevice *mmdevice;
MIXERCONTROLDETAILS *control = details->details;
HRESULT hr;
TRACE("(%p)\n", details->hmix);
mmdevice = WINMM_GetMixerMMDevice(details->hmix, details->flags, NULL);
if(!mmdevice)
return MMSYSERR_INVALHANDLE;
EnterCriticalSection(&mmdevice->lock);
if(!mmdevice->volume){
MMRESULT mr;
mr = WINMM_SetupMMDeviceVolume(mmdevice);
if(mr != MMSYSERR_NOERROR){
LeaveCriticalSection(&mmdevice->lock);
return mr;
}
}
if(control->dwControlID == 0){
float vol;
MIXERCONTROLDETAILS_UNSIGNED *udet;
if(!control->paDetails ||
control->cbDetails < sizeof(MIXERCONTROLDETAILS_UNSIGNED)){
LeaveCriticalSection(&mmdevice->lock);
return MMSYSERR_INVALPARAM;
}
udet = (MIXERCONTROLDETAILS_UNSIGNED*)control->paDetails;
if(udet->dwValue > 65535){
LeaveCriticalSection(&mmdevice->lock);
return MMSYSERR_INVALPARAM;
}
vol = udet->dwValue / 65535.f;
hr = ISimpleAudioVolume_SetMasterVolume(mmdevice->volume, vol, NULL);
if(FAILED(hr)){
WARN("SetMasterVolume failed: %08x\n", hr);
LeaveCriticalSection(&mmdevice->lock);
return MMSYSERR_ERROR;
}
}else if(control->dwControlID == 1){
BOOL mute;
MIXERCONTROLDETAILS_BOOLEAN *bdet;
if(!control->paDetails ||
control->cbDetails < sizeof(MIXERCONTROLDETAILS_BOOLEAN)){
LeaveCriticalSection(&mmdevice->lock);
return MMSYSERR_INVALPARAM;
}
bdet = (MIXERCONTROLDETAILS_BOOLEAN*)control->paDetails;
mute = bdet->fValue;
hr = ISimpleAudioVolume_SetMute(mmdevice->volume, mute, NULL);
if(FAILED(hr)){
WARN("SetMute failed: %08x\n", hr);
LeaveCriticalSection(&mmdevice->lock);
return MMSYSERR_ERROR;
}
}else if(control->dwControlID == 2 || control->dwControlID == 3){
FIXME("What should the sw-side mixer controls map to?\n");
}else{
LeaveCriticalSection(&mmdevice->lock);
return MIXERR_INVALCONTROL;
}
LeaveCriticalSection(&mmdevice->lock);
return MMSYSERR_NOERROR;
}
static LRESULT DRV_QueryDeviceInterface(WINMM_QueryInterfaceInfo *info)
{
WINMM_MMDevice *mmdevice;
IMMDevice *device;
IPropertyStore *ps;
PROPVARIANT pv;
DWORD len_bytes;
HRESULT hr;
static const PROPERTYKEY deviceinterface_key = {
{0x233164c8, 0x1b2c, 0x4c7d, {0xbc, 0x68, 0xb6, 0x71, 0x68, 0x7a, 0x25, 0x67}}, 1
};
if(WINMM_IsMapper(info->index)){
if(info->str){
if(*info->len_bytes < sizeof(WCHAR))
return MMSYSERR_INVALPARAM;
*info->str = 0;
}else
*info->len_bytes = sizeof(WCHAR);
return MMSYSERR_NOERROR;
}
if(info->is_out){
if(info->index >= g_outmmdevices_count)
return MMSYSERR_INVALHANDLE;
mmdevice = &g_out_mmdevices[info->index];
}else{
if(info->index >= g_inmmdevices_count)
return MMSYSERR_INVALHANDLE;
mmdevice = &g_in_mmdevices[info->index];
}
hr = IMMDeviceEnumerator_GetDevice(g_devenum, mmdevice->dev_id,
&device);
if(FAILED(hr)){
WARN("Device %s unavailable: %08x\n", wine_dbgstr_w(mmdevice->dev_id), hr);
return MMSYSERR_ERROR;
}
hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &ps);
if(FAILED(hr)){
WARN("OpenPropertyStore failed: %08x\n", hr);
IMMDevice_Release(device);
return MMSYSERR_ERROR;
}
PropVariantInit(&pv);
hr = IPropertyStore_GetValue(ps, &deviceinterface_key, &pv);
if(FAILED(hr)){
WARN("GetValue failed: %08x\n", hr);
IPropertyStore_Release(ps);
IMMDevice_Release(device);
return MMSYSERR_ERROR;
}
if(pv.vt != VT_LPWSTR){
WARN("Got unexpected property type: %u\n", pv.vt);
PropVariantClear(&pv);
IPropertyStore_Release(ps);
IMMDevice_Release(device);
return MMSYSERR_ERROR;
}
len_bytes = (lstrlenW(pv.u.pwszVal) + 1) * sizeof(WCHAR);
if(info->str){
if(len_bytes > *info->len_bytes){
PropVariantClear(&pv);
IPropertyStore_Release(ps);
IMMDevice_Release(device);
return MMSYSERR_INVALPARAM;
}
memcpy(info->str, pv.u.pwszVal, len_bytes);
}else
*info->len_bytes = len_bytes;
PropVariantClear(&pv);
IPropertyStore_Release(ps);
IMMDevice_Release(device);
return MMSYSERR_NOERROR;
}
static LRESULT CALLBACK WINMM_DevicesMsgProc(HWND hwnd, UINT msg, WPARAM wparam,
LPARAM lparam)
{
switch(msg){
case WODM_OPEN:
return WOD_Open((WINMM_OpenInfo*)wparam);
case WODM_CLOSE:
return WOD_Close((HWAVEOUT)wparam);
case WIDM_OPEN:
return WID_Open((WINMM_OpenInfo*)wparam);
case WIDM_CLOSE:
return WID_Close((HWAVEIN)wparam);
case MXDM_GETCONTROLDETAILS:
return MXD_GetControlDetails((WINMM_ControlDetails*)wparam);
case MXDM_SETCONTROLDETAILS:
return MXD_SetControlDetails((WINMM_ControlDetails*)wparam);
case DRV_QUERYDEVICEINTERFACESIZE:
case DRV_QUERYDEVICEINTERFACE:
return DRV_QueryDeviceInterface((WINMM_QueryInterfaceInfo*)wparam);
}
return DefWindowProcW(hwnd, msg, wparam, lparam);
}
static BOOL WINMM_DevicesThreadDone(void)
{
UINT i;
EnterCriticalSection(&g_devthread_lock);
if(g_devthread_token > 0){
LeaveCriticalSection(&g_devthread_lock);
return FALSE;
}
for(i = 0; i < g_devhandle_count; ++i){
if(g_handle_devices[i]->open){
LeaveCriticalSection(&g_devthread_lock);
return FALSE;
}
}
DestroyWindow(g_devices_hwnd);
g_devices_hwnd = NULL;
IMMDeviceEnumerator_Release(g_devenum);
g_devenum = NULL;
CoUninitialize();
LeaveCriticalSection(&g_devthread_lock);
return TRUE;
}
static DWORD WINAPI WINMM_DevicesThreadProc(void *arg)
{
HANDLE evt = arg;
HRESULT hr;
static const WCHAR messageW[] = {'M','e','s','s','a','g','e',0};
hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if(FAILED(hr)){
WARN("CoInitializeEx failed: %08x\n", hr);
FreeLibraryAndExitThread(g_devthread_module, 1);
}
hr = WINMM_InitMMDevices();
if(FAILED(hr)){
CoUninitialize();
FreeLibraryAndExitThread(g_devthread_module, 1);
}
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL,
CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&g_devenum);
if(FAILED(hr)){
WARN("CoCreateInstance failed: %08x\n", hr);
CoUninitialize();
FreeLibraryAndExitThread(g_devthread_module, 1);
}
g_devices_hwnd = CreateWindowW(messageW, NULL, 0, 0, 0, 0, 0,
HWND_MESSAGE, NULL, NULL, NULL);
if(!g_devices_hwnd){
WARN("CreateWindow failed: %d\n", GetLastError());
CoUninitialize();
FreeLibraryAndExitThread(g_devthread_module, 1);
}
SetWindowLongPtrW(g_devices_hwnd, GWLP_WNDPROC,
(LONG_PTR)WINMM_DevicesMsgProc);
/* inform caller that the thread is ready to process messages */
SetEvent(evt);
evt = NULL; /* do not use after this point */
while(1){
DWORD wait;
wait = MsgWaitForMultipleObjects(g_devhandle_count, g_device_handles,
FALSE, INFINITE, QS_ALLINPUT);
if(wait == g_devhandle_count + WAIT_OBJECT_0){
MSG msg;
if(PeekMessageW(&msg, g_devices_hwnd, 0, 0, PM_REMOVE))
WARN("Unexpected message: 0x%x\n", msg.message);
if(!g_devices_hwnd)
break;
}else if(wait < g_devhandle_count + WAIT_OBJECT_0){
WINMM_Device *device = g_handle_devices[wait - WAIT_OBJECT_0];
if(device->render)
WOD_PushData(device);
else
WID_PullData(device);
}else
WARN("Unexpected MsgWait result 0x%x, GLE: %d\n", wait,
GetLastError());
if(WINMM_DevicesThreadDone()){
TRACE("Quitting devices thread\n");
FreeLibraryAndExitThread(g_devthread_module, 0);
}
}
FreeLibraryAndExitThread(g_devthread_module, 0);
}
/* on success, increments g_devthread_token to prevent
* device thread shutdown. caller must decrement. */
static BOOL WINMM_StartDevicesThread(void)
{
HANDLE events[2];
DWORD wait;
EnterCriticalSection(&g_devthread_lock);
if(g_devices_hwnd){
wait = WaitForSingleObject(g_devices_thread, 0);
if(wait == WAIT_TIMEOUT){
/* thread still running */
InterlockedIncrement(&g_devthread_token);
LeaveCriticalSection(&g_devthread_lock);
return TRUE;
}
if(wait != WAIT_OBJECT_0){
/* error */
LeaveCriticalSection(&g_devthread_lock);
return FALSE;
}
TRACE("Devices thread left dangling message window?\n");
g_devices_hwnd = NULL;
CloseHandle(g_devices_thread);
g_devices_thread = NULL;
}else if(g_devices_thread){
WaitForSingleObject(g_devices_thread, INFINITE);
CloseHandle(g_devices_thread);
g_devices_thread = NULL;
}
TRACE("Starting up devices thread\n");
/* The devices thread holds a reference to the winmm module
* to prevent it from unloading while it's running. */
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
(const WCHAR *)WINMM_StartDevicesThread, &g_devthread_module);
events[0] = CreateEventW(NULL, FALSE, FALSE, NULL);
g_devices_thread = CreateThread(NULL, 0, WINMM_DevicesThreadProc,
events[0], 0, NULL);
if(!g_devices_thread){
LeaveCriticalSection(&g_devthread_lock);
CloseHandle(events[0]);
FreeLibrary(g_devthread_module);
return FALSE;
}
events[1] = g_devices_thread;
wait = WaitForMultipleObjects(2, events, FALSE, INFINITE);
CloseHandle(events[0]);
if(wait != WAIT_OBJECT_0){
if(wait == 1 + WAIT_OBJECT_0){
CloseHandle(g_devices_thread);
g_devices_thread = NULL;
g_devices_hwnd = NULL;
}
LeaveCriticalSection(&g_devthread_lock);
return FALSE;
}
InterlockedIncrement(&g_devthread_token);
LeaveCriticalSection(&g_devthread_lock);
return TRUE;
}
/**************************************************************************
* waveOutGetNumDevs [WINMM.@]
*/
UINT WINAPI waveOutGetNumDevs(void)
{
HRESULT hr = WINMM_InitMMDevices();
if(FAILED(hr))
return 0;
TRACE("count: %u\n", g_outmmdevices_count);
return g_outmmdevices_count;
}
/**************************************************************************
* waveOutGetDevCapsA [WINMM.@]
*/
UINT WINAPI waveOutGetDevCapsA(UINT_PTR uDeviceID, LPWAVEOUTCAPSA lpCaps,
UINT uSize)
{
WAVEOUTCAPSW wocW;
UINT ret;
TRACE("(%lu, %p, %u)\n", uDeviceID, lpCaps, uSize);
if(!lpCaps)
return MMSYSERR_INVALPARAM;
ret = waveOutGetDevCapsW(uDeviceID, &wocW, sizeof(wocW));
if (ret == MMSYSERR_NOERROR) {
WAVEOUTCAPSA wocA;
wocA.wMid = wocW.wMid;
wocA.wPid = wocW.wPid;
wocA.vDriverVersion = wocW.vDriverVersion;
WideCharToMultiByte( CP_ACP, 0, wocW.szPname, -1, wocA.szPname,
sizeof(wocA.szPname), NULL, NULL );
wocA.dwFormats = wocW.dwFormats;
wocA.wChannels = wocW.wChannels;
wocA.dwSupport = wocW.dwSupport;
memcpy(lpCaps, &wocA, min(uSize, sizeof(wocA)));
}
return ret;
}
/**************************************************************************
* waveOutGetDevCapsW [WINMM.@]
*/
UINT WINAPI waveOutGetDevCapsW(UINT_PTR uDeviceID, LPWAVEOUTCAPSW lpCaps,
UINT uSize)
{
WAVEOUTCAPSW mapper_caps, *caps;
HRESULT hr;
TRACE("(%lu, %p, %u)\n", uDeviceID, lpCaps, uSize);
hr = WINMM_InitMMDevices();
if(FAILED(hr))
return MMSYSERR_NODRIVER;
if (lpCaps == NULL) return MMSYSERR_INVALPARAM;
if(WINMM_IsMapper(uDeviceID)){
/* FIXME: Should be localized */
static const WCHAR mapper_pnameW[] = {'W','i','n','e',' ','S','o','u',
'n','d',' ','M','a','p','p','e','r',0};
mapper_caps.wMid = 0xFF;
mapper_caps.wPid = 0xFF;
mapper_caps.vDriverVersion = 0x00010001;
mapper_caps.dwFormats = 0xFFFFFFFF;
mapper_caps.wReserved1 = 0;
mapper_caps.dwSupport = WAVECAPS_LRVOLUME | WAVECAPS_VOLUME |
WAVECAPS_SAMPLEACCURATE;
mapper_caps.wChannels = 2;
lstrcpyW(mapper_caps.szPname, mapper_pnameW);
caps = &mapper_caps;
}else{
if(uDeviceID >= g_outmmdevices_count)
return MMSYSERR_BADDEVICEID;
caps = &read_map(g_out_map, uDeviceID)->out_caps;
}
memcpy(lpCaps, caps, min(uSize, sizeof(*lpCaps)));
return MMSYSERR_NOERROR;
}
/**************************************************************************
* waveOutGetErrorTextA [WINMM.@]
* waveInGetErrorTextA [WINMM.@]
*/
UINT WINAPI waveOutGetErrorTextA(UINT uError, LPSTR lpText, UINT uSize)
{
UINT ret;
if (lpText == NULL) ret = MMSYSERR_INVALPARAM;
else if (uSize == 0) ret = MMSYSERR_NOERROR;
else
{
LPWSTR xstr = HeapAlloc(GetProcessHeap(), 0, uSize * sizeof(WCHAR));
if (!xstr) ret = MMSYSERR_NOMEM;
else
{
ret = waveOutGetErrorTextW(uError, xstr, uSize);
if (ret == MMSYSERR_NOERROR)
WideCharToMultiByte(CP_ACP, 0, xstr, -1, lpText, uSize, NULL, NULL);
HeapFree(GetProcessHeap(), 0, xstr);
}
}
return ret;
}
/**************************************************************************
* waveOutGetErrorTextW [WINMM.@]
* waveInGetErrorTextW [WINMM.@]
*/
UINT WINAPI waveOutGetErrorTextW(UINT uError, LPWSTR lpText, UINT uSize)
{
UINT ret = MMSYSERR_BADERRNUM;
if (lpText == NULL) ret = MMSYSERR_INVALPARAM;
else if (uSize == 0) ret = MMSYSERR_NOERROR;
else if (
/* test has been removed because MMSYSERR_BASE is 0, and gcc did emit
* a warning for the test was always true */
(/*uError >= MMSYSERR_BASE && */ uError <= MMSYSERR_LASTERROR) ||
(uError >= WAVERR_BASE && uError <= WAVERR_LASTERROR)) {
if (LoadStringW(hWinMM32Instance,
uError, lpText, uSize) > 0) {
ret = MMSYSERR_NOERROR;
}
}
return ret;
}
/**************************************************************************
* waveOutOpen [WINMM.@]
* All the args/structs have the same layout as the win16 equivalents
*/
MMRESULT WINAPI waveOutOpen(LPHWAVEOUT lphWaveOut, UINT uDeviceID,
LPCWAVEFORMATEX lpFormat, DWORD_PTR dwCallback,
DWORD_PTR dwInstance, DWORD dwFlags)
{
LRESULT res;
WINMM_OpenInfo info;
WINMM_CBInfo cb_info;
TRACE("(%p, %u, %p, %lx, %lx, %08x)\n", lphWaveOut, uDeviceID, lpFormat,
dwCallback, dwInstance, dwFlags);
if(!lphWaveOut && !(dwFlags & WAVE_FORMAT_QUERY))
return MMSYSERR_INVALPARAM;
res = WINMM_CheckCallback(dwCallback, dwFlags, FALSE);
if(res != MMSYSERR_NOERROR)
return res;
if(!WINMM_StartDevicesThread())
return MMSYSERR_NODRIVER;
info.handle = 0;
info.format = (WAVEFORMATEX*)lpFormat;
info.callback = dwCallback;
info.cb_user = dwInstance;
info.req_device = uDeviceID;
info.flags = dwFlags;
info.reset = TRUE;
res = SendMessageW(g_devices_hwnd, WODM_OPEN, (DWORD_PTR)&info, 0);
InterlockedDecrement(&g_devthread_token);
if(res != MMSYSERR_NOERROR || (dwFlags & WAVE_FORMAT_QUERY))
return res;
if(lphWaveOut)
*lphWaveOut = (HWAVEOUT)info.handle;
cb_info.flags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
cb_info.callback = dwCallback;
cb_info.user = dwInstance;
cb_info.hwave = info.handle;
WINMM_NotifyClient(&cb_info, WOM_OPEN, 0, 0);
return res;
}
/**************************************************************************
* waveOutClose [WINMM.@]
*/
UINT WINAPI waveOutClose(HWAVEOUT hWaveOut)
{
UINT res;
WINMM_Device *device;
WINMM_CBInfo cb_info;
TRACE("(%p)\n", hWaveOut);
device = WINMM_GetDeviceFromHWAVE((HWAVE)hWaveOut);
if(!WINMM_ValidateAndLock(device))
return MMSYSERR_INVALHANDLE;
cb_info = device->cb_info;
LeaveCriticalSection(&device->lock);
res = SendMessageW(g_devices_hwnd, WODM_CLOSE, (WPARAM)hWaveOut, 0);
if(res == MMSYSERR_NOERROR)
WINMM_NotifyClient(&cb_info, WOM_CLOSE, 0, 0);
return res;
}
/**************************************************************************
* waveOutPrepareHeader [WINMM.@]
*/
UINT WINAPI waveOutPrepareHeader(HWAVEOUT hWaveOut,
WAVEHDR* lpWaveOutHdr, UINT uSize)
{
TRACE("(%p, %p, %u)\n", hWaveOut, lpWaveOutHdr, uSize);
if(!lpWaveOutHdr || uSize < sizeof(WAVEHDR))
return MMSYSERR_INVALPARAM;
if(lpWaveOutHdr->dwFlags & WHDR_PREPARED)
return MMSYSERR_NOERROR;
return WINMM_PrepareHeader((HWAVE)hWaveOut, lpWaveOutHdr);
}
/**************************************************************************
* waveOutUnprepareHeader [WINMM.@]
*/
UINT WINAPI waveOutUnprepareHeader(HWAVEOUT hWaveOut,
LPWAVEHDR lpWaveOutHdr, UINT uSize)
{
TRACE("(%p, %p, %u)\n", hWaveOut, lpWaveOutHdr, uSize);
if(!lpWaveOutHdr || uSize < sizeof(WAVEHDR))
return MMSYSERR_INVALPARAM;
if(lpWaveOutHdr->dwFlags & WHDR_INQUEUE)
return WAVERR_STILLPLAYING;
if(!(lpWaveOutHdr->dwFlags & WHDR_PREPARED))
return MMSYSERR_NOERROR;
return WINMM_UnprepareHeader((HWAVE)hWaveOut, lpWaveOutHdr);
}
/**************************************************************************
* waveOutWrite [WINMM.@]
*/
UINT WINAPI waveOutWrite(HWAVEOUT hWaveOut, WAVEHDR *header, UINT uSize)
{
WINMM_Device *device;
MMRESULT mr;
TRACE("(%p, %p, %u)\n", hWaveOut, header, uSize);
device = WINMM_GetDeviceFromHWAVE((HWAVE)hWaveOut);
if(!WINMM_ValidateAndLock(device))
return MMSYSERR_INVALHANDLE;
if(!header->lpData || !(header->dwFlags & WHDR_PREPARED)){
LeaveCriticalSection(&device->lock);
return WAVERR_UNPREPARED;
}
if(header->dwFlags & WHDR_INQUEUE){
LeaveCriticalSection(&device->lock);
return WAVERR_STILLPLAYING;
}
TRACE("dwBufferLength: %u\n", header->dwBufferLength);
if(device->acm_handle){
ACMSTREAMHEADER *ash = (ACMSTREAMHEADER*)header->reserved;
ash->cbSrcLength = header->dwBufferLength;
mr = acmStreamConvert(device->acm_handle, ash, 0);
if(mr != MMSYSERR_NOERROR){
LeaveCriticalSection(&device->lock);
return mr;
}
}
if(device->first){
device->last->lpNext = header;
device->last = header;
if(!device->playing)
device->playing = header;
}else{
device->playing = device->first = device->last = header;
if(header->dwFlags & WHDR_BEGINLOOP){
device->loop_counter = header->dwLoops;
device->loop_start = header;
}
}
header->lpNext = NULL;
header->dwFlags &= ~WHDR_DONE;
header->dwFlags |= WHDR_INQUEUE;
mr = WINMM_BeginPlaying(device);
LeaveCriticalSection(&device->lock);
return mr;
}
/**************************************************************************
* waveOutBreakLoop [WINMM.@]
*/
UINT WINAPI waveOutBreakLoop(HWAVEOUT hWaveOut)
{
WINMM_Device *device;
TRACE("(%p)\n", hWaveOut);
device = WINMM_GetDeviceFromHWAVE((HWAVE)hWaveOut);
if(!WINMM_ValidateAndLock(device))
return MMSYSERR_INVALHANDLE;
device->loop_counter = 0;
LeaveCriticalSection(&device->lock);
return MMSYSERR_NOERROR;
}
/**************************************************************************
* waveOutPause [WINMM.@]
*/
UINT WINAPI waveOutPause(HWAVEOUT hWaveOut)
{
TRACE("(%p)\n", hWaveOut);
return WINMM_Pause((HWAVE)hWaveOut);
}
/**************************************************************************
* waveOutReset [WINMM.@]
*/
UINT WINAPI waveOutReset(HWAVEOUT hWaveOut)
{
TRACE("(%p)\n", hWaveOut);
return WINMM_Reset((HWAVE)hWaveOut);
}
/**************************************************************************
* waveOutRestart [WINMM.@]
*/
UINT WINAPI waveOutRestart(HWAVEOUT hWaveOut)
{
WINMM_Device *device;
MMRESULT mr;
TRACE("(%p)\n", hWaveOut);
device = WINMM_GetDeviceFromHWAVE((HWAVE)hWaveOut);
if(!WINMM_ValidateAndLock(device))
return MMSYSERR_INVALHANDLE;
device->stopped = TRUE;
mr = WINMM_BeginPlaying(device);
LeaveCriticalSection(&device->lock);
return mr;
}
/**************************************************************************
* waveOutGetPosition [WINMM.@]
*/
UINT WINAPI waveOutGetPosition(HWAVEOUT hWaveOut, LPMMTIME lpTime,
UINT uSize)
{
TRACE("(%p, %p, %u)\n", hWaveOut, lpTime, uSize);
if(!uSize || !lpTime || uSize != sizeof(MMTIME))
return MMSYSERR_INVALPARAM;
return WINMM_GetPosition((HWAVE)hWaveOut, lpTime);
}
/**************************************************************************
* waveOutGetPitch [WINMM.@]
*/
UINT WINAPI waveOutGetPitch(HWAVEOUT hWaveOut, LPDWORD lpdw)
{
TRACE("(%p, %p)\n", hWaveOut, lpdw);
return MMSYSERR_NOTSUPPORTED;
}
/**************************************************************************
* waveOutSetPitch [WINMM.@]
*/
UINT WINAPI waveOutSetPitch(HWAVEOUT hWaveOut, DWORD dw)
{
TRACE("(%p, %08x)\n", hWaveOut, dw);
return MMSYSERR_NOTSUPPORTED;
}
/**************************************************************************
* waveOutGetPlaybackRate [WINMM.@]
*/
UINT WINAPI waveOutGetPlaybackRate(HWAVEOUT hWaveOut, LPDWORD lpdw)
{
TRACE("(%p, %p)\n", hWaveOut, lpdw);
return MMSYSERR_NOTSUPPORTED;
}
/**************************************************************************
* waveOutSetPlaybackRate [WINMM.@]
*/
UINT WINAPI waveOutSetPlaybackRate(HWAVEOUT hWaveOut, DWORD dw)
{
TRACE("(%p, %08x)\n", hWaveOut, dw);
return MMSYSERR_NOTSUPPORTED;
}
/**************************************************************************
* waveOutGetVolume [WINMM.@]
*/
UINT WINAPI waveOutGetVolume(HWAVEOUT hWaveOut, DWORD *out)
{
WINMM_Device *device;
UINT32 channels;
float *vols;
HRESULT hr;
TRACE("(%p, %p)\n", hWaveOut, out);
if(!out)
return MMSYSERR_INVALPARAM;
device = WINMM_GetDeviceFromHWAVE((HWAVE)hWaveOut);
if(!WINMM_ValidateAndLock(device))
return MMSYSERR_INVALHANDLE;
hr = IAudioStreamVolume_GetChannelCount(device->volume, &channels);
if(FAILED(hr)){
LeaveCriticalSection(&device->lock);
WARN("GetChannelCount failed: %08x\n", hr);
return MMSYSERR_ERROR;
}
vols = HeapAlloc(GetProcessHeap(), 0, sizeof(float) * channels);
if(!vols){
LeaveCriticalSection(&device->lock);
return MMSYSERR_NOMEM;
}
hr = IAudioStreamVolume_GetAllVolumes(device->volume, channels, vols);
if(FAILED(hr)){
LeaveCriticalSection(&device->lock);
HeapFree(GetProcessHeap(), 0, vols);
WARN("GetAllVolumes failed: %08x\n", hr);
return MMSYSERR_ERROR;
}
LeaveCriticalSection(&device->lock);
*out = ((UINT16)(vols[0] * (DWORD)0xFFFF));
if(channels > 1)
*out |= ((UINT16)(vols[1] * (DWORD)0xFFFF)) << 16;
HeapFree(GetProcessHeap(), 0, vols);
return MMSYSERR_NOERROR;
}
/**************************************************************************
* waveOutSetVolume [WINMM.@]
*/
UINT WINAPI waveOutSetVolume(HWAVEOUT hWaveOut, DWORD in)
{
WINMM_Device *device;
UINT32 channels;
float *vols;
HRESULT hr;
TRACE("(%p, %08x)\n", hWaveOut, in);
device = WINMM_GetDeviceFromHWAVE((HWAVE)hWaveOut);
if(!WINMM_ValidateAndLock(device))
return MMSYSERR_INVALHANDLE;
hr = IAudioStreamVolume_GetChannelCount(device->volume, &channels);
if(FAILED(hr)){
LeaveCriticalSection(&device->lock);
WARN("GetChannelCount failed: %08x\n", hr);
return MMSYSERR_ERROR;
}
vols = HeapAlloc(GetProcessHeap(), 0, sizeof(float) * channels);
if(!vols){
LeaveCriticalSection(&device->lock);
return MMSYSERR_NOMEM;
}
hr = IAudioStreamVolume_GetAllVolumes(device->volume, channels, vols);
if(FAILED(hr)){
LeaveCriticalSection(&device->lock);
HeapFree(GetProcessHeap(), 0, vols);
WARN("GetAllVolumes failed: %08x\n", hr);
return MMSYSERR_ERROR;
}
vols[0] = (float)((DWORD)(in & 0xFFFF) / (float)0xFFFF);
if(channels > 1)
vols[1] = (float)((DWORD)(in >> 16) / (float)0xFFFF);
hr = IAudioStreamVolume_SetAllVolumes(device->volume, channels, vols);
if(FAILED(hr)){
LeaveCriticalSection(&device->lock);
HeapFree(GetProcessHeap(), 0, vols);
WARN("SetAllVolumes failed: %08x\n", hr);
return MMSYSERR_ERROR;
}
LeaveCriticalSection(&device->lock);
HeapFree(GetProcessHeap(), 0, vols);
return MMSYSERR_NOERROR;
}
/**************************************************************************
* waveOutGetID [WINMM.@]
*/
UINT WINAPI waveOutGetID(HWAVEOUT hWaveOut, UINT* lpuDeviceID)
{
WINMM_Device *device;
UINT dev, junk;
BOOL is_out;
TRACE("(%p, %p)\n", hWaveOut, lpuDeviceID);
if(!lpuDeviceID)
return MMSYSERR_INVALPARAM;
device = WINMM_GetDeviceFromHWAVE((HWAVE)hWaveOut);
if(!WINMM_ValidateAndLock(device))
return MMSYSERR_INVALHANDLE;
LeaveCriticalSection(&device->lock);
WINMM_DecomposeHWAVE((HWAVE)hWaveOut, lpuDeviceID, &is_out, &dev, &junk);
return MMSYSERR_NOERROR;
}
static UINT WINMM_QueryInstanceIDSize(UINT device, DWORD_PTR *len, BOOL is_out)
{
UINT count;
WINMM_MMDevice **devices;
TRACE("(%u, %p, %d)\n", device, len, is_out);
if(is_out){
count = g_outmmdevices_count;
devices = g_out_map;
}else{
count = g_inmmdevices_count;
devices = g_in_map;
}
if(device >= count)
return MMSYSERR_INVALHANDLE;
EnterCriticalSection(&g_devthread_lock);
*len = (lstrlenW(devices[device]->dev_id) + 1) * sizeof(WCHAR);
LeaveCriticalSection(&g_devthread_lock);
return MMSYSERR_NOERROR;
}
static UINT WINMM_QueryInstanceID(UINT device, WCHAR *str, DWORD_PTR len,
BOOL is_out)
{
UINT count, id_len;
WINMM_MMDevice **devices;
TRACE("(%u, %p, %d)\n", device, str, is_out);
if(is_out){
count = g_outmmdevices_count;
devices = g_out_map;
}else{
count = g_inmmdevices_count;
devices = g_in_map;
}
if(device >= count)
return MMSYSERR_INVALHANDLE;
EnterCriticalSection(&g_devthread_lock);
id_len = (lstrlenW(devices[device]->dev_id) + 1) * sizeof(WCHAR);
if(len < id_len){
LeaveCriticalSection(&g_devthread_lock);
return MMSYSERR_ERROR;
}
memcpy(str, devices[device]->dev_id, id_len);
LeaveCriticalSection(&g_devthread_lock);
return MMSYSERR_NOERROR;
}
static UINT get_device_interface(UINT msg, BOOL is_out, UINT index, WCHAR *out, ULONG *out_len)
{
WINMM_QueryInterfaceInfo info;
UINT ret;
if(!WINMM_StartDevicesThread())
return MMSYSERR_NODRIVER;
info.is_out = is_out;
info.index = index;
info.str = out;
info.len_bytes = out_len;
ret = SendMessageW(g_devices_hwnd, msg, (DWORD_PTR)&info, 0);
InterlockedDecrement(&g_devthread_token);
return ret;
}
/**************************************************************************
* waveOutMessage [WINMM.@]
*/
UINT WINAPI waveOutMessage(HWAVEOUT hWaveOut, UINT uMessage,
DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
TRACE("(%p, %u, %lx, %lx)\n", hWaveOut, uMessage, dwParam1, dwParam2);
switch(uMessage){
case DRV_QUERYFUNCTIONINSTANCEIDSIZE:
return WINMM_QueryInstanceIDSize(HandleToULong(hWaveOut),
(DWORD_PTR*)dwParam1, TRUE);
case DRV_QUERYFUNCTIONINSTANCEID:
return WINMM_QueryInstanceID(HandleToULong(hWaveOut), (WCHAR*)dwParam1, dwParam2, TRUE);
case DRV_QUERYDEVICEINTERFACESIZE:
return get_device_interface(DRV_QUERYDEVICEINTERFACESIZE, TRUE, HandleToULong(hWaveOut),
NULL, (ULONG*)dwParam1);
case DRV_QUERYDEVICEINTERFACE:
{
ULONG size = dwParam2;
return get_device_interface(DRV_QUERYDEVICEINTERFACE, TRUE, HandleToULong(hWaveOut),
(WCHAR*)dwParam1, &size);
}
case DRV_QUERYMAPPABLE:
return MMSYSERR_NOERROR;
case DRVM_MAPPER_PREFERRED_GET:
if(dwParam1) {
if(g_outmmdevices_count > 0)
/* Device 0 is always the default device */
*(DWORD *)dwParam1 = 0;
else
*(DWORD *)dwParam1 = -1;
}
if(dwParam2)
/* Status flags */
*(DWORD *)dwParam2 = 0;
return MMSYSERR_NOERROR;
}
TRACE("Message not supported: %u\n", uMessage);
return MMSYSERR_NOTSUPPORTED;
}
/**************************************************************************
* waveInGetNumDevs [WINMM.@]
*/
UINT WINAPI waveInGetNumDevs(void)
{
HRESULT hr = WINMM_InitMMDevices();
if(FAILED(hr))
return 0;
TRACE("count: %u\n", g_inmmdevices_count);
return g_inmmdevices_count;
}
/**************************************************************************
* waveInGetDevCapsW [WINMM.@]
*/
UINT WINAPI waveInGetDevCapsW(UINT_PTR uDeviceID, LPWAVEINCAPSW lpCaps, UINT uSize)
{
WAVEINCAPSW mapper_caps, *caps;
HRESULT hr;
TRACE("(%lu, %p, %u)\n", uDeviceID, lpCaps, uSize);
hr = WINMM_InitMMDevices();
if(FAILED(hr))
return MMSYSERR_NODRIVER;
if(!lpCaps)
return MMSYSERR_INVALPARAM;
if(WINMM_IsMapper(uDeviceID)){
/* FIXME: Should be localized */
static const WCHAR mapper_pnameW[] = {'W','i','n','e',' ','S','o','u',
'n','d',' ','M','a','p','p','e','r',0};
mapper_caps.wMid = 0xFF;
mapper_caps.wPid = 0xFF;
mapper_caps.vDriverVersion = 0x00010001;
mapper_caps.dwFormats = 0xFFFFFFFF;
mapper_caps.wReserved1 = 0;
mapper_caps.wChannels = 2;
lstrcpyW(mapper_caps.szPname, mapper_pnameW);
caps = &mapper_caps;
}else{
if(uDeviceID >= g_inmmdevices_count)
return MMSYSERR_BADDEVICEID;
caps = &read_map(g_in_map, uDeviceID)->in_caps;
}
memcpy(lpCaps, caps, min(uSize, sizeof(*lpCaps)));
return MMSYSERR_NOERROR;
}
/**************************************************************************
* waveInGetDevCapsA [WINMM.@]
*/
UINT WINAPI waveInGetDevCapsA(UINT_PTR uDeviceID, LPWAVEINCAPSA lpCaps, UINT uSize)
{
UINT ret;
WAVEINCAPSW wicW;
TRACE("(%lu, %p, %u)\n", uDeviceID, lpCaps, uSize);
if(!lpCaps)
return MMSYSERR_INVALPARAM;
ret = waveInGetDevCapsW(uDeviceID, &wicW, sizeof(wicW));
if (ret == MMSYSERR_NOERROR) {
WAVEINCAPSA wicA;
wicA.wMid = wicW.wMid;
wicA.wPid = wicW.wPid;
wicA.vDriverVersion = wicW.vDriverVersion;
WideCharToMultiByte( CP_ACP, 0, wicW.szPname, -1, wicA.szPname,
sizeof(wicA.szPname), NULL, NULL );
wicA.dwFormats = wicW.dwFormats;
wicA.wChannels = wicW.wChannels;
memcpy(lpCaps, &wicA, min(uSize, sizeof(wicA)));
}
return ret;
}
/**************************************************************************
* waveInOpen [WINMM.@]
*/
MMRESULT WINAPI waveInOpen(HWAVEIN* lphWaveIn, UINT uDeviceID,
LPCWAVEFORMATEX lpFormat, DWORD_PTR dwCallback,
DWORD_PTR dwInstance, DWORD dwFlags)
{
LRESULT res;
WINMM_OpenInfo info;
WINMM_CBInfo cb_info;
TRACE("(%p, %x, %p, %lx, %lx, %08x)\n", lphWaveIn, uDeviceID, lpFormat,
dwCallback, dwInstance, dwFlags);
if(!lphWaveIn && !(dwFlags & WAVE_FORMAT_QUERY))
return MMSYSERR_INVALPARAM;
res = WINMM_CheckCallback(dwCallback, dwFlags, FALSE);
if(res != MMSYSERR_NOERROR)
return res;
if(!WINMM_StartDevicesThread())
return MMSYSERR_NODRIVER;
info.handle = 0;
info.format = (WAVEFORMATEX*)lpFormat;
info.callback = dwCallback;
info.cb_user = dwInstance;
info.req_device = uDeviceID;
info.flags = dwFlags;
info.reset = TRUE;
res = SendMessageW(g_devices_hwnd, WIDM_OPEN, (DWORD_PTR)&info, 0);
InterlockedDecrement(&g_devthread_token);
if(res != MMSYSERR_NOERROR || (dwFlags & WAVE_FORMAT_QUERY))
return res;
if(lphWaveIn)
*lphWaveIn = (HWAVEIN)info.handle;
cb_info.flags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
cb_info.callback = dwCallback;
cb_info.user = dwInstance;
cb_info.hwave = info.handle;
WINMM_NotifyClient(&cb_info, WIM_OPEN, 0, 0);
return res;
}
/**************************************************************************
* waveInClose [WINMM.@]
*/
UINT WINAPI waveInClose(HWAVEIN hWaveIn)
{
WINMM_Device *device;
WINMM_CBInfo cb_info;
UINT res;
TRACE("(%p)\n", hWaveIn);
device = WINMM_GetDeviceFromHWAVE((HWAVE)hWaveIn);
if(!WINMM_ValidateAndLock(device))
return MMSYSERR_INVALHANDLE;
cb_info = device->cb_info;
LeaveCriticalSection(&device->lock);
res = SendMessageW(g_devices_hwnd, WIDM_CLOSE, (WPARAM)hWaveIn, 0);
if(res == MMSYSERR_NOERROR)
WINMM_NotifyClient(&cb_info, WIM_CLOSE, 0, 0);
return res;
}
/**************************************************************************
* waveInPrepareHeader [WINMM.@]
*/
UINT WINAPI waveInPrepareHeader(HWAVEIN hWaveIn, WAVEHDR* lpWaveInHdr,
UINT uSize)
{
TRACE("(%p, %p, %u)\n", hWaveIn, lpWaveInHdr, uSize);
if(!lpWaveInHdr || uSize < sizeof(WAVEHDR))
return MMSYSERR_INVALPARAM;
if(lpWaveInHdr->dwFlags & WHDR_PREPARED)
return MMSYSERR_NOERROR;
return WINMM_PrepareHeader((HWAVE)hWaveIn, lpWaveInHdr);
}
/**************************************************************************
* waveInUnprepareHeader [WINMM.@]
*/
UINT WINAPI waveInUnprepareHeader(HWAVEIN hWaveIn, WAVEHDR* lpWaveInHdr,
UINT uSize)
{
TRACE("(%p, %p, %u)\n", hWaveIn, lpWaveInHdr, uSize);
if(!lpWaveInHdr || uSize < sizeof(WAVEHDR))
return MMSYSERR_INVALPARAM;
if(lpWaveInHdr->dwFlags & WHDR_INQUEUE)
return WAVERR_STILLPLAYING;
if(!(lpWaveInHdr->dwFlags & WHDR_PREPARED))
return MMSYSERR_NOERROR;
return WINMM_UnprepareHeader((HWAVE)hWaveIn, lpWaveInHdr);
}
/**************************************************************************
* waveInAddBuffer [WINMM.@]
*/
UINT WINAPI waveInAddBuffer(HWAVEIN hWaveIn, WAVEHDR *header, UINT uSize)
{
WINMM_Device *device;
TRACE("(%p, %p, %u)\n", hWaveIn, header, uSize);
if(!header || uSize < sizeof(WAVEHDR))
return MMSYSERR_INVALPARAM;
if(!(header->dwFlags & WHDR_PREPARED))
return WAVERR_UNPREPARED;
device = WINMM_GetDeviceFromHWAVE((HWAVE)hWaveIn);
if(!WINMM_ValidateAndLock(device))
return MMSYSERR_INVALHANDLE;
if(!device->first)
device->first = device->last = header;
else{
device->last->lpNext = header;
device->last = header;
}
header->dwBytesRecorded = 0;
header->lpNext = NULL;
header->dwFlags &= ~WHDR_DONE;
header->dwFlags |= WHDR_INQUEUE;
LeaveCriticalSection(&device->lock);
return MMSYSERR_NOERROR;
}
/**************************************************************************
* waveInReset [WINMM.@]
*/
UINT WINAPI waveInReset(HWAVEIN hWaveIn)
{
TRACE("(%p)\n", hWaveIn);
return WINMM_Reset((HWAVE)hWaveIn);
}
/**************************************************************************
* waveInStart [WINMM.@]
*/
UINT WINAPI waveInStart(HWAVEIN hWaveIn)
{
WINMM_Device *device;
MMRESULT mr;
TRACE("(%p)\n", hWaveIn);
device = WINMM_GetDeviceFromHWAVE((HWAVE)hWaveIn);
if(!WINMM_ValidateAndLock(device))
return MMSYSERR_INVALHANDLE;
mr = WINMM_BeginPlaying(device);
LeaveCriticalSection(&device->lock);
return mr;
}
/**************************************************************************
* waveInStop [WINMM.@]
*/
UINT WINAPI waveInStop(HWAVEIN hWaveIn)
{
WINMM_CBInfo cb_info;
WINMM_Device *device;
WAVEHDR *buf;
HRESULT hr;
TRACE("(%p)\n", hWaveIn);
device = WINMM_GetDeviceFromHWAVE((HWAVE)hWaveIn);
if(!WINMM_ValidateAndLock(device))
return MMSYSERR_INVALHANDLE;
hr = WINMM_Pause((HWAVE)hWaveIn);
if(FAILED(hr)){
LeaveCriticalSection(&device->lock);
return MMSYSERR_ERROR;
}
device->stopped = TRUE;
buf = device->first;
if(buf && buf->dwBytesRecorded > 0){
device->first = buf->lpNext;
}else
buf = NULL;
cb_info = device->cb_info;
LeaveCriticalSection(&device->lock);
if(buf){
buf->dwFlags &= ~WHDR_INQUEUE;
buf->dwFlags |= WHDR_DONE;
WINMM_NotifyClient(&cb_info, WIM_DATA, (DWORD_PTR)buf, 0);
}
return MMSYSERR_NOERROR;
}
/**************************************************************************
* waveInGetPosition [WINMM.@]
*/
UINT WINAPI waveInGetPosition(HWAVEIN hWaveIn, LPMMTIME lpTime,
UINT uSize)
{
TRACE("(%p, %p, %u)\n", hWaveIn, lpTime, uSize);
if(!uSize || !lpTime || uSize != sizeof(MMTIME))
return MMSYSERR_INVALPARAM;
return WINMM_GetPosition((HWAVE)hWaveIn, lpTime);
}
/**************************************************************************
* waveInGetID [WINMM.@]
*/
UINT WINAPI waveInGetID(HWAVEIN hWaveIn, UINT* lpuDeviceID)
{
UINT dev, junk;
BOOL is_out;
WINMM_Device *device;
TRACE("(%p, %p)\n", hWaveIn, lpuDeviceID);
if(!lpuDeviceID)
return MMSYSERR_INVALPARAM;
device = WINMM_GetDeviceFromHWAVE((HWAVE)hWaveIn);
if(!WINMM_ValidateAndLock(device))
return MMSYSERR_INVALHANDLE;
LeaveCriticalSection(&device->lock);
WINMM_DecomposeHWAVE((HWAVE)hWaveIn, lpuDeviceID, &is_out, &dev, &junk);
return MMSYSERR_NOERROR;
}
/**************************************************************************
* waveInMessage [WINMM.@]
*/
UINT WINAPI waveInMessage(HWAVEIN hWaveIn, UINT uMessage,
DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
TRACE("(%p, %u, %ld, %ld)\n", hWaveIn, uMessage, dwParam1, dwParam2);
switch(uMessage){
case DRV_QUERYFUNCTIONINSTANCEIDSIZE:
return WINMM_QueryInstanceIDSize(HandleToULong(hWaveIn),
(DWORD_PTR*)dwParam1, FALSE);
case DRV_QUERYFUNCTIONINSTANCEID:
return WINMM_QueryInstanceID(HandleToULong(hWaveIn), (WCHAR*)dwParam1, dwParam2, FALSE);
case DRV_QUERYDEVICEINTERFACESIZE:
return get_device_interface(DRV_QUERYDEVICEINTERFACESIZE, FALSE, HandleToULong(hWaveIn),
NULL, (ULONG*)dwParam1);
case DRV_QUERYDEVICEINTERFACE:
{
ULONG size = dwParam2;
return get_device_interface(DRV_QUERYDEVICEINTERFACE, FALSE, HandleToULong(hWaveIn),
(WCHAR*)dwParam1, &size);
}
case DRV_QUERYMAPPABLE:
return MMSYSERR_NOERROR;
case DRVM_MAPPER_PREFERRED_GET:
if(dwParam1) {
if(g_inmmdevices_count > 0)
/* Device 0 is always the default device */
*(DWORD *)dwParam1 = 0;
else
*(DWORD *)dwParam1 = -1;
}
if(dwParam2)
/* Status flags */
*(DWORD *)dwParam2 = 0;
return MMSYSERR_NOERROR;
}
TRACE("Message not supported: %u\n", uMessage);
return MMSYSERR_NOTSUPPORTED;
}
UINT WINAPI mixerGetNumDevs(void)
{
HRESULT hr;
TRACE("\n");
hr = WINMM_InitMMDevices();
if(FAILED(hr))
return 0;
return g_outmmdevices_count + g_inmmdevices_count;
}
/**************************************************************************
* mixerGetDevCapsA [WINMM.@]
*/
UINT WINAPI mixerGetDevCapsA(UINT_PTR uDeviceID, LPMIXERCAPSA lpCaps, UINT uSize)
{
MIXERCAPSW micW;
UINT ret;
TRACE("(%lu, %p, %u)\n", uDeviceID, lpCaps, uSize);
if(!lpCaps)
return MMSYSERR_INVALPARAM;
ret = mixerGetDevCapsW(uDeviceID, &micW, sizeof(micW));
if (ret == MMSYSERR_NOERROR) {
MIXERCAPSA micA;
micA.wMid = micW.wMid;
micA.wPid = micW.wPid;
micA.vDriverVersion = micW.vDriverVersion;
WideCharToMultiByte( CP_ACP, 0, micW.szPname, -1, micA.szPname,
sizeof(micA.szPname), NULL, NULL );
micA.fdwSupport = micW.fdwSupport;
micA.cDestinations = micW.cDestinations;
memcpy(lpCaps, &micA, min(uSize, sizeof(micA)));
}
return ret;
}
/**************************************************************************
* mixerGetDevCapsW [WINMM.@]
*/
UINT WINAPI mixerGetDevCapsW(UINT_PTR uDeviceID, LPMIXERCAPSW lpCaps, UINT uSize)
{
WINMM_MMDevice *mmdevice;
MIXERCAPSW caps;
HRESULT hr;
TRACE("(%lu, %p, %u)\n", uDeviceID, lpCaps, uSize);
hr = WINMM_InitMMDevices();
if(FAILED(hr))
return MMSYSERR_NODRIVER;
if(!lpCaps)
return MMSYSERR_INVALPARAM;
if(!uSize)
return MMSYSERR_NOERROR;
if(uDeviceID >= g_outmmdevices_count + g_inmmdevices_count)
return MMSYSERR_BADDEVICEID;
if(uDeviceID < g_outmmdevices_count){
mmdevice = read_map(g_out_map, uDeviceID);
memcpy(caps.szPname, mmdevice->out_caps.szPname, sizeof(caps.szPname));
}else{
mmdevice = read_map(g_in_map, uDeviceID - g_outmmdevices_count);
memcpy(caps.szPname, mmdevice->in_caps.szPname, sizeof(caps.szPname));
}
caps.wMid = 0xFF;
caps.wPid = 0xFF;
caps.vDriverVersion = 0x00010001;
caps.fdwSupport = 0;
caps.cDestinations = 1;
memcpy(lpCaps, &caps, uSize);
return MMSYSERR_NOERROR;
}
/**************************************************************************
* mixerOpen [WINMM.@]
*/
UINT WINAPI mixerOpen(LPHMIXER lphMix, UINT uDeviceID, DWORD_PTR dwCallback,
DWORD_PTR dwInstance, DWORD fdwOpen)
{
WINMM_MMDevice *mmdevice;
MMRESULT mr;
HRESULT hr;
TRACE("(%p, %d, %lx, %lx, %x)\n", lphMix, uDeviceID, dwCallback,
dwInstance, fdwOpen);
hr = WINMM_InitMMDevices();
if(FAILED(hr))
return MMSYSERR_NODRIVER;
if(!lphMix)
return MMSYSERR_INVALPARAM;
mr = WINMM_CheckCallback(dwCallback, fdwOpen, TRUE);
if(mr != MMSYSERR_NOERROR)
return mr;
if(uDeviceID >= g_outmmdevices_count + g_inmmdevices_count)
return MMSYSERR_BADDEVICEID;
if(uDeviceID < g_outmmdevices_count){
mmdevice = read_map(g_out_map, uDeviceID);
*lphMix = (HMIXER)WINMM_MakeHWAVE(uDeviceID, TRUE,
mmdevice->mixer_count);
}else{
mmdevice = read_map(g_in_map, uDeviceID - g_outmmdevices_count);
*lphMix = (HMIXER)WINMM_MakeHWAVE(uDeviceID - g_outmmdevices_count,
FALSE, mmdevice->mixer_count);
}
++mmdevice->mixer_count;
return MMSYSERR_NOERROR;
}
/**************************************************************************
* mixerClose [WINMM.@]
*/
UINT WINAPI mixerClose(HMIXER hMix)
{
TRACE("(%p)\n", hMix);
return MMSYSERR_NOERROR;
}
/**************************************************************************
* mixerGetID [WINMM.@]
*/
UINT WINAPI mixerGetID(HMIXEROBJ hmix, LPUINT lpid, DWORD fdwID)
{
WINMM_MMDevice *mmdevice;
HRESULT hr;
TRACE("(%p, %p, %x)\n", hmix, lpid, fdwID);
hr = WINMM_InitMMDevices();
if(FAILED(hr))
return MMSYSERR_NODRIVER;
if(!lpid)
return MMSYSERR_INVALPARAM;
mmdevice = WINMM_GetMixerMMDevice(hmix, fdwID, lpid);
if(!mmdevice)
return MMSYSERR_INVALHANDLE;
if(mmdevice->in_caps.szPname[0] != '\0')
*lpid += g_outmmdevices_count;
return MMSYSERR_NOERROR;
}
/**************************************************************************
* mixerGetControlDetailsW [WINMM.@]
*/
UINT WINAPI mixerGetControlDetailsW(HMIXEROBJ hmix, LPMIXERCONTROLDETAILS lpmcdW,
DWORD fdwDetails)
{
WINMM_ControlDetails details;
HRESULT hr;
TRACE("(%p, %p, %x)\n", hmix, lpmcdW, fdwDetails);
hr = WINMM_InitMMDevices();
if(FAILED(hr))
return MMSYSERR_NODRIVER;
if(!lpmcdW)
return MMSYSERR_INVALPARAM;
TRACE("dwControlID: %u\n", lpmcdW->dwControlID);
details.hmix = hmix;
details.details = lpmcdW;
details.flags = fdwDetails;
return SendMessageW(g_devices_hwnd, MXDM_GETCONTROLDETAILS,
(DWORD_PTR)&details, 0);
}
/**************************************************************************
* mixerGetControlDetailsA [WINMM.@]
*/
UINT WINAPI mixerGetControlDetailsA(HMIXEROBJ hmix, LPMIXERCONTROLDETAILS lpmcdA,
DWORD fdwDetails)
{
UINT ret = MMSYSERR_NOTSUPPORTED;
TRACE("(%p, %p, %08x)\n", hmix, lpmcdA, fdwDetails);
if (lpmcdA == NULL || lpmcdA->cbStruct != sizeof(*lpmcdA))
return MMSYSERR_INVALPARAM;
switch (fdwDetails & MIXER_GETCONTROLDETAILSF_QUERYMASK) {
case MIXER_GETCONTROLDETAILSF_VALUE:
/* can safely use A structure as it is, no string inside */
ret = mixerGetControlDetailsW(hmix, lpmcdA, fdwDetails);
break;
case MIXER_GETCONTROLDETAILSF_LISTTEXT:
{
MIXERCONTROLDETAILS_LISTTEXTA *pDetailsA = lpmcdA->paDetails;
MIXERCONTROLDETAILS_LISTTEXTW *pDetailsW;
int size = max(1, lpmcdA->cChannels) * sizeof(MIXERCONTROLDETAILS_LISTTEXTW);
unsigned int i;
if (lpmcdA->u.cMultipleItems != 0) {
size *= lpmcdA->u.cMultipleItems;
}
pDetailsW = HeapAlloc(GetProcessHeap(), 0, size);
lpmcdA->paDetails = pDetailsW;
lpmcdA->cbDetails = sizeof(MIXERCONTROLDETAILS_LISTTEXTW);
/* set up lpmcd->paDetails */
ret = mixerGetControlDetailsW(hmix, lpmcdA, fdwDetails);
/* copy from lpmcd->paDetails back to paDetailsW; */
if (ret == MMSYSERR_NOERROR) {
for (i = 0; i < lpmcdA->u.cMultipleItems * lpmcdA->cChannels; i++) {
pDetailsA->dwParam1 = pDetailsW->dwParam1;
pDetailsA->dwParam2 = pDetailsW->dwParam2;
WideCharToMultiByte( CP_ACP, 0, pDetailsW->szName, -1,
pDetailsA->szName,
sizeof(pDetailsA->szName), NULL, NULL );
pDetailsA++;
pDetailsW++;
}
pDetailsA -= lpmcdA->u.cMultipleItems * lpmcdA->cChannels;
pDetailsW -= lpmcdA->u.cMultipleItems * lpmcdA->cChannels;
}
HeapFree(GetProcessHeap(), 0, pDetailsW);
lpmcdA->paDetails = pDetailsA;
lpmcdA->cbDetails = sizeof(MIXERCONTROLDETAILS_LISTTEXTA);
}
break;
default:
WARN("Unsupported fdwDetails=0x%08x\n", fdwDetails);
}
return ret;
}
/**************************************************************************
* mixerGetLineControlsA [WINMM.@]
*/
UINT WINAPI mixerGetLineControlsA(HMIXEROBJ hmix, LPMIXERLINECONTROLSA lpmlcA,
DWORD fdwControls)
{
MIXERLINECONTROLSW mlcW;
DWORD ret;
unsigned int i;
TRACE("(%p, %p, %x)\n", hmix, lpmlcA, fdwControls);
if (lpmlcA == NULL || lpmlcA->cbStruct != sizeof(*lpmlcA) ||
lpmlcA->cbmxctrl != sizeof(MIXERCONTROLA))
return MMSYSERR_INVALPARAM;
mlcW.cbStruct = sizeof(mlcW);
mlcW.dwLineID = lpmlcA->dwLineID;
mlcW.u.dwControlID = lpmlcA->u.dwControlID;
mlcW.u.dwControlType = lpmlcA->u.dwControlType;
/* Debugging on Windows shows for MIXER_GETLINECONTROLSF_ONEBYTYPE only,
the control count is assumed to be 1 - This is relied upon by a game,
"Dynomite Deluze" */
if (MIXER_GETLINECONTROLSF_ONEBYTYPE == (fdwControls & MIXER_GETLINECONTROLSF_QUERYMASK)) {
mlcW.cControls = 1;
} else {
mlcW.cControls = lpmlcA->cControls;
}
mlcW.cbmxctrl = sizeof(MIXERCONTROLW);
mlcW.pamxctrl = HeapAlloc(GetProcessHeap(), 0,
mlcW.cControls * mlcW.cbmxctrl);
ret = mixerGetLineControlsW(hmix, &mlcW, fdwControls);
if (ret == MMSYSERR_NOERROR) {
lpmlcA->dwLineID = mlcW.dwLineID;
lpmlcA->u.dwControlID = mlcW.u.dwControlID;
lpmlcA->u.dwControlType = mlcW.u.dwControlType;
for (i = 0; i < mlcW.cControls; i++) {
lpmlcA->pamxctrl[i].cbStruct = sizeof(MIXERCONTROLA);
lpmlcA->pamxctrl[i].dwControlID = mlcW.pamxctrl[i].dwControlID;
lpmlcA->pamxctrl[i].dwControlType = mlcW.pamxctrl[i].dwControlType;
lpmlcA->pamxctrl[i].fdwControl = mlcW.pamxctrl[i].fdwControl;
lpmlcA->pamxctrl[i].cMultipleItems = mlcW.pamxctrl[i].cMultipleItems;
WideCharToMultiByte( CP_ACP, 0, mlcW.pamxctrl[i].szShortName, -1,
lpmlcA->pamxctrl[i].szShortName,
sizeof(lpmlcA->pamxctrl[i].szShortName), NULL, NULL );
WideCharToMultiByte( CP_ACP, 0, mlcW.pamxctrl[i].szName, -1,
lpmlcA->pamxctrl[i].szName,
sizeof(lpmlcA->pamxctrl[i].szName), NULL, NULL );
/* sizeof(lpmlcA->pamxctrl[i].Bounds) ==
* sizeof(mlcW.pamxctrl[i].Bounds) */
memcpy(&lpmlcA->pamxctrl[i].Bounds, &mlcW.pamxctrl[i].Bounds,
sizeof(mlcW.pamxctrl[i].Bounds));
/* sizeof(lpmlcA->pamxctrl[i].Metrics) ==
* sizeof(mlcW.pamxctrl[i].Metrics) */
memcpy(&lpmlcA->pamxctrl[i].Metrics, &mlcW.pamxctrl[i].Metrics,
sizeof(mlcW.pamxctrl[i].Metrics));
}
}
HeapFree(GetProcessHeap(), 0, mlcW.pamxctrl);
return ret;
}
static UINT WINMM_GetVolumeLineControl(WINMM_MMDevice *mmdevice, DWORD line,
MIXERCONTROLW *ctl, DWORD flags)
{
ctl->dwControlID = (line == 0xFFFF0000) ? 0 : 2;
ctl->dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
ctl->fdwControl = MIXERCONTROL_CONTROLF_UNIFORM;
ctl->cMultipleItems = 0;
lstrcpyW(ctl->szShortName, volumeW);
lstrcpyW(ctl->szName, volumeW);
ctl->Bounds.s1.dwMinimum = 0;
ctl->Bounds.s1.dwMaximum = 0xFFFF;
ctl->Metrics.cSteps = 192;
return MMSYSERR_NOERROR;
}
static UINT WINMM_GetMuteLineControl(WINMM_MMDevice *mmdevice, DWORD line,
MIXERCONTROLW *ctl, DWORD flags)
{
ctl->dwControlID = (line == 0xFFFF0000) ? 1 : 3;
ctl->dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE;
ctl->fdwControl = MIXERCONTROL_CONTROLF_UNIFORM;
ctl->cMultipleItems = 0;
lstrcpyW(ctl->szShortName, muteW);
lstrcpyW(ctl->szName, muteW);
ctl->Bounds.s1.dwMinimum = 0;
ctl->Bounds.s1.dwMaximum = 1;
ctl->Metrics.cSteps = 0;
return MMSYSERR_NOERROR;
}
/**************************************************************************
* mixerGetLineControlsW [WINMM.@]
*/
UINT WINAPI mixerGetLineControlsW(HMIXEROBJ hmix, LPMIXERLINECONTROLSW lpmlcW,
DWORD fdwControls)
{
WINMM_MMDevice *mmdevice;
HRESULT hr;
TRACE("(%p, %p, %08x)\n", hmix, lpmlcW, fdwControls);
hr = WINMM_InitMMDevices();
if(FAILED(hr))
return MMSYSERR_NODRIVER;
if(fdwControls & ~(MIXER_GETLINECONTROLSF_ALL |
MIXER_GETLINECONTROLSF_ONEBYID |
MIXER_GETLINECONTROLSF_ONEBYTYPE |
MIXER_OBJECTF_HMIXER |
MIXER_OBJECTF_MIXER)){
WARN("Unknown GetLineControls flag: %x\n", fdwControls);
return MMSYSERR_INVALFLAG;
}
if(!lpmlcW || lpmlcW->cbStruct < sizeof(*lpmlcW) || !lpmlcW->pamxctrl)
return MMSYSERR_INVALPARAM;
TRACE("dwLineID: %u\n", lpmlcW->dwLineID);
TRACE("dwControl: %x\n", lpmlcW->u.dwControlID);
TRACE("cControls: %u\n", lpmlcW->cControls);
mmdevice = WINMM_GetMixerMMDevice(hmix, fdwControls, NULL);
if(!mmdevice)
return MMSYSERR_INVALHANDLE;
switch(fdwControls & MIXER_GETLINECONTROLSF_QUERYMASK){
case MIXER_GETLINECONTROLSF_ALL:
if(lpmlcW->cControls != 2)
return MMSYSERR_INVALPARAM;
if(lpmlcW->cbmxctrl < sizeof(MIXERCONTROLW))
return MMSYSERR_INVALPARAM;
if(lpmlcW->dwLineID != 0 && lpmlcW->dwLineID != 0xFFFF0000)
return MIXERR_INVALLINE;
WINMM_GetVolumeLineControl(mmdevice, lpmlcW->dwLineID,
&lpmlcW->pamxctrl[0], fdwControls);
WINMM_GetMuteLineControl(mmdevice, lpmlcW->dwLineID,
&lpmlcW->pamxctrl[1], fdwControls);
return MMSYSERR_NOERROR;
case MIXER_GETLINECONTROLSF_ONEBYID:
if(lpmlcW->cControls != 1)
return MMSYSERR_INVALPARAM;
if(lpmlcW->cbmxctrl < sizeof(MIXERCONTROLW))
return MMSYSERR_INVALPARAM;
if(lpmlcW->dwLineID != 0 && lpmlcW->dwLineID != 0xFFFF0000)
return MIXERR_INVALLINE;
if(lpmlcW->u.dwControlID == 0)
return WINMM_GetVolumeLineControl(mmdevice, lpmlcW->dwLineID,
lpmlcW->pamxctrl, fdwControls);
if(lpmlcW->u.dwControlID == 1)
return WINMM_GetMuteLineControl(mmdevice, lpmlcW->dwLineID,
lpmlcW->pamxctrl, fdwControls);
return MMSYSERR_NOTSUPPORTED;
case MIXER_GETLINECONTROLSF_ONEBYTYPE:
if(lpmlcW->cControls != 1)
return MMSYSERR_INVALPARAM;
if(lpmlcW->cbmxctrl < sizeof(MIXERCONTROLW))
return MMSYSERR_INVALPARAM;
if(lpmlcW->dwLineID != 0 && lpmlcW->dwLineID != 0xFFFF0000)
return MIXERR_INVALLINE;
if(lpmlcW->u.dwControlType == MIXERCONTROL_CONTROLTYPE_VOLUME)
return WINMM_GetVolumeLineControl(mmdevice, lpmlcW->dwLineID,
lpmlcW->pamxctrl, fdwControls);
if(lpmlcW->u.dwControlType == MIXERCONTROL_CONTROLTYPE_MUTE)
return WINMM_GetMuteLineControl(mmdevice, lpmlcW->dwLineID,
lpmlcW->pamxctrl, fdwControls);
return MMSYSERR_NOTSUPPORTED;
}
return MMSYSERR_NOTSUPPORTED;
}
static UINT WINMM_GetSourceLineInfo(WINMM_MMDevice *mmdevice, UINT mmdev_index,
MIXERLINEW *info, DWORD flags)
{
BOOL is_out = TRUE;
if(mmdevice->in_caps.szPname[0] != '\0')
is_out = FALSE;
if(info->dwSource != 0)
return MIXERR_INVALLINE;
info->dwDestination = 0;
info->dwLineID = 0;
info->fdwLine = MIXERLINE_LINEF_ACTIVE | MIXERLINE_LINEF_SOURCE;
info->cConnections = 0;
info->cControls = 2;
/* volume & mute always affect all channels, so claim 1 channel */
info->cChannels = 1;
info->Target.dwDeviceID = mmdev_index;
info->Target.wMid = ~0;
info->Target.wPid = ~0;
info->Target.vDriverVersion = 0;
lstrcpyW(info->szShortName, volumeW);
lstrcpyW(info->szName, mastervolumeW);
if(is_out){
info->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT;
info->Target.dwType = MIXERLINE_TARGETTYPE_WAVEOUT;
memcpy(info->Target.szPname, mmdevice->out_caps.szPname,
sizeof(info->Target.szPname));
}else{
info->dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE;
info->Target.dwType = MIXERLINE_TARGETTYPE_UNDEFINED;
info->Target.szPname[0] = '\0';
}
return MMSYSERR_NOERROR;
}
static UINT WINMM_GetDestinationLineInfo(WINMM_MMDevice *mmdevice,
UINT mmdev_index, MIXERLINEW *info, DWORD flags)
{
BOOL is_out = TRUE;
if(mmdevice->in_caps.szPname[0] != '\0')
is_out = FALSE;
if(info->dwDestination != 0)
return MIXERR_INVALLINE;
info->dwSource = 0xFFFFFFFF;
info->dwLineID = 0xFFFF0000;
info->fdwLine = MIXERLINE_LINEF_ACTIVE;
info->cConnections = 1;
info->cControls = 2;
lstrcpyW(info->szShortName, volumeW);
lstrcpyW(info->szName, mastervolumeW);
info->Target.dwDeviceID = mmdev_index;
info->Target.wMid = ~0;
info->Target.wPid = ~0;
info->Target.vDriverVersion = 0;
if(is_out){
info->dwComponentType = MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
info->cChannels = mmdevice->out_caps.wChannels;
info->Target.dwType = MIXERLINE_TARGETTYPE_UNDEFINED;
info->Target.szPname[0] = '\0';
}else{
info->dwComponentType = MIXERLINE_COMPONENTTYPE_DST_WAVEIN;
info->cChannels = mmdevice->in_caps.wChannels;
info->Target.dwType = MIXERLINE_TARGETTYPE_WAVEIN;
memcpy(info->Target.szPname, mmdevice->in_caps.szPname,
sizeof(info->Target.szPname));
}
return MMSYSERR_NOERROR;
}
static UINT WINMM_GetComponentTypeLineInfo(WINMM_MMDevice *mmdevice,
UINT mmdev_index, MIXERLINEW *info, DWORD flags)
{
BOOL is_out = TRUE;
if(mmdevice->in_caps.szPname[0] != '\0')
is_out = FALSE;
if(info->dwComponentType == MIXERLINE_COMPONENTTYPE_DST_WAVEIN){
if(is_out)
return MIXERR_INVALLINE;
info->dwDestination = 0;
return WINMM_GetDestinationLineInfo(mmdevice, mmdev_index, info, flags);
}
if(info->dwComponentType == MIXERLINE_COMPONENTTYPE_DST_SPEAKERS){
if(!is_out)
return MIXERR_INVALLINE;
info->dwDestination = 0;
return WINMM_GetDestinationLineInfo(mmdevice, mmdev_index, info, flags);
}
if(info->dwComponentType == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE){
if(is_out)
return MIXERR_INVALLINE;
info->dwSource = 0;
return WINMM_GetSourceLineInfo(mmdevice, mmdev_index, info, flags);
}
if(info->dwComponentType == MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT){
if(!is_out)
return MIXERR_INVALLINE;
info->dwSource = 0;
return WINMM_GetSourceLineInfo(mmdevice, mmdev_index, info, flags);
}
TRACE("Returning INVALLINE on this component type: %u\n",
info->dwComponentType);
return MIXERR_INVALLINE;
}
static UINT WINMM_GetLineIDLineInfo(WINMM_MMDevice *mmdevice,
UINT mmdev_index, MIXERLINEW *info, DWORD flags)
{
if(info->dwLineID == 0xFFFF0000){
info->dwDestination = 0;
return WINMM_GetDestinationLineInfo(mmdevice, mmdev_index, info, flags);
}
if(info->dwLineID == 0){
info->dwSource = 0;
return WINMM_GetSourceLineInfo(mmdevice, mmdev_index, info, flags);
}
TRACE("Returning INVALLINE on this dwLineID: %u\n", info->dwLineID);
return MIXERR_INVALLINE;
}
/**************************************************************************
* mixerGetLineInfoW [WINMM.@]
*/
UINT WINAPI mixerGetLineInfoW(HMIXEROBJ hmix, LPMIXERLINEW lpmliW, DWORD fdwInfo)
{
UINT mmdev_index;
WINMM_MMDevice *mmdevice;
HRESULT hr;
TRACE("(%p, %p, %x)\n", hmix, lpmliW, fdwInfo);
hr = WINMM_InitMMDevices();
if(FAILED(hr))
return MMSYSERR_NODRIVER;
if(!lpmliW || lpmliW->cbStruct < sizeof(MIXERLINEW))
return MMSYSERR_INVALPARAM;
TRACE("dwDestination: %u\n", lpmliW->dwDestination);
TRACE("dwSource: %u\n", lpmliW->dwSource);
TRACE("dwLineID: %u\n", lpmliW->dwLineID);
TRACE("fdwLine: 0x%x\n", lpmliW->fdwLine);
TRACE("dwComponentType: 0x%x\n", lpmliW->dwComponentType);
if(fdwInfo & ~(MIXER_GETLINEINFOF_COMPONENTTYPE |
MIXER_GETLINEINFOF_DESTINATION |
MIXER_GETLINEINFOF_LINEID |
MIXER_GETLINEINFOF_SOURCE |
MIXER_GETLINEINFOF_TARGETTYPE |
MIXER_OBJECTF_HMIXER |
MIXER_OBJECTF_MIXER)){
WARN("Unknown GetLineInfo flag: %x\n", fdwInfo);
return MMSYSERR_INVALFLAG;
}
mmdevice = WINMM_GetMixerMMDevice(hmix, fdwInfo, &mmdev_index);
if(!mmdevice)
return MMSYSERR_INVALHANDLE;
switch(fdwInfo & MIXER_GETLINEINFOF_QUERYMASK){
case MIXER_GETLINEINFOF_DESTINATION:
return WINMM_GetDestinationLineInfo(mmdevice, mmdev_index, lpmliW,
fdwInfo);
case MIXER_GETLINEINFOF_SOURCE:
return WINMM_GetSourceLineInfo(mmdevice, mmdev_index, lpmliW, fdwInfo);
case MIXER_GETLINEINFOF_COMPONENTTYPE:
return WINMM_GetComponentTypeLineInfo(mmdevice, mmdev_index, lpmliW,
fdwInfo);
case MIXER_GETLINEINFOF_LINEID:
return WINMM_GetLineIDLineInfo(mmdevice, mmdev_index, lpmliW, fdwInfo);
case MIXER_GETLINEINFOF_TARGETTYPE:
FIXME("TARGETTYPE flag not implemented!\n");
return MIXERR_INVALLINE;
}
TRACE("Returning INVALFLAG on these flags: %x\n", fdwInfo & MIXER_GETLINEINFOF_QUERYMASK);
return MMSYSERR_INVALFLAG;
}
/**************************************************************************
* mixerGetLineInfoA [WINMM.@]
*/
UINT WINAPI mixerGetLineInfoA(HMIXEROBJ hmix, LPMIXERLINEA lpmliA,
DWORD fdwInfo)
{
MIXERLINEW mliW;
UINT ret;
TRACE("(%p, %p, %x)\n", hmix, lpmliA, fdwInfo);
if (lpmliA == NULL || lpmliA->cbStruct != sizeof(*lpmliA))
return MMSYSERR_INVALPARAM;
mliW.cbStruct = sizeof(mliW);
switch (fdwInfo & MIXER_GETLINEINFOF_QUERYMASK) {
case MIXER_GETLINEINFOF_COMPONENTTYPE:
mliW.dwComponentType = lpmliA->dwComponentType;
break;
case MIXER_GETLINEINFOF_DESTINATION:
mliW.dwDestination = lpmliA->dwDestination;
break;
case MIXER_GETLINEINFOF_LINEID:
mliW.dwLineID = lpmliA->dwLineID;
break;
case MIXER_GETLINEINFOF_SOURCE:
mliW.dwDestination = lpmliA->dwDestination;
mliW.dwSource = lpmliA->dwSource;
break;
case MIXER_GETLINEINFOF_TARGETTYPE:
mliW.Target.dwType = lpmliA->Target.dwType;
mliW.Target.wMid = lpmliA->Target.wMid;
mliW.Target.wPid = lpmliA->Target.wPid;
mliW.Target.vDriverVersion = lpmliA->Target.vDriverVersion;
MultiByteToWideChar( CP_ACP, 0, lpmliA->Target.szPname, -1, mliW.Target.szPname, sizeof(mliW.Target.szPname)/sizeof(WCHAR));
break;
default:
WARN("Unsupported fdwControls=0x%08x\n", fdwInfo);
return MMSYSERR_INVALFLAG;
}
ret = mixerGetLineInfoW(hmix, &mliW, fdwInfo);
if(ret == MMSYSERR_NOERROR)
{
lpmliA->dwDestination = mliW.dwDestination;
lpmliA->dwSource = mliW.dwSource;
lpmliA->dwLineID = mliW.dwLineID;
lpmliA->fdwLine = mliW.fdwLine;
lpmliA->dwUser = mliW.dwUser;
lpmliA->dwComponentType = mliW.dwComponentType;
lpmliA->cChannels = mliW.cChannels;
lpmliA->cConnections = mliW.cConnections;
lpmliA->cControls = mliW.cControls;
WideCharToMultiByte( CP_ACP, 0, mliW.szShortName, -1, lpmliA->szShortName,
sizeof(lpmliA->szShortName), NULL, NULL);
WideCharToMultiByte( CP_ACP, 0, mliW.szName, -1, lpmliA->szName,
sizeof(lpmliA->szName), NULL, NULL );
lpmliA->Target.dwType = mliW.Target.dwType;
lpmliA->Target.dwDeviceID = mliW.Target.dwDeviceID;
lpmliA->Target.wMid = mliW.Target.wMid;
lpmliA->Target.wPid = mliW.Target.wPid;
lpmliA->Target.vDriverVersion = mliW.Target.vDriverVersion;
WideCharToMultiByte( CP_ACP, 0, mliW.Target.szPname, -1, lpmliA->Target.szPname,
sizeof(lpmliA->Target.szPname), NULL, NULL );
}
return ret;
}
/**************************************************************************
* mixerSetControlDetails [WINMM.@]
*/
UINT WINAPI mixerSetControlDetails(HMIXEROBJ hmix, LPMIXERCONTROLDETAILS lpmcd,
DWORD fdwDetails)
{
WINMM_ControlDetails details;
UINT ret;
TRACE("(%p, %p, %x)\n", hmix, lpmcd, fdwDetails);
if((fdwDetails & MIXER_SETCONTROLDETAILSF_QUERYMASK) ==
MIXER_SETCONTROLDETAILSF_CUSTOM)
return MMSYSERR_NOTSUPPORTED;
if(!lpmcd)
return MMSYSERR_INVALPARAM;
if(!WINMM_StartDevicesThread())
return MMSYSERR_NODRIVER;
TRACE("dwControlID: %u\n", lpmcd->dwControlID);
details.hmix = hmix;
details.details = lpmcd;
details.flags = fdwDetails;
ret = SendMessageW(g_devices_hwnd, MXDM_SETCONTROLDETAILS,
(DWORD_PTR)&details, 0);
InterlockedDecrement(&g_devthread_token);
return ret;
}
/**************************************************************************
* mixerMessage [WINMM.@]
*/
DWORD WINAPI mixerMessage(HMIXER hmix, UINT uMsg, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
TRACE("(%p, %d, %lx, %lx)\n", hmix, uMsg, dwParam1, dwParam2);
return MMSYSERR_NOTSUPPORTED;
}