wine-wine/dlls/winmm/winecoreaudio/audio.c

1434 lines
49 KiB
C

/*
* Wine Driver for CoreAudio based on Jack Driver
*
* Copyright 1994 Martin Ayotte
* Copyright 1999 Eric Pouech (async playing in waveOut/waveIn)
* Copyright 2000 Eric Pouech (loops in waveOut)
* Copyright 2002 Chris Morgan (jack version of this file)
* Copyright 2005, 2006 Emmanuel Maillard
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "config.h"
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <fcntl.h>
#include <pthread.h>
#include "windef.h"
#include "winbase.h"
#include "winnls.h"
#include "wingdi.h"
#include "winerror.h"
#include "mmddk.h"
#include "dsound.h"
#include "dsdriver.h"
#include "coreaudio.h"
#include "wine/unicode.h"
#include "wine/library.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(wave);
#if defined(HAVE_COREAUDIO_COREAUDIO_H) && defined(HAVE_AUDIOUNIT_AUDIOUNIT_H)
#include <CoreAudio/CoreAudio.h>
#include <CoreFoundation/CoreFoundation.h>
/*
Due to AudioUnit headers conflict define some needed types.
*/
typedef void *AudioUnit;
/* From AudioUnit/AUComponents.h */
typedef UInt32 AudioUnitRenderActionFlags;
/* only allow 10 output devices through this driver, this ought to be adequate */
#define MAX_WAVEOUTDRV (1)
#define MAX_WAVEINDRV (1)
/* state diagram for waveOut writing:
*
* +---------+-------------+---------------+---------------------------------+
* | state | function | event | new state |
* +---------+-------------+---------------+---------------------------------+
* | | open() | | STOPPED |
* | PAUSED | write() | | PAUSED |
* | STOPPED | write() | <thrd create> | PLAYING |
* | PLAYING | write() | HEADER | PLAYING |
* | (other) | write() | <error> | |
* | (any) | pause() | PAUSING | PAUSED |
* | PAUSED | restart() | RESTARTING | PLAYING (if no thrd => STOPPED) |
* | (any) | reset() | RESETTING | STOPPED |
* | (any) | close() | CLOSING | CLOSED |
* +---------+-------------+---------------+---------------------------------+
*/
/* states of the playing device */
#define WINE_WS_PLAYING 0
#define WINE_WS_PAUSED 1
#define WINE_WS_STOPPED 2
#define WINE_WS_CLOSED 3
typedef struct tagCoreAudio_Device {
char dev_name[32];
char mixer_name[32];
unsigned open_count;
char* interface_name;
WAVEOUTCAPSA out_caps;
WAVEINCAPSA in_caps;
DWORD in_caps_support;
int sample_rate;
int stereo;
int format;
unsigned audio_fragment;
BOOL full_duplex;
BOOL bTriggerSupport;
BOOL bOutputEnabled;
BOOL bInputEnabled;
DSDRIVERDESC ds_desc;
DSDRIVERCAPS ds_caps;
DSCDRIVERCAPS dsc_caps;
GUID ds_guid;
GUID dsc_guid;
AudioDeviceID outputDeviceID;
AudioDeviceID inputDeviceID;
AudioStreamBasicDescription streamDescription;
} CoreAudio_Device;
/* for now use the default device */
static CoreAudio_Device CoreAudio_DefaultDevice;
typedef struct {
volatile int state; /* one of the WINE_WS_ manifest constants */
CoreAudio_Device *cadev;
WAVEOPENDESC waveDesc;
WORD wFlags;
PCMWAVEFORMAT format;
DWORD woID;
AudioUnit audioUnit;
AudioStreamBasicDescription streamDescription;
WAVEOUTCAPSW caps;
char interface_name[32];
LPWAVEHDR lpQueuePtr; /* start of queued WAVEHDRs (waiting to be notified) */
LPWAVEHDR lpPlayPtr; /* start of not yet fully played buffers */
DWORD dwPartialOffset; /* Offset of not yet written bytes in lpPlayPtr */
LPWAVEHDR lpLoopPtr; /* pointer of first buffer in loop, if any */
DWORD dwLoops; /* private copy of loop counter */
DWORD dwPlayedTotal; /* number of bytes actually played since opening */
DWORD dwWrittenTotal; /* number of bytes written to OSS buffer since opening */
DWORD tickCountMS; /* time in MS of last AudioUnit callback */
pthread_mutex_t lock; /* synchronization stuff */
} WINE_WAVEOUT;
typedef struct {
volatile int state;
CoreAudio_Device *cadev;
WAVEOPENDESC waveDesc;
WORD wFlags;
PCMWAVEFORMAT format;
LPWAVEHDR lpQueuePtr;
DWORD dwTotalRecorded;
WAVEINCAPSW caps;
AudioUnit audioUnit;
AudioStreamBasicDescription streamDescription;
/* BOOL bTriggerSupport;
WORD wDevID;
char interface_name[32];*/
/* synchronization stuff */
pthread_mutex_t lock;
} WINE_WAVEIN;
static WINE_WAVEOUT WOutDev [MAX_WAVEOUTDRV];
static WINE_WAVEIN WInDev [MAX_WAVEINDRV ];
static DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv);
static DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc);
static LPWAVEHDR wodHelper_PlayPtrNext(WINE_WAVEOUT* wwo);
static DWORD wodHelper_NotifyCompletions(WINE_WAVEOUT* wwo, BOOL force);
extern int AudioUnit_CreateDefaultAudioUnit(void *wwo, AudioUnit *au);
extern int AudioUnit_CloseAudioUnit(AudioUnit au);
extern int AudioUnit_InitializeWithStreamDescription(AudioUnit au, AudioStreamBasicDescription streamFormat);
extern OSStatus AudioOutputUnitStart(AudioUnit au);
extern OSStatus AudioOutputUnitStop(AudioUnit au);
extern OSStatus AudioUnitUninitialize(AudioUnit au);
extern int AudioUnit_SetVolume(AudioUnit au, float left, float right);
extern int AudioUnit_GetVolume(AudioUnit au, float *left, float *right);
OSStatus CoreAudio_woAudioUnitIOProc(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData);
/* These strings used only for tracing */
static const char * getMessage(UINT msg)
{
static char unknown[32];
#define MSG_TO_STR(x) case x: return #x
switch(msg) {
MSG_TO_STR(DRVM_INIT);
MSG_TO_STR(DRVM_EXIT);
MSG_TO_STR(DRVM_ENABLE);
MSG_TO_STR(DRVM_DISABLE);
MSG_TO_STR(WIDM_OPEN);
MSG_TO_STR(WIDM_CLOSE);
MSG_TO_STR(WIDM_ADDBUFFER);
MSG_TO_STR(WIDM_PREPARE);
MSG_TO_STR(WIDM_UNPREPARE);
MSG_TO_STR(WIDM_GETDEVCAPS);
MSG_TO_STR(WIDM_GETNUMDEVS);
MSG_TO_STR(WIDM_GETPOS);
MSG_TO_STR(WIDM_RESET);
MSG_TO_STR(WIDM_START);
MSG_TO_STR(WIDM_STOP);
MSG_TO_STR(WODM_OPEN);
MSG_TO_STR(WODM_CLOSE);
MSG_TO_STR(WODM_WRITE);
MSG_TO_STR(WODM_PAUSE);
MSG_TO_STR(WODM_GETPOS);
MSG_TO_STR(WODM_BREAKLOOP);
MSG_TO_STR(WODM_PREPARE);
MSG_TO_STR(WODM_UNPREPARE);
MSG_TO_STR(WODM_GETDEVCAPS);
MSG_TO_STR(WODM_GETNUMDEVS);
MSG_TO_STR(WODM_GETPITCH);
MSG_TO_STR(WODM_SETPITCH);
MSG_TO_STR(WODM_GETPLAYBACKRATE);
MSG_TO_STR(WODM_SETPLAYBACKRATE);
MSG_TO_STR(WODM_GETVOLUME);
MSG_TO_STR(WODM_SETVOLUME);
MSG_TO_STR(WODM_RESTART);
MSG_TO_STR(WODM_RESET);
MSG_TO_STR(DRV_QUERYDEVICEINTERFACESIZE);
MSG_TO_STR(DRV_QUERYDEVICEINTERFACE);
MSG_TO_STR(DRV_QUERYDSOUNDIFACE);
MSG_TO_STR(DRV_QUERYDSOUNDDESC);
}
#undef MSG_TO_STR
sprintf(unknown, "UNKNOWN(0x%04x)", msg);
return unknown;
}
#define kStopLoopMessage 0
#define kWaveOutCallbackMessage 1
#define kWaveInCallbackMessage 2
/* Mach Message Handling */
static CFDataRef wodMessageHandler(CFMessagePortRef local, SInt32 msgid, CFDataRef data, void *info)
{
UInt32 *buffer = NULL;
WINE_WAVEOUT* wwo = NULL;
switch (msgid)
{
case kWaveOutCallbackMessage:
buffer = (UInt32 *) CFDataGetBytePtr(data);
wwo = &WOutDev[buffer[0]];
pthread_mutex_lock(&wwo->lock);
DriverCallback(wwo->waveDesc.dwCallback, wwo->wFlags,
(HDRVR)wwo->waveDesc.hWave, (WORD)buffer[1], wwo->waveDesc.dwInstance,
(DWORD)buffer[2], (DWORD)buffer[3]);
pthread_mutex_unlock(&wwo->lock);
break;
case kWaveInCallbackMessage:
default:
CFRunLoopStop(CFRunLoopGetCurrent());
break;
}
return NULL;
}
static DWORD messageThread(LPVOID p)
{
CFMessagePortRef local;
CFRunLoopSourceRef source;
Boolean info;
local = CFMessagePortCreateLocal(kCFAllocatorDefault, CFSTR("WaveMessagePort"),
&wodMessageHandler, NULL, &info);
source = CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, local, (CFIndex)0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRunLoopRun();
return 0;
}
static DWORD wodSendDriverCallbackMessage(WINE_WAVEOUT* wwo, WORD wMsg, DWORD dwParam1, DWORD dwParam2)
{
CFDataRef data;
UInt32 *buffer;
SInt32 ret;
CFMessagePortRef messagePort;
messagePort = CFMessagePortCreateRemote(kCFAllocatorDefault, CFSTR("WaveMessagePort"));
buffer = CFAllocatorAllocate(NULL, sizeof(UInt32) * 4, 0);
if (!buffer)
return 0;
buffer[0] = (UInt32) wwo->woID;
buffer[1] = (UInt32) wMsg;
buffer[2] = (UInt32) dwParam1;
buffer[3] = (UInt32) dwParam2;
data = CFDataCreate(kCFAllocatorDefault, buffer, sizeof(UInt32) * 4);
if (!data)
{
CFAllocatorDeallocate(NULL, buffer);
return 0;
}
ret = CFMessagePortSendRequest(messagePort, kWaveOutCallbackMessage, data, 0.0, 0.0, NULL, NULL);
CFRelease(data);
CFAllocatorDeallocate(NULL, buffer);
return (ret == kCFMessagePortSuccess)?1:0;
}
static DWORD bytes_to_mmtime(LPMMTIME lpTime, DWORD position,
PCMWAVEFORMAT* format)
{
TRACE("wType=%04X wBitsPerSample=%u nSamplesPerSec=%lu nChannels=%u nAvgBytesPerSec=%lu\n",
lpTime->wType, format->wBitsPerSample, format->wf.nSamplesPerSec,
format->wf.nChannels, format->wf.nAvgBytesPerSec);
TRACE("Position in bytes=%lu\n", position);
switch (lpTime->wType) {
case TIME_SAMPLES:
lpTime->u.sample = position / (format->wBitsPerSample / 8 * format->wf.nChannels);
TRACE("TIME_SAMPLES=%lu\n", lpTime->u.sample);
break;
case TIME_MS:
lpTime->u.ms = 1000.0 * position / (format->wBitsPerSample / 8 * format->wf.nChannels * format->wf.nSamplesPerSec);
TRACE("TIME_MS=%lu\n", lpTime->u.ms);
break;
case TIME_SMPTE:
lpTime->u.smpte.fps = 30;
position = position / (format->wBitsPerSample / 8 * format->wf.nChannels);
position += (format->wf.nSamplesPerSec / lpTime->u.smpte.fps) - 1; /* round up */
lpTime->u.smpte.sec = position / format->wf.nSamplesPerSec;
position -= lpTime->u.smpte.sec * format->wf.nSamplesPerSec;
lpTime->u.smpte.min = lpTime->u.smpte.sec / 60;
lpTime->u.smpte.sec -= 60 * lpTime->u.smpte.min;
lpTime->u.smpte.hour = lpTime->u.smpte.min / 60;
lpTime->u.smpte.min -= 60 * lpTime->u.smpte.hour;
lpTime->u.smpte.fps = 30;
lpTime->u.smpte.frame = position * lpTime->u.smpte.fps / format->wf.nSamplesPerSec;
TRACE("TIME_SMPTE=%02u:%02u:%02u:%02u\n",
lpTime->u.smpte.hour, lpTime->u.smpte.min,
lpTime->u.smpte.sec, lpTime->u.smpte.frame);
break;
default:
WARN("Format %d not supported, using TIME_BYTES !\n", lpTime->wType);
lpTime->wType = TIME_BYTES;
/* fall through */
case TIME_BYTES:
lpTime->u.cb = position;
TRACE("TIME_BYTES=%lu\n", lpTime->u.cb);
break;
}
return MMSYSERR_NOERROR;
}
/**************************************************************************
* CoreAudio_GetDevCaps [internal]
*/
BOOL CoreAudio_GetDevCaps (void)
{
OSStatus status;
UInt32 propertySize;
AudioDeviceID devId = CoreAudio_DefaultDevice.outputDeviceID;
char name[MAXPNAMELEN];
propertySize = MAXPNAMELEN;
status = AudioDeviceGetProperty(devId, 0 , FALSE, kAudioDevicePropertyDeviceName, &propertySize, name);
if (status) {
ERR("AudioHardwareGetProperty for kAudioDevicePropertyDeviceName return %c%c%c%c\n", (char) (status >> 24),
(char) (status >> 16),
(char) (status >> 8),
(char) status);
return FALSE;
}
memcpy(CoreAudio_DefaultDevice.ds_desc.szDesc, name, sizeof(name));
strcpy(CoreAudio_DefaultDevice.ds_desc.szDrvname, "winecoreaudio.drv");
MultiByteToWideChar(CP_ACP, 0, name, sizeof(name),
CoreAudio_DefaultDevice.out_caps.szPname,
sizeof(CoreAudio_DefaultDevice.out_caps.szPname) / sizeof(WCHAR));
memcpy(CoreAudio_DefaultDevice.dev_name, name, 32);
propertySize = sizeof(CoreAudio_DefaultDevice.streamDescription);
status = AudioDeviceGetProperty(devId, 0, FALSE , kAudioDevicePropertyStreamFormat, &propertySize, &CoreAudio_DefaultDevice.streamDescription);
if (status != noErr) {
ERR("AudioHardwareGetProperty for kAudioDevicePropertyStreamFormat return %c%c%c%c\n", (char) (status >> 24),
(char) (status >> 16),
(char) (status >> 8),
(char) status);
return FALSE;
}
TRACE("Device Stream Description mSampleRate : %f\n mFormatID : %c%c%c%c\n"
"mFormatFlags : %lX\n mBytesPerPacket : %u\n mFramesPerPacket : %u\n"
"mBytesPerFrame : %u\n mChannelsPerFrame : %u\n mBitsPerChannel : %u\n",
CoreAudio_DefaultDevice.streamDescription.mSampleRate,
(char) (CoreAudio_DefaultDevice.streamDescription.mFormatID >> 24),
(char) (CoreAudio_DefaultDevice.streamDescription.mFormatID >> 16),
(char) (CoreAudio_DefaultDevice.streamDescription.mFormatID >> 8),
(char) CoreAudio_DefaultDevice.streamDescription.mFormatID,
CoreAudio_DefaultDevice.streamDescription.mFormatFlags,
CoreAudio_DefaultDevice.streamDescription.mBytesPerPacket,
CoreAudio_DefaultDevice.streamDescription.mFramesPerPacket,
CoreAudio_DefaultDevice.streamDescription.mBytesPerFrame,
CoreAudio_DefaultDevice.streamDescription.mChannelsPerFrame,
CoreAudio_DefaultDevice.streamDescription.mBitsPerChannel);
CoreAudio_DefaultDevice.out_caps.wMid = 0xcafe;
CoreAudio_DefaultDevice.out_caps.wPid = 0x0001;
CoreAudio_DefaultDevice.out_caps.vDriverVersion = 0x0001;
CoreAudio_DefaultDevice.out_caps.dwFormats = 0x00000000;
CoreAudio_DefaultDevice.out_caps.wReserved1 = 0;
CoreAudio_DefaultDevice.out_caps.dwSupport = WAVECAPS_VOLUME;
CoreAudio_DefaultDevice.out_caps.dwSupport |= WAVECAPS_LRVOLUME;
CoreAudio_DefaultDevice.out_caps.wChannels = 2;
CoreAudio_DefaultDevice.out_caps.dwFormats|= WAVE_FORMAT_4S16;
return TRUE;
}
/******************************************************************
* CoreAudio_WaveInit
*
* Initialize CoreAudio_DefaultDevice
*/
LONG CoreAudio_WaveInit(void)
{
OSStatus status;
UInt32 propertySize;
CHAR szPname[MAXPNAMELEN];
int i;
HANDLE hThread;
TRACE("()\n");
/* number of sound cards */
AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &propertySize, NULL);
propertySize /= sizeof(AudioDeviceID);
TRACE("sound cards : %u\n", propertySize);
/* Get the output device */
propertySize = sizeof(CoreAudio_DefaultDevice.outputDeviceID);
status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &propertySize, &CoreAudio_DefaultDevice.outputDeviceID);
if (status) {
ERR("AudioHardwareGetProperty return %c%c%c%c for kAudioHardwarePropertyDefaultOutputDevice\n", (char) (status >> 24),
(char) (status >> 16),
(char) (status >> 8),
(char) status);
return 1;
}
if (CoreAudio_DefaultDevice.outputDeviceID == kAudioDeviceUnknown) {
ERR("AudioHardwareGetProperty: CoreAudio_DefaultDevice.outputDeviceID == kAudioDeviceUnknown\n");
return 1;
}
if ( ! CoreAudio_GetDevCaps() )
return 1;
CoreAudio_DefaultDevice.interface_name=HeapAlloc(GetProcessHeap(),0,strlen(CoreAudio_DefaultDevice.dev_name)+1);
sprintf(CoreAudio_DefaultDevice.interface_name, "%s", CoreAudio_DefaultDevice.dev_name);
for (i = 0; i < MAX_WAVEOUTDRV; ++i)
{
WOutDev[i].state = WINE_WS_CLOSED;
WOutDev[i].cadev = &CoreAudio_DefaultDevice;
WOutDev[i].woID = i;
memset(&WOutDev[i].caps, 0, sizeof(WOutDev[i].caps));
WOutDev[i].caps.wMid = 0xcafe; /* Manufac ID */
WOutDev[i].caps.wPid = 0x0001; /* Product ID */
snprintf(szPname, sizeof(szPname), "CoreAudio WaveOut %d", i);
MultiByteToWideChar(CP_ACP, 0, szPname, -1, WOutDev[i].caps.szPname, sizeof(WOutDev[i].caps.szPname)/sizeof(WCHAR));
snprintf(WOutDev[i].interface_name, sizeof(WOutDev[i].interface_name), "winecoreaudio: %d", i);
WOutDev[i].caps.vDriverVersion = 0x0001;
WOutDev[i].caps.dwFormats = 0x00000000;
WOutDev[i].caps.dwSupport = WAVECAPS_VOLUME;
WOutDev[i].caps.wChannels = 2;
/* WOutDev[i].caps.dwSupport |= WAVECAPS_LRVOLUME; */ /* FIXME */
WOutDev[i].caps.dwFormats |= WAVE_FORMAT_4M08;
WOutDev[i].caps.dwFormats |= WAVE_FORMAT_4S08;
WOutDev[i].caps.dwFormats |= WAVE_FORMAT_4S16;
WOutDev[i].caps.dwFormats |= WAVE_FORMAT_4M16;
WOutDev[i].caps.dwFormats |= WAVE_FORMAT_2M08;
WOutDev[i].caps.dwFormats |= WAVE_FORMAT_2S08;
WOutDev[i].caps.dwFormats |= WAVE_FORMAT_2M16;
WOutDev[i].caps.dwFormats |= WAVE_FORMAT_2S16;
WOutDev[i].caps.dwFormats |= WAVE_FORMAT_1M08;
WOutDev[i].caps.dwFormats |= WAVE_FORMAT_1S08;
WOutDev[i].caps.dwFormats |= WAVE_FORMAT_1M16;
WOutDev[i].caps.dwFormats |= WAVE_FORMAT_1S16;
}
/* create mach messages handler */
hThread = CreateThread(NULL, 0, messageThread, NULL, 0, NULL);
if ( !hThread )
{
ERR("Can't create message thread\n");
return 1;
}
return 0;
}
void CoreAudio_WaveRelease(void)
{
TRACE("()\n");
/* Stop CFRunLoop in messageThread */
CFMessagePortRef messagePort;
messagePort = CFMessagePortCreateRemote(kCFAllocatorDefault, CFSTR("WaveMessagePort"));
CFMessagePortSendRequest(messagePort, kStopLoopMessage, NULL, 0.0, 0.0, NULL, NULL);
CFRelease(messagePort);
}
/*======================================================================*
* Low level WAVE OUT implementation *
*======================================================================*/
/**************************************************************************
* wodNotifyClient [internal]
* Call from AudioUnit IO thread can't use Wine debug channels.
*/
static DWORD wodNotifyClient(WINE_WAVEOUT* wwo, WORD wMsg, DWORD dwParam1, DWORD dwParam2)
{
switch (wMsg) {
case WOM_OPEN:
case WOM_CLOSE:
if (wwo->wFlags != DCB_NULL &&
!DriverCallback(wwo->waveDesc.dwCallback, wwo->wFlags,
(HDRVR)wwo->waveDesc.hWave, wMsg, wwo->waveDesc.dwInstance,
dwParam1, dwParam2))
{
return MMSYSERR_ERROR;
}
break;
case WOM_DONE:
if (wwo->wFlags != DCB_NULL &&
! wodSendDriverCallbackMessage(wwo, wMsg, dwParam1, dwParam2))
{
return MMSYSERR_ERROR;
}
break;
default:
return MMSYSERR_INVALPARAM;
}
return MMSYSERR_NOERROR;
}
/**************************************************************************
* wodGetDevCaps [internal]
*/
static DWORD wodGetDevCaps(WORD wDevID, LPWAVEOUTCAPSW lpCaps, DWORD dwSize)
{
TRACE("(%u, %p, %lu);\n", wDevID, lpCaps, dwSize);
if (lpCaps == NULL) return MMSYSERR_NOTENABLED;
if (wDevID >= MAX_WAVEOUTDRV)
{
TRACE("MAX_WAVOUTDRV reached !\n");
return MMSYSERR_BADDEVICEID;
}
TRACE("dwSupport=(0x%lx), dwFormats=(0x%lx)\n", WOutDev[wDevID].caps.dwSupport, WOutDev[wDevID].caps.dwFormats);
memcpy(lpCaps, &WOutDev[wDevID].caps, min(dwSize, sizeof(*lpCaps)));
return MMSYSERR_NOERROR;
}
/**************************************************************************
* wodOpen [internal]
*
* NOTE: doesn't it seem like there is a race condition if you try to open
* the same device twice?
*/
static DWORD wodOpen(WORD wDevID, LPWAVEOPENDESC lpDesc, DWORD dwFlags)
{
WINE_WAVEOUT* wwo;
DWORD retval;
DWORD ret;
int audio_fragment;
TRACE("(%u, %p, %08lX);\n", wDevID, lpDesc, dwFlags);
if (lpDesc == NULL)
{
WARN("Invalid Parameter !\n");
return MMSYSERR_INVALPARAM;
}
if (wDevID >= MAX_WAVEOUTDRV) {
TRACE("MAX_WAVOUTDRV reached !\n");
return MMSYSERR_BADDEVICEID;
}
TRACE("Format: tag=%04X nChannels=%d nSamplesPerSec=%ld wBitsPerSample=%d !\n",
lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
lpDesc->lpFormat->nSamplesPerSec, lpDesc->lpFormat->wBitsPerSample);
if (lpDesc->lpFormat->wFormatTag != WAVE_FORMAT_PCM ||
lpDesc->lpFormat->nChannels == 0 ||
lpDesc->lpFormat->nSamplesPerSec == 0
)
{
WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%ld wBitsPerSample=%d !\n",
lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
lpDesc->lpFormat->nSamplesPerSec, lpDesc->lpFormat->wBitsPerSample);
return WAVERR_BADFORMAT;
}
if (dwFlags & WAVE_FORMAT_QUERY)
{
TRACE("Query format: tag=%04X nChannels=%d nSamplesPerSec=%ld !\n",
lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels,
lpDesc->lpFormat->nSamplesPerSec);
return MMSYSERR_NOERROR;
}
wwo = &WOutDev[wDevID];
if (!AudioUnit_CreateDefaultAudioUnit((void *) wwo, &wwo->audioUnit))
{
ERR("CoreAudio_CreateDefaultAudioUnit(%p) failed\n", wwo);
return MMSYSERR_ERROR;
}
if ((dwFlags & WAVE_DIRECTSOUND) &&
!(wwo->caps.dwSupport & WAVECAPS_DIRECTSOUND))
/* not supported, ignore it */
dwFlags &= ~WAVE_DIRECTSOUND;
if (wwo->state != WINE_WS_CLOSED) return MMSYSERR_ALLOCATED;
pthread_mutex_init(&wwo->lock, NULL); /* initialize the mutex */
pthread_mutex_lock(&wwo->lock);
AudioStreamBasicDescription streamFormat;
streamFormat.mFormatID = kAudioFormatLinearPCM;
/* FIXME check for 32bits float -> kLinearPCMFormatFlagIsFloat */
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger
# ifdef __powerpc__
| kLinearPCMFormatFlagIsBigEndian /* FIXME Wave format is little endian */
# endif
| kLinearPCMFormatFlagIsPacked;
streamFormat.mSampleRate = lpDesc->lpFormat->nSamplesPerSec;
streamFormat.mChannelsPerFrame = lpDesc->lpFormat->nChannels;
streamFormat.mFramesPerPacket = 1;
streamFormat.mBitsPerChannel = lpDesc->lpFormat->wBitsPerSample;
streamFormat.mBytesPerFrame = streamFormat.mBitsPerChannel * streamFormat.mChannelsPerFrame / 8;
streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame * streamFormat.mFramesPerPacket;
ret = AudioUnit_InitializeWithStreamDescription(wwo->audioUnit, streamFormat);
if (!ret)
{
pthread_mutex_unlock(&wwo->lock);
return WAVERR_BADFORMAT; /* FIXME return an error based on the OSStatus */
}
wwo->streamDescription = streamFormat;
wwo->state = WINE_WS_STOPPED;
pthread_mutex_unlock(&wwo->lock);
wwo->wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
memcpy(&wwo->waveDesc, lpDesc, sizeof(WAVEOPENDESC));
memcpy(&wwo->format, lpDesc->lpFormat, sizeof(PCMWAVEFORMAT));
if (wwo->format.wBitsPerSample == 0) {
WARN("Resetting zeroed wBitsPerSample\n");
wwo->format.wBitsPerSample = 8 *
(wwo->format.wf.nAvgBytesPerSec /
wwo->format.wf.nSamplesPerSec) /
wwo->format.wf.nChannels;
}
wwo->dwPlayedTotal = 0;
wwo->dwWrittenTotal = 0;
retval = wodNotifyClient(wwo, WOM_OPEN, 0L, 0L);
return retval;
}
/**************************************************************************
* wodClose [internal]
*/
static DWORD wodClose(WORD wDevID)
{
DWORD ret = MMSYSERR_NOERROR;
WINE_WAVEOUT* wwo;
TRACE("(%u);\n", wDevID);
if (wDevID >= MAX_WAVEOUTDRV)
{
WARN("bad device ID !\n");
return MMSYSERR_BADDEVICEID;
}
wwo = &WOutDev[wDevID];
if (wwo->lpQueuePtr)
{
WARN("buffers still playing !\n");
ret = WAVERR_STILLPLAYING;
} else
{
OSStatus err;
/* sanity check: this should not happen since the device must have been reset before */
if (wwo->lpQueuePtr || wwo->lpPlayPtr) ERR("out of sync\n");
wwo->state = WINE_WS_CLOSED; /* mark the device as closed */
err = AudioUnitUninitialize(wwo->audioUnit);
if (err) {
ERR("AudioUnitUninitialize return %c%c%c%c\n", (char) (err >> 24),
(char) (err >> 16),
(char) (err >> 8),
(char) err);
pthread_mutex_destroy(&wwo->lock);
return MMSYSERR_ERROR; /* FIXME return an error based on the OSStatus */
}
if ( !AudioUnit_CloseAudioUnit(wwo->audioUnit) )
{
ERR("Can't close AudioUnit\n");
pthread_mutex_destroy(&wwo->lock);
return MMSYSERR_ERROR; /* FIXME return an error based on the OSStatus */
}
pthread_mutex_destroy(&wwo->lock);
ret = wodNotifyClient(wwo, WOM_CLOSE, 0L, 0L);
}
return ret;
}
/**************************************************************************
* wodPrepare [internal]
*/
static DWORD wodPrepare(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
{
OSStatus status;
TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize);
if (wDevID >= MAX_WAVEOUTDRV) {
WARN("bad device ID !\n");
return MMSYSERR_BADDEVICEID;
}
if (lpWaveHdr->dwFlags & WHDR_INQUEUE)
return WAVERR_STILLPLAYING;
lpWaveHdr->dwFlags |= WHDR_PREPARED;
lpWaveHdr->dwFlags &= ~WHDR_DONE;
WOutDev[wDevID].state = WINE_WS_STOPPED;
status = AudioOutputUnitStart(WOutDev[wDevID].audioUnit);
if (status) {
ERR("AudioOutputUnitStart return %c%c%c%c\n", (char) (status >> 24),
(char) (status >> 16),
(char) (status >> 8),
(char) status);
return MMSYSERR_ERROR; /* FIXME return an error based on the OSStatus */
}
return MMSYSERR_NOERROR;
}
/**************************************************************************
* wodUnprepare [internal]
*/
static DWORD wodUnprepare(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
{
OSStatus status;
TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize);
if (wDevID >= MAX_WAVEOUTDRV) {
WARN("bad device ID !\n");
return MMSYSERR_BADDEVICEID;
}
if (lpWaveHdr->dwFlags & WHDR_INQUEUE)
return WAVERR_STILLPLAYING;
lpWaveHdr->dwFlags &= ~WHDR_PREPARED;
lpWaveHdr->dwFlags |= WHDR_DONE;
status = AudioOutputUnitStop(WOutDev[wDevID].audioUnit);
if (status) {
ERR("AudioOutputUnitStop return %c%c%c%c\n", (char) (status >> 24), (char) (status >> 16), (char) (status >> 8), (char) status);
return MMSYSERR_ERROR; /* FIXME return an error based on the OSStatus */
}
return MMSYSERR_NOERROR;
}
/**************************************************************************
* wodHelper_BeginWaveHdr [internal]
*
* Makes the specified lpWaveHdr the currently playing wave header.
* If the specified wave header is a begin loop and we're not already in
* a loop, setup the loop.
* Call from AudioUnit IO thread can't use Wine debug channels.
*/
static void wodHelper_BeginWaveHdr(WINE_WAVEOUT* wwo, LPWAVEHDR lpWaveHdr)
{
wwo->lpPlayPtr = lpWaveHdr;
if (!lpWaveHdr)
{
return;
}
if (lpWaveHdr->dwFlags & WHDR_BEGINLOOP)
{
if (wwo->lpLoopPtr)
{
fprintf(stderr, "trace:winecoreaudio:wodHelper_BeginWaveHdr Already in a loop. Discarding loop on this header (%p)\n", lpWaveHdr);
} else
{
fprintf(stderr, "trace:winecoreaudio:wodHelper_BeginWaveHdr Starting loop (%ldx) with %p\n", lpWaveHdr->dwLoops, lpWaveHdr);
wwo->lpLoopPtr = lpWaveHdr;
/* Windows does not touch WAVEHDR.dwLoops,
* so we need to make an internal copy */
wwo->dwLoops = lpWaveHdr->dwLoops;
}
}
wwo->dwPartialOffset = 0;
}
/**************************************************************************
* wodHelper_PlayPtrNext [internal]
*
* Advance the play pointer to the next waveheader, looping if required.
* Call from AudioUnit IO thread can't use Wine debug channels.
*/
static LPWAVEHDR wodHelper_PlayPtrNext(WINE_WAVEOUT* wwo)
{
LPWAVEHDR lpWaveHdr;
pthread_mutex_lock(&wwo->lock);
lpWaveHdr = wwo->lpPlayPtr;
wwo->dwPartialOffset = 0;
if ((lpWaveHdr->dwFlags & WHDR_ENDLOOP) && wwo->lpLoopPtr)
{
/* We're at the end of a loop, loop if required */
if (--wwo->dwLoops > 0)
{
wwo->lpPlayPtr = wwo->lpLoopPtr;
} else
{
/* Handle overlapping loops correctly */
if (wwo->lpLoopPtr != lpWaveHdr && (lpWaveHdr->dwFlags & WHDR_BEGINLOOP)) {
fprintf(stderr, "trace:winecoreaudio:wodHelper_PlayPtrNext Correctly handled case ? (ending loop buffer also starts a new loop)\n");
/* shall we consider the END flag for the closing loop or for
* the opening one or for both ???
* code assumes for closing loop only
*/
} else
{
lpWaveHdr = lpWaveHdr->lpNext;
}
wwo->lpLoopPtr = NULL;
wodHelper_BeginWaveHdr(wwo, lpWaveHdr);
}
} else
{
/* We're not in a loop. Advance to the next wave header */
wodHelper_BeginWaveHdr(wwo, lpWaveHdr = lpWaveHdr->lpNext);
}
pthread_mutex_unlock(&wwo->lock);
return lpWaveHdr;
}
/* if force is TRUE then notify the client that all the headers were completed
* Call from AudioUnit IO thread can't use Wine debug channels.
*/
static DWORD wodHelper_NotifyCompletions(WINE_WAVEOUT* wwo, BOOL force)
{
LPWAVEHDR lpWaveHdr;
DWORD retval;
pthread_mutex_lock(&wwo->lock);
/* Start from lpQueuePtr and keep notifying until:
* - we hit an unwritten wavehdr
* - we hit the beginning of a running loop
* - we hit a wavehdr which hasn't finished playing
*/
while ((lpWaveHdr = wwo->lpQueuePtr) &&
(force ||
(lpWaveHdr != wwo->lpPlayPtr &&
lpWaveHdr != wwo->lpLoopPtr)))
{
wwo->lpQueuePtr = lpWaveHdr->lpNext;
lpWaveHdr->dwFlags &= ~WHDR_INQUEUE;
lpWaveHdr->dwFlags |= WHDR_DONE;
wodNotifyClient(wwo, WOM_DONE, (DWORD)lpWaveHdr, 0);
}
retval = (lpWaveHdr && lpWaveHdr != wwo->lpPlayPtr && lpWaveHdr !=
wwo->lpLoopPtr) ? 0 : INFINITE;
pthread_mutex_unlock(&wwo->lock);
return retval;
}
/**************************************************************************
* wodHelper_Reset [internal]
*
* Resets current output stream.
*/
static void wodHelper_Reset(WINE_WAVEOUT* wwo, BOOL reset)
{
FIXME("\n");
/* updates current notify list */
wodHelper_NotifyCompletions(wwo, FALSE);
if (reset)
{
/* remove all wave headers and notify client that all headers were completed */
wodHelper_NotifyCompletions(wwo, TRUE);
pthread_mutex_lock(&wwo->lock);
wwo->lpPlayPtr = wwo->lpQueuePtr = wwo->lpLoopPtr = NULL;
wwo->state = WINE_WS_STOPPED;
wwo->dwPlayedTotal = wwo->dwWrittenTotal = 0;
wwo->dwPartialOffset = 0; /* Clear partial wavehdr */
pthread_mutex_unlock(&wwo->lock);
}
else
{
pthread_mutex_lock(&wwo->lock);
if (wwo->lpLoopPtr)
{
/* complicated case, not handled yet (could imply modifying the loop counter) */
FIXME("Pausing while in loop isn't correctly handled yet, except strange results\n");
wwo->lpPlayPtr = wwo->lpLoopPtr;
wwo->dwPartialOffset = 0;
wwo->dwWrittenTotal = wwo->dwPlayedTotal; /* this is wrong !!! */
} else
{
LPWAVEHDR ptr;
DWORD sz = wwo->dwPartialOffset;
/* reset all the data as if we had written only up to lpPlayedTotal bytes */
/* compute the max size playable from lpQueuePtr */
for (ptr = wwo->lpQueuePtr; ptr != wwo->lpPlayPtr; ptr = ptr->lpNext)
{
sz += ptr->dwBufferLength;
}
/* because the reset lpPlayPtr will be lpQueuePtr */
if (wwo->dwWrittenTotal > wwo->dwPlayedTotal + sz) ERR("doh\n");
wwo->dwPartialOffset = sz - (wwo->dwWrittenTotal - wwo->dwPlayedTotal);
wwo->dwWrittenTotal = wwo->dwPlayedTotal;
wwo->lpPlayPtr = wwo->lpQueuePtr;
}
wwo->state = WINE_WS_PAUSED;
pthread_mutex_unlock(&wwo->lock);
}
}
/**************************************************************************
* wodWrite [internal]
*
*/
static DWORD wodWrite(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize)
{
LPWAVEHDR*wh;
WINE_WAVEOUT *wwo;
TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize);
/* first, do the sanity checks... */
if (wDevID >= MAX_WAVEOUTDRV)
{
WARN("bad dev ID !\n");
return MMSYSERR_BADDEVICEID;
}
wwo = &WOutDev[wDevID];
if (lpWaveHdr->lpData == NULL || !(lpWaveHdr->dwFlags & WHDR_PREPARED))
{
TRACE("unprepared\n");
return WAVERR_UNPREPARED;
}
if (lpWaveHdr->dwFlags & WHDR_INQUEUE)
{
TRACE("still playing\n");
return WAVERR_STILLPLAYING;
}
lpWaveHdr->dwFlags &= ~WHDR_DONE;
lpWaveHdr->dwFlags |= WHDR_INQUEUE;
lpWaveHdr->lpNext = 0;
pthread_mutex_lock(&wwo->lock);
/* insert buffer at the end of queue */
for (wh = &(wwo->lpQueuePtr); *wh; wh = &((*wh)->lpNext));
*wh = lpWaveHdr;
if (!wwo->lpPlayPtr)
wodHelper_BeginWaveHdr(wwo,lpWaveHdr);
if (wwo->state == WINE_WS_STOPPED)
wwo->state = WINE_WS_PLAYING;
pthread_mutex_unlock(&wwo->lock);
return MMSYSERR_NOERROR;
}
/**************************************************************************
* wodPause [internal]
*/
static DWORD wodPause(WORD wDevID)
{
TRACE("(%u);!\n", wDevID);
if (wDevID >= MAX_WAVEOUTDRV)
{
WARN("bad device ID !\n");
return MMSYSERR_BADDEVICEID;
}
if (WOutDev[wDevID].state == WINE_WS_PLAYING)
{
pthread_mutex_lock(&WOutDev[wDevID].lock);
WOutDev[wDevID].state = WINE_WS_PAUSED;
pthread_mutex_unlock(&WOutDev[wDevID].lock);
}
return MMSYSERR_NOERROR;
}
/**************************************************************************
* wodRestart [internal]
*/
static DWORD wodRestart(WORD wDevID)
{
TRACE("(%u);\n", wDevID);
if (wDevID >= MAX_WAVEOUTDRV )
{
WARN("bad device ID !\n");
return MMSYSERR_BADDEVICEID;
}
if (WOutDev[wDevID].state == WINE_WS_PAUSED)
{
pthread_mutex_lock(&WOutDev[wDevID].lock);
WOutDev[wDevID].state = WINE_WS_PLAYING;
pthread_mutex_unlock(&WOutDev[wDevID].lock);
}
return MMSYSERR_NOERROR;
}
/**************************************************************************
* wodReset [internal]
*/
static DWORD wodReset(WORD wDevID)
{
TRACE("(%u);\n", wDevID);
if (wDevID >= MAX_WAVEOUTDRV)
{
WARN("bad device ID !\n");
return MMSYSERR_BADDEVICEID;
}
wodHelper_Reset(&WOutDev[wDevID], TRUE);
return MMSYSERR_NOERROR;
}
/**************************************************************************
* wodGetPosition [internal]
*/
static DWORD wodGetPosition(WORD wDevID, LPMMTIME lpTime, DWORD uSize)
{
DWORD val;
WINE_WAVEOUT* wwo;
DWORD elapsedMS;
TRACE("(%u, %p, %lu);\n", wDevID, lpTime, uSize);
if (wDevID >= MAX_WAVEOUTDRV)
{
WARN("bad device ID !\n");
return MMSYSERR_BADDEVICEID;
}
/* if null pointer to time structure return error */
if (lpTime == NULL) return MMSYSERR_INVALPARAM;
wwo = &WOutDev[wDevID];
pthread_mutex_lock(&WOutDev[wDevID].lock);
val = wwo->dwPlayedTotal;
pthread_mutex_unlock(&WOutDev[wDevID].lock);
return bytes_to_mmtime(lpTime, val, &wwo->format);
}
/**************************************************************************
* wodBreakLoop [internal]
*/
static DWORD wodBreakLoop(WORD wDevID)
{
FIXME("(%u);\n", wDevID);
if (wDevID >= MAX_WAVEOUTDRV)
{
WARN("bad device ID !\n");
return MMSYSERR_BADDEVICEID;
}
pthread_mutex_lock(&WOutDev[wDevID].lock);
if (WOutDev[wDevID].state == WINE_WS_PLAYING && WOutDev[wDevID].lpLoopPtr != NULL)
{
/* ensure exit at end of current loop */
WOutDev[wDevID].dwLoops = 1;
}
pthread_mutex_unlock(&WOutDev[wDevID].lock);
return MMSYSERR_NOERROR;
}
/**************************************************************************
* wodGetVolume [internal]
*/
static DWORD wodGetVolume(WORD wDevID, LPDWORD lpdwVol)
{
float left;
float right;
if (wDevID >= MAX_WAVEOUTDRV)
{
WARN("bad device ID !\n");
return MMSYSERR_BADDEVICEID;
}
TRACE("(%u, %08lX);\n", wDevID, lpdwVol);
pthread_mutex_lock(&WOutDev[wDevID].lock);
AudioUnit_GetVolume(WOutDev[wDevID].audioUnit, &left, &right);
pthread_mutex_unlock(&WOutDev[wDevID].lock);
*lpdwVol = ((WORD) left * 0xFFFFl) + (((WORD) right * 0xFFFFl) << 16);
return MMSYSERR_NOERROR;
}
/**************************************************************************
* wodSetVolume [internal]
*/
static DWORD wodSetVolume(WORD wDevID, DWORD dwParam)
{
float left;
float right;
if (wDevID >= MAX_WAVEOUTDRV)
{
WARN("bad device ID !\n");
return MMSYSERR_BADDEVICEID;
}
left = LOWORD(dwParam) / 65535.0f;
right = HIWORD(dwParam) / 65535.0f;
TRACE("(%u, %08lX);\n", wDevID, dwParam);
pthread_mutex_lock(&WOutDev[wDevID].lock);
AudioUnit_SetVolume(WOutDev[wDevID].audioUnit, left, right);
pthread_mutex_unlock(&WOutDev[wDevID].lock);
return MMSYSERR_NOERROR;
}
/**************************************************************************
* wodGetNumDevs [internal]
*/
static DWORD wodGetNumDevs(void)
{
TRACE("\n");
return MAX_WAVEOUTDRV;
}
/**************************************************************************
* wodDevInterfaceSize [internal]
*/
static DWORD wodDevInterfaceSize(UINT wDevID, LPDWORD dwParam1)
{
TRACE("(%u, %p)\n", wDevID, dwParam1);
*dwParam1 = MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].cadev->interface_name, -1,
NULL, 0 ) * sizeof(WCHAR);
return MMSYSERR_NOERROR;
}
/**************************************************************************
* wodDevInterface [internal]
*/
static DWORD wodDevInterface(UINT wDevID, PWCHAR dwParam1, DWORD dwParam2)
{
TRACE("\n");
if (dwParam2 >= MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].cadev->interface_name, -1,
NULL, 0 ) * sizeof(WCHAR))
{
MultiByteToWideChar(CP_ACP, 0, WOutDev[wDevID].cadev->interface_name, -1,
dwParam1, dwParam2 / sizeof(WCHAR));
return MMSYSERR_NOERROR;
}
return MMSYSERR_INVALPARAM;
}
/**************************************************************************
* wodMessage (WINEJACK.7)
*/
DWORD WINAPI CoreAudio_wodMessage(UINT wDevID, UINT wMsg, DWORD dwUser,
DWORD dwParam1, DWORD dwParam2)
{
TRACE("(%u, %s, %08lX, %08lX, %08lX);\n",
wDevID, getMessage(wMsg), dwUser, dwParam1, dwParam2);
switch (wMsg) {
case DRVM_INIT:
case DRVM_EXIT:
case DRVM_ENABLE:
case DRVM_DISABLE:
// FIXME: Pretend this is supported //
return 0;
case WODM_OPEN: return wodOpen(wDevID, (LPWAVEOPENDESC) dwParam1, dwParam2);
case WODM_CLOSE: return wodClose(wDevID);
case WODM_WRITE: return wodWrite(wDevID, (LPWAVEHDR) dwParam1, dwParam2);
case WODM_PAUSE: return wodPause(wDevID);
case WODM_GETPOS: return wodGetPosition(wDevID, (LPMMTIME) dwParam1, dwParam2);
case WODM_BREAKLOOP: return MMSYSERR_NOTSUPPORTED;
case WODM_PREPARE: return wodPrepare(wDevID, (LPWAVEHDR)dwParam1, dwParam2);
case WODM_UNPREPARE: return wodUnprepare(wDevID, (LPWAVEHDR)dwParam1, dwParam2);
case WODM_GETDEVCAPS: return wodGetDevCaps(wDevID, (LPWAVEOUTCAPSW) dwParam1, dwParam2);
case WODM_GETNUMDEVS: return wodGetNumDevs();
case WODM_GETPITCH:
case WODM_SETPITCH:
case WODM_GETPLAYBACKRATE:
case WODM_SETPLAYBACKRATE: return MMSYSERR_NOTSUPPORTED;
case WODM_GETVOLUME: return wodGetVolume(wDevID, (LPDWORD)dwParam1);
case WODM_SETVOLUME: return wodSetVolume(wDevID, dwParam1);
case WODM_RESTART: return wodRestart(wDevID);
case WODM_RESET: return wodReset(wDevID);
case DRV_QUERYDEVICEINTERFACESIZE: return wodDevInterfaceSize (wDevID, (LPDWORD)dwParam1);
case DRV_QUERYDEVICEINTERFACE: return wodDevInterface (wDevID, (PWCHAR)dwParam1, dwParam2);
case DRV_QUERYDSOUNDIFACE:
case DRV_QUERYDSOUNDDESC:
return MMSYSERR_NOTSUPPORTED;
default:
FIXME("unknown message %d!\n", wMsg);
}
return MMSYSERR_NOTSUPPORTED;
}
/*======================================================================*
* Low level DSOUND implementation *
*======================================================================*/
typedef struct IDsDriverImpl IDsDriverImpl;
typedef struct IDsDriverBufferImpl IDsDriverBufferImpl;
struct IDsDriverImpl
{
/* IUnknown fields */
const IDsDriverVtbl *lpVtbl;
DWORD ref;
/* IDsDriverImpl fields */
UINT wDevID;
IDsDriverBufferImpl*primary;
};
struct IDsDriverBufferImpl
{
/* IUnknown fields */
const IDsDriverBufferVtbl *lpVtbl;
DWORD ref;
/* IDsDriverBufferImpl fields */
IDsDriverImpl* drv;
DWORD buflen;
};
static DWORD wodDsCreate(UINT wDevID, PIDSDRIVER* drv)
{
/* we can't perform memory mapping as we don't have a file stream
interface with jack like we do with oss */
MESSAGE("This sound card's driver does not support direct access\n");
MESSAGE("The (slower) DirectSound HEL mode will be used instead.\n");
return MMSYSERR_NOTSUPPORTED;
}
static DWORD wodDsDesc(UINT wDevID, PDSDRIVERDESC desc)
{
memset(desc, 0, sizeof(*desc));
strcpy(desc->szDesc, "Wine CoreAudio DirectSound Driver");
strcpy(desc->szDrvname, "winecoreaudio.drv");
return MMSYSERR_NOERROR;
}
/*
CoreAudio IO threaded callback,
we can't call Wine debug channels, critical section or anything using NtCurrentTeb here.
*/
OSStatus CoreAudio_woAudioUnitIOProc(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData)
{
UInt32 channel;
WINE_WAVEOUT *wwo = (WINE_WAVEOUT *) inRefCon;
int nextPtr = 0;
int needNotify = 0;
pthread_mutex_lock(&wwo->lock);
if(wwo->state == WINE_WS_PLAYING)
{
unsigned int available;
unsigned int count = ioData->mBuffers[0].mDataByteSize;
available = wwo->lpPlayPtr->dwBufferLength - wwo->dwPartialOffset;
for (channel = 0; channel < ioData->mNumberBuffers; channel++)
{
if ( available >= count)
{
memcpy(ioData->mBuffers[channel].mData, wwo->lpPlayPtr->lpData + wwo->dwPartialOffset, count);
wwo->dwPartialOffset += count;
wwo->dwPlayedTotal += count;
}
else
{
int s;
memcpy(ioData->mBuffers[channel].mData, wwo->lpPlayPtr->lpData + wwo->dwPartialOffset, available);
wwo->dwPartialOffset += available;
wwo->dwPlayedTotal += available;
/* Fill with silence */
for (s = available; s < count; s++)
((int *)ioData->mBuffers[channel].mData)[s] = 0;
nextPtr = 1;
}
}
needNotify = 1;
}
else
{
for (channel = 0; channel < ioData->mNumberBuffers; channel++)
memset(ioData->mBuffers[channel].mData, 0, ioData->mBuffers[channel].mDataByteSize);
}
pthread_mutex_unlock(&wwo->lock);
if (nextPtr) wodHelper_PlayPtrNext(wwo);
if (needNotify) wodHelper_NotifyCompletions(wwo, FALSE);
return noErr;
}
#else
/**************************************************************************
* wodMessage (WINECOREAUDIO.7)
*/
DWORD WINAPI CoreAudio_wodMessage(WORD wDevID, WORD wMsg, DWORD dwUser,
DWORD dwParam1, DWORD dwParam2)
{
FIXME("(%u, %04X, %08lX, %08lX, %08lX): CoreAudio support not compiled into wine\n", wDevID, wMsg, dwUser, dwParam1, dwParam2);
return MMSYSERR_NOTENABLED;
}
#endif