1786 lines
62 KiB
C
1786 lines
62 KiB
C
/*
|
|
* Wine Driver for MCI wave forms
|
|
*
|
|
* Copyright 1994 Martin Ayotte
|
|
* 1999,2000,2005 Eric Pouech
|
|
* 2000 Francois Jacques
|
|
* 2009 Jörg Höhle
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdarg.h>
|
|
|
|
#include "windef.h"
|
|
#include "winbase.h"
|
|
#include "wingdi.h"
|
|
#include "winuser.h"
|
|
#include "mmddk.h"
|
|
#include "wownt32.h"
|
|
#include "digitalv.h"
|
|
#include "wine/debug.h"
|
|
#include "wine/unicode.h"
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(mciwave);
|
|
|
|
typedef struct {
|
|
UINT wDevID;
|
|
HANDLE hWave;
|
|
int nUseCount; /* Incremented for each shared open */
|
|
HMMIO hFile; /* mmio file handle open as Element */
|
|
MCIDEVICEID wNotifyDeviceID; /* MCI device ID with a pending notification */
|
|
HANDLE hCallback; /* Callback handle for pending notification */
|
|
LPWSTR lpFileName; /* Name of file (if any) */
|
|
WAVEFORMATEX wfxRef;
|
|
LPWAVEFORMATEX lpWaveFormat; /* Points to wfxRef until set by OPEN or RECORD */
|
|
BOOL fInput; /* FALSE = Output, TRUE = Input */
|
|
WORD wInput; /* wave input device */
|
|
WORD wOutput; /* wave output device */
|
|
volatile WORD dwStatus; /* one from MCI_MODE_xxxx */
|
|
DWORD dwMciTimeFormat;/* One of the supported MCI_FORMAT_xxxx */
|
|
DWORD dwPosition; /* position in bytes in chunk */
|
|
HANDLE hEvent; /* for synchronization */
|
|
LONG dwEventCount; /* for synchronization */
|
|
MMCKINFO ckMainRIFF; /* main RIFF chunk */
|
|
MMCKINFO ckWaveData; /* data chunk */
|
|
} WINE_MCIWAVE;
|
|
|
|
/* ===================================================================
|
|
* ===================================================================
|
|
* FIXME: should be using the new mmThreadXXXX functions from WINMM
|
|
* instead of those
|
|
* it would require to add a wine internal flag to mmThreadCreate
|
|
* in order to pass a 32 bit function instead of a 16 bit one
|
|
* ===================================================================
|
|
* =================================================================== */
|
|
|
|
typedef DWORD (*async_cmd)(MCIDEVICEID wDevID, DWORD_PTR dwFlags, DWORD_PTR pmt, HANDLE evt);
|
|
|
|
struct SCA {
|
|
async_cmd cmd;
|
|
HANDLE evt;
|
|
UINT wDevID;
|
|
DWORD_PTR dwParam1;
|
|
DWORD_PTR dwParam2;
|
|
};
|
|
|
|
/**************************************************************************
|
|
* MCI_SCAStarter [internal]
|
|
*/
|
|
static DWORD CALLBACK MCI_SCAStarter(LPVOID arg)
|
|
{
|
|
struct SCA* sca = (struct SCA*)arg;
|
|
DWORD ret;
|
|
|
|
TRACE("In thread before async command (%08x,%08lx,%08lx)\n",
|
|
sca->wDevID, sca->dwParam1, sca->dwParam2);
|
|
ret = sca->cmd(sca->wDevID, sca->dwParam1 | MCI_WAIT, sca->dwParam2, sca->evt);
|
|
TRACE("In thread after async command (%08x,%08lx,%08lx)\n",
|
|
sca->wDevID, sca->dwParam1, sca->dwParam2);
|
|
HeapFree(GetProcessHeap(), 0, sca);
|
|
return ret;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* MCI_SendCommandAsync [internal]
|
|
*/
|
|
static DWORD MCI_SendCommandAsync(UINT wDevID, async_cmd cmd, DWORD_PTR dwParam1,
|
|
DWORD_PTR dwParam2, UINT size)
|
|
{
|
|
HANDLE handles[2];
|
|
struct SCA* sca = HeapAlloc(GetProcessHeap(), 0, sizeof(struct SCA) + size);
|
|
|
|
if (sca == 0)
|
|
return MCIERR_OUT_OF_MEMORY;
|
|
|
|
sca->wDevID = wDevID;
|
|
sca->cmd = cmd;
|
|
sca->dwParam1 = dwParam1;
|
|
|
|
if (size && dwParam2) {
|
|
sca->dwParam2 = (DWORD_PTR)sca + sizeof(struct SCA);
|
|
/* copy structure passed by program in dwParam2 to be sure
|
|
* we can still use it whatever the program does
|
|
*/
|
|
memcpy((LPVOID)sca->dwParam2, (LPVOID)dwParam2, size);
|
|
} else {
|
|
sca->dwParam2 = dwParam2;
|
|
}
|
|
|
|
if ((sca->evt = handles[1] = CreateEventW(NULL, FALSE, FALSE, NULL)) == NULL ||
|
|
(handles[0] = CreateThread(NULL, 0, MCI_SCAStarter, sca, 0, NULL)) == 0) {
|
|
WARN("Couldn't allocate thread for async command handling, sending synchronously\n");
|
|
if (handles[1]) CloseHandle(handles[1]);
|
|
sca->evt = NULL;
|
|
return MCI_SCAStarter(&sca);
|
|
}
|
|
|
|
SetThreadPriority(handles[0], THREAD_PRIORITY_TIME_CRITICAL);
|
|
/* wait until either:
|
|
* - the thread has finished (handles[0], likely an error)
|
|
* - init phase of async command is done (handles[1])
|
|
*/
|
|
WaitForMultipleObjects(2, handles, FALSE, INFINITE);
|
|
CloseHandle(handles[0]);
|
|
CloseHandle(handles[1]);
|
|
return 0;
|
|
}
|
|
|
|
/*======================================================================*
|
|
* MCI WAVE implementation *
|
|
*======================================================================*/
|
|
|
|
static DWORD WAVE_mciResume(UINT wDevID, DWORD dwFlags, LPMCI_GENERIC_PARMS lpParms);
|
|
|
|
/**************************************************************************
|
|
* MCIWAVE_drvOpen [internal]
|
|
*/
|
|
static LRESULT WAVE_drvOpen(LPCWSTR str, LPMCI_OPEN_DRIVER_PARMSW modp)
|
|
{
|
|
WINE_MCIWAVE* wmw;
|
|
|
|
if (modp == NULL) return 0xFFFFFFFF;
|
|
|
|
wmw = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WINE_MCIWAVE));
|
|
|
|
if (!wmw)
|
|
return 0;
|
|
|
|
wmw->wDevID = modp->wDeviceID;
|
|
mciSetDriverData(wmw->wDevID, (DWORD_PTR)wmw);
|
|
modp->wCustomCommandTable = MCI_NO_COMMAND_TABLE;
|
|
modp->wType = MCI_DEVTYPE_WAVEFORM_AUDIO;
|
|
|
|
wmw->wfxRef.wFormatTag = WAVE_FORMAT_PCM;
|
|
wmw->wfxRef.nChannels = 1; /* MONO */
|
|
wmw->wfxRef.nSamplesPerSec = 11025;
|
|
wmw->wfxRef.nAvgBytesPerSec = 11025;
|
|
wmw->wfxRef.nBlockAlign = 1;
|
|
wmw->wfxRef.wBitsPerSample = 8;
|
|
wmw->wfxRef.cbSize = 0; /* don't care */
|
|
|
|
return modp->wDeviceID;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* MCIWAVE_drvClose [internal]
|
|
*/
|
|
static LRESULT WAVE_drvClose(MCIDEVICEID dwDevID)
|
|
{
|
|
WINE_MCIWAVE* wmw = (WINE_MCIWAVE*)mciGetDriverData(dwDevID);
|
|
|
|
if (wmw) {
|
|
HeapFree(GetProcessHeap(), 0, wmw);
|
|
mciSetDriverData(dwDevID, 0);
|
|
return 1;
|
|
}
|
|
return (dwDevID == 0xFFFFFFFF) ? 1 : 0;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_mciGetOpenDev [internal]
|
|
*/
|
|
static WINE_MCIWAVE *WAVE_mciGetOpenDev(MCIDEVICEID wDevID)
|
|
{
|
|
WINE_MCIWAVE* wmw = (WINE_MCIWAVE*)mciGetDriverData(wDevID);
|
|
|
|
if (wmw == NULL || wmw->nUseCount == 0) {
|
|
WARN("Invalid wDevID=%u\n", wDevID);
|
|
return 0;
|
|
}
|
|
return wmw;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_mciNotify [internal]
|
|
*
|
|
* Notifications in MCI work like a 1-element queue.
|
|
* Each new notification request supersedes the previous one.
|
|
* This affects Play and Record; other commands are immediate.
|
|
*/
|
|
static void WAVE_mciNotify(DWORD_PTR hWndCallBack, WINE_MCIWAVE* wmw, UINT wStatus)
|
|
{
|
|
/* We simply save one parameter by not passing the wDevID local
|
|
* to the command. They are the same (via mciGetDriverData).
|
|
*/
|
|
MCIDEVICEID wDevID = wmw->wNotifyDeviceID;
|
|
HANDLE old = InterlockedExchangePointer(&wmw->hCallback, NULL);
|
|
if (old) mciDriverNotify(old, wDevID, MCI_NOTIFY_SUPERSEDED);
|
|
mciDriverNotify(HWND_32(LOWORD(hWndCallBack)), wDevID, wStatus);
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_ConvertByteToTimeFormat [internal]
|
|
*/
|
|
static DWORD WAVE_ConvertByteToTimeFormat(WINE_MCIWAVE* wmw, DWORD val)
|
|
{
|
|
DWORD ret = 0;
|
|
|
|
switch (wmw->dwMciTimeFormat) {
|
|
case MCI_FORMAT_MILLISECONDS:
|
|
ret = MulDiv(val,1000,wmw->lpWaveFormat->nAvgBytesPerSec);
|
|
break;
|
|
case MCI_FORMAT_BYTES:
|
|
ret = val;
|
|
break;
|
|
case MCI_FORMAT_SAMPLES:
|
|
ret = MulDiv(val,wmw->lpWaveFormat->nSamplesPerSec,wmw->lpWaveFormat->nAvgBytesPerSec);
|
|
break;
|
|
default:
|
|
WARN("Bad time format %u!\n", wmw->dwMciTimeFormat);
|
|
}
|
|
TRACE("val=%u=0x%08x [tf=%u] => ret=%u\n", val, val, wmw->dwMciTimeFormat, ret);
|
|
return ret;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_ConvertTimeFormatToByte [internal]
|
|
*/
|
|
static DWORD WAVE_ConvertTimeFormatToByte(WINE_MCIWAVE* wmw, DWORD val)
|
|
{
|
|
DWORD ret = 0;
|
|
|
|
switch (wmw->dwMciTimeFormat) {
|
|
case MCI_FORMAT_MILLISECONDS:
|
|
ret = MulDiv(val,wmw->lpWaveFormat->nAvgBytesPerSec,1000);
|
|
if (ret > wmw->ckWaveData.cksize &&
|
|
val == WAVE_ConvertByteToTimeFormat(wmw, wmw->ckWaveData.cksize))
|
|
ret = wmw->ckWaveData.cksize;
|
|
break;
|
|
case MCI_FORMAT_BYTES:
|
|
ret = val;
|
|
break;
|
|
case MCI_FORMAT_SAMPLES:
|
|
ret = MulDiv(val,wmw->lpWaveFormat->nAvgBytesPerSec,wmw->lpWaveFormat->nSamplesPerSec);
|
|
break;
|
|
default:
|
|
WARN("Bad time format %u!\n", wmw->dwMciTimeFormat);
|
|
}
|
|
TRACE("val=%u=0x%08x [tf=%u] => ret=%u\n", val, val, wmw->dwMciTimeFormat, ret);
|
|
return ret;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_mciReadFmt [internal]
|
|
*/
|
|
static DWORD WAVE_mciReadFmt(WINE_MCIWAVE* wmw, const MMCKINFO* pckMainRIFF)
|
|
{
|
|
MMCKINFO mmckInfo;
|
|
LONG r;
|
|
LPWAVEFORMATEX pwfx;
|
|
|
|
mmckInfo.ckid = mmioFOURCC('f', 'm', 't', ' ');
|
|
if (mmioDescend(wmw->hFile, &mmckInfo, pckMainRIFF, MMIO_FINDCHUNK) != 0)
|
|
return MCIERR_INVALID_FILE;
|
|
TRACE("Chunk Found ckid=%.4s fccType=%.4s cksize=%08X\n",
|
|
(LPSTR)&mmckInfo.ckid, (LPSTR)&mmckInfo.fccType, mmckInfo.cksize);
|
|
|
|
pwfx = HeapAlloc(GetProcessHeap(), 0, mmckInfo.cksize);
|
|
if (!pwfx) return MCIERR_OUT_OF_MEMORY;
|
|
|
|
r = mmioRead(wmw->hFile, (HPSTR)pwfx, mmckInfo.cksize);
|
|
if (r < sizeof(PCMWAVEFORMAT)) {
|
|
HeapFree(GetProcessHeap(), 0, pwfx);
|
|
return MCIERR_INVALID_FILE;
|
|
}
|
|
TRACE("wFormatTag=%04X !\n", pwfx->wFormatTag);
|
|
TRACE("nChannels=%d\n", pwfx->nChannels);
|
|
TRACE("nSamplesPerSec=%d\n", pwfx->nSamplesPerSec);
|
|
TRACE("nAvgBytesPerSec=%d\n", pwfx->nAvgBytesPerSec);
|
|
TRACE("nBlockAlign=%d\n", pwfx->nBlockAlign);
|
|
TRACE("wBitsPerSample=%u !\n", pwfx->wBitsPerSample);
|
|
if (r >= sizeof(WAVEFORMATEX))
|
|
TRACE("cbSize=%u !\n", pwfx->cbSize);
|
|
if ((pwfx->wFormatTag != WAVE_FORMAT_PCM)
|
|
&& (r < sizeof(WAVEFORMATEX) || (r < sizeof(WAVEFORMATEX) + pwfx->cbSize))) {
|
|
HeapFree(GetProcessHeap(), 0, pwfx);
|
|
return MCIERR_INVALID_FILE;
|
|
}
|
|
wmw->lpWaveFormat = pwfx;
|
|
|
|
mmioAscend(wmw->hFile, &mmckInfo, 0);
|
|
wmw->ckWaveData.ckid = mmioFOURCC('d', 'a', 't', 'a');
|
|
if (mmioDescend(wmw->hFile, &wmw->ckWaveData, pckMainRIFF, MMIO_FINDCHUNK) != 0) {
|
|
TRACE("can't find data chunk\n");
|
|
return MCIERR_INVALID_FILE;
|
|
}
|
|
TRACE("Chunk Found ckid=%.4s fccType=%.4s cksize=%08X\n",
|
|
(LPSTR)&wmw->ckWaveData.ckid, (LPSTR)&wmw->ckWaveData.fccType, wmw->ckWaveData.cksize);
|
|
return 0;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_mciDefaultFmt [internal]
|
|
*
|
|
* wmw->lpWaveFormat points to the default wave format at wmw->wfxRef
|
|
* until either Open File or Record. It becomes immutable afterwards,
|
|
* i.e. Set wave format or channels etc. is subsequently refused.
|
|
*/
|
|
static void WAVE_mciDefaultFmt(WINE_MCIWAVE* wmw)
|
|
{
|
|
wmw->lpWaveFormat = &wmw->wfxRef;
|
|
wmw->lpWaveFormat->wFormatTag = WAVE_FORMAT_PCM;
|
|
wmw->lpWaveFormat->nChannels = 1;
|
|
wmw->lpWaveFormat->nSamplesPerSec = 11025;
|
|
wmw->lpWaveFormat->nAvgBytesPerSec = 11025;
|
|
wmw->lpWaveFormat->nBlockAlign = 1;
|
|
wmw->lpWaveFormat->wBitsPerSample = 8;
|
|
wmw->lpWaveFormat->cbSize = 0;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_mciCreateRIFFSkeleton [internal]
|
|
*/
|
|
static DWORD WAVE_mciCreateRIFFSkeleton(WINE_MCIWAVE* wmw)
|
|
{
|
|
MMCKINFO ckWaveFormat;
|
|
LPMMCKINFO lpckRIFF = &(wmw->ckMainRIFF);
|
|
LPMMCKINFO lpckWaveData = &(wmw->ckWaveData);
|
|
|
|
lpckRIFF->ckid = FOURCC_RIFF;
|
|
lpckRIFF->fccType = mmioFOURCC('W', 'A', 'V', 'E');
|
|
lpckRIFF->cksize = 0;
|
|
|
|
if (MMSYSERR_NOERROR != mmioCreateChunk(wmw->hFile, lpckRIFF, MMIO_CREATERIFF))
|
|
goto err;
|
|
|
|
ckWaveFormat.fccType = 0;
|
|
ckWaveFormat.ckid = mmioFOURCC('f', 'm', 't', ' ');
|
|
ckWaveFormat.cksize = sizeof(PCMWAVEFORMAT);
|
|
|
|
/* Set wave format accepts PCM only, however open an
|
|
* existing ADPCM file, record into it and the MCI will
|
|
* happily save back in that format. */
|
|
if (wmw->lpWaveFormat->wFormatTag == WAVE_FORMAT_PCM) {
|
|
if (wmw->lpWaveFormat->nBlockAlign !=
|
|
wmw->lpWaveFormat->nChannels * wmw->lpWaveFormat->wBitsPerSample/8) {
|
|
WORD size = wmw->lpWaveFormat->nChannels *
|
|
wmw->lpWaveFormat->wBitsPerSample/8;
|
|
WARN("Incorrect nBlockAlign (%d), setting it to %d\n",
|
|
wmw->lpWaveFormat->nBlockAlign, size);
|
|
wmw->lpWaveFormat->nBlockAlign = size;
|
|
}
|
|
if (wmw->lpWaveFormat->nAvgBytesPerSec !=
|
|
wmw->lpWaveFormat->nSamplesPerSec * wmw->lpWaveFormat->nBlockAlign) {
|
|
DWORD speed = wmw->lpWaveFormat->nSamplesPerSec *
|
|
wmw->lpWaveFormat->nBlockAlign;
|
|
WARN("Incorrect nAvgBytesPerSec (%d), setting it to %d\n",
|
|
wmw->lpWaveFormat->nAvgBytesPerSec, speed);
|
|
wmw->lpWaveFormat->nAvgBytesPerSec = speed;
|
|
}
|
|
}
|
|
if (wmw->lpWaveFormat == &wmw->wfxRef) {
|
|
LPWAVEFORMATEX pwfx = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WAVEFORMATEX));
|
|
if (!pwfx) return MCIERR_OUT_OF_MEMORY;
|
|
/* Set wave format accepts PCM only so the size is known. */
|
|
assert(wmw->wfxRef.wFormatTag == WAVE_FORMAT_PCM);
|
|
*pwfx = wmw->wfxRef;
|
|
wmw->lpWaveFormat = pwfx;
|
|
}
|
|
|
|
if (MMSYSERR_NOERROR != mmioCreateChunk(wmw->hFile, &ckWaveFormat, 0))
|
|
goto err;
|
|
|
|
if (-1 == mmioWrite(wmw->hFile, (HPCSTR)wmw->lpWaveFormat, (WAVE_FORMAT_PCM==wmw->lpWaveFormat->wFormatTag)
|
|
? sizeof(PCMWAVEFORMAT) : sizeof(WAVEFORMATEX)+wmw->lpWaveFormat->cbSize))
|
|
goto err;
|
|
|
|
if (MMSYSERR_NOERROR != mmioAscend(wmw->hFile, &ckWaveFormat, 0))
|
|
goto err;
|
|
|
|
lpckWaveData->cksize = 0;
|
|
lpckWaveData->fccType = 0;
|
|
lpckWaveData->ckid = mmioFOURCC('d', 'a', 't', 'a');
|
|
|
|
/* create data chunk */
|
|
if (MMSYSERR_NOERROR != mmioCreateChunk(wmw->hFile, lpckWaveData, 0))
|
|
goto err;
|
|
|
|
return 0;
|
|
|
|
err:
|
|
/* mciClose takes care of wmw->lpWaveFormat. */
|
|
return MCIERR_INVALID_FILE;
|
|
}
|
|
|
|
static DWORD create_tmp_file(HMMIO* hFile, LPWSTR* pszTmpFileName)
|
|
{
|
|
WCHAR szTmpPath[MAX_PATH];
|
|
WCHAR szPrefix[4];
|
|
DWORD dwRet = MMSYSERR_NOERROR;
|
|
|
|
szPrefix[0] = 'M';
|
|
szPrefix[1] = 'C';
|
|
szPrefix[2] = 'I';
|
|
szPrefix[3] = '\0';
|
|
|
|
if (!GetTempPathW(sizeof(szTmpPath)/sizeof(szTmpPath[0]), szTmpPath)) {
|
|
WARN("can't retrieve temp path!\n");
|
|
*pszTmpFileName = NULL;
|
|
return MCIERR_FILE_NOT_FOUND;
|
|
}
|
|
|
|
*pszTmpFileName = HeapAlloc(GetProcessHeap(),
|
|
HEAP_ZERO_MEMORY,
|
|
MAX_PATH * sizeof(WCHAR));
|
|
if (!GetTempFileNameW(szTmpPath, szPrefix, 0, *pszTmpFileName)) {
|
|
WARN("can't retrieve temp file name!\n");
|
|
HeapFree(GetProcessHeap(), 0, *pszTmpFileName);
|
|
return MCIERR_FILE_NOT_FOUND;
|
|
}
|
|
|
|
TRACE("%s!\n", debugstr_w(*pszTmpFileName));
|
|
|
|
if (*pszTmpFileName && (*pszTmpFileName)[0]) {
|
|
|
|
*hFile = mmioOpenW(*pszTmpFileName, NULL,
|
|
MMIO_ALLOCBUF | MMIO_READWRITE | MMIO_CREATE);
|
|
|
|
if (*hFile == 0) {
|
|
WARN("can't create file=%s!\n", debugstr_w(*pszTmpFileName));
|
|
/* temporary file could not be created. clean filename. */
|
|
HeapFree(GetProcessHeap(), 0, *pszTmpFileName);
|
|
dwRet = MCIERR_FILE_NOT_FOUND;
|
|
}
|
|
}
|
|
return dwRet;
|
|
}
|
|
|
|
static LRESULT WAVE_mciOpenFile(WINE_MCIWAVE* wmw, LPCWSTR filename)
|
|
{
|
|
LRESULT dwRet = MMSYSERR_NOERROR;
|
|
LPWSTR fn;
|
|
|
|
fn = HeapAlloc(GetProcessHeap(), 0, (lstrlenW(filename) + 1) * sizeof(WCHAR));
|
|
if (!fn) return MCIERR_OUT_OF_MEMORY;
|
|
strcpyW(fn, filename);
|
|
HeapFree(GetProcessHeap(), 0, wmw->lpFileName);
|
|
wmw->lpFileName = fn;
|
|
|
|
if (filename[0]) {
|
|
/* FIXME : what should be done if wmw->hFile is already != 0, or the driver is playin' */
|
|
TRACE("MCI_OPEN_ELEMENT %s!\n", debugstr_w(filename));
|
|
|
|
wmw->hFile = mmioOpenW((LPWSTR)filename, NULL,
|
|
MMIO_ALLOCBUF | MMIO_DENYWRITE | MMIO_READ);
|
|
|
|
if (wmw->hFile == 0) {
|
|
WARN("can't find file=%s!\n", debugstr_w(filename));
|
|
dwRet = MCIERR_FILE_NOT_FOUND;
|
|
}
|
|
else
|
|
{
|
|
LPMMCKINFO lpckMainRIFF = &wmw->ckMainRIFF;
|
|
|
|
/* make sure we're at the beginning of the file */
|
|
mmioSeek(wmw->hFile, 0, SEEK_SET);
|
|
|
|
/* first reading of this file. read the waveformat chunk */
|
|
if (mmioDescend(wmw->hFile, lpckMainRIFF, NULL, 0) != 0) {
|
|
dwRet = MCIERR_INVALID_FILE;
|
|
} else {
|
|
TRACE("ParentChunk ckid=%.4s fccType=%.4s cksize=%08X\n",
|
|
(LPSTR)&(lpckMainRIFF->ckid),
|
|
(LPSTR) &(lpckMainRIFF->fccType),
|
|
(lpckMainRIFF->cksize));
|
|
|
|
if ((lpckMainRIFF->ckid != FOURCC_RIFF) ||
|
|
lpckMainRIFF->fccType != mmioFOURCC('W', 'A', 'V', 'E')) {
|
|
dwRet = MCIERR_INVALID_FILE;
|
|
} else {
|
|
dwRet = WAVE_mciReadFmt(wmw, lpckMainRIFF);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return dwRet;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_mciOpen [internal]
|
|
*/
|
|
static LRESULT WAVE_mciOpen(MCIDEVICEID wDevID, DWORD dwFlags, LPMCI_WAVE_OPEN_PARMSW lpOpenParms)
|
|
{
|
|
DWORD dwRet = 0;
|
|
WINE_MCIWAVE* wmw = (WINE_MCIWAVE*)mciGetDriverData(wDevID);
|
|
|
|
TRACE("(%04X, %08X, %p)\n", wDevID, dwFlags, lpOpenParms);
|
|
if (lpOpenParms == NULL) return MCIERR_NULL_PARAMETER_BLOCK;
|
|
if (wmw == NULL) return MCIERR_INVALID_DEVICE_ID;
|
|
|
|
if (dwFlags & MCI_OPEN_SHAREABLE)
|
|
return MCIERR_UNSUPPORTED_FUNCTION;
|
|
|
|
if (wmw->nUseCount > 0) {
|
|
/* The driver is already opened on this channel
|
|
* Wave driver cannot be shared
|
|
*/
|
|
return MCIERR_DEVICE_OPEN;
|
|
}
|
|
|
|
wmw->nUseCount++;
|
|
|
|
wmw->wInput = wmw->wOutput = WAVE_MAPPER;
|
|
wmw->fInput = FALSE;
|
|
wmw->hWave = 0;
|
|
wmw->dwStatus = MCI_MODE_NOT_READY;
|
|
wmw->hFile = 0;
|
|
wmw->lpFileName = NULL; /* will be set by WAVE_mciOpenFile */
|
|
wmw->hCallback = NULL;
|
|
WAVE_mciDefaultFmt(wmw);
|
|
|
|
TRACE("wDevID=%04X (lpParams->wDeviceID=%08X)\n", wDevID, lpOpenParms->wDeviceID);
|
|
/* Logs show the native winmm calls us with 0 still in lpOpenParms.wDeviceID */
|
|
wmw->wNotifyDeviceID = wDevID;
|
|
|
|
if (dwFlags & MCI_OPEN_ELEMENT) {
|
|
if (dwFlags & MCI_OPEN_ELEMENT_ID) {
|
|
/* could it be that (DWORD)lpOpenParms->lpstrElementName
|
|
* contains the hFile value ?
|
|
*/
|
|
dwRet = MCIERR_UNRECOGNIZED_COMMAND;
|
|
} else {
|
|
dwRet = WAVE_mciOpenFile(wmw, lpOpenParms->lpstrElementName);
|
|
}
|
|
}
|
|
TRACE("hFile=%p\n", wmw->hFile);
|
|
|
|
if (dwRet == 0) {
|
|
wmw->dwPosition = 0;
|
|
|
|
wmw->dwStatus = MCI_MODE_STOP;
|
|
|
|
if (dwFlags & MCI_NOTIFY)
|
|
WAVE_mciNotify(lpOpenParms->dwCallback, wmw, MCI_NOTIFY_SUCCESSFUL);
|
|
} else {
|
|
wmw->nUseCount--;
|
|
if (wmw->hFile != 0)
|
|
mmioClose(wmw->hFile, 0);
|
|
wmw->hFile = 0;
|
|
HeapFree(GetProcessHeap(), 0, wmw->lpFileName);
|
|
wmw->lpFileName = NULL;
|
|
}
|
|
return dwRet;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_mciCue [internal]
|
|
*/
|
|
static DWORD WAVE_mciCue(MCIDEVICEID wDevID, DWORD dwFlags, LPMCI_GENERIC_PARMS lpParms)
|
|
{
|
|
WINE_MCIWAVE* wmw = WAVE_mciGetOpenDev(wDevID);
|
|
|
|
TRACE("(%u, %08X, %p);\n", wDevID, dwFlags, lpParms);
|
|
|
|
/* Tests on systems without sound drivers show that Cue, like
|
|
* Record and Play, opens winmm, returning MCIERR_WAVE_xyPUTSUNSUITABLE.
|
|
* The first Cue Notify does not immediately return the
|
|
* notification, as if a player or recorder thread is started.
|
|
* PAUSE mode is reported when successful, but this mode is
|
|
* different from the normal Pause, because a) Pause then returns
|
|
* NONAPPLICABLE_FUNCTION instead of 0 and b) Set Channels etc. is
|
|
* still accepted, returning the original notification as ABORTED.
|
|
* I.e. Cue allows subsequent format changes, unlike Record or
|
|
* Open file, closes winmm if the format changes and stops this
|
|
* thread.
|
|
* Wine creates one player or recorder thread per async. Play or
|
|
* Record command. Notification behaviour suggests that MS-W*
|
|
* reuses a single thread to improve response times. Having Cue
|
|
* start this thread early helps to improve Play/Record's initial
|
|
* response time. In effect, Cue is a performance hint, which
|
|
* justifies our almost no-op implementation.
|
|
*/
|
|
|
|
if (wmw == NULL) return MCIERR_INVALID_DEVICE_ID;
|
|
if (wmw->dwStatus != MCI_MODE_STOP) return MCIERR_NONAPPLICABLE_FUNCTION;
|
|
|
|
if ((dwFlags & MCI_NOTIFY) && lpParms)
|
|
WAVE_mciNotify(lpParms->dwCallback,wmw,MCI_NOTIFY_SUCCESSFUL);
|
|
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_mciStop [internal]
|
|
*/
|
|
static DWORD WAVE_mciStop(MCIDEVICEID wDevID, DWORD dwFlags, LPMCI_GENERIC_PARMS lpParms)
|
|
{
|
|
DWORD dwRet = 0;
|
|
WINE_MCIWAVE* wmw = WAVE_mciGetOpenDev(wDevID);
|
|
|
|
TRACE("(%u, %08X, %p);\n", wDevID, dwFlags, lpParms);
|
|
|
|
if (wmw == NULL) return MCIERR_INVALID_DEVICE_ID;
|
|
|
|
if (wmw->dwStatus != MCI_MODE_STOP) {
|
|
HANDLE old = InterlockedExchangePointer(&wmw->hCallback, NULL);
|
|
if (old) mciDriverNotify(old, wDevID, MCI_NOTIFY_ABORTED);
|
|
}
|
|
|
|
/* wait for playback thread (if any) to exit before processing further */
|
|
switch (wmw->dwStatus) {
|
|
case MCI_MODE_PAUSE:
|
|
case MCI_MODE_PLAY:
|
|
case MCI_MODE_RECORD:
|
|
{
|
|
int oldStat = wmw->dwStatus;
|
|
wmw->dwStatus = MCI_MODE_NOT_READY;
|
|
if (oldStat == MCI_MODE_PAUSE)
|
|
dwRet = (wmw->fInput) ? waveInReset(wmw->hWave) : waveOutReset(wmw->hWave);
|
|
}
|
|
while (wmw->dwStatus != MCI_MODE_STOP)
|
|
Sleep(10);
|
|
break;
|
|
}
|
|
|
|
/* sanity resets */
|
|
wmw->dwStatus = MCI_MODE_STOP;
|
|
|
|
if ((dwFlags & MCI_NOTIFY) && lpParms && MMSYSERR_NOERROR==dwRet)
|
|
WAVE_mciNotify(lpParms->dwCallback, wmw, MCI_NOTIFY_SUCCESSFUL);
|
|
|
|
return dwRet;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_mciClose [internal]
|
|
*/
|
|
static DWORD WAVE_mciClose(MCIDEVICEID wDevID, DWORD dwFlags, LPMCI_GENERIC_PARMS lpParms)
|
|
{
|
|
DWORD dwRet = 0;
|
|
WINE_MCIWAVE* wmw = WAVE_mciGetOpenDev(wDevID);
|
|
|
|
TRACE("(%u, %08X, %p);\n", wDevID, dwFlags, lpParms);
|
|
|
|
if (wmw == NULL) return MCIERR_INVALID_DEVICE_ID;
|
|
|
|
if (wmw->dwStatus != MCI_MODE_STOP) {
|
|
/* mciStop handles MCI_NOTIFY_ABORTED */
|
|
dwRet = WAVE_mciStop(wDevID, MCI_WAIT, lpParms);
|
|
}
|
|
|
|
wmw->nUseCount--;
|
|
|
|
if (wmw->nUseCount == 0) {
|
|
if (wmw->hFile != 0) {
|
|
mmioClose(wmw->hFile, 0);
|
|
wmw->hFile = 0;
|
|
}
|
|
}
|
|
|
|
if (wmw->lpWaveFormat != &wmw->wfxRef)
|
|
HeapFree(GetProcessHeap(), 0, wmw->lpWaveFormat);
|
|
wmw->lpWaveFormat = &wmw->wfxRef;
|
|
HeapFree(GetProcessHeap(), 0, wmw->lpFileName);
|
|
wmw->lpFileName = NULL;
|
|
|
|
if ((dwFlags & MCI_NOTIFY) && lpParms) {
|
|
WAVE_mciNotify(lpParms->dwCallback, wmw,
|
|
(dwRet == 0) ? MCI_NOTIFY_SUCCESSFUL : MCI_NOTIFY_FAILURE);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_mciPlayCallback [internal]
|
|
*/
|
|
static void CALLBACK WAVE_mciPlayCallback(HWAVEOUT hwo, UINT uMsg,
|
|
DWORD_PTR dwInstance,
|
|
LPARAM dwParam1, LPARAM dwParam2)
|
|
{
|
|
WINE_MCIWAVE* wmw = (WINE_MCIWAVE*)dwInstance;
|
|
|
|
switch (uMsg) {
|
|
case WOM_OPEN:
|
|
case WOM_CLOSE:
|
|
break;
|
|
case WOM_DONE:
|
|
InterlockedIncrement(&wmw->dwEventCount);
|
|
TRACE("Returning waveHdr=%lx\n", dwParam1);
|
|
SetEvent(wmw->hEvent);
|
|
break;
|
|
default:
|
|
ERR("Unknown uMsg=%d\n", uMsg);
|
|
}
|
|
}
|
|
|
|
/******************************************************************
|
|
* WAVE_mciPlayWaitDone [internal]
|
|
*/
|
|
static void WAVE_mciPlayWaitDone(WINE_MCIWAVE* wmw)
|
|
{
|
|
for (;;) {
|
|
ResetEvent(wmw->hEvent);
|
|
if (InterlockedDecrement(&wmw->dwEventCount) >= 0) {
|
|
break;
|
|
}
|
|
InterlockedIncrement(&wmw->dwEventCount);
|
|
|
|
WaitForSingleObject(wmw->hEvent, INFINITE);
|
|
}
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_mciPlay [internal]
|
|
*/
|
|
static DWORD WAVE_mciPlay(MCIDEVICEID wDevID, DWORD_PTR dwFlags, DWORD_PTR pmt, HANDLE hEvent)
|
|
{
|
|
LPMCI_PLAY_PARMS lpParms = (void*)pmt;
|
|
DWORD end;
|
|
LONG bufsize, count, left;
|
|
DWORD dwRet;
|
|
LPWAVEHDR waveHdr = NULL;
|
|
WINE_MCIWAVE* wmw = WAVE_mciGetOpenDev(wDevID);
|
|
HANDLE oldcb;
|
|
int whidx;
|
|
|
|
TRACE("(%u, %08lX, %p);\n", wDevID, dwFlags, lpParms);
|
|
|
|
if (wmw == NULL) return MCIERR_INVALID_DEVICE_ID;
|
|
if (lpParms == NULL) return MCIERR_NULL_PARAMETER_BLOCK;
|
|
|
|
if (wmw->hFile == 0) {
|
|
WARN("Can't play: no file=%s!\n", debugstr_w(wmw->lpFileName));
|
|
return MCIERR_FILE_NOT_FOUND;
|
|
}
|
|
|
|
if (wmw->dwStatus == MCI_MODE_PAUSE && !wmw->fInput && !(dwFlags & (MCI_FROM | MCI_TO))) {
|
|
/* FIXME: notification is different with Resume than Play */
|
|
return WAVE_mciResume(wDevID, dwFlags, (LPMCI_GENERIC_PARMS)lpParms);
|
|
}
|
|
|
|
/** This function will be called again by a thread when async is used.
|
|
* We have to set MCI_MODE_PLAY before we do this so that the app can spin
|
|
* on MCI_STATUS, so we have to allow it here if we're not going to start this thread.
|
|
*/
|
|
if ( !(wmw->dwStatus == MCI_MODE_STOP) &&
|
|
!((wmw->dwStatus == MCI_MODE_PLAY) && (dwFlags & MCI_WAIT) && !wmw->hWave)) {
|
|
/* FIXME: Check FROM/TO parameters first. */
|
|
/* FIXME: Play; Play [notify|wait] must hook into the running player. */
|
|
dwRet = WAVE_mciStop(wDevID, MCI_WAIT, NULL);
|
|
if (dwRet) return dwRet;
|
|
}
|
|
|
|
if (wmw->lpWaveFormat->wFormatTag == WAVE_FORMAT_PCM) {
|
|
if (wmw->lpWaveFormat->nBlockAlign !=
|
|
wmw->lpWaveFormat->nChannels * wmw->lpWaveFormat->wBitsPerSample/8) {
|
|
WARN("Incorrect nBlockAlign (%d), setting it to %d\n",
|
|
wmw->lpWaveFormat->nBlockAlign,
|
|
wmw->lpWaveFormat->nChannels *
|
|
wmw->lpWaveFormat->wBitsPerSample/8);
|
|
wmw->lpWaveFormat->nBlockAlign =
|
|
wmw->lpWaveFormat->nChannels *
|
|
wmw->lpWaveFormat->wBitsPerSample/8;
|
|
}
|
|
if (wmw->lpWaveFormat->nAvgBytesPerSec !=
|
|
wmw->lpWaveFormat->nSamplesPerSec * wmw->lpWaveFormat->nBlockAlign) {
|
|
WARN("Incorrect nAvgBytesPerSec (%d), setting it to %d\n",
|
|
wmw->lpWaveFormat->nAvgBytesPerSec,
|
|
wmw->lpWaveFormat->nSamplesPerSec *
|
|
wmw->lpWaveFormat->nBlockAlign);
|
|
wmw->lpWaveFormat->nAvgBytesPerSec =
|
|
wmw->lpWaveFormat->nSamplesPerSec *
|
|
wmw->lpWaveFormat->nBlockAlign;
|
|
}
|
|
}
|
|
|
|
end = wmw->ckWaveData.cksize;
|
|
if (dwFlags & MCI_TO) {
|
|
DWORD position = WAVE_ConvertTimeFormatToByte(wmw, lpParms->dwTo);
|
|
if (position > end) return MCIERR_OUTOFRANGE;
|
|
end = position;
|
|
}
|
|
if (dwFlags & MCI_FROM) {
|
|
DWORD position = WAVE_ConvertTimeFormatToByte(wmw, lpParms->dwFrom);
|
|
if (position > end) return MCIERR_OUTOFRANGE;
|
|
/* Seek rounds down, so do we. */
|
|
position /= wmw->lpWaveFormat->nBlockAlign;
|
|
position *= wmw->lpWaveFormat->nBlockAlign;
|
|
wmw->dwPosition = position;
|
|
}
|
|
if (end < wmw->dwPosition) return MCIERR_OUTOFRANGE;
|
|
left = end - wmw->dwPosition;
|
|
if (0==left) return MMSYSERR_NOERROR; /* FIXME: NOTIFY */
|
|
|
|
wmw->fInput = FALSE; /* FIXME: waveInOpen may have been called. */
|
|
wmw->dwStatus = MCI_MODE_PLAY;
|
|
|
|
if (!(dwFlags & MCI_WAIT)) {
|
|
return MCI_SendCommandAsync(wDevID, WAVE_mciPlay, dwFlags,
|
|
(DWORD_PTR)lpParms, sizeof(MCI_PLAY_PARMS));
|
|
}
|
|
|
|
TRACE("Playing from byte=%u to byte=%u\n", wmw->dwPosition, end);
|
|
|
|
oldcb = InterlockedExchangePointer(&wmw->hCallback,
|
|
(dwFlags & MCI_NOTIFY) ? HWND_32(LOWORD(lpParms->dwCallback)) : NULL);
|
|
if (oldcb) mciDriverNotify(oldcb, wDevID, MCI_NOTIFY_ABORTED);
|
|
oldcb = NULL;
|
|
|
|
#define WAVE_ALIGN_ON_BLOCK(wmw,v) \
|
|
((((v) + (wmw)->lpWaveFormat->nBlockAlign - 1) / (wmw)->lpWaveFormat->nBlockAlign) * (wmw)->lpWaveFormat->nBlockAlign)
|
|
|
|
/* go back to beginning of chunk plus the requested position */
|
|
/* FIXME: I'm not sure this is correct, notably because some data linked to
|
|
* the decompression state machine will not be correctly initialized.
|
|
* try it this way (other way would be to decompress from 0 up to dwPosition
|
|
* and to start sending to hWave when dwPosition is reached)
|
|
*/
|
|
mmioSeek(wmw->hFile, wmw->ckWaveData.dwDataOffset + wmw->dwPosition, SEEK_SET); /* >= 0 */
|
|
|
|
dwRet = waveOutOpen((HWAVEOUT *)&wmw->hWave, wmw->wOutput, wmw->lpWaveFormat,
|
|
(DWORD_PTR)WAVE_mciPlayCallback, (DWORD_PTR)wmw, CALLBACK_FUNCTION);
|
|
|
|
if (dwRet != 0) {
|
|
TRACE("Can't open low level audio device %d\n", dwRet);
|
|
dwRet = MCIERR_DEVICE_OPEN;
|
|
wmw->hWave = 0;
|
|
goto cleanUp;
|
|
}
|
|
|
|
/* make it so that 3 buffers per second are needed */
|
|
bufsize = WAVE_ALIGN_ON_BLOCK(wmw, wmw->lpWaveFormat->nAvgBytesPerSec / 3);
|
|
|
|
waveHdr = HeapAlloc(GetProcessHeap(), 0, 2 * sizeof(WAVEHDR) + 2 * bufsize);
|
|
waveHdr[0].lpData = (char*)waveHdr + 2 * sizeof(WAVEHDR);
|
|
waveHdr[1].lpData = (char*)waveHdr + 2 * sizeof(WAVEHDR) + bufsize;
|
|
waveHdr[0].dwUser = waveHdr[1].dwUser = 0L;
|
|
waveHdr[0].dwLoops = waveHdr[1].dwLoops = 0L;
|
|
waveHdr[0].dwFlags = waveHdr[1].dwFlags = 0L;
|
|
waveHdr[0].dwBufferLength = waveHdr[1].dwBufferLength = bufsize;
|
|
if (waveOutPrepareHeader(wmw->hWave, &waveHdr[0], sizeof(WAVEHDR)) ||
|
|
waveOutPrepareHeader(wmw->hWave, &waveHdr[1], sizeof(WAVEHDR))) {
|
|
dwRet = MCIERR_INTERNAL;
|
|
goto cleanUp;
|
|
}
|
|
|
|
whidx = 0;
|
|
wmw->hEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
|
|
if (!wmw->hEvent) {
|
|
dwRet = MCIERR_OUT_OF_MEMORY;
|
|
goto cleanUp;
|
|
}
|
|
wmw->dwEventCount = 1L; /* for first buffer */
|
|
|
|
TRACE("Playing (normalized) from byte=%u for %u bytes\n", wmw->dwPosition, left);
|
|
if (hEvent) SetEvent(hEvent);
|
|
|
|
/* FIXME: this doesn't work if wmw->dwPosition != 0 */
|
|
while (left > 0 && wmw->dwStatus != MCI_MODE_STOP && wmw->dwStatus != MCI_MODE_NOT_READY) {
|
|
count = mmioRead(wmw->hFile, waveHdr[whidx].lpData, min(bufsize, left));
|
|
TRACE("mmioRead bufsize=%d count=%d\n", bufsize, count);
|
|
if (count < 1)
|
|
break;
|
|
/* count is always <= bufsize, so this is correct regarding the
|
|
* waveOutPrepareHeader function
|
|
*/
|
|
waveHdr[whidx].dwBufferLength = count;
|
|
waveHdr[whidx].dwFlags &= ~WHDR_DONE;
|
|
TRACE("before WODM_WRITE lpWaveHdr=%p dwBufferLength=%u\n",
|
|
&waveHdr[whidx], waveHdr[whidx].dwBufferLength);
|
|
dwRet = waveOutWrite(wmw->hWave, &waveHdr[whidx], sizeof(WAVEHDR));
|
|
if (dwRet) {
|
|
ERR("Aborting play loop, WODM_WRITE error %d\n", dwRet);
|
|
dwRet = MCIERR_HARDWARE;
|
|
break;
|
|
}
|
|
left -= count;
|
|
wmw->dwPosition += count;
|
|
TRACE("after WODM_WRITE dwPosition=%u\n", wmw->dwPosition);
|
|
/* InterlockedDecrement if and only if waveOutWrite is successful */
|
|
WAVE_mciPlayWaitDone(wmw);
|
|
whidx ^= 1;
|
|
}
|
|
|
|
WAVE_mciPlayWaitDone(wmw); /* to balance first buffer */
|
|
|
|
/* just to get rid of some race conditions between play, stop and pause */
|
|
waveOutReset(wmw->hWave);
|
|
|
|
waveOutUnprepareHeader(wmw->hWave, &waveHdr[0], sizeof(WAVEHDR));
|
|
waveOutUnprepareHeader(wmw->hWave, &waveHdr[1], sizeof(WAVEHDR));
|
|
|
|
cleanUp:
|
|
if (dwFlags & MCI_NOTIFY)
|
|
oldcb = InterlockedExchangePointer(&wmw->hCallback, NULL);
|
|
|
|
HeapFree(GetProcessHeap(), 0, waveHdr);
|
|
|
|
if (wmw->hWave) {
|
|
waveOutClose(wmw->hWave);
|
|
wmw->hWave = 0;
|
|
}
|
|
CloseHandle(wmw->hEvent);
|
|
wmw->hEvent = NULL;
|
|
|
|
wmw->dwStatus = MCI_MODE_STOP;
|
|
|
|
/* Let the potentially asynchronous commands support FAILURE notification. */
|
|
if (oldcb) mciDriverNotify(oldcb, wDevID,
|
|
dwRet ? MCI_NOTIFY_FAILURE : MCI_NOTIFY_SUCCESSFUL);
|
|
|
|
return dwRet;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_mciRecordCallback [internal]
|
|
*/
|
|
static void CALLBACK WAVE_mciRecordCallback(HWAVEOUT hwo, UINT uMsg,
|
|
DWORD_PTR dwInstance,
|
|
LPARAM dwParam1, LPARAM dwParam2)
|
|
{
|
|
WINE_MCIWAVE* wmw = (WINE_MCIWAVE*)dwInstance;
|
|
LPWAVEHDR lpWaveHdr;
|
|
LONG count = 0;
|
|
|
|
switch (uMsg) {
|
|
case WIM_OPEN:
|
|
case WIM_CLOSE:
|
|
break;
|
|
case WIM_DATA:
|
|
lpWaveHdr = (LPWAVEHDR) dwParam1;
|
|
|
|
InterlockedIncrement(&wmw->dwEventCount);
|
|
|
|
count = mmioWrite(wmw->hFile, lpWaveHdr->lpData, lpWaveHdr->dwBytesRecorded);
|
|
|
|
lpWaveHdr->dwFlags &= ~WHDR_DONE;
|
|
if (count > 0)
|
|
wmw->dwPosition += count;
|
|
/* else error reporting ?? */
|
|
if (wmw->dwStatus == MCI_MODE_RECORD)
|
|
{
|
|
/* Only queue up another buffer if we are recording. We could receive this
|
|
message also when waveInReset() is called, since it notifies on all wave
|
|
buffers that are outstanding. Queueing up more sometimes causes waveInClose
|
|
to fail. */
|
|
waveInAddBuffer(wmw->hWave, lpWaveHdr, sizeof(*lpWaveHdr));
|
|
TRACE("after mmioWrite dwPosition=%u\n", wmw->dwPosition);
|
|
}
|
|
|
|
SetEvent(wmw->hEvent);
|
|
break;
|
|
default:
|
|
ERR("Unknown uMsg=%d\n", uMsg);
|
|
}
|
|
}
|
|
|
|
/******************************************************************
|
|
* WAVE_mciRecordWaitDone [internal]
|
|
*/
|
|
static void WAVE_mciRecordWaitDone(WINE_MCIWAVE* wmw)
|
|
{
|
|
for (;;) {
|
|
ResetEvent(wmw->hEvent);
|
|
if (InterlockedDecrement(&wmw->dwEventCount) >= 0) {
|
|
break;
|
|
}
|
|
InterlockedIncrement(&wmw->dwEventCount);
|
|
|
|
WaitForSingleObject(wmw->hEvent, INFINITE);
|
|
}
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_mciRecord [internal]
|
|
*/
|
|
static DWORD WAVE_mciRecord(MCIDEVICEID wDevID, DWORD_PTR dwFlags, DWORD_PTR pmt, HANDLE hEvent)
|
|
{
|
|
LPMCI_RECORD_PARMS lpParms = (void*)pmt;
|
|
DWORD end;
|
|
DWORD dwRet = MMSYSERR_NOERROR;
|
|
LONG bufsize;
|
|
LPWAVEHDR waveHdr = NULL;
|
|
WINE_MCIWAVE* wmw = WAVE_mciGetOpenDev(wDevID);
|
|
HANDLE oldcb;
|
|
|
|
TRACE("(%u, %08lX, %p);\n", wDevID, dwFlags, lpParms);
|
|
|
|
if (wmw == NULL) return MCIERR_INVALID_DEVICE_ID;
|
|
if (lpParms == NULL) return MCIERR_NULL_PARAMETER_BLOCK;
|
|
|
|
if (wmw->dwStatus == MCI_MODE_PAUSE && wmw->fInput) {
|
|
/* FIXME: parameters (start/end) in lpParams may not be used */
|
|
return WAVE_mciResume(wDevID, dwFlags, (LPMCI_GENERIC_PARMS)lpParms);
|
|
}
|
|
|
|
/** This function will be called again by a thread when async is used.
|
|
* We have to set MCI_MODE_RECORD before we do this so that the app can spin
|
|
* on MCI_STATUS, so we have to allow it here if we're not going to start this thread.
|
|
*/
|
|
if ( !(wmw->dwStatus == MCI_MODE_STOP) &&
|
|
!((wmw->dwStatus == MCI_MODE_RECORD) && (dwFlags & MCI_WAIT) && !wmw->hWave)) {
|
|
return MCIERR_INTERNAL;
|
|
}
|
|
|
|
wmw->fInput = TRUE; /* FIXME: waveOutOpen may have been called. */
|
|
wmw->dwStatus = MCI_MODE_RECORD;
|
|
|
|
if (!(dwFlags & MCI_WAIT)) {
|
|
return MCI_SendCommandAsync(wDevID, WAVE_mciRecord, dwFlags,
|
|
(DWORD_PTR)lpParms, sizeof(MCI_RECORD_PARMS));
|
|
}
|
|
|
|
/* FIXME: we only re-create the RIFF structure from an existing file (if any)
|
|
* we don't modify the wave part of an existing file (ie. we always erase an
|
|
* existing content, we don't overwrite)
|
|
*/
|
|
HeapFree(GetProcessHeap(), 0, wmw->lpFileName);
|
|
dwRet = create_tmp_file(&wmw->hFile, (WCHAR**)&wmw->lpFileName);
|
|
if (dwRet != 0) return dwRet;
|
|
|
|
/* new RIFF file, lpWaveFormat now valid */
|
|
dwRet = WAVE_mciCreateRIFFSkeleton(wmw);
|
|
if (dwRet != 0) return dwRet;
|
|
|
|
if (dwFlags & MCI_TO) {
|
|
end = WAVE_ConvertTimeFormatToByte(wmw, lpParms->dwTo);
|
|
} else end = 0xFFFFFFFF;
|
|
if (dwFlags & MCI_FROM) {
|
|
DWORD position = WAVE_ConvertTimeFormatToByte(wmw, lpParms->dwFrom);
|
|
if (wmw->ckWaveData.cksize < position) return MCIERR_OUTOFRANGE;
|
|
/* Seek rounds down, so do we. */
|
|
position /= wmw->lpWaveFormat->nBlockAlign;
|
|
position *= wmw->lpWaveFormat->nBlockAlign;
|
|
wmw->dwPosition = position;
|
|
}
|
|
if (end==wmw->dwPosition) return MMSYSERR_NOERROR; /* FIXME: NOTIFY */
|
|
|
|
TRACE("Recording from byte=%u to byte=%u\n", wmw->dwPosition, end);
|
|
|
|
oldcb = InterlockedExchangePointer(&wmw->hCallback,
|
|
(dwFlags & MCI_NOTIFY) ? HWND_32(LOWORD(lpParms->dwCallback)) : NULL);
|
|
if (oldcb) mciDriverNotify(oldcb, wDevID, MCI_NOTIFY_ABORTED);
|
|
oldcb = NULL;
|
|
|
|
#define WAVE_ALIGN_ON_BLOCK(wmw,v) \
|
|
((((v) + (wmw)->lpWaveFormat->nBlockAlign - 1) / (wmw)->lpWaveFormat->nBlockAlign) * (wmw)->lpWaveFormat->nBlockAlign)
|
|
|
|
wmw->ckWaveData.cksize = WAVE_ALIGN_ON_BLOCK(wmw, wmw->ckWaveData.cksize);
|
|
|
|
/* Go back to the beginning of the chunk plus the requested position */
|
|
/* FIXME: I'm not sure this is correct, notably because some data linked to
|
|
* the decompression state machine will not be correctly initialized.
|
|
* Try it this way (other way would be to decompress from 0 up to dwPosition
|
|
* and to start sending to hWave when dwPosition is reached).
|
|
*/
|
|
mmioSeek(wmw->hFile, wmw->ckWaveData.dwDataOffset + wmw->dwPosition, SEEK_SET); /* >= 0 */
|
|
|
|
dwRet = waveInOpen((HWAVEIN*)&wmw->hWave, wmw->wInput, wmw->lpWaveFormat,
|
|
(DWORD_PTR)WAVE_mciRecordCallback, (DWORD_PTR)wmw, CALLBACK_FUNCTION);
|
|
|
|
if (dwRet != MMSYSERR_NOERROR) {
|
|
TRACE("Can't open low level audio device %d\n", dwRet);
|
|
dwRet = MCIERR_DEVICE_OPEN;
|
|
wmw->hWave = 0;
|
|
goto cleanUp;
|
|
}
|
|
|
|
/* make it so that 3 buffers per second are needed */
|
|
bufsize = WAVE_ALIGN_ON_BLOCK(wmw, wmw->lpWaveFormat->nAvgBytesPerSec / 3);
|
|
|
|
waveHdr = HeapAlloc(GetProcessHeap(), 0, 2 * sizeof(WAVEHDR) + 2 * bufsize);
|
|
waveHdr[0].lpData = (char*)waveHdr + 2 * sizeof(WAVEHDR);
|
|
waveHdr[1].lpData = (char*)waveHdr + 2 * sizeof(WAVEHDR) + bufsize;
|
|
waveHdr[0].dwUser = waveHdr[1].dwUser = 0L;
|
|
waveHdr[0].dwLoops = waveHdr[1].dwLoops = 0L;
|
|
waveHdr[0].dwFlags = waveHdr[1].dwFlags = 0L;
|
|
waveHdr[0].dwBufferLength = waveHdr[1].dwBufferLength = bufsize;
|
|
|
|
if (waveInPrepareHeader(wmw->hWave, &waveHdr[0], sizeof(WAVEHDR)) ||
|
|
waveInPrepareHeader(wmw->hWave, &waveHdr[1], sizeof(WAVEHDR))) {
|
|
dwRet = MCIERR_INTERNAL;
|
|
goto cleanUp;
|
|
}
|
|
|
|
if (waveInAddBuffer(wmw->hWave, &waveHdr[0], sizeof(WAVEHDR)) ||
|
|
waveInAddBuffer(wmw->hWave, &waveHdr[1], sizeof(WAVEHDR))) {
|
|
dwRet = MCIERR_INTERNAL;
|
|
goto cleanUp;
|
|
}
|
|
|
|
wmw->hEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
|
|
wmw->dwEventCount = 1L; /* for first buffer */
|
|
|
|
TRACE("Recording (normalized) from byte=%u for %u bytes\n", wmw->dwPosition, end - wmw->dwPosition);
|
|
|
|
waveInStart(wmw->hWave);
|
|
|
|
if (hEvent) SetEvent(hEvent);
|
|
|
|
while (wmw->dwPosition < end && wmw->dwStatus != MCI_MODE_STOP && wmw->dwStatus != MCI_MODE_NOT_READY) {
|
|
WAVE_mciRecordWaitDone(wmw);
|
|
}
|
|
/* Grab callback before another thread kicks in after we change dwStatus. */
|
|
if (dwFlags & MCI_NOTIFY) {
|
|
oldcb = InterlockedExchangePointer(&wmw->hCallback, NULL);
|
|
dwFlags &= ~MCI_NOTIFY;
|
|
}
|
|
/* needed so that the callback above won't add again the buffers returned by the reset */
|
|
wmw->dwStatus = MCI_MODE_STOP;
|
|
|
|
waveInReset(wmw->hWave);
|
|
|
|
waveInUnprepareHeader(wmw->hWave, &waveHdr[0], sizeof(WAVEHDR));
|
|
waveInUnprepareHeader(wmw->hWave, &waveHdr[1], sizeof(WAVEHDR));
|
|
|
|
dwRet = 0;
|
|
|
|
cleanUp:
|
|
if (dwFlags & MCI_NOTIFY)
|
|
oldcb = InterlockedExchangePointer(&wmw->hCallback, NULL);
|
|
|
|
HeapFree(GetProcessHeap(), 0, waveHdr);
|
|
|
|
if (wmw->hWave) {
|
|
waveInClose(wmw->hWave);
|
|
wmw->hWave = 0;
|
|
}
|
|
CloseHandle(wmw->hEvent);
|
|
|
|
wmw->dwStatus = MCI_MODE_STOP;
|
|
|
|
if (oldcb) mciDriverNotify(oldcb, wDevID,
|
|
dwRet ? MCI_NOTIFY_FAILURE : MCI_NOTIFY_SUCCESSFUL);
|
|
|
|
return dwRet;
|
|
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_mciPause [internal]
|
|
*/
|
|
static DWORD WAVE_mciPause(MCIDEVICEID wDevID, DWORD dwFlags, LPMCI_GENERIC_PARMS lpParms)
|
|
{
|
|
DWORD dwRet;
|
|
WINE_MCIWAVE* wmw = WAVE_mciGetOpenDev(wDevID);
|
|
|
|
TRACE("(%u, %08X, %p);\n", wDevID, dwFlags, lpParms);
|
|
|
|
if (wmw == NULL) return MCIERR_INVALID_DEVICE_ID;
|
|
|
|
switch (wmw->dwStatus) {
|
|
case MCI_MODE_PLAY:
|
|
dwRet = waveOutPause(wmw->hWave);
|
|
if (dwRet==MMSYSERR_NOERROR) wmw->dwStatus = MCI_MODE_PAUSE;
|
|
else { /* When playthread was not started yet, winmm not opened, error 5 MMSYSERR_INVALHANDLE */
|
|
ERR("waveOutPause error %d\n",dwRet);
|
|
dwRet = MCIERR_INTERNAL;
|
|
}
|
|
break;
|
|
case MCI_MODE_RECORD:
|
|
dwRet = waveInStop(wmw->hWave);
|
|
if (dwRet==MMSYSERR_NOERROR) wmw->dwStatus = MCI_MODE_PAUSE;
|
|
else {
|
|
ERR("waveInStop error %d\n",dwRet);
|
|
dwRet = MCIERR_INTERNAL;
|
|
}
|
|
break;
|
|
case MCI_MODE_PAUSE:
|
|
dwRet = MMSYSERR_NOERROR;
|
|
break;
|
|
default:
|
|
dwRet = MCIERR_NONAPPLICABLE_FUNCTION;
|
|
}
|
|
if (MMSYSERR_NOERROR==dwRet && (dwFlags & MCI_NOTIFY) && lpParms)
|
|
WAVE_mciNotify(lpParms->dwCallback, wmw, MCI_NOTIFY_SUCCESSFUL);
|
|
return dwRet;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_mciResume [internal]
|
|
*/
|
|
static DWORD WAVE_mciResume(MCIDEVICEID wDevID, DWORD dwFlags, LPMCI_GENERIC_PARMS lpParms)
|
|
{
|
|
WINE_MCIWAVE* wmw = WAVE_mciGetOpenDev(wDevID);
|
|
DWORD dwRet;
|
|
|
|
TRACE("(%u, %08X, %p);\n", wDevID, dwFlags, lpParms);
|
|
|
|
if (wmw == NULL) return MCIERR_INVALID_DEVICE_ID;
|
|
|
|
switch (wmw->dwStatus) {
|
|
case MCI_MODE_PAUSE:
|
|
/* Only update dwStatus if wave* succeeds and will exchange buffers buffers. */
|
|
if (wmw->fInput) {
|
|
dwRet = waveInStart(wmw->hWave);
|
|
if (dwRet==MMSYSERR_NOERROR) wmw->dwStatus = MCI_MODE_RECORD;
|
|
else {
|
|
ERR("waveInStart error %d\n",dwRet);
|
|
dwRet = MCIERR_INTERNAL;
|
|
}
|
|
} else {
|
|
dwRet = waveOutRestart(wmw->hWave);
|
|
if (dwRet==MMSYSERR_NOERROR) wmw->dwStatus = MCI_MODE_PLAY;
|
|
else {
|
|
ERR("waveOutRestart error %d\n",dwRet);
|
|
dwRet = MCIERR_INTERNAL;
|
|
}
|
|
}
|
|
break;
|
|
case MCI_MODE_PLAY:
|
|
case MCI_MODE_RECORD:
|
|
dwRet = MMSYSERR_NOERROR;
|
|
break;
|
|
default:
|
|
dwRet = MCIERR_NONAPPLICABLE_FUNCTION;
|
|
}
|
|
if (MMSYSERR_NOERROR==dwRet && (dwFlags & MCI_NOTIFY) && lpParms)
|
|
WAVE_mciNotify(lpParms->dwCallback, wmw, MCI_NOTIFY_SUCCESSFUL);
|
|
return dwRet;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_mciSeek [internal]
|
|
*/
|
|
static DWORD WAVE_mciSeek(MCIDEVICEID wDevID, DWORD dwFlags, LPMCI_SEEK_PARMS lpParms)
|
|
{
|
|
WINE_MCIWAVE* wmw = WAVE_mciGetOpenDev(wDevID);
|
|
DWORD position, dwRet;
|
|
|
|
TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
|
|
|
|
if (lpParms == NULL) return MCIERR_NULL_PARAMETER_BLOCK;
|
|
if (wmw == NULL) return MCIERR_INVALID_DEVICE_ID;
|
|
|
|
position = dwFlags & (MCI_SEEK_TO_START|MCI_SEEK_TO_END|MCI_TO);
|
|
if (!position) return MCIERR_MISSING_PARAMETER;
|
|
if (position&(position-1)) return MCIERR_FLAGS_NOT_COMPATIBLE;
|
|
|
|
/* Stop sends MCI_NOTIFY_ABORTED when needed */
|
|
dwRet = WAVE_mciStop(wDevID, MCI_WAIT, 0);
|
|
if (dwRet != MMSYSERR_NOERROR) return dwRet;
|
|
|
|
if (dwFlags & MCI_TO) {
|
|
position = WAVE_ConvertTimeFormatToByte(wmw, lpParms->dwTo);
|
|
if (position > wmw->ckWaveData.cksize)
|
|
return MCIERR_OUTOFRANGE;
|
|
} else if (dwFlags & MCI_SEEK_TO_START) {
|
|
position = 0;
|
|
} else {
|
|
position = wmw->ckWaveData.cksize;
|
|
}
|
|
/* Seek rounds down, unless at end */
|
|
if (position != wmw->ckWaveData.cksize) {
|
|
position /= wmw->lpWaveFormat->nBlockAlign;
|
|
position *= wmw->lpWaveFormat->nBlockAlign;
|
|
}
|
|
wmw->dwPosition = position;
|
|
TRACE("Seeking to position=%u bytes\n", position);
|
|
|
|
if (dwFlags & MCI_NOTIFY)
|
|
WAVE_mciNotify(lpParms->dwCallback, wmw, MCI_NOTIFY_SUCCESSFUL);
|
|
|
|
return MMSYSERR_NOERROR;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_mciSet [internal]
|
|
*/
|
|
static DWORD WAVE_mciSet(MCIDEVICEID wDevID, DWORD dwFlags, LPMCI_WAVE_SET_PARMS lpParms)
|
|
{
|
|
WINE_MCIWAVE* wmw = WAVE_mciGetOpenDev(wDevID);
|
|
|
|
TRACE("(%u, %08X, %p);\n", wDevID, dwFlags, lpParms);
|
|
|
|
if (lpParms == NULL) return MCIERR_NULL_PARAMETER_BLOCK;
|
|
if (wmw == NULL) return MCIERR_INVALID_DEVICE_ID;
|
|
|
|
if (dwFlags & MCI_SET_TIME_FORMAT) {
|
|
switch (lpParms->dwTimeFormat) {
|
|
case MCI_FORMAT_MILLISECONDS:
|
|
TRACE("MCI_FORMAT_MILLISECONDS !\n");
|
|
wmw->dwMciTimeFormat = MCI_FORMAT_MILLISECONDS;
|
|
break;
|
|
case MCI_FORMAT_BYTES:
|
|
TRACE("MCI_FORMAT_BYTES !\n");
|
|
wmw->dwMciTimeFormat = MCI_FORMAT_BYTES;
|
|
break;
|
|
case MCI_FORMAT_SAMPLES:
|
|
TRACE("MCI_FORMAT_SAMPLES !\n");
|
|
wmw->dwMciTimeFormat = MCI_FORMAT_SAMPLES;
|
|
break;
|
|
default:
|
|
WARN("Bad time format %u!\n", lpParms->dwTimeFormat);
|
|
return MCIERR_BAD_TIME_FORMAT;
|
|
}
|
|
}
|
|
if (dwFlags & MCI_SET_VIDEO) {
|
|
TRACE("No support for video !\n");
|
|
return MCIERR_UNSUPPORTED_FUNCTION;
|
|
}
|
|
if (dwFlags & MCI_SET_DOOR_OPEN) {
|
|
TRACE("No support for door open !\n");
|
|
return MCIERR_UNSUPPORTED_FUNCTION;
|
|
}
|
|
if (dwFlags & MCI_SET_DOOR_CLOSED) {
|
|
TRACE("No support for door close !\n");
|
|
return MCIERR_UNSUPPORTED_FUNCTION;
|
|
}
|
|
if (dwFlags & MCI_SET_AUDIO) {
|
|
if (dwFlags & MCI_SET_ON) {
|
|
TRACE("MCI_SET_ON audio !\n");
|
|
} else if (dwFlags & MCI_SET_OFF) {
|
|
TRACE("MCI_SET_OFF audio !\n");
|
|
} else {
|
|
WARN("MCI_SET_AUDIO without SET_ON or SET_OFF\n");
|
|
return MCIERR_BAD_INTEGER;
|
|
}
|
|
|
|
switch (lpParms->dwAudio)
|
|
{
|
|
case MCI_SET_AUDIO_ALL: TRACE("MCI_SET_AUDIO_ALL !\n"); break;
|
|
case MCI_SET_AUDIO_LEFT: TRACE("MCI_SET_AUDIO_LEFT !\n"); break;
|
|
case MCI_SET_AUDIO_RIGHT: TRACE("MCI_SET_AUDIO_RIGHT !\n"); break;
|
|
default: WARN("Unknown audio channel %u\n", lpParms->dwAudio); break;
|
|
}
|
|
}
|
|
if (dwFlags & MCI_WAVE_INPUT) {
|
|
TRACE("MCI_WAVE_INPUT = %d\n", lpParms->wInput);
|
|
if (lpParms->wInput >= waveInGetNumDevs())
|
|
return MCIERR_OUTOFRANGE;
|
|
if (wmw->wInput != (WORD)lpParms->wInput)
|
|
WAVE_mciStop(wDevID, MCI_WAIT, NULL);
|
|
wmw->wInput = lpParms->wInput;
|
|
}
|
|
if (dwFlags & MCI_WAVE_OUTPUT) {
|
|
TRACE("MCI_WAVE_OUTPUT = %d\n", lpParms->wOutput);
|
|
if (lpParms->wOutput >= waveOutGetNumDevs())
|
|
return MCIERR_OUTOFRANGE;
|
|
if (wmw->wOutput != (WORD)lpParms->wOutput)
|
|
WAVE_mciStop(wDevID, MCI_WAIT, NULL);
|
|
wmw->wOutput = lpParms->wOutput;
|
|
}
|
|
if (dwFlags & MCI_WAVE_SET_ANYINPUT) {
|
|
TRACE("MCI_WAVE_SET_ANYINPUT\n");
|
|
if (wmw->wInput != (WORD)lpParms->wInput)
|
|
WAVE_mciStop(wDevID, MCI_WAIT, NULL);
|
|
wmw->wInput = WAVE_MAPPER;
|
|
}
|
|
if (dwFlags & MCI_WAVE_SET_ANYOUTPUT) {
|
|
TRACE("MCI_WAVE_SET_ANYOUTPUT\n");
|
|
if (wmw->wOutput != (WORD)lpParms->wOutput)
|
|
WAVE_mciStop(wDevID, MCI_WAIT, NULL);
|
|
wmw->wOutput = WAVE_MAPPER;
|
|
}
|
|
/* Set wave format parameters is refused after Open or Record.*/
|
|
if (dwFlags & MCI_WAVE_SET_FORMATTAG) {
|
|
TRACE("MCI_WAVE_SET_FORMATTAG = %d\n", lpParms->wFormatTag);
|
|
if (wmw->lpWaveFormat != &wmw->wfxRef) return MCIERR_NONAPPLICABLE_FUNCTION;
|
|
if (lpParms->wFormatTag != WAVE_FORMAT_PCM)
|
|
return MCIERR_OUTOFRANGE;
|
|
}
|
|
if (dwFlags & MCI_WAVE_SET_AVGBYTESPERSEC) {
|
|
if (wmw->lpWaveFormat != &wmw->wfxRef) return MCIERR_NONAPPLICABLE_FUNCTION;
|
|
wmw->wfxRef.nAvgBytesPerSec = lpParms->nAvgBytesPerSec;
|
|
TRACE("MCI_WAVE_SET_AVGBYTESPERSEC = %d\n", wmw->wfxRef.nAvgBytesPerSec);
|
|
}
|
|
if (dwFlags & MCI_WAVE_SET_BITSPERSAMPLE) {
|
|
if (wmw->lpWaveFormat != &wmw->wfxRef) return MCIERR_NONAPPLICABLE_FUNCTION;
|
|
wmw->wfxRef.wBitsPerSample = lpParms->wBitsPerSample;
|
|
TRACE("MCI_WAVE_SET_BITSPERSAMPLE = %d\n", wmw->wfxRef.wBitsPerSample);
|
|
}
|
|
if (dwFlags & MCI_WAVE_SET_BLOCKALIGN) {
|
|
if (wmw->lpWaveFormat != &wmw->wfxRef) return MCIERR_NONAPPLICABLE_FUNCTION;
|
|
wmw->wfxRef.nBlockAlign = lpParms->nBlockAlign;
|
|
TRACE("MCI_WAVE_SET_BLOCKALIGN = %d\n", wmw->wfxRef.nBlockAlign);
|
|
}
|
|
if (dwFlags & MCI_WAVE_SET_CHANNELS) {
|
|
if (wmw->lpWaveFormat != &wmw->wfxRef) return MCIERR_NONAPPLICABLE_FUNCTION;
|
|
wmw->wfxRef.nChannels = lpParms->nChannels;
|
|
TRACE("MCI_WAVE_SET_CHANNELS = %d\n", wmw->wfxRef.nChannels);
|
|
}
|
|
if (dwFlags & MCI_WAVE_SET_SAMPLESPERSEC) {
|
|
if (wmw->lpWaveFormat != &wmw->wfxRef) return MCIERR_NONAPPLICABLE_FUNCTION;
|
|
wmw->wfxRef.nSamplesPerSec = lpParms->nSamplesPerSec;
|
|
TRACE("MCI_WAVE_SET_SAMPLESPERSEC = %d\n", wmw->wfxRef.nSamplesPerSec);
|
|
}
|
|
if (dwFlags & MCI_NOTIFY)
|
|
WAVE_mciNotify(lpParms->dwCallback, wmw, MCI_NOTIFY_SUCCESSFUL);
|
|
return 0;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_mciSave [internal]
|
|
*/
|
|
static DWORD WAVE_mciSave(MCIDEVICEID wDevID, DWORD dwFlags, LPMCI_SAVE_PARMSW lpParms)
|
|
{
|
|
WINE_MCIWAVE* wmw = WAVE_mciGetOpenDev(wDevID);
|
|
DWORD ret = MCIERR_FILE_NOT_SAVED, tmpRet;
|
|
|
|
TRACE("%d, %08X, %p);\n", wDevID, dwFlags, lpParms);
|
|
if (lpParms == NULL) return MCIERR_NULL_PARAMETER_BLOCK;
|
|
if (wmw == NULL) return MCIERR_INVALID_DEVICE_ID;
|
|
|
|
if (dwFlags & MCI_WAIT)
|
|
{
|
|
FIXME("MCI_WAIT not implemented\n");
|
|
}
|
|
WAVE_mciStop(wDevID, 0, NULL);
|
|
|
|
ret = mmioAscend(wmw->hFile, &wmw->ckWaveData, 0);
|
|
ret = mmioAscend(wmw->hFile, &wmw->ckMainRIFF, 0);
|
|
|
|
ret = mmioClose(wmw->hFile, 0);
|
|
wmw->hFile = 0;
|
|
|
|
/*
|
|
If the destination file already exists, it has to be overwritten. (Behaviour
|
|
verified in Windows (2000)). If it doesn't overwrite, it is breaking one of
|
|
my applications. We are making use of mmioRename, which WILL NOT overwrite
|
|
the destination file (which is what Windows does, also verified in Win2K)
|
|
So, lets delete the destination file before calling mmioRename. If the
|
|
destination file DOESN'T exist, the delete will fail silently. Let's also be
|
|
careful not to lose our previous error code.
|
|
*/
|
|
tmpRet = GetLastError();
|
|
DeleteFileW (lpParms->lpfilename);
|
|
SetLastError(tmpRet);
|
|
|
|
/* FIXME: Open file.wav; Save; must not rename the original file.
|
|
* Nor must Save a.wav; Save b.wav rename a. */
|
|
if (0 == mmioRenameW(wmw->lpFileName, lpParms->lpfilename, 0, 0 )) {
|
|
ret = MMSYSERR_NOERROR;
|
|
}
|
|
|
|
if (MMSYSERR_NOERROR==ret && (dwFlags & MCI_NOTIFY))
|
|
WAVE_mciNotify(lpParms->dwCallback, wmw, MCI_NOTIFY_SUCCESSFUL);
|
|
|
|
if (ret == MMSYSERR_NOERROR)
|
|
ret = WAVE_mciOpenFile(wmw, lpParms->lpfilename);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_mciStatus [internal]
|
|
*/
|
|
static DWORD WAVE_mciStatus(MCIDEVICEID wDevID, DWORD dwFlags, LPMCI_STATUS_PARMS lpParms)
|
|
{
|
|
WINE_MCIWAVE* wmw = WAVE_mciGetOpenDev(wDevID);
|
|
DWORD ret = 0;
|
|
|
|
TRACE("(%u, %08X, %p);\n", wDevID, dwFlags, lpParms);
|
|
if (lpParms == NULL) return MCIERR_NULL_PARAMETER_BLOCK;
|
|
if (wmw == NULL) return MCIERR_INVALID_DEVICE_ID;
|
|
if (!(dwFlags & MCI_STATUS_ITEM)) return MCIERR_MISSING_PARAMETER;
|
|
|
|
if (dwFlags & MCI_STATUS_ITEM) {
|
|
switch (lpParms->dwItem) {
|
|
case MCI_STATUS_CURRENT_TRACK:
|
|
lpParms->dwReturn = 1;
|
|
TRACE("MCI_STATUS_CURRENT_TRACK => %lu\n", lpParms->dwReturn);
|
|
break;
|
|
case MCI_STATUS_LENGTH:
|
|
if (!wmw->hFile) {
|
|
lpParms->dwReturn = 0;
|
|
return MCIERR_UNSUPPORTED_FUNCTION;
|
|
}
|
|
/* only one track in file is currently handled, so don't take care of MCI_TRACK flag */
|
|
lpParms->dwReturn = WAVE_ConvertByteToTimeFormat(wmw, wmw->ckWaveData.cksize);
|
|
TRACE("MCI_STATUS_LENGTH => %lu\n", lpParms->dwReturn);
|
|
break;
|
|
case MCI_STATUS_MODE:
|
|
TRACE("MCI_STATUS_MODE => %u\n", wmw->dwStatus);
|
|
lpParms->dwReturn = MAKEMCIRESOURCE(wmw->dwStatus, wmw->dwStatus);
|
|
ret = MCI_RESOURCE_RETURNED;
|
|
break;
|
|
case MCI_STATUS_MEDIA_PRESENT:
|
|
TRACE("MCI_STATUS_MEDIA_PRESENT => TRUE!\n");
|
|
lpParms->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
|
|
ret = MCI_RESOURCE_RETURNED;
|
|
break;
|
|
case MCI_STATUS_NUMBER_OF_TRACKS:
|
|
/* only one track in file is currently handled, so don't take care of MCI_TRACK flag */
|
|
lpParms->dwReturn = 1;
|
|
TRACE("MCI_STATUS_NUMBER_OF_TRACKS => %lu\n", lpParms->dwReturn);
|
|
break;
|
|
case MCI_STATUS_POSITION:
|
|
if (!wmw->hFile) {
|
|
lpParms->dwReturn = 0;
|
|
return MCIERR_UNSUPPORTED_FUNCTION;
|
|
}
|
|
/* only one track in file is currently handled, so don't take care of MCI_TRACK flag */
|
|
lpParms->dwReturn = WAVE_ConvertByteToTimeFormat(wmw,
|
|
(dwFlags & MCI_STATUS_START) ? 0 : wmw->dwPosition);
|
|
TRACE("MCI_STATUS_POSITION %s => %lu\n",
|
|
(dwFlags & MCI_STATUS_START) ? "start" : "current", lpParms->dwReturn);
|
|
break;
|
|
case MCI_STATUS_READY:
|
|
lpParms->dwReturn = (wmw->dwStatus == MCI_MODE_NOT_READY) ?
|
|
MAKEMCIRESOURCE(FALSE, MCI_FALSE) : MAKEMCIRESOURCE(TRUE, MCI_TRUE);
|
|
TRACE("MCI_STATUS_READY => %u!\n", LOWORD(lpParms->dwReturn));
|
|
ret = MCI_RESOURCE_RETURNED;
|
|
break;
|
|
case MCI_STATUS_TIME_FORMAT:
|
|
lpParms->dwReturn = MAKEMCIRESOURCE(wmw->dwMciTimeFormat, MCI_FORMAT_RETURN_BASE + wmw->dwMciTimeFormat);
|
|
TRACE("MCI_STATUS_TIME_FORMAT => %lu\n", lpParms->dwReturn);
|
|
ret = MCI_RESOURCE_RETURNED;
|
|
break;
|
|
case MCI_WAVE_INPUT:
|
|
if (wmw->wInput != (WORD)WAVE_MAPPER)
|
|
lpParms->dwReturn = wmw->wInput;
|
|
else {
|
|
lpParms->dwReturn = MAKEMCIRESOURCE(WAVE_MAPPER, WAVE_MAPPER_S);
|
|
ret = MCI_RESOURCE_RETURNED;
|
|
}
|
|
TRACE("MCI_WAVE_INPUT => %d\n", (signed)wmw->wInput);
|
|
break;
|
|
case MCI_WAVE_OUTPUT:
|
|
if (wmw->wOutput != (WORD)WAVE_MAPPER)
|
|
lpParms->dwReturn = wmw->wOutput;
|
|
else {
|
|
lpParms->dwReturn = MAKEMCIRESOURCE(WAVE_MAPPER, WAVE_MAPPER_S);
|
|
ret = MCI_RESOURCE_RETURNED;
|
|
}
|
|
TRACE("MCI_WAVE_OUTPUT => %d\n", (signed)wmw->wOutput);
|
|
break;
|
|
/* It is always ok to query wave format parameters,
|
|
* except on auto-open yield MCIERR_UNSUPPORTED_FUNCTION. */
|
|
case MCI_WAVE_STATUS_FORMATTAG:
|
|
if (wmw->lpWaveFormat->wFormatTag != WAVE_FORMAT_PCM)
|
|
lpParms->dwReturn = wmw->lpWaveFormat->wFormatTag;
|
|
else {
|
|
lpParms->dwReturn = MAKEMCIRESOURCE(WAVE_FORMAT_PCM, WAVE_FORMAT_PCM_S);
|
|
ret = MCI_RESOURCE_RETURNED;
|
|
}
|
|
TRACE("MCI_WAVE_FORMATTAG => %lu\n", lpParms->dwReturn);
|
|
break;
|
|
case MCI_WAVE_STATUS_AVGBYTESPERSEC:
|
|
lpParms->dwReturn = wmw->lpWaveFormat->nAvgBytesPerSec;
|
|
TRACE("MCI_WAVE_STATUS_AVGBYTESPERSEC => %lu\n", lpParms->dwReturn);
|
|
break;
|
|
case MCI_WAVE_STATUS_BITSPERSAMPLE:
|
|
lpParms->dwReturn = wmw->lpWaveFormat->wBitsPerSample;
|
|
TRACE("MCI_WAVE_STATUS_BITSPERSAMPLE => %lu\n", lpParms->dwReturn);
|
|
break;
|
|
case MCI_WAVE_STATUS_BLOCKALIGN:
|
|
lpParms->dwReturn = wmw->lpWaveFormat->nBlockAlign;
|
|
TRACE("MCI_WAVE_STATUS_BLOCKALIGN => %lu\n", lpParms->dwReturn);
|
|
break;
|
|
case MCI_WAVE_STATUS_CHANNELS:
|
|
lpParms->dwReturn = wmw->lpWaveFormat->nChannels;
|
|
TRACE("MCI_WAVE_STATUS_CHANNELS => %lu\n", lpParms->dwReturn);
|
|
break;
|
|
case MCI_WAVE_STATUS_SAMPLESPERSEC:
|
|
lpParms->dwReturn = wmw->lpWaveFormat->nSamplesPerSec;
|
|
TRACE("MCI_WAVE_STATUS_SAMPLESPERSEC => %lu\n", lpParms->dwReturn);
|
|
break;
|
|
case MCI_WAVE_STATUS_LEVEL:
|
|
TRACE("MCI_WAVE_STATUS_LEVEL !\n");
|
|
lpParms->dwReturn = 0xAAAA5555;
|
|
break;
|
|
default:
|
|
WARN("unknown command %08X !\n", lpParms->dwItem);
|
|
return MCIERR_UNSUPPORTED_FUNCTION;
|
|
}
|
|
}
|
|
if ((dwFlags & MCI_NOTIFY) && HRESULT_CODE(ret)==0)
|
|
WAVE_mciNotify(lpParms->dwCallback, wmw, MCI_NOTIFY_SUCCESSFUL);
|
|
return ret;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_mciGetDevCaps [internal]
|
|
*/
|
|
static DWORD WAVE_mciGetDevCaps(MCIDEVICEID wDevID, DWORD dwFlags,
|
|
LPMCI_GETDEVCAPS_PARMS lpParms)
|
|
{
|
|
WINE_MCIWAVE* wmw = WAVE_mciGetOpenDev(wDevID);
|
|
DWORD ret = 0;
|
|
|
|
TRACE("(%u, %08X, %p);\n", wDevID, dwFlags, lpParms);
|
|
|
|
if (lpParms == NULL) return MCIERR_NULL_PARAMETER_BLOCK;
|
|
if (wmw == NULL) return MCIERR_INVALID_DEVICE_ID;
|
|
|
|
if (dwFlags & MCI_GETDEVCAPS_ITEM) {
|
|
switch(lpParms->dwItem) {
|
|
case MCI_GETDEVCAPS_DEVICE_TYPE:
|
|
lpParms->dwReturn = MAKEMCIRESOURCE(MCI_DEVTYPE_WAVEFORM_AUDIO, MCI_DEVTYPE_WAVEFORM_AUDIO);
|
|
ret = MCI_RESOURCE_RETURNED;
|
|
break;
|
|
case MCI_GETDEVCAPS_HAS_AUDIO:
|
|
lpParms->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
|
|
ret = MCI_RESOURCE_RETURNED;
|
|
break;
|
|
case MCI_GETDEVCAPS_HAS_VIDEO:
|
|
lpParms->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
|
|
ret = MCI_RESOURCE_RETURNED;
|
|
break;
|
|
case MCI_GETDEVCAPS_USES_FILES:
|
|
lpParms->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
|
|
ret = MCI_RESOURCE_RETURNED;
|
|
break;
|
|
case MCI_GETDEVCAPS_COMPOUND_DEVICE:
|
|
lpParms->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
|
|
ret = MCI_RESOURCE_RETURNED;
|
|
break;
|
|
case MCI_GETDEVCAPS_CAN_RECORD:
|
|
lpParms->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
|
|
ret = MCI_RESOURCE_RETURNED;
|
|
break;
|
|
case MCI_GETDEVCAPS_CAN_EJECT:
|
|
lpParms->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
|
|
ret = MCI_RESOURCE_RETURNED;
|
|
break;
|
|
case MCI_GETDEVCAPS_CAN_PLAY:
|
|
lpParms->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
|
|
ret = MCI_RESOURCE_RETURNED;
|
|
break;
|
|
case MCI_GETDEVCAPS_CAN_SAVE:
|
|
lpParms->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
|
|
ret = MCI_RESOURCE_RETURNED;
|
|
break;
|
|
case MCI_WAVE_GETDEVCAPS_INPUTS:
|
|
lpParms->dwReturn = waveInGetNumDevs();
|
|
break;
|
|
case MCI_WAVE_GETDEVCAPS_OUTPUTS:
|
|
lpParms->dwReturn = waveOutGetNumDevs();
|
|
break;
|
|
default:
|
|
FIXME("Unknown capability (%08x) !\n", lpParms->dwItem);
|
|
return MCIERR_UNRECOGNIZED_COMMAND;
|
|
}
|
|
} else {
|
|
WARN("No GetDevCaps-Item !\n");
|
|
return MCIERR_UNRECOGNIZED_COMMAND;
|
|
}
|
|
if ((dwFlags & MCI_NOTIFY) && HRESULT_CODE(ret)==0)
|
|
WAVE_mciNotify(lpParms->dwCallback, wmw, MCI_NOTIFY_SUCCESSFUL);
|
|
return ret;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* WAVE_mciInfo [internal]
|
|
*/
|
|
static DWORD WAVE_mciInfo(MCIDEVICEID wDevID, DWORD dwFlags, LPMCI_INFO_PARMSW lpParms)
|
|
{
|
|
DWORD ret = 0;
|
|
LPCWSTR str = 0;
|
|
WINE_MCIWAVE* wmw = WAVE_mciGetOpenDev(wDevID);
|
|
|
|
TRACE("(%u, %08X, %p);\n", wDevID, dwFlags, lpParms);
|
|
|
|
if (!lpParms || !lpParms->lpstrReturn)
|
|
return MCIERR_NULL_PARAMETER_BLOCK;
|
|
|
|
TRACE("buf=%p, len=%u\n", lpParms->lpstrReturn, lpParms->dwRetSize);
|
|
|
|
if (wmw == NULL) {
|
|
ret = MCIERR_INVALID_DEVICE_ID;
|
|
} else {
|
|
static const WCHAR wszAudio [] = {'W','i','n','e','\'','s',' ','a','u','d','i','o',' ','p','l','a','y','e','r',0};
|
|
static const WCHAR wszWaveIn [] = {'W','i','n','e',' ','W','a','v','e',' ','I','n',0};
|
|
static const WCHAR wszWaveOut[] = {'W','i','n','e',' ','W','a','v','e',' ','O','u','t',0};
|
|
|
|
switch (dwFlags & ~(MCI_WAIT|MCI_NOTIFY)) {
|
|
case MCI_INFO_PRODUCT: str = wszAudio; break;
|
|
case MCI_INFO_FILE: str = wmw->lpFileName; break;
|
|
case MCI_WAVE_INPUT: str = wszWaveIn; break;
|
|
case MCI_WAVE_OUTPUT: str = wszWaveOut; break;
|
|
default:
|
|
WARN("Don't know this info command (%u)\n", dwFlags);
|
|
ret = MCIERR_UNRECOGNIZED_KEYWORD;
|
|
}
|
|
}
|
|
if (!ret) {
|
|
if (lpParms->dwRetSize) {
|
|
WCHAR zero = 0;
|
|
/* FIXME? Since NT, mciwave, mciseq and mcicda set dwRetSize
|
|
* to the number of characters written, excluding \0. */
|
|
lstrcpynW(lpParms->lpstrReturn, str ? str : &zero, lpParms->dwRetSize);
|
|
} else ret = MCIERR_PARAM_OVERFLOW;
|
|
}
|
|
if (MMSYSERR_NOERROR==ret && (dwFlags & MCI_NOTIFY))
|
|
WAVE_mciNotify(lpParms->dwCallback, wmw, MCI_NOTIFY_SUCCESSFUL);
|
|
return ret;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* DriverProc (MCIWAVE.@)
|
|
*/
|
|
LRESULT CALLBACK MCIWAVE_DriverProc(DWORD_PTR dwDevID, HDRVR hDriv, UINT wMsg,
|
|
LPARAM dwParam1, LPARAM dwParam2)
|
|
{
|
|
TRACE("(%08lX, %p, %08X, %08lX, %08lX)\n",
|
|
dwDevID, hDriv, wMsg, dwParam1, dwParam2);
|
|
|
|
switch (wMsg) {
|
|
case DRV_LOAD: return 1;
|
|
case DRV_FREE: return 1;
|
|
case DRV_OPEN: return WAVE_drvOpen((LPCWSTR)dwParam1, (LPMCI_OPEN_DRIVER_PARMSW)dwParam2);
|
|
case DRV_CLOSE: return WAVE_drvClose(dwDevID);
|
|
case DRV_ENABLE: return 1;
|
|
case DRV_DISABLE: return 1;
|
|
case DRV_QUERYCONFIGURE: return 1;
|
|
case DRV_CONFIGURE: MessageBoxA(0, "MCI waveaudio Driver !", "Wine Driver", MB_OK); return 1;
|
|
case DRV_INSTALL: return DRVCNF_RESTART;
|
|
case DRV_REMOVE: return DRVCNF_RESTART;
|
|
}
|
|
|
|
if (dwDevID == 0xFFFFFFFF) return MCIERR_UNSUPPORTED_FUNCTION;
|
|
|
|
switch (wMsg) {
|
|
case MCI_OPEN_DRIVER: return WAVE_mciOpen (dwDevID, dwParam1, (LPMCI_WAVE_OPEN_PARMSW) dwParam2);
|
|
case MCI_CLOSE_DRIVER: return WAVE_mciClose (dwDevID, dwParam1, (LPMCI_GENERIC_PARMS) dwParam2);
|
|
case MCI_CUE: return WAVE_mciCue (dwDevID, dwParam1, (LPMCI_GENERIC_PARMS) dwParam2);
|
|
case MCI_PLAY: return WAVE_mciPlay (dwDevID, dwParam1, dwParam2, NULL);
|
|
case MCI_RECORD: return WAVE_mciRecord (dwDevID, dwParam1, dwParam2, NULL);
|
|
case MCI_STOP: return WAVE_mciStop (dwDevID, dwParam1, (LPMCI_GENERIC_PARMS) dwParam2);
|
|
case MCI_SET: return WAVE_mciSet (dwDevID, dwParam1, (LPMCI_WAVE_SET_PARMS) dwParam2);
|
|
case MCI_PAUSE: return WAVE_mciPause (dwDevID, dwParam1, (LPMCI_GENERIC_PARMS) dwParam2);
|
|
case MCI_RESUME: return WAVE_mciResume (dwDevID, dwParam1, (LPMCI_GENERIC_PARMS) dwParam2);
|
|
case MCI_STATUS: return WAVE_mciStatus (dwDevID, dwParam1, (LPMCI_STATUS_PARMS) dwParam2);
|
|
case MCI_GETDEVCAPS: return WAVE_mciGetDevCaps(dwDevID, dwParam1, (LPMCI_GETDEVCAPS_PARMS) dwParam2);
|
|
case MCI_INFO: return WAVE_mciInfo (dwDevID, dwParam1, (LPMCI_INFO_PARMSW) dwParam2);
|
|
case MCI_SEEK: return WAVE_mciSeek (dwDevID, dwParam1, (LPMCI_SEEK_PARMS) dwParam2);
|
|
case MCI_SAVE: return WAVE_mciSave (dwDevID, dwParam1, (LPMCI_SAVE_PARMSW) dwParam2);
|
|
/* commands that should be supported */
|
|
case MCI_LOAD:
|
|
case MCI_FREEZE:
|
|
case MCI_PUT:
|
|
case MCI_REALIZE:
|
|
case MCI_UNFREEZE:
|
|
case MCI_UPDATE:
|
|
case MCI_WHERE:
|
|
case MCI_STEP:
|
|
case MCI_SPIN:
|
|
case MCI_ESCAPE:
|
|
case MCI_COPY:
|
|
case MCI_CUT:
|
|
case MCI_DELETE:
|
|
case MCI_PASTE:
|
|
FIXME("Unsupported command [%u]\n", wMsg);
|
|
break;
|
|
case MCI_WINDOW:
|
|
TRACE("Unsupported command [%u]\n", wMsg);
|
|
break;
|
|
/* option which can be silenced */
|
|
case MCI_CONFIGURE:
|
|
return 0;
|
|
case MCI_OPEN:
|
|
case MCI_CLOSE:
|
|
ERR("Shouldn't receive a MCI_OPEN or CLOSE message\n");
|
|
break;
|
|
default:
|
|
FIXME("is probably wrong msg [%u]\n", wMsg);
|
|
return DefDriverProc(dwDevID, hDriv, wMsg, dwParam1, dwParam2);
|
|
}
|
|
return MCIERR_UNRECOGNIZED_COMMAND;
|
|
}
|