--- /dev/null 2006-05-24 19:21:54.000000000 +0200 +++ dlls/winmm/winecoreaudio/audio.c 2006-05-24 19:20:56.000000000 +0200 @@ -0,0 +1,1542 @@ +/* + * 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 +#include +#include +#include +#ifdef HAVE_UNISTD_H +# include +#endif +#include +#include + +#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 +#include + +/* + 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() | | PLAYING | +* | PLAYING | write() | HEADER | PLAYING | +* | (other) | write() | | | +* | (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 CFStringRef MessageThreadPortName; + +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 TRACE + +#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 WINAPI messageThread(LPVOID p) +{ + CFMessagePortRef local; + CFRunLoopSourceRef source; + Boolean info; + + local = CFMessagePortCreateLocal(kCFAllocatorDefault, MessageThreadPortName, + &wodMessageHandler, NULL, &info); + + source = CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, local, (CFIndex)0); + CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); + + CFRunLoopRun(); + + CFRunLoopSourceInvalidate(source); + CFRelease(source); + CFRelease(local); + CFRelease(MessageThreadPortName); + MessageThreadPortName = NULL; + + return 0; +} + +static DWORD wodSendDriverCallbackMessage(WINE_WAVEOUT* wwo, WORD wMsg, DWORD dwParam1, DWORD dwParam2) +{ + CFDataRef data; + UInt32 buffer[4]; + SInt32 ret; + + CFMessagePortRef messagePort; + messagePort = CFMessagePortCreateRemote(kCFAllocatorDefault, MessageThreadPortName); + + buffer[0] = (UInt32) wwo->woID; + buffer[1] = (UInt32) wMsg; + buffer[2] = (UInt32) dwParam1; + buffer[3] = (UInt32) dwParam2; + + data = CFDataCreate(kCFAllocatorDefault, buffer, sizeof(buffer)); + if (!data) + return 0; + + ret = CFMessagePortSendRequest(messagePort, kWaveOutCallbackMessage, data, 0.0, 0.0, NULL, NULL); + CFRelease(data); + CFRelease(messagePort); + + 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; + + printf("out dwFormats = %08lX, dwSupport = %08lX\n", + CoreAudio_DefaultDevice.out_caps.dwFormats, CoreAudio_DefaultDevice.out_caps.dwSupport); + + return TRUE; +} + +/****************************************************************** +* CoreAudio_WaveInit +* +* Initialize CoreAudio_DefaultDevice +*/ +LONG CoreAudio_WaveInit(void) +{ + OSStatus status; + UInt32 propertySize; + CHAR szPname[MAXPNAMELEN]; + pthread_mutexattr_t mutexattr; + 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); + + pthread_mutexattr_init(&mutexattr); + pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_RECURSIVE); + + 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; + + pthread_mutex_init(&WOutDev[i].lock, &mutexattr); /* initialize the mutex */ + } + + pthread_mutexattr_destroy(&mutexattr); + + /* create mach messages handler */ + srandomdev(); + MessageThreadPortName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, + CFSTR("WaveMessagePort.%d.%lu"), getpid(), (unsigned long)random()); + if (!MessageThreadPortName) + { + ERR("Can't create message thread port name\n"); + return 1; + } + + 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) +{ + int i; + + TRACE("()\n"); + + /* Stop CFRunLoop in messageThread */ + CFMessagePortRef messagePort; + + messagePort = CFMessagePortCreateRemote(kCFAllocatorDefault, MessageThreadPortName); + CFMessagePortSendRequest(messagePort, kStopLoopMessage, NULL, 0.0, 0.0, NULL, NULL); + CFRelease(messagePort); + + for (i = 0; i < MAX_WAVEOUTDRV; ++i) + { + pthread_mutex_destroy(&WOutDev[i].lock); + } +} + +/*======================================================================* +* Low level WAVE OUT implementation * +*======================================================================*/ + +/************************************************************************** +* wodNotifyClient [internal] +*/ +static DWORD wodNotifyClient(WINE_WAVEOUT* wwo, WORD wMsg, DWORD dwParam1, DWORD dwParam2) +{ +#ifdef TRACE + fprintf(stderr, "trace:winecoreaudio:wodNotifyClient wMsg = 0x%04x dwParm1 = %04lX dwParam2 = %04lX\n", wMsg, dwParam1, dwParam2); +#endif + + 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)) + { + fprintf(stderr, "can't notify client !\n"); + return MMSYSERR_ERROR; + } + break; + case WOM_DONE: + if (wwo->wFlags != DCB_NULL && + ! wodSendDriverCallbackMessage(wwo, wMsg, dwParam1, dwParam2)) + { + fprintf(stderr, "can't notify client !\n"); + return MMSYSERR_ERROR; + } + break; + default: + fprintf(stderr, "Unknown callback message %u\n", wMsg); + 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] +*/ +static DWORD wodOpen(WORD wDevID, LPWAVEOPENDESC lpDesc, DWORD dwFlags) +{ + WINE_WAVEOUT* wwo; + DWORD retval; + DWORD ret; + + 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]; + pthread_mutex_lock(&wwo->lock); + + if (wwo->state != WINE_WS_CLOSED) + { + pthread_mutex_unlock(&wwo->lock); + return MMSYSERR_ALLOCATED; + } + + if (!AudioUnit_CreateDefaultAudioUnit((void *) wwo, &wwo->audioUnit)) + { + ERR("CoreAudio_CreateDefaultAudioUnit(%p) failed\n", wwo); + pthread_mutex_unlock(&wwo->lock); + return MMSYSERR_ERROR; + } + + if ((dwFlags & WAVE_DIRECTSOUND) && + !(wwo->caps.dwSupport & WAVECAPS_DIRECTSOUND)) + /* not supported, ignore it */ + dwFlags &= ~WAVE_DIRECTSOUND; + + AudioStreamBasicDescription streamFormat; + + streamFormat.mFormatID = kAudioFormatLinearPCM; + + /* FIXME check for 32bits float -> kLinearPCMFormatFlagIsFloat */ + streamFormat.mFormatFlags = (lpDesc->lpFormat->wBitsPerSample == 8 ? 0 : + 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) + { + AudioUnit_CloseAudioUnit(wwo->audioUnit); + pthread_mutex_unlock(&wwo->lock); + return WAVERR_BADFORMAT; /* FIXME return an error based on the OSStatus */ + } + wwo->streamDescription = streamFormat; + wwo->state = WINE_WS_STOPPED; + + 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; + + pthread_mutex_unlock(&wwo->lock); + + 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]; + pthread_mutex_lock(&wwo->lock); + if (wwo->lpQueuePtr) + { + WARN("buffers still playing !\n"); + pthread_mutex_unlock(&wwo->lock); + 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_unlock(&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_unlock(&wwo->lock); + return MMSYSERR_ERROR; /* FIXME return an error based on the OSStatus */ + } + pthread_mutex_unlock(&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; + + 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; + + 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. +*/ +static void wodHelper_BeginWaveHdr(WINE_WAVEOUT* wwo, LPWAVEHDR lpWaveHdr) +{ + OSStatus status; +#ifdef TRACE + fprintf(stderr, "trace:winecoreaudio:wodHelper_BeginWaveHdr\n"); +#endif + wwo->lpPlayPtr = lpWaveHdr; + + if (!lpWaveHdr) + { + if (wwo->state == WINE_WS_PLAYING) + { + wwo->state = WINE_WS_STOPPED; + status = AudioOutputUnitStop(wwo->audioUnit); + if (status) + fprintf(stderr, "err:winecoreaudio:wodHelper_BeginWaveHdr AudioOutputUnitStop return %c%c%c%c\n", (char) (status >> 24), + (char) (status >> 16), + (char) (status >> 8), + (char) status); + } + return; + } + + if (wwo->state == WINE_WS_STOPPED) + { + status = AudioOutputUnitStart(wwo->audioUnit); + if (status) { + ERR("AudioOutputUnitStart return %c%c%c%c\n", (char) (status >> 24), + (char) (status >> 16), + (char) (status >> 8), + (char) status); + } + else wwo->state = WINE_WS_PLAYING; + } + + if (lpWaveHdr->dwFlags & WHDR_BEGINLOOP) + { + if (wwo->lpLoopPtr) + { +#ifdef TRACE + fprintf(stderr, "trace:winecoreaudio:wodHelper_BeginWaveHdr Already in a loop. Discarding loop on this header (%p)\n", lpWaveHdr); +#endif + } else + { +#ifdef TRACE + fprintf(stderr, "trace:winecoreaudio:wodHelper_BeginWaveHdr Starting loop (%ldx) with %p\n", lpWaveHdr->dwLoops, lpWaveHdr); +#endif + 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. +*/ +static LPWAVEHDR wodHelper_PlayPtrNext(WINE_WAVEOUT* wwo) +{ + LPWAVEHDR lpWaveHdr; +#ifdef TRACE + fprintf(stderr, "trace:winecoreaudio:wodHelper_PlayPtrNext\n"); +#endif + pthread_mutex_lock(&wwo->lock); + + lpWaveHdr = wwo->lpPlayPtr; + if (!lpWaveHdr) + { + pthread_mutex_unlock(&wwo->lock); + return NULL; + } + + wwo->dwPartialOffset = 0; + if ((lpWaveHdr->dwFlags & WHDR_ENDLOOP) && wwo->lpLoopPtr) + { + /* We're at the end of a loop, loop if required */ + if (wwo->dwLoops > 1) + { + wwo->dwLoops--; + wwo->lpPlayPtr = wwo->lpLoopPtr; + } else + { + /* Handle overlapping loops correctly */ + if (wwo->lpLoopPtr != lpWaveHdr && (lpWaveHdr->dwFlags & WHDR_BEGINLOOP)) { +#ifdef TRACE + fprintf(stderr, "trace:winecoreaudio:wodHelper_PlayPtrNext Correctly handled case ? (ending loop buffer also starts a new loop)\n"); +#endif + /* 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 */ +#ifdef TRACE + fprintf(stderr, "trace:winecoreaudio:wodHelper_PlayPtrNext not inside of a loop, advancing to next wave header\n"); +#endif + 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 */ +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; +#ifdef TRACE + fprintf(stderr, "trace:winecoreaudio:wodHelper_NotifyCompletions notifying client: lpWaveHdr=(%p) lpPlayPtr=(%p) dwFlags=(%ld)\n", + lpWaveHdr, wwo->lpPlayPtr, lpWaveHdr->dwFlags); +#endif + + wodNotifyClient(wwo, WOM_DONE, (DWORD)lpWaveHdr, 0); + } +#ifdef TRACE + fprintf(stderr, "trace:winecoreaudio:wodHelper_NotifyCompletions Not notifying client: lpWaveHdr=(%p) lpPlayPtr=(%p) lpLoopPtr=(%p)\n", + lpWaveHdr, wwo->lpPlayPtr, wwo->lpLoopPtr); +#endif + 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 DWORD wodHelper_Reset(WINE_WAVEOUT* wwo, BOOL reset) +{ + OSStatus status = noErr; + FIXME("\n"); + + /* updates current notify list */ + /* if resetting, remove all wave headers and notify client that all headers were completed */ + wodHelper_NotifyCompletions(wwo, reset); + + pthread_mutex_lock(&wwo->lock); + + if (reset) + { + wwo->lpPlayPtr = wwo->lpQueuePtr = wwo->lpLoopPtr = NULL; + wwo->state = WINE_WS_STOPPED; + wwo->dwPlayedTotal = wwo->dwWrittenTotal = 0; + + wwo->dwPartialOffset = 0; /* Clear partial wavehdr */ + } + else + { + 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 && 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; + } + + status = AudioOutputUnitStop(wwo->audioUnit); + + pthread_mutex_unlock(&wwo->lock); + + if (status) { + fprintf(stderr, "err:winecoreaudio:wodHelper_Reset 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; +} + + +/************************************************************************** +* 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)) + /* Do nothing */; + *wh = lpWaveHdr; + + if (!wwo->lpPlayPtr) + wodHelper_BeginWaveHdr(wwo,lpWaveHdr); + 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; + } + + pthread_mutex_lock(&WOutDev[wDevID].lock); + if (WOutDev[wDevID].state == WINE_WS_PLAYING || WOutDev[wDevID].state == WINE_WS_STOPPED) + { + WOutDev[wDevID].state = WINE_WS_PAUSED; + OSStatus status = AudioOutputUnitStop(WOutDev[wDevID].audioUnit); + if (status) { + fprintf(stderr, "err:winecoreaudio:wodPause AudioOutputUnitStop return %c%c%c%c\n", (char) (status >> 24), + (char) (status >> 16), + (char) (status >> 8), + (char) status); + pthread_mutex_unlock(&WOutDev[wDevID].lock); + return MMSYSERR_ERROR; /* FIXME return an error based on the OSStatus */ + } + } + 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; + } + + pthread_mutex_lock(&WOutDev[wDevID].lock); + if (WOutDev[wDevID].state == WINE_WS_PAUSED) + { + if (WOutDev[wDevID].lpPlayPtr) + { + OSStatus 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); + pthread_mutex_unlock(&WOutDev[wDevID].lock); + return MMSYSERR_ERROR; /* FIXME return an error based on the OSStatus */ + } + WOutDev[wDevID].state = WINE_WS_PLAYING; + } + else + WOutDev[wDevID].state = WINE_WS_STOPPED; + } + 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; + } + + return wodHelper_Reset(&WOutDev[wDevID], TRUE); +} + +/************************************************************************** +* 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; +} + +OSStatus CoreAudio_woAudioUnitIOProc(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) +{ + UInt32 buffer; + WINE_WAVEOUT *wwo = (WINE_WAVEOUT *) inRefCon; + int nextPtr = 0; + int needNotify = 0; + + unsigned int dataNeeded = ioData->mBuffers[0].mDataByteSize; + unsigned int dataProvided = 0; + + while (dataNeeded > 0) + { + pthread_mutex_lock(&wwo->lock); + + if (wwo->state == WINE_WS_PLAYING && wwo->lpPlayPtr) + { + unsigned int available = wwo->lpPlayPtr->dwBufferLength - wwo->dwPartialOffset; + unsigned int toCopy; + + if (available >= dataNeeded) + toCopy = dataNeeded; + else + toCopy = available; + + if (toCopy > 0) + { + memcpy((char*)ioData->mBuffers[0].mData + dataProvided, + wwo->lpPlayPtr->lpData + wwo->dwPartialOffset, toCopy); + wwo->dwPartialOffset += toCopy; + wwo->dwPlayedTotal += toCopy; + dataProvided += toCopy; + dataNeeded -= toCopy; + available -= toCopy; + } + + if (available == 0) + nextPtr = 1; + + needNotify = 1; + } + else + { + memset((char*)ioData->mBuffers[0].mData + dataProvided, 0, dataNeeded); + dataProvided += dataNeeded; + dataNeeded = 0; + } + + pthread_mutex_unlock(&wwo->lock); + + if (nextPtr) + { + wodHelper_PlayPtrNext(wwo); + nextPtr = 0; + } + } + + // We only fill buffer 0. Set any others that might be requested to 0. + for (buffer = 1; buffer < ioData->mNumberBuffers; buffer++) + { + memset(ioData->mBuffers[buffer].mData, 0, ioData->mBuffers[buffer].mDataByteSize); + } + + 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 --- /dev/null 2006-05-24 19:21:54.000000000 +0200 +++ dlls/winmm/winecoreaudio/audiounit.c 2006-05-22 20:23:51.000000000 +0200 @@ -0,0 +1,144 @@ +/* + * Wine Driver for CoreAudio / AudioUnit + * + * 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" + +#ifdef HAVE_AUDIOUNIT_AUDIOUNIT_H +#include + +static char streamStr[512] = {0}; +static char *streamDescription(AudioStreamBasicDescription stream) +{ + sprintf(streamStr, "\n 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", + stream.mSampleRate, + (char) (stream.mFormatID >> 24), + (char) (stream.mFormatID >> 16), + (char) (stream.mFormatID >> 8), + (char) stream.mFormatID, + stream.mFormatFlags, + stream.mBytesPerPacket, + stream.mFramesPerPacket, + stream.mBytesPerFrame, + stream.mChannelsPerFrame, + stream.mBitsPerChannel); + return streamStr; +} + +extern OSStatus CoreAudio_woAudioUnitIOProc(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData); + +int AudioUnit_CreateDefaultAudioUnit(void *wwo, AudioUnit *au) +{ + OSStatus err; + ComponentDescription desc; + AURenderCallbackStruct callbackStruct; + + fprintf (stderr, "%s\n", __FUNCTION__); + + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_DefaultOutput; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + Component comp = FindNextComponent(NULL, &desc); + if (comp == NULL) + return 0; + + err = OpenAComponent(comp, au); + if (comp == NULL) + return 0; + + callbackStruct.inputProc = CoreAudio_woAudioUnitIOProc; + callbackStruct.inputProcRefCon = wwo; + + err = AudioUnitSetProperty( *au, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, + 0, + &callbackStruct, + sizeof(callbackStruct)); + return (err == noErr); +} + +int AudioUnit_CloseAudioUnit(AudioUnit au) +{ + OSStatus err = CloseComponent(au); + return (err == noErr); +} + +int AudioUnit_InitializeWithStreamDescription(AudioUnit au, AudioStreamBasicDescription stream) +{ + OSStatus err = noErr; + + fprintf (stderr, "%s %s\n", __FUNCTION__, streamDescription(stream)); + + err = AudioUnitSetProperty(au, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, + 0, &stream, sizeof(AudioStreamBasicDescription)); + + if (err != noErr) + { + fprintf (stderr, "%s AudioUnitSetProperty return an error %c%c%c%c\n", __FUNCTION__, (char) (err >> 24), (char) (err >> 16), (char) (err >> 8), (char) err); + return 0; + } + + err = AudioUnitInitialize(au); + if (err != noErr) + { + fprintf (stderr, "%s AudioUnitInitialize return an error %c%c%c%c\n", __FUNCTION__, (char) (err >> 24), (char) (err >> 16), (char) (err >> 8), (char) err); + return 0; + } + return 1; +} + +int AudioUnit_SetVolume(AudioUnit au, float left, float right) +{ + OSStatus err = noErr; + fprintf (stderr, "fixme:winecoreaudio:%s (%f, %f)\n", __FUNCTION__, left, right); + + err = AudioUnitSetParameter(au, kHALOutputParam_Volume, kAudioUnitParameterFlag_Output, 0, left, 0); + + if (err != noErr) + { + fprintf (stderr, "%s AudioUnitSetParameter return an error %c%c%c%c\n", __FUNCTION__, (char) (err >> 24), (char) (err >> 16), (char) (err >> 8), (char) err); + return 0; + } + return 1; +} + +int AudioUnit_GetVolume(AudioUnit au, float *left, float *right) +{ + OSStatus err = noErr; + fprintf (stderr, "fixme:winecoreaudio:%s\n", __FUNCTION__); + + err = AudioUnitGetParameter(au, kHALOutputParam_Volume, kAudioUnitParameterFlag_Output, 0, left); + if (err != noErr) + { + fprintf (stderr, "%s AudioUnitGetParameter return an error %c%c%c%c\n", __FUNCTION__, (char) (err >> 24), (char) (err >> 16), (char) (err >> 8), (char) err); + return 0; + } + *right = *left; + return 1; +} +#endif --- /dev/null 2006-05-24 19:22:46.000000000 +0200 +++ dlls/winmm/winecoreaudio/coreaudio.h 2006-05-23 21:46:45.000000000 +0200 @@ -0,0 +1,27 @@ +/* Definition for CoreAudio drivers + * + * Copyright 2005 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 + */ + +#ifndef __WINE_COREAUDIO_H +#define __WINE_COREAUDIO_H + +extern LONG CoreAudio_WaveInit(void); +extern void CoreAudio_WaveRelease(void); + + +#endif /* __WINE_COREAUDIO_H */ --- /dev/null 2006-05-24 19:22:46.000000000 +0200 +++ dlls/winmm/winecoreaudio/coreaudio.c 2006-05-23 21:46:45.000000000 +0200 @@ -0,0 +1,117 @@ +/* + * Wine Driver for CoreAudio + * + * Copyright 2002 Chris Morgan + * Copyright 2005 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 + +#include "windef.h" +#include "winbase.h" +#include "wingdi.h" +#include "winuser.h" +#include "mmddk.h" +#include "coreaudio.h" +#include "wine/library.h" +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(coreaudio); + +#if defined(HAVE_COREAUDIO_COREAUDIO_H) && defined(HAVE_AUDIOUNIT_AUDIOUNIT_H) +/************************************************************************** + * CoreAudio_drvLoad [internal] + */ +static LRESULT CoreAudio_drvLoad(void) +{ + TRACE("()\n"); + CoreAudio_WaveInit(); + return 1; +} + +/************************************************************************** + * CoreAudio_drvFree [internal] + */ +static LRESULT CoreAudio_drvFree(void) +{ + TRACE("()\n"); + CoreAudio_WaveRelease(); + return 1; +} + +/************************************************************************** + * CoreAudio_drvOpen [internal] + */ +static LRESULT CoreAudio_drvOpen(LPSTR str) +{ + TRACE("(%s)\n", str); + return 1; +} + +/************************************************************************** + * CoreAudio_drvClose [internal] + */ +static DWORD CoreAudio_drvClose(DWORD dwDevID) +{ + TRACE("(%08lx)\n", dwDevID); + return 1; +} + +/************************************************************************** + * DriverProc (WINECOREAUDIO.1) + */ +LRESULT CALLBACK CoreAudio_DriverProc(DWORD_PTR dwDevID, HDRVR hDriv, UINT wMsg, + LPARAM dwParam1, LPARAM dwParam2) +{ + TRACE("(%08lX, %p, %s (%08X), %08lX, %08lX)\n", + dwDevID, hDriv, wMsg == DRV_LOAD ? "DRV_LOAD" : + wMsg == DRV_FREE ? "DRV_FREE" : + wMsg == DRV_OPEN ? "DRV_OPEN" : + wMsg == DRV_CLOSE ? "DRV_CLOSE" : + wMsg == DRV_ENABLE ? "DRV_ENABLE" : + wMsg == DRV_DISABLE ? "DRV_DISABLE" : + wMsg == DRV_QUERYCONFIGURE ? "DRV_QUERYCONFIGURE" : + wMsg == DRV_CONFIGURE ? "DRV_CONFIGURE" : + wMsg == DRV_INSTALL ? "DRV_INSTALL" : + wMsg == DRV_REMOVE ? "DRV_REMOVE" : "UNKNOWN", + wMsg, dwParam1, dwParam2); + + switch(wMsg) { + case DRV_LOAD: return CoreAudio_drvLoad(); + case DRV_FREE: return CoreAudio_drvFree(); + case DRV_OPEN: return CoreAudio_drvOpen((LPSTR)dwParam1); + case DRV_CLOSE: return CoreAudio_drvClose(dwDevID); + case DRV_ENABLE: return 1; + case DRV_DISABLE: return 1; + case DRV_QUERYCONFIGURE: return 1; + case DRV_CONFIGURE: MessageBoxA(0, "CoreAudio driver!", "CoreAudio driver", MB_OK); return 1; + case DRV_INSTALL: return DRVCNF_RESTART; + case DRV_REMOVE: return DRVCNF_RESTART; + default: + return DefDriverProc(dwDevID, hDriv, wMsg, dwParam1, dwParam2); + } +} +#else +LRESULT CALLBACK CoreAudio_DriverProc(DWORD_PTR dwDevID, HDRVR hDriv, UINT wMsg, + LPARAM dwParam1, LPARAM dwParam2) +{ + return DefDriverProc(dwDevID, hDriv, wMsg, dwParam1, dwParam2); +} +#endif --- /dev/null 2006-05-24 19:22:46.000000000 +0200 +++ dlls/winmm/winecoreaudio/Makefile.in 2006-05-22 23:54:33.000000000 +0200 @@ -0,0 +1,16 @@ +TOPSRCDIR = @top_srcdir@ +TOPOBJDIR = ../../.. +SRCDIR = @srcdir@ +VPATH = @srcdir@ +MODULE = winecoreaudio.drv +IMPORTS = winmm user32 kernel32 +EXTRALIBS = $(LIBUUID) @COREAUDIO@ + +C_SRCS = \ + audio.c \ + audiounit.c \ + coreaudio.c + +@MAKE_DLL_RULES@ + +### Dependencies: --- /dev/null 2006-05-24 19:22:46.000000000 +0200 +++ dlls/winmm/winecoreaudio/winecoreaudio.drv.spec 2005-12-28 02:42:58.000000000 +0100 @@ -0,0 +1,2 @@ +@ stdcall -private DriverProc(long long long long long) CoreAudio_DriverProc +@ stdcall -private wodMessage(long long long long long) CoreAudio_wodMessage