/* -*- tab-width: 8; c-basic-offset: 4 -*- */ /* * Sample Wine Driver for Open Sound System (featured in Linux and FreeBSD) * * Copyright 1994 Martin Ayotte * 1999 Eric Pouech (async playing in waveOut) */ /* * FIXME: * pause in waveOut does not work correctly * implement async handling in waveIn */ /*#define EMULATE_SB16*/ #include #include #include #include #include #include #include "wine/winuser16.h" #include "driver.h" #include "mmddk.h" #include "oss.h" #include "heap.h" #include "ldt.h" #include "debugtools.h" DEFAULT_DEBUG_CHANNEL(wave) #ifdef HAVE_OSS #define SOUND_DEV "/dev/dsp" #define MIXER_DEV "/dev/mixer" #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 /* events to be send to device */ #define WINE_WM_PAUSING (WM_USER + 1) #define WINE_WM_RESTARTING (WM_USER + 2) #define WINE_WM_RESETTING (WM_USER + 3) #define WINE_WM_CLOSING (WM_USER + 4) #define WINE_WM_HEADER (WM_USER + 5) typedef struct { int unixdev; volatile int state; /* one of the WINE_WS_ manifest constants */ DWORD dwFragmentSize; /* size of OSS buffer fragment */ WAVEOPENDESC waveDesc; WORD wFlags; PCMWAVEFORMAT format; LPWAVEHDR lpQueueHdr; /* pending buffers for playing */ LPWAVEHDR lpNotifyHdr; /* list of wavehdr for which write() has been called, pending for notification */ DWORD dwPlayedTotal; /* number of bytes played since opening */ DWORD dwPlayed; /* number of bytes played since last DSP_RESET */ DWORD dwNotifiedBytes; /* number of bytes for which wavehdr notification has been done */ /* info on current lpQueueHdr->lpWaveHdr */ DWORD dwOffCurrHdr; /* offset in lpQueueHdr->lpWaveHdr->lpData for fragments */ DWORD dwRemain; /* number of bytes to write to end the current fragment */ /* synchronization stuff */ HANDLE hThread; DWORD dwThreadID; HANDLE hEvent; WORD wMaxFragments; /* max number of fragments that can be written to dsp */ WORD wFragsUsedInQueue; /* current number of used fragments inside dsp queue */ } WINE_WAVEOUT; typedef struct { int unixdev; volatile int state; DWORD dwFragmentSize; /* OpenSound '/dev/dsp' give us that size */ WAVEOPENDESC waveDesc; WORD wFlags; PCMWAVEFORMAT format; LPWAVEHDR lpQueueHdr; DWORD dwTotalRecorded; } WINE_WAVEIN; static WINE_WAVEOUT WOutDev [MAX_WAVEOUTDRV]; static WINE_WAVEIN WInDev [MAX_WAVEOUTDRV]; /*======================================================================* * Low level WAVE implemantation * *======================================================================*/ /************************************************************************** * WAVE_NotifyClient [internal] */ static DWORD WAVE_NotifyClient(UINT wDevID, WORD wMsg, DWORD dwParam1, DWORD dwParam2) { TRACE("wDevID = %04X wMsg = %d dwParm1 = %04lX dwParam2 = %04lX\n",wDevID, wMsg, dwParam1, dwParam2); switch (wMsg) { case WOM_OPEN: case WOM_CLOSE: case WOM_DONE: if (wDevID > MAX_WAVEOUTDRV) return MCIERR_INTERNAL; if (WOutDev[wDevID].wFlags != DCB_NULL && !DriverCallback(WOutDev[wDevID].waveDesc.dwCallback, WOutDev[wDevID].wFlags, WOutDev[wDevID].waveDesc.hWave, wMsg, WOutDev[wDevID].waveDesc.dwInstance, dwParam1, dwParam2)) { WARN("can't notify client !\n"); return MMSYSERR_NOERROR; } break; case WIM_OPEN: case WIM_CLOSE: case WIM_DATA: if (wDevID > MAX_WAVEINDRV) return MCIERR_INTERNAL; if (WInDev[wDevID].wFlags != DCB_NULL && !DriverCallback(WInDev[wDevID].waveDesc.dwCallback, WInDev[wDevID].wFlags, WInDev[wDevID].waveDesc.hWave, wMsg, WInDev[wDevID].waveDesc.dwInstance, dwParam1, dwParam2)) { WARN("can't notify client !\n"); return MMSYSERR_NOERROR; } break; default: FIXME("Unknown CB message %u\n", wMsg); break; } return 0; } /*======================================================================* * Low level WAVE OUT implemantation * *======================================================================*/ /************************************************************************** * wodPlayer_WriteFragments [internal] * * wodPlayer helper. Writes as many fragments it can to unixdev. * Returns TRUE in case of buffer underrun. */ static BOOL wodPlayer_WriteFragments(WINE_WAVEOUT* wwo) { LPWAVEHDR lpWaveHdr; LPBYTE lpData; int count; for (;;) { TRACE("Fragments: %d used on fd %d\n", wwo->wFragsUsedInQueue, wwo->unixdev); if (wwo->wFragsUsedInQueue == wwo->wMaxFragments) /* output queue is full, wait a bit */ return FALSE; lpWaveHdr = wwo->lpQueueHdr; if (!lpWaveHdr) { if (wwo->dwRemain > 0 && /* still data to send to complete current fragment */ wwo->dwNotifiedBytes >= wwo->dwFragmentSize && /* first fragment has been played */ wwo->wFragsUsedInQueue < 2) { /* done with all waveOutWrite()' fragments */ /* FIXME: should do better handling here */ TRACE("Oooch, buffer underrun !\n"); return TRUE; /* force resetting of waveOut device */ } return FALSE; /* wait a bit */ } if (wwo->dwOffCurrHdr == 0) { TRACE("Starting a new wavehdr %p of %ld bytes\n", lpWaveHdr, lpWaveHdr->dwBufferLength); if (lpWaveHdr->dwFlags & WHDR_BEGINLOOP) FIXME("NIY: loops (%lu) in wavehdr\n", lpWaveHdr->dwLoops); } lpData = lpWaveHdr->lpData; /* finish current wave hdr ? */ if (wwo->dwOffCurrHdr + wwo->dwRemain >= lpWaveHdr->dwBufferLength) { DWORD toWrite = lpWaveHdr->dwBufferLength - wwo->dwOffCurrHdr; /* write end of current wave hdr */ count = write(wwo->unixdev, lpData + wwo->dwOffCurrHdr, toWrite); TRACE("write(%p[%5lu], %5lu) => %d\n", lpData, wwo->dwOffCurrHdr, toWrite, count); if (count > 0) { LPWAVEHDR* wh; /* move lpWaveHdr to the end of notify list */ for (wh = &(wwo->lpNotifyHdr); *wh; wh = &((*wh)->lpNext)); *wh = lpWaveHdr; wwo->lpQueueHdr = lpWaveHdr->lpNext; lpWaveHdr->lpNext = 0; wwo->dwOffCurrHdr = 0; if ((wwo->dwRemain -= count) == 0) { wwo->dwRemain = wwo->dwFragmentSize; wwo->wFragsUsedInQueue++; } } continue; /* try to go to use next wavehdr */ } else { count = write(wwo->unixdev, lpData + wwo->dwOffCurrHdr, wwo->dwRemain); TRACE("write(%p[%5lu], %5lu) => %d\n", lpData, wwo->dwOffCurrHdr, wwo->dwRemain, count); if (count > 0) { wwo->dwOffCurrHdr += count; wwo->dwRemain = wwo->dwFragmentSize; wwo->wFragsUsedInQueue++; } } } } /************************************************************************** * wodPlayer_WriteFragments [internal] * * wodPlayer helper. Notifies (and remove from queue) all the wavehdr which content * have been played (actually to speaker, not to unixdev fd). */ static void wodPlayer_Notify(WINE_WAVEOUT* wwo, WORD uDevID, BOOL force) { LPWAVEHDR lpWaveHdr; count_info cinfo; /* get effective number of written bytes */ if (!force) { if (ioctl(wwo->unixdev, SNDCTL_DSP_GETOPTR, &cinfo) == -1) { perror("ioctl SNDCTL_DSP_GETOPTR"); wwo->hThread = 0; wwo->state = WINE_WS_STOPPED; ExitThread(-1); } TRACE("Played %d bytes (played=%ld) on fd %d\n", cinfo.bytes, wwo->dwPlayed, wwo->unixdev); wwo->wFragsUsedInQueue -= cinfo.bytes / wwo->dwFragmentSize - wwo->dwPlayed / wwo->dwFragmentSize; wwo->dwPlayed = cinfo.bytes; } if (force || cinfo.bytes > wwo->dwNotifiedBytes) { /* remove all wavehdr which can be notified */ while (wwo->lpNotifyHdr && (force || (cinfo.bytes >= wwo->dwNotifiedBytes + wwo->lpNotifyHdr->dwBufferLength))) { lpWaveHdr = wwo->lpNotifyHdr; lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; lpWaveHdr->dwFlags |= WHDR_DONE; if (!force) wwo->dwNotifiedBytes += lpWaveHdr->dwBufferLength; wwo->lpNotifyHdr = lpWaveHdr->lpNext; TRACE("Notifying client with %p\n", lpWaveHdr); if (WAVE_NotifyClient(uDevID, WOM_DONE, (DWORD)lpWaveHdr, 0) != MMSYSERR_NOERROR) { WARN("can't notify client !\n"); } } } } /************************************************************************** * wodPlayer_Reset [internal] * * wodPlayer helper. Resets current output stream. */ static void wodPlayer_Reset(WINE_WAVEOUT* wwo, WORD uDevID, BOOL reset) { LPWAVEHDR lpWaveHdr; /* updates current notify list */ wodPlayer_Notify(wwo, uDevID, FALSE); /* flush all possible output */ if (ioctl(wwo->unixdev, SNDCTL_DSP_RESET, 0) == -1) { perror("ioctl SNDCTL_DSP_RESET"); wwo->hThread = 0; wwo->state = WINE_WS_STOPPED; ExitThread(-1); } wwo->dwOffCurrHdr = 0; if (reset) { /* empty notify list */ wodPlayer_Notify(wwo, uDevID, TRUE); if (wwo->lpNotifyHdr) { ERR("out of sync\n"); } /* get rid also of all the current queue */ for (lpWaveHdr = wwo->lpQueueHdr; lpWaveHdr; lpWaveHdr = lpWaveHdr->lpNext) { lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; lpWaveHdr->dwFlags |= WHDR_DONE; if (WAVE_NotifyClient(uDevID, WOM_DONE, (DWORD)lpWaveHdr, 0) != MMSYSERR_NOERROR) { WARN("can't notify client !\n"); } } wwo->lpQueueHdr = 0; wwo->state = WINE_WS_STOPPED; wwo->dwPlayedTotal = 0; } else { /* move notify list to begining of lpQueueHdr list */ while (wwo->lpNotifyHdr) { lpWaveHdr = wwo->lpNotifyHdr; wwo->lpNotifyHdr = lpWaveHdr->lpNext; lpWaveHdr->lpNext = wwo->lpQueueHdr; wwo->lpQueueHdr = lpWaveHdr; } wwo->state = WINE_WS_PAUSED; wwo->dwPlayedTotal += wwo->dwPlayed; } wwo->dwNotifiedBytes = wwo->dwPlayed = 0; wwo->wFragsUsedInQueue = 0; } /************************************************************************** * wodPlayer [internal] */ static DWORD CALLBACK wodPlayer(LPVOID pmt) { WORD uDevID = (DWORD)pmt; WINE_WAVEOUT* wwo = (WINE_WAVEOUT*)&WOutDev[uDevID]; WAVEHDR* lpWaveHdr; DWORD dwSleepTime; MSG msg; PeekMessageA(&msg, 0, 0, 0, 0); wwo->state = WINE_WS_STOPPED; wwo->dwNotifiedBytes = 0; wwo->dwOffCurrHdr = 0; wwo->dwRemain = wwo->dwFragmentSize; wwo->lpQueueHdr = NULL; wwo->lpNotifyHdr = NULL; wwo->wFragsUsedInQueue = 0; wwo->dwPlayedTotal = 0; wwo->dwPlayed = 0; TRACE("imhere[0]\n"); SetEvent(wwo->hEvent); /* make sleep time to be # of ms to output a fragment */ dwSleepTime = (wwo->dwFragmentSize * 1000) / wwo->format.wf.nAvgBytesPerSec; for (;;) { /* wait for dwSleepTime or an event in thread's queue */ /* FIXME: could improve wait time depending on queue state, * ie, number of queued fragments */ TRACE("imhere[1]\n"); MsgWaitForMultipleObjects(0, NULL, FALSE, (wwo->state == WINE_WS_PLAYING) ? (MAX(wwo->wFragsUsedInQueue, 4) - 2) * dwSleepTime : /*INFINITE*/100, QS_POSTMESSAGE); TRACE("imhere[2]\n"); wodPlayer_Notify(wwo, uDevID, FALSE); while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) { switch (msg.message) { case WINE_WM_PAUSING: wodPlayer_Reset(wwo, uDevID, FALSE); wwo->state = WINE_WS_PAUSED; SetEvent(wwo->hEvent); break; case WINE_WM_RESTARTING: wwo->state = WINE_WS_PLAYING; SetEvent(wwo->hEvent); break; case WINE_WM_HEADER: lpWaveHdr = (LPWAVEHDR)msg.lParam; lpWaveHdr->dwFlags &= ~WHDR_DONE; lpWaveHdr->dwFlags |= WHDR_INQUEUE; lpWaveHdr->lpNext = 0; /* insert buffer at the end of queue */ { LPWAVEHDR* wh; for (wh = &(wwo->lpQueueHdr); *wh; wh = &((*wh)->lpNext)); *wh = lpWaveHdr; } if (wwo->state == WINE_WS_STOPPED) wwo->state = WINE_WS_PLAYING; break; case WINE_WM_RESETTING: wodPlayer_Reset(wwo, uDevID, TRUE); SetEvent(wwo->hEvent); break; case WINE_WM_CLOSING: /* sanity check: this should not happen since the device must have been reset before */ if (wwo->lpNotifyHdr || wwo->lpQueueHdr) { ERR("out of sync\n"); } wwo->hThread = 0; wwo->state = WINE_WS_CLOSED; SetEvent(wwo->hEvent); ExitThread(0); /* shouldn't go here */ default: FIXME("unknown message %d\n", msg.message); break; } } if (wwo->state == WINE_WS_PLAYING) { wodPlayer_WriteFragments(wwo); } wodPlayer_Notify(wwo, uDevID, FALSE); } ExitThread(0); /* just for not generating compilation warnings... should never be executed */ return 0; } /************************************************************************** * wodGetDevCaps [internal] */ static DWORD wodGetDevCaps(WORD wDevID, LPWAVEOUTCAPSA lpCaps, DWORD dwSize) { int audio; int smplrate; int samplesize = 16; int dsp_stereo = 1; int bytespersmpl; int caps; int mask; TRACE("(%u, %p, %lu);\n", wDevID, lpCaps, dwSize); if (lpCaps == NULL) return MMSYSERR_NOTENABLED; if (access(SOUND_DEV,0) != 0) return MMSYSERR_NOTENABLED; if (wDevID >= MAX_WAVEOUTDRV) { TRACE("MAX_WAVOUTDRV reached !\n"); return MMSYSERR_BADDEVICEID; } if (WOutDev[wDevID].unixdev == 0) { audio = open(SOUND_DEV, O_WRONLY, 0); if (audio == -1) return MMSYSERR_ALLOCATED; } else { audio = WOutDev[wDevID].unixdev; } /* FIXME: some programs compare this string against the content of the registry * for MM drivers. The name have to match in order the program to work * (e.g. MS win9x mplayer.exe) */ #ifdef EMULATE_SB16 lpCaps->wMid = 0x0002; lpCaps->wPid = 0x0104; strcpy(lpCaps->szPname, "SB16 Wave Out"); #else lpCaps->wMid = 0x00FF; /* Manufac ID */ lpCaps->wPid = 0x0001; /* Product ID */ /* strcpy(lpCaps->szPname, "OpenSoundSystem WAVOUT Driver");*/ strcpy(lpCaps->szPname, "CS4236/37/38"); #endif lpCaps->vDriverVersion = 0x0100; lpCaps->dwFormats = 0x00000000; lpCaps->dwSupport = WAVECAPS_VOLUME; IOCTL(audio, SNDCTL_DSP_GETFMTS, mask); TRACE("OSS dsp mask=%08x\n", mask); mask = AFMT_QUERY; IOCTL(audio, SNDCTL_DSP_SETFMT, mask); TRACE("OSS dsp current=%08x\n", mask); /* First bytespersampl, then stereo */ bytespersmpl = (IOCTL(audio, SNDCTL_DSP_SAMPLESIZE, samplesize) != 0) ? 1 : 2; lpCaps->wChannels = (IOCTL(audio, SNDCTL_DSP_STEREO, dsp_stereo) != 0) ? 1 : 2; if (lpCaps->wChannels > 1) lpCaps->dwSupport |= WAVECAPS_LRVOLUME; smplrate = 44100; if (IOCTL(audio, SNDCTL_DSP_SPEED, smplrate) == 0) { lpCaps->dwFormats |= WAVE_FORMAT_4M08; if (lpCaps->wChannels > 1) lpCaps->dwFormats |= WAVE_FORMAT_4S08; if (bytespersmpl > 1) { lpCaps->dwFormats |= WAVE_FORMAT_4M16; if (lpCaps->wChannels > 1) lpCaps->dwFormats |= WAVE_FORMAT_4S16; } } smplrate = 22050; if (IOCTL(audio, SNDCTL_DSP_SPEED, smplrate) == 0) { lpCaps->dwFormats |= WAVE_FORMAT_2M08; if (lpCaps->wChannels > 1) lpCaps->dwFormats |= WAVE_FORMAT_2S08; if (bytespersmpl > 1) { lpCaps->dwFormats |= WAVE_FORMAT_2M16; if (lpCaps->wChannels > 1) lpCaps->dwFormats |= WAVE_FORMAT_2S16; } } smplrate = 11025; if (IOCTL(audio, SNDCTL_DSP_SPEED, smplrate) == 0) { lpCaps->dwFormats |= WAVE_FORMAT_1M08; if (lpCaps->wChannels > 1) lpCaps->dwFormats |= WAVE_FORMAT_1S08; if (bytespersmpl > 1) { lpCaps->dwFormats |= WAVE_FORMAT_1M16; if (lpCaps->wChannels > 1) lpCaps->dwFormats |= WAVE_FORMAT_1S16; } } if (IOCTL(audio, SNDCTL_DSP_GETCAPS, caps) == 0) { if ((caps & DSP_CAP_REALTIME) && !(caps && DSP_CAP_BATCH)) lpCaps->dwFormats |= WAVECAPS_SAMPLEACCURATE; } if (WOutDev[wDevID].unixdev == 0) { close(audio); } TRACE("dwFormats = %08lX\n", lpCaps->dwFormats); return MMSYSERR_NOERROR; } /************************************************************************** * wodOpen [internal] */ static DWORD wodOpen(WORD wDevID, LPWAVEOPENDESC lpDesc, DWORD dwFlags) { int audio; int sample_rate; int sample_size; int dsp_stereo; int audio_fragment; int fragment_size; WAVEOUTCAPSA woc; 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; } wodGetDevCaps(wDevID, &woc, sizeof(woc)); /* only PCM format is supported so far... */ if (lpDesc->lpFormat->wFormatTag != WAVE_FORMAT_PCM || lpDesc->lpFormat->nChannels == 0 || lpDesc->lpFormat->nSamplesPerSec == 0) { WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%ld !\n", lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels, lpDesc->lpFormat->nSamplesPerSec); 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; } WOutDev[wDevID].unixdev = 0; if (access(SOUND_DEV, 0) != 0) return MMSYSERR_NOTENABLED; audio = open(SOUND_DEV, O_WRONLY, 0); if (audio == -1) { WARN("can't open !\n"); return MMSYSERR_ALLOCATED ; } WOutDev[wDevID].wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK); memcpy(&WOutDev[wDevID].waveDesc, lpDesc, sizeof(WAVEOPENDESC)); memcpy(&WOutDev[wDevID].format, lpDesc->lpFormat, sizeof(PCMWAVEFORMAT)); if (WOutDev[wDevID].format.wBitsPerSample == 0) { WOutDev[wDevID].format.wBitsPerSample = 8 * (WOutDev[wDevID].format.wf.nAvgBytesPerSec / WOutDev[wDevID].format.wf.nSamplesPerSec) / WOutDev[wDevID].format.wf.nChannels; } /* shockwave player uses only 4 1k-fragments at a rate of 22050 bytes/sec * thus leading to 46ms per fragment, and a turnaround time of 185ms */ /* 2^10=1024 bytes per fragment, 16 fragments max */ audio_fragment = 0x000F000A; sample_size = WOutDev[wDevID].format.wBitsPerSample; sample_rate = WOutDev[wDevID].format.wf.nSamplesPerSec; dsp_stereo = (WOutDev[wDevID].format.wf.nChannels > 1) ? 1 : 0; IOCTL(audio, SNDCTL_DSP_SETFRAGMENT, audio_fragment); /* First size and stereo then samplerate */ IOCTL(audio, SNDCTL_DSP_SAMPLESIZE, sample_size); IOCTL(audio, SNDCTL_DSP_STEREO, dsp_stereo); IOCTL(audio, SNDCTL_DSP_SPEED, sample_rate); /* paranoid checks */ if (sample_size != WOutDev[wDevID].format.wBitsPerSample) ERR("Can't set sample_size to %u (%d)\n", WOutDev[wDevID].format.wBitsPerSample, sample_size); if (dsp_stereo != (WOutDev[wDevID].format.wf.nChannels > 1) ? 1 : 0) ERR("Can't set stereo to %u (%d)\n", (WOutDev[wDevID].format.wf.nChannels > 1) ? 1 : 0, dsp_stereo); if (sample_rate != WOutDev[wDevID].format.wf.nSamplesPerSec) ERR("Can't set sample_rate to %lu (%d)\n", WOutDev[wDevID].format.wf.nSamplesPerSec, sample_rate); /* even if we set fragment size above, read it again, just in case */ IOCTL(audio, SNDCTL_DSP_GETBLKSIZE, fragment_size); WOutDev[wDevID].unixdev = audio; WOutDev[wDevID].dwFragmentSize = fragment_size; WOutDev[wDevID].wMaxFragments = HIWORD(audio_fragment) + 1; WOutDev[wDevID].hEvent = CreateEventA(NULL, FALSE, FALSE, NULL); WOutDev[wDevID].hThread = CreateThread(NULL, 0, wodPlayer, (LPVOID)(DWORD)wDevID, 0, &(WOutDev[wDevID].dwThreadID)); WaitForSingleObject(WOutDev[wDevID].hEvent, INFINITE); TRACE("fd=%d fragmentSize=%ld\n", WOutDev[wDevID].unixdev, WOutDev[wDevID].dwFragmentSize); TRACE("wBitsPerSample=%u, nAvgBytesPerSec=%lu, nSamplesPerSec=%lu, nChannels=%u nBlockAlign=%u!\n", WOutDev[wDevID].format.wBitsPerSample, WOutDev[wDevID].format.wf.nAvgBytesPerSec, WOutDev[wDevID].format.wf.nSamplesPerSec, WOutDev[wDevID].format.wf.nChannels, WOutDev[wDevID].format.wf.nBlockAlign); if (WAVE_NotifyClient(wDevID, WOM_OPEN, 0L, 0L) != MMSYSERR_NOERROR) { WARN("can't notify client !\n"); return MMSYSERR_INVALPARAM; } return MMSYSERR_NOERROR; } /************************************************************************** * wodClose [internal] */ static DWORD wodClose(WORD wDevID) { DWORD ret = MMSYSERR_NOERROR; TRACE("(%u);\n", wDevID); if (wDevID > MAX_WAVEOUTDRV || WOutDev[wDevID].unixdev == 0) { WARN("bad device ID !\n"); return MMSYSERR_BADDEVICEID; } if (WOutDev[wDevID].lpQueueHdr != NULL || WOutDev[wDevID].lpNotifyHdr != NULL) { WARN("buffers still playing !\n"); ret = WAVERR_STILLPLAYING; } else { TRACE("imhere[3-close]\n"); PostThreadMessageA(WOutDev[wDevID].dwThreadID, WINE_WM_CLOSING, 0, 0); WaitForSingleObject(WOutDev[wDevID].hEvent, INFINITE); CloseHandle(WOutDev[wDevID].hEvent); close(WOutDev[wDevID].unixdev); WOutDev[wDevID].unixdev = 0; WOutDev[wDevID].dwFragmentSize = 0; if (WAVE_NotifyClient(wDevID, WOM_CLOSE, 0L, 0L) != MMSYSERR_NOERROR) { WARN("can't notify client !\n"); ret = MMSYSERR_INVALPARAM; } } return ret; } /************************************************************************** * wodWrite [internal] * */ static DWORD wodWrite(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize) { TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize); /* first, do the sanity checks... */ if (wDevID >= MAX_WAVEOUTDRV || WOutDev[wDevID].unixdev == 0) { WARN("bad dev ID !\n"); return MMSYSERR_BADDEVICEID; } if (lpWaveHdr->lpData == NULL || !(lpWaveHdr->dwFlags & WHDR_PREPARED)) return WAVERR_UNPREPARED; if (lpWaveHdr->dwFlags & WHDR_INQUEUE) return WAVERR_STILLPLAYING; TRACE("imhere[3-HEADER]\n"); PostThreadMessageA(WOutDev[wDevID].dwThreadID, WINE_WM_HEADER, 0, (DWORD)lpWaveHdr); return MMSYSERR_NOERROR; } /************************************************************************** * wodPrepare [internal] */ static DWORD wodPrepare(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize) { TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize); if (wDevID > MAX_WAVEOUTDRV || WOutDev[wDevID].unixdev == 0) { 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) { TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize); if (wDevID > MAX_WAVEOUTDRV || WOutDev[wDevID].unixdev == 0) { 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; } /************************************************************************** * wodPause [internal] */ static DWORD wodPause(WORD wDevID) { TRACE("(%u);!\n", wDevID); if (wDevID > MAX_WAVEOUTDRV || WOutDev[wDevID].unixdev == 0) { WARN("bad device ID !\n"); return MMSYSERR_BADDEVICEID; } TRACE("imhere[3-PAUSING]\n"); PostThreadMessageA(WOutDev[wDevID].dwThreadID, WINE_WM_PAUSING, 0, 0); WaitForSingleObject(WOutDev[wDevID].hEvent, INFINITE); return MMSYSERR_NOERROR; } /************************************************************************** * wodRestart [internal] */ static DWORD wodRestart(WORD wDevID) { TRACE("(%u);\n", wDevID); if (wDevID > MAX_WAVEOUTDRV || WOutDev[wDevID].unixdev == 0) { WARN("bad device ID !\n"); return MMSYSERR_BADDEVICEID; } if (WOutDev[wDevID].state == WINE_WS_PAUSED) { TRACE("imhere[3-RESTARTING]\n"); PostThreadMessageA(WOutDev[wDevID].dwThreadID, WINE_WM_RESTARTING, 0, 0); WaitForSingleObject(WOutDev[wDevID].hEvent, INFINITE); } /* FIXME: is NotifyClient with WOM_DONE right ? (Comet Busters 1.3.3 needs this notification) */ /* FIXME: Myst crashes with this ... hmm -MM if (WAVE_NotifyClient(wDevID, WOM_DONE, 0L, 0L) != MMSYSERR_NOERROR) { WARN("can't notify client !\n"); return MMSYSERR_INVALPARAM; } */ return MMSYSERR_NOERROR; } /************************************************************************** * wodReset [internal] */ static DWORD wodReset(WORD wDevID) { TRACE("(%u);\n", wDevID); if (wDevID > MAX_WAVEOUTDRV || WOutDev[wDevID].unixdev == 0) { WARN("bad device ID !\n"); return MMSYSERR_BADDEVICEID; } TRACE("imhere[3-RESET]\n"); PostThreadMessageA(WOutDev[wDevID].dwThreadID, WINE_WM_RESETTING, 0, 0); WaitForSingleObject(WOutDev[wDevID].hEvent, INFINITE); return MMSYSERR_NOERROR; } /************************************************************************** * wodGetPosition [internal] */ static DWORD wodGetPosition(WORD wDevID, LPMMTIME lpTime, DWORD uSize) { int time; DWORD val; TRACE("(%u, %p, %lu);\n", wDevID, lpTime, uSize); if (wDevID > MAX_WAVEOUTDRV || WOutDev[wDevID].unixdev == 0) { WARN("bad device ID !\n"); return MMSYSERR_BADDEVICEID; } if (lpTime == NULL) return MMSYSERR_INVALPARAM; val = WOutDev[wDevID].dwPlayedTotal + WOutDev[wDevID].dwPlayed; TRACE("wType=%04X wBitsPerSample=%u nSamplesPerSec=%lu nChannels=%u nAvgBytesPerSec=%lu\n", lpTime->wType, WOutDev[wDevID].format.wBitsPerSample, WOutDev[wDevID].format.wf.nSamplesPerSec, WOutDev[wDevID].format.wf.nChannels, WOutDev[wDevID].format.wf.nAvgBytesPerSec); TRACE("dwTotalPlayed=%lu\n", val); switch (lpTime->wType) { case TIME_BYTES: lpTime->u.cb = val; TRACE("TIME_BYTES=%lu\n", lpTime->u.cb); break; case TIME_SAMPLES: lpTime->u.sample = val * 8 / WOutDev[wDevID].format.wBitsPerSample; TRACE("TIME_SAMPLES=%lu\n", lpTime->u.sample); break; case TIME_SMPTE: time = val / (WOutDev[wDevID].format.wf.nAvgBytesPerSec / 1000); lpTime->u.smpte.hour = time / 108000; time -= lpTime->u.smpte.hour * 108000; lpTime->u.smpte.min = time / 1800; time -= lpTime->u.smpte.min * 1800; lpTime->u.smpte.sec = time / 30; time -= lpTime->u.smpte.sec * 30; lpTime->u.smpte.frame = time; lpTime->u.smpte.fps = 30; 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: FIXME("Format %d not supported ! use TIME_MS !\n", lpTime->wType); lpTime->wType = TIME_MS; case TIME_MS: lpTime->u.ms = val / (WOutDev[wDevID].format.wf.nAvgBytesPerSec / 1000); TRACE("TIME_MS=%lu\n", lpTime->u.ms); break; } return MMSYSERR_NOERROR; } /************************************************************************** * wodGetVolume [internal] */ static DWORD wodGetVolume(WORD wDevID, LPDWORD lpdwVol) { int mixer; int volume; DWORD left, right; TRACE("(%u, %p);\n", wDevID, lpdwVol); if (lpdwVol == NULL) return MMSYSERR_NOTENABLED; if ((mixer = open(MIXER_DEV, O_RDONLY)) < 0) { WARN("mixer device not available !\n"); return MMSYSERR_NOTENABLED; } if (ioctl(mixer, SOUND_MIXER_READ_PCM, &volume) == -1) { WARN("unable read mixer !\n"); return MMSYSERR_NOTENABLED; } close(mixer); left = LOBYTE(volume); right = HIBYTE(volume); TRACE("left=%ld right=%ld !\n", left, right); *lpdwVol = ((left * 0xFFFFl) / 100) + (((right * 0xFFFFl) / 100) << 16); return MMSYSERR_NOERROR; } /************************************************************************** * wodSetVolume [internal] */ static DWORD wodSetVolume(WORD wDevID, DWORD dwParam) { int mixer; int volume; DWORD left, right; TRACE("(%u, %08lX);\n", wDevID, dwParam); left = (LOWORD(dwParam) * 100) / 0xFFFFl; right = (HIWORD(dwParam) * 100) / 0xFFFFl; volume = left + (right << 8); if ((mixer = open(MIXER_DEV, O_WRONLY)) < 0) { WARN("mixer device not available !\n"); return MMSYSERR_NOTENABLED; } if (ioctl(mixer, SOUND_MIXER_WRITE_PCM, &volume) == -1) { WARN("unable set mixer !\n"); return MMSYSERR_NOTENABLED; } close(mixer); return MMSYSERR_NOERROR; } /************************************************************************** * wodGetNumDevs [internal] */ static DWORD wodGetNumDevs(void) { DWORD ret = 1; /* FIXME: For now, only one sound device (SOUND_DEV) is allowed */ int audio = open(SOUND_DEV, O_WRONLY, 0); if (audio == -1) { if (errno != EBUSY) ret = 0; } else { close(audio); } return ret; } /************************************************************************** * OSS_wodMessage [sample driver] */ DWORD WINAPI OSS_wodMessage(UINT wDevID, UINT wMsg, DWORD dwUser, DWORD dwParam1, DWORD dwParam2) { TRACE("(%u, %04X, %08lX, %08lX, %08lX);\n", wDevID, 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, (LPWAVEOUTCAPSA)dwParam1, dwParam2); case WODM_GETNUMDEVS: return wodGetNumDevs (); case WODM_GETPITCH: return MMSYSERR_NOTSUPPORTED; case WODM_SETPITCH: return MMSYSERR_NOTSUPPORTED; case WODM_GETPLAYBACKRATE: return MMSYSERR_NOTSUPPORTED; 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); default: FIXME("unknown message %d!\n", wMsg); } return MMSYSERR_NOTSUPPORTED; } /*======================================================================* * Low level WAVE IN implemantation * *======================================================================*/ /************************************************************************** * widGetDevCaps [internal] */ static DWORD widGetDevCaps(WORD wDevID, LPWAVEINCAPSA lpCaps, DWORD dwSize) { int audio, smplrate, samplesize=16, dsp_stereo=1, bytespersmpl; TRACE("(%u, %p, %lu);\n", wDevID, lpCaps, dwSize); if (lpCaps == NULL) return MMSYSERR_NOTENABLED; if (access(SOUND_DEV,0) != 0) return MMSYSERR_NOTENABLED; audio = open(SOUND_DEV, O_RDONLY, 0); if (audio == -1) return MMSYSERR_ALLOCATED ; #ifdef EMULATE_SB16 lpCaps->wMid = 0x0002; lpCaps->wPid = 0x0004; strcpy(lpCaps->szPname, "SB16 Wave In"); #else lpCaps->wMid = 0x00FF; /* Manufac ID */ lpCaps->wPid = 0x0001; /* Product ID */ strcpy(lpCaps->szPname, "OpenSoundSystem WAVIN Driver"); #endif lpCaps->dwFormats = 0x00000000; lpCaps->wChannels = (IOCTL(audio, SNDCTL_DSP_STEREO, dsp_stereo) != 0) ? 1 : 2; bytespersmpl = (IOCTL(audio, SNDCTL_DSP_SAMPLESIZE, samplesize) != 0) ? 1 : 2; smplrate = 44100; if (IOCTL(audio, SNDCTL_DSP_SPEED, smplrate) == 0) { lpCaps->dwFormats |= WAVE_FORMAT_4M08; if (lpCaps->wChannels > 1) lpCaps->dwFormats |= WAVE_FORMAT_4S08; if (bytespersmpl > 1) { lpCaps->dwFormats |= WAVE_FORMAT_4M16; if (lpCaps->wChannels > 1) lpCaps->dwFormats |= WAVE_FORMAT_4S16; } } smplrate = 22050; if (IOCTL(audio, SNDCTL_DSP_SPEED, smplrate) == 0) { lpCaps->dwFormats |= WAVE_FORMAT_2M08; if (lpCaps->wChannels > 1) lpCaps->dwFormats |= WAVE_FORMAT_2S08; if (bytespersmpl > 1) { lpCaps->dwFormats |= WAVE_FORMAT_2M16; if (lpCaps->wChannels > 1) lpCaps->dwFormats |= WAVE_FORMAT_2S16; } } smplrate = 11025; if (IOCTL(audio, SNDCTL_DSP_SPEED, smplrate) == 0) { lpCaps->dwFormats |= WAVE_FORMAT_1M08; if (lpCaps->wChannels > 1) lpCaps->dwFormats |= WAVE_FORMAT_1S08; if (bytespersmpl > 1) { lpCaps->dwFormats |= WAVE_FORMAT_1M16; if (lpCaps->wChannels > 1) lpCaps->dwFormats |= WAVE_FORMAT_1S16; } } close(audio); TRACE("dwFormats = %08lX\n", lpCaps->dwFormats); return MMSYSERR_NOERROR; } /************************************************************************** * widOpen [internal] */ static DWORD widOpen(WORD wDevID, LPWAVEOPENDESC lpDesc, DWORD dwFlags) { int audio, abuf_size, smplrate, samplesize, dsp_stereo; LPWAVEFORMAT lpFormat; TRACE("(%u, %p, %08lX);\n", wDevID, lpDesc, dwFlags); if (lpDesc == NULL) { WARN("Invalid Parameter !\n"); return MMSYSERR_INVALPARAM; } if (wDevID >= MAX_WAVEINDRV) { TRACE("MAX_WAVINDRV reached !\n"); return MMSYSERR_ALLOCATED; } /* only PCM format is supported so far... */ if (lpDesc->lpFormat->wFormatTag != WAVE_FORMAT_PCM || lpDesc->lpFormat->nChannels == 0 || lpDesc->lpFormat->nSamplesPerSec == 0) { WARN("Bad format: tag=%04X nChannels=%d nSamplesPerSec=%ld !\n", lpDesc->lpFormat->wFormatTag, lpDesc->lpFormat->nChannels, lpDesc->lpFormat->nSamplesPerSec); 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; } WInDev[wDevID].unixdev = 0; if (access(SOUND_DEV,0) != 0) return MMSYSERR_NOTENABLED; audio = open(SOUND_DEV, O_RDONLY, 0); if (audio == -1) { WARN("can't open !\n"); return MMSYSERR_ALLOCATED; } IOCTL(audio, SNDCTL_DSP_GETBLKSIZE, abuf_size); if (abuf_size < 1024 || abuf_size > 65536) { if (abuf_size == -1) WARN("IOCTL can't 'SNDCTL_DSP_GETBLKSIZE' !\n"); else WARN("SNDCTL_DSP_GETBLKSIZE Invalid dwFragmentSize !\n"); return MMSYSERR_NOTENABLED; } WInDev[wDevID].wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK); if (WInDev[wDevID].lpQueueHdr) { HeapFree(GetProcessHeap(), 0, WInDev[wDevID].lpQueueHdr); WInDev[wDevID].lpQueueHdr = NULL; } WInDev[wDevID].unixdev = audio; WInDev[wDevID].dwFragmentSize = abuf_size; WInDev[wDevID].dwTotalRecorded = 0; memcpy(&WInDev[wDevID].waveDesc, lpDesc, sizeof(WAVEOPENDESC)); lpFormat = (LPWAVEFORMAT) lpDesc->lpFormat; memcpy(&WInDev[wDevID].format, lpFormat, sizeof(PCMWAVEFORMAT)); WInDev[wDevID].format.wBitsPerSample = 8; /* <-------------- */ if (WInDev[wDevID].format.wf.nChannels == 0) return WAVERR_BADFORMAT; if (WInDev[wDevID].format.wf.nSamplesPerSec == 0) return WAVERR_BADFORMAT; if (WInDev[wDevID].format.wBitsPerSample == 0) { WInDev[wDevID].format.wBitsPerSample = 8 * (WInDev[wDevID].format.wf.nAvgBytesPerSec / WInDev[wDevID].format.wf.nSamplesPerSec) / WInDev[wDevID].format.wf.nChannels; } samplesize = WInDev[wDevID].format.wBitsPerSample; smplrate = WInDev[wDevID].format.wf.nSamplesPerSec; dsp_stereo = (WInDev[wDevID].format.wf.nChannels > 1) ? TRUE : FALSE; IOCTL(audio, SNDCTL_DSP_SPEED, smplrate); IOCTL(audio, SNDCTL_DSP_SAMPLESIZE, samplesize); IOCTL(audio, SNDCTL_DSP_STEREO, dsp_stereo); TRACE("wBitsPerSample=%u !\n", WInDev[wDevID].format.wBitsPerSample); TRACE("nSamplesPerSec=%lu !\n", WInDev[wDevID].format.wf.nSamplesPerSec); TRACE("nChannels=%u !\n", WInDev[wDevID].format.wf.nChannels); TRACE("nAvgBytesPerSec=%lu\n", WInDev[wDevID].format.wf.nAvgBytesPerSec); if (WAVE_NotifyClient(wDevID, WIM_OPEN, 0L, 0L) != MMSYSERR_NOERROR) { WARN("can't notify client !\n"); return MMSYSERR_INVALPARAM; } return MMSYSERR_NOERROR; } /************************************************************************** * widClose [internal] */ static DWORD widClose(WORD wDevID) { TRACE("(%u);\n", wDevID); if (wDevID > MAX_WAVEINDRV) return MMSYSERR_INVALPARAM; if (WInDev[wDevID].unixdev == 0) { WARN("can't close !\n"); return MMSYSERR_NOTENABLED; } if (WInDev[wDevID].lpQueueHdr != NULL) { WARN("still buffers open !\n"); return WAVERR_STILLPLAYING; } close(WInDev[wDevID].unixdev); WInDev[wDevID].unixdev = 0; WInDev[wDevID].dwFragmentSize = 0; if (WAVE_NotifyClient(wDevID, WIM_CLOSE, 0L, 0L) != MMSYSERR_NOERROR) { WARN("can't notify client !\n"); return MMSYSERR_INVALPARAM; } return MMSYSERR_NOERROR; } /************************************************************************** * widAddBuffer [internal] */ static DWORD widAddBuffer(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize) { int count = 1; LPWAVEHDR lpWIHdr; TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize); if (WInDev[wDevID].unixdev == 0) { WARN("can't do it !\n"); return MMSYSERR_NOTENABLED; } if (!(lpWaveHdr->dwFlags & WHDR_PREPARED)) { TRACE("never been prepared !\n"); return WAVERR_UNPREPARED; } if (lpWaveHdr->dwFlags & WHDR_INQUEUE) { TRACE("header already in use !\n"); return WAVERR_STILLPLAYING; } lpWaveHdr->dwFlags |= WHDR_PREPARED; lpWaveHdr->dwFlags |= WHDR_INQUEUE; lpWaveHdr->dwFlags &= ~WHDR_DONE; lpWaveHdr->dwBytesRecorded = 0; if (WInDev[wDevID].lpQueueHdr == NULL) { WInDev[wDevID].lpQueueHdr = lpWaveHdr; } else { lpWIHdr = WInDev[wDevID].lpQueueHdr; while (lpWIHdr->lpNext != NULL) { lpWIHdr = lpWIHdr->lpNext; count++; } lpWIHdr->lpNext = lpWaveHdr; lpWaveHdr->lpNext = NULL; count++; } TRACE("buffer added ! (now %u in queue)\n", count); return MMSYSERR_NOERROR; } /************************************************************************** * widPrepare [internal] */ static DWORD widPrepare(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize) { TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize); if (WInDev[wDevID].unixdev == 0) { WARN("can't prepare !\n"); return MMSYSERR_NOTENABLED; } if (lpWaveHdr->dwFlags & WHDR_INQUEUE) return WAVERR_STILLPLAYING; lpWaveHdr->dwFlags |= WHDR_PREPARED; lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; lpWaveHdr->dwFlags &= ~WHDR_DONE; lpWaveHdr->dwBytesRecorded = 0; TRACE("header prepared !\n"); return MMSYSERR_NOERROR; } /************************************************************************** * widUnprepare [internal] */ static DWORD widUnprepare(WORD wDevID, LPWAVEHDR lpWaveHdr, DWORD dwSize) { TRACE("(%u, %p, %08lX);\n", wDevID, lpWaveHdr, dwSize); if (WInDev[wDevID].unixdev == 0) { WARN("can't unprepare !\n"); return MMSYSERR_NOTENABLED; } lpWaveHdr->dwFlags &= ~WHDR_PREPARED; lpWaveHdr->dwFlags &= ~WHDR_INQUEUE; lpWaveHdr->dwFlags |= WHDR_DONE; return MMSYSERR_NOERROR; } /************************************************************************** * widStart [internal] */ static DWORD widStart(WORD wDevID) { int count = 1; int bytesRead; LPWAVEHDR *lpWaveHdr; LPBYTE lpData; TRACE("(%u);\n", wDevID); if (WInDev[wDevID].unixdev == 0) { WARN("can't start recording !\n"); return MMSYSERR_NOTENABLED; } lpWaveHdr = &(WInDev[wDevID].lpQueueHdr); TRACE("lpWaveHdr = %08lx\n",(DWORD)lpWaveHdr); if (!*lpWaveHdr || !(*lpWaveHdr)->lpData) { TRACE("never been prepared !\n"); return WAVERR_UNPREPARED; } while (*lpWaveHdr != NULL) { lpData = (*lpWaveHdr)->lpData; TRACE("recording buf#%u=%p size=%lu \n", count, lpData, (*lpWaveHdr)->dwBufferLength); bytesRead = read(WInDev[wDevID].unixdev, lpData, (*lpWaveHdr)->dwBufferLength); if (bytesRead == -1) perror("read from audio device"); TRACE("bytesread=%d (%ld)\n", bytesRead, (*lpWaveHdr)->dwBufferLength); (*lpWaveHdr)->dwBytesRecorded = bytesRead; WInDev[wDevID].dwTotalRecorded += (*lpWaveHdr)->dwBytesRecorded; (*lpWaveHdr)->dwFlags &= ~WHDR_INQUEUE; (*lpWaveHdr)->dwFlags |= WHDR_DONE; if (WAVE_NotifyClient(wDevID, WIM_DATA, (DWORD)*lpWaveHdr, (*lpWaveHdr)->dwBytesRecorded) != MMSYSERR_NOERROR) { WARN("can't notify client !\n"); return MMSYSERR_INVALPARAM; } /* removes the current block from the queue */ *lpWaveHdr = (*lpWaveHdr)->lpNext; count++; } TRACE("end of recording !\n"); return MMSYSERR_NOERROR; } /************************************************************************** * widStop [internal] */ static DWORD widStop(WORD wDevID) { TRACE("(%u);\n", wDevID); if (WInDev[wDevID].unixdev == 0) { WARN("can't stop !\n"); return MMSYSERR_NOTENABLED; } return MMSYSERR_NOERROR; } /************************************************************************** * widReset [internal] */ static DWORD widReset(WORD wDevID) { TRACE("(%u);\n", wDevID); if (WInDev[wDevID].unixdev == 0) { WARN("can't reset !\n"); return MMSYSERR_NOTENABLED; } return MMSYSERR_NOERROR; } /************************************************************************** * widGetPosition [internal] */ static DWORD widGetPosition(WORD wDevID, LPMMTIME lpTime, DWORD uSize) { int time; TRACE("(%u, %p, %lu);\n", wDevID, lpTime, uSize); if (WInDev[wDevID].unixdev == 0) { WARN("can't get pos !\n"); return MMSYSERR_NOTENABLED; } if (lpTime == NULL) return MMSYSERR_INVALPARAM; TRACE("wType=%04X !\n", lpTime->wType); TRACE("wBitsPerSample=%u\n", WInDev[wDevID].format.wBitsPerSample); TRACE("nSamplesPerSec=%lu\n", WInDev[wDevID].format.wf.nSamplesPerSec); TRACE("nChannels=%u\n", WInDev[wDevID].format.wf.nChannels); TRACE("nAvgBytesPerSec=%lu\n", WInDev[wDevID].format.wf.nAvgBytesPerSec); switch (lpTime->wType) { case TIME_BYTES: lpTime->u.cb = WInDev[wDevID].dwTotalRecorded; TRACE("TIME_BYTES=%lu\n", lpTime->u.cb); break; case TIME_SAMPLES: lpTime->u.sample = WInDev[wDevID].dwTotalRecorded * 8 / WInDev[wDevID].format.wBitsPerSample; TRACE("TIME_SAMPLES=%lu\n", lpTime->u.sample); break; case TIME_SMPTE: time = WInDev[wDevID].dwTotalRecorded / (WInDev[wDevID].format.wf.nAvgBytesPerSec / 1000); lpTime->u.smpte.hour = time / 108000; time -= lpTime->u.smpte.hour * 108000; lpTime->u.smpte.min = time / 1800; time -= lpTime->u.smpte.min * 1800; lpTime->u.smpte.sec = time / 30; time -= lpTime->u.smpte.sec * 30; lpTime->u.smpte.frame = time; lpTime->u.smpte.fps = 30; 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; case TIME_MS: lpTime->u.ms = WInDev[wDevID].dwTotalRecorded / (WInDev[wDevID].format.wf.nAvgBytesPerSec / 1000); TRACE("TIME_MS=%lu\n", lpTime->u.ms); break; default: FIXME("format not supported (%u) ! use TIME_MS !\n", lpTime->wType); lpTime->wType = TIME_MS; } return MMSYSERR_NOERROR; } /************************************************************************** * OSS_widMessage [sample driver] */ DWORD WINAPI OSS_widMessage(WORD wDevID, WORD wMsg, DWORD dwUser, DWORD dwParam1, DWORD dwParam2) { TRACE("(%u, %04X, %08lX, %08lX, %08lX);\n", wDevID, 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 WIDM_OPEN: return widOpen(wDevID, (LPWAVEOPENDESC)dwParam1, dwParam2); case WIDM_CLOSE: return widClose(wDevID); case WIDM_ADDBUFFER: return widAddBuffer(wDevID, (LPWAVEHDR)dwParam1, dwParam2); case WIDM_PREPARE: return widPrepare(wDevID, (LPWAVEHDR)dwParam1, dwParam2); case WIDM_UNPREPARE: return widUnprepare(wDevID, (LPWAVEHDR)dwParam1, dwParam2); case WIDM_GETDEVCAPS: return widGetDevCaps(wDevID, (LPWAVEINCAPSA)dwParam1, dwParam2); case WIDM_GETNUMDEVS: return wodGetNumDevs(); /* same number of devices in output as in input */ case WIDM_GETPOS: return widGetPosition(wDevID, (LPMMTIME)dwParam1, dwParam2); case WIDM_RESET: return widReset(wDevID); case WIDM_START: return widStart(wDevID); case WIDM_STOP: return widStop(wDevID); default: FIXME("unknown message %u!\n", wMsg); } return MMSYSERR_NOTSUPPORTED; } #else /* !HAVE_OSS */ /************************************************************************** * wodMessage [sample driver] */ DWORD WINAPI OSS_wodMessage(WORD wDevID, WORD wMsg, DWORD dwUser, DWORD dwParam1, DWORD dwParam2) { FIXME("(%u, %04X, %08lX, %08lX, %08lX):stub\n", wDevID, wMsg, dwUser, dwParam1, dwParam2); return MMSYSERR_NOTENABLED; } /************************************************************************** * widMessage [sample driver] */ DWORD WINAPI OSS_widMessage(WORD wDevID, WORD wMsg, DWORD dwUser, DWORD dwParam1, DWORD dwParam2) { FIXME("(%u, %04X, %08lX, %08lX, %08lX):stub\n", wDevID, wMsg, dwUser, dwParam1, dwParam2); return MMSYSERR_NOTENABLED; } #endif /* HAVE_OSS */