Chris Robinson fe6e532c87 Use a separate backend callback to start playback of the device
This allows us to properly update the ALCdevice and its resources with the new
parameters before starting playback, instead of expecting the mixer to block
and wait after it has begun.

This also lets us avoid holding the device lock while resetting and starting
the device, which helps prevent lock inversion on some backends (ie, one thread
locking A then B, and another thread locking B then A), ultimately allowing
certain backends to asynchronously update the ALCdevice without risk of lockup.
Capture still has issues here, however.
2012-03-05 07:11:09 -08:00

762 lines
21 KiB
C

/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2007 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include <windows.h>
#include <mmsystem.h>
#include "alMain.h"
#include "AL/al.h"
#include "AL/alc.h"
#ifndef WAVE_FORMAT_IEEE_FLOAT
#define WAVE_FORMAT_IEEE_FLOAT 0x0003
#endif
typedef struct {
// MMSYSTEM Device
volatile ALboolean bWaveShutdown;
HANDLE hWaveThreadEvent;
HANDLE hWaveThread;
DWORD ulWaveThreadID;
volatile LONG lWaveBuffersCommitted;
WAVEHDR WaveBuffer[4];
union {
HWAVEIN In;
HWAVEOUT Out;
} hWaveHandle;
ALuint Frequency;
RingBuffer *pRing;
} WinMMData;
static ALCchar **PlaybackDeviceList;
static ALuint NumPlaybackDevices;
static ALCchar **CaptureDeviceList;
static ALuint NumCaptureDevices;
static void ProbePlaybackDevices(void)
{
ALuint i;
for(i = 0;i < NumPlaybackDevices;i++)
free(PlaybackDeviceList[i]);
NumPlaybackDevices = waveOutGetNumDevs();
PlaybackDeviceList = realloc(PlaybackDeviceList, sizeof(ALCchar*) * NumPlaybackDevices);
for(i = 0;i < NumPlaybackDevices;i++)
{
WAVEOUTCAPS WaveCaps;
PlaybackDeviceList[i] = NULL;
if(waveOutGetDevCaps(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
{
char name[1024];
ALuint count, j;
count = 0;
do {
if(count == 0)
snprintf(name, sizeof(name), "%s", WaveCaps.szPname);
else
snprintf(name, sizeof(name), "%s #%d", WaveCaps.szPname, count+1);
count++;
for(j = 0;j < i;j++)
{
if(strcmp(name, PlaybackDeviceList[j]) == 0)
break;
}
} while(j != i);
PlaybackDeviceList[i] = strdup(name);
}
}
}
static void ProbeCaptureDevices(void)
{
ALuint i;
for(i = 0;i < NumCaptureDevices;i++)
free(CaptureDeviceList[i]);
NumCaptureDevices = waveInGetNumDevs();
CaptureDeviceList = realloc(CaptureDeviceList, sizeof(ALCchar*) * NumCaptureDevices);
for(i = 0;i < NumCaptureDevices;i++)
{
WAVEINCAPS WaveInCaps;
CaptureDeviceList[i] = NULL;
if(waveInGetDevCaps(i, &WaveInCaps, sizeof(WAVEINCAPS)) == MMSYSERR_NOERROR)
{
char name[1024];
ALuint count, j;
count = 0;
do {
if(count == 0)
snprintf(name, sizeof(name), "%s", WaveInCaps.szPname);
else
snprintf(name, sizeof(name), "%s #%d", WaveInCaps.szPname, count+1);
count++;
for(j = 0;j < i;j++)
{
if(strcmp(name, CaptureDeviceList[j]) == 0)
break;
}
} while(j != i);
CaptureDeviceList[i] = strdup(name);
}
}
}
/*
WaveOutProc
Posts a message to 'PlaybackThreadProc' everytime a WaveOut Buffer is completed and
returns to the application (for more data)
*/
static void CALLBACK WaveOutProc(HWAVEOUT hDevice,UINT uMsg,DWORD_PTR dwInstance,DWORD_PTR dwParam1,DWORD_PTR dwParam2)
{
ALCdevice *pDevice = (ALCdevice*)dwInstance;
WinMMData *pData = pDevice->ExtraData;
(void)hDevice;
(void)dwParam2;
if(uMsg != WOM_DONE)
return;
InterlockedDecrement(&pData->lWaveBuffersCommitted);
PostThreadMessage(pData->ulWaveThreadID, uMsg, 0, dwParam1);
}
/*
PlaybackThreadProc
Used by "MMSYSTEM" Device. Called when a WaveOut buffer has used up its
audio data.
*/
static DWORD WINAPI PlaybackThreadProc(LPVOID lpParameter)
{
ALCdevice *pDevice = (ALCdevice*)lpParameter;
WinMMData *pData = pDevice->ExtraData;
LPWAVEHDR pWaveHdr;
ALuint FrameSize;
MSG msg;
FrameSize = FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType);
SetRTPriority();
while(GetMessage(&msg, NULL, 0, 0))
{
if(msg.message != WOM_DONE)
continue;
if(pData->bWaveShutdown)
{
if(pData->lWaveBuffersCommitted == 0)
break;
continue;
}
pWaveHdr = ((LPWAVEHDR)msg.lParam);
aluMixData(pDevice, pWaveHdr->lpData, pWaveHdr->dwBufferLength/FrameSize);
// Send buffer back to play more data
waveOutWrite(pData->hWaveHandle.Out, pWaveHdr, sizeof(WAVEHDR));
InterlockedIncrement(&pData->lWaveBuffersCommitted);
}
// Signal Wave Thread completed event
if(pData->hWaveThreadEvent)
SetEvent(pData->hWaveThreadEvent);
ExitThread(0);
return 0;
}
/*
WaveInProc
Posts a message to 'CaptureThreadProc' everytime a WaveIn Buffer is completed and
returns to the application (with more data)
*/
static void CALLBACK WaveInProc(HWAVEIN hDevice,UINT uMsg,DWORD_PTR dwInstance,DWORD_PTR dwParam1,DWORD_PTR dwParam2)
{
ALCdevice *pDevice = (ALCdevice*)dwInstance;
WinMMData *pData = pDevice->ExtraData;
(void)hDevice;
(void)dwParam2;
if(uMsg != WIM_DATA)
return;
InterlockedDecrement(&pData->lWaveBuffersCommitted);
PostThreadMessage(pData->ulWaveThreadID,uMsg,0,dwParam1);
}
/*
CaptureThreadProc
Used by "MMSYSTEM" Device. Called when a WaveIn buffer had been filled with new
audio data.
*/
static DWORD WINAPI CaptureThreadProc(LPVOID lpParameter)
{
ALCdevice *pDevice = (ALCdevice*)lpParameter;
WinMMData *pData = pDevice->ExtraData;
LPWAVEHDR pWaveHdr;
ALuint FrameSize;
MSG msg;
FrameSize = FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType);
while(GetMessage(&msg, NULL, 0, 0))
{
if(msg.message != WIM_DATA)
continue;
/* Don't wait for other buffers to finish before quitting. We're
* closing so we don't need them. */
if(pData->bWaveShutdown)
break;
pWaveHdr = ((LPWAVEHDR)msg.lParam);
WriteRingBuffer(pData->pRing, (ALubyte*)pWaveHdr->lpData,
pWaveHdr->dwBytesRecorded/FrameSize);
// Send buffer back to capture more data
waveInAddBuffer(pData->hWaveHandle.In,pWaveHdr,sizeof(WAVEHDR));
InterlockedIncrement(&pData->lWaveBuffersCommitted);
}
// Signal Wave Thread completed event
if(pData->hWaveThreadEvent)
SetEvent(pData->hWaveThreadEvent);
ExitThread(0);
return 0;
}
static ALCenum WinMMOpenPlayback(ALCdevice *pDevice, const ALCchar *deviceName)
{
WAVEFORMATEX wfexFormat;
WinMMData *pData = NULL;
UINT lDeviceID = 0;
MMRESULT res;
ALuint i = 0;
if(!PlaybackDeviceList)
ProbePlaybackDevices();
// Find the Device ID matching the deviceName if valid
for(i = 0;i < NumPlaybackDevices;i++)
{
if(PlaybackDeviceList[i] &&
(!deviceName || strcmp(deviceName, PlaybackDeviceList[i]) == 0))
{
lDeviceID = i;
break;
}
}
if(i == NumPlaybackDevices)
return ALC_INVALID_VALUE;
pData = calloc(1, sizeof(*pData));
if(!pData)
return ALC_OUT_OF_MEMORY;
pDevice->ExtraData = pData;
if(pDevice->FmtChans != DevFmtMono)
{
if((pDevice->Flags&DEVICE_CHANNELS_REQUEST) &&
pDevice->FmtChans != DevFmtStereo)
{
ERR("Failed to set %s, got Stereo instead\n", DevFmtChannelsString(pDevice->FmtChans));
pDevice->Flags &= ~DEVICE_CHANNELS_REQUEST;
}
pDevice->FmtChans = DevFmtStereo;
}
switch(pDevice->FmtType)
{
case DevFmtByte:
pDevice->FmtType = DevFmtUByte;
break;
case DevFmtInt:
case DevFmtUInt:
case DevFmtUShort:
pDevice->FmtType = DevFmtShort;
break;
case DevFmtUByte:
case DevFmtShort:
case DevFmtFloat:
break;
}
retry_open:
memset(&wfexFormat, 0, sizeof(WAVEFORMATEX));
wfexFormat.wFormatTag = ((pDevice->FmtType == DevFmtFloat) ?
WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM);
wfexFormat.nChannels = ChannelsFromDevFmt(pDevice->FmtChans);
wfexFormat.wBitsPerSample = BytesFromDevFmt(pDevice->FmtType) * 8;
wfexFormat.nBlockAlign = wfexFormat.wBitsPerSample *
wfexFormat.nChannels / 8;
wfexFormat.nSamplesPerSec = pDevice->Frequency;
wfexFormat.nAvgBytesPerSec = wfexFormat.nSamplesPerSec *
wfexFormat.nBlockAlign;
wfexFormat.cbSize = 0;
if((res=waveOutOpen(&pData->hWaveHandle.Out, lDeviceID, &wfexFormat, (DWORD_PTR)&WaveOutProc, (DWORD_PTR)pDevice, CALLBACK_FUNCTION)) != MMSYSERR_NOERROR)
{
if(pDevice->FmtType == DevFmtFloat)
{
pDevice->FmtType = DevFmtShort;
goto retry_open;
}
ERR("waveOutOpen failed: %u\n", res);
goto failure;
}
pData->hWaveThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if(pData->hWaveThreadEvent == NULL)
{
ERR("CreateEvent failed: %lu\n", GetLastError());
goto failure;
}
pData->Frequency = pDevice->Frequency;
pDevice->szDeviceName = strdup(PlaybackDeviceList[lDeviceID]);
return ALC_NO_ERROR;
failure:
if(pData->hWaveThreadEvent)
CloseHandle(pData->hWaveThreadEvent);
if(pData->hWaveHandle.Out)
waveOutClose(pData->hWaveHandle.Out);
free(pData);
pDevice->ExtraData = NULL;
return ALC_INVALID_VALUE;
}
static void WinMMClosePlayback(ALCdevice *device)
{
WinMMData *pData = (WinMMData*)device->ExtraData;
// Close the Wave device
CloseHandle(pData->hWaveThreadEvent);
pData->hWaveThreadEvent = 0;
waveOutClose(pData->hWaveHandle.Out);
pData->hWaveHandle.Out = 0;
free(pData);
device->ExtraData = NULL;
}
static ALCboolean WinMMResetPlayback(ALCdevice *device)
{
WinMMData *pData = (WinMMData*)device->ExtraData;
device->UpdateSize = (ALuint)((ALuint64)device->UpdateSize *
pData->Frequency / device->Frequency);
device->UpdateSize = device->UpdateSize*device->NumUpdates / 4;
device->NumUpdates = 4;
device->Frequency = pData->Frequency;
SetDefaultWFXChannelOrder(device);
return ALC_TRUE;
}
static ALCboolean WinMMStartPlayback(ALCdevice *device)
{
WinMMData *pData = (WinMMData*)device->ExtraData;
ALbyte *BufferData;
ALint lBufferSize;
ALuint i;
pData->hWaveThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PlaybackThreadProc, (LPVOID)device, 0, &pData->ulWaveThreadID);
if(pData->hWaveThread == NULL)
return ALC_FALSE;
pData->lWaveBuffersCommitted = 0;
// Create 4 Buffers
lBufferSize = device->UpdateSize*device->NumUpdates / 4;
lBufferSize *= FrameSizeFromDevFmt(device->FmtChans, device->FmtType);
BufferData = calloc(4, lBufferSize);
for(i = 0;i < 4;i++)
{
memset(&pData->WaveBuffer[i], 0, sizeof(WAVEHDR));
pData->WaveBuffer[i].dwBufferLength = lBufferSize;
pData->WaveBuffer[i].lpData = ((i==0) ? (LPSTR)BufferData :
(pData->WaveBuffer[i-1].lpData +
pData->WaveBuffer[i-1].dwBufferLength));
waveOutPrepareHeader(pData->hWaveHandle.Out, &pData->WaveBuffer[i], sizeof(WAVEHDR));
waveOutWrite(pData->hWaveHandle.Out, &pData->WaveBuffer[i], sizeof(WAVEHDR));
InterlockedIncrement(&pData->lWaveBuffersCommitted);
}
return ALC_TRUE;
}
static void WinMMStopPlayback(ALCdevice *device)
{
WinMMData *pData = (WinMMData*)device->ExtraData;
void *buffer = NULL;
int i;
if(pData->hWaveThread == NULL)
return;
// Set flag to stop processing headers
pData->bWaveShutdown = AL_TRUE;
// Wait for signal that Wave Thread has been destroyed
WaitForSingleObjectEx(pData->hWaveThreadEvent, 5000, FALSE);
CloseHandle(pData->hWaveThread);
pData->hWaveThread = 0;
pData->bWaveShutdown = AL_FALSE;
// Release the wave buffers
for(i = 0;i < 4;i++)
{
waveOutUnprepareHeader(pData->hWaveHandle.Out, &pData->WaveBuffer[i], sizeof(WAVEHDR));
if(i == 0) buffer = pData->WaveBuffer[i].lpData;
pData->WaveBuffer[i].lpData = NULL;
}
free(buffer);
}
static ALCenum WinMMOpenCapture(ALCdevice *pDevice, const ALCchar *deviceName)
{
WAVEFORMATEX wfexCaptureFormat;
ALbyte *BufferData = NULL;
DWORD ulCapturedDataSize;
WinMMData *pData = NULL;
UINT lDeviceID = 0;
ALint lBufferSize;
MMRESULT res;
ALuint i;
if(!CaptureDeviceList)
ProbeCaptureDevices();
// Find the Device ID matching the deviceName if valid
for(i = 0;i < NumCaptureDevices;i++)
{
if(CaptureDeviceList[i] &&
(!deviceName || strcmp(deviceName, CaptureDeviceList[i]) == 0))
{
lDeviceID = i;
break;
}
}
if(i == NumCaptureDevices)
return ALC_INVALID_VALUE;
switch(pDevice->FmtChans)
{
case DevFmtMono:
case DevFmtStereo:
break;
case DevFmtQuad:
case DevFmtX51:
case DevFmtX51Side:
case DevFmtX61:
case DevFmtX71:
return ALC_INVALID_ENUM;
}
switch(pDevice->FmtType)
{
case DevFmtUByte:
case DevFmtShort:
case DevFmtInt:
case DevFmtFloat:
break;
case DevFmtByte:
case DevFmtUShort:
case DevFmtUInt:
return ALC_INVALID_ENUM;
}
pData = calloc(1, sizeof(*pData));
if(!pData)
return ALC_OUT_OF_MEMORY;
pDevice->ExtraData = pData;
memset(&wfexCaptureFormat, 0, sizeof(WAVEFORMATEX));
wfexCaptureFormat.wFormatTag = ((pDevice->FmtType == DevFmtFloat) ?
WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM);
wfexCaptureFormat.nChannels = ChannelsFromDevFmt(pDevice->FmtChans);
wfexCaptureFormat.wBitsPerSample = BytesFromDevFmt(pDevice->FmtType) * 8;
wfexCaptureFormat.nBlockAlign = wfexCaptureFormat.wBitsPerSample *
wfexCaptureFormat.nChannels / 8;
wfexCaptureFormat.nSamplesPerSec = pDevice->Frequency;
wfexCaptureFormat.nAvgBytesPerSec = wfexCaptureFormat.nSamplesPerSec *
wfexCaptureFormat.nBlockAlign;
wfexCaptureFormat.cbSize = 0;
if((res=waveInOpen(&pData->hWaveHandle.In, lDeviceID, &wfexCaptureFormat, (DWORD_PTR)&WaveInProc, (DWORD_PTR)pDevice, CALLBACK_FUNCTION)) != MMSYSERR_NOERROR)
{
ERR("waveInOpen failed: %u\n", res);
goto failure;
}
pData->hWaveThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if(pData->hWaveThreadEvent == NULL)
{
ERR("CreateEvent failed: %lu\n", GetLastError());
goto failure;
}
pData->Frequency = pDevice->Frequency;
// Allocate circular memory buffer for the captured audio
ulCapturedDataSize = pDevice->UpdateSize*pDevice->NumUpdates;
// Make sure circular buffer is at least 100ms in size
if(ulCapturedDataSize < (wfexCaptureFormat.nSamplesPerSec / 10))
ulCapturedDataSize = wfexCaptureFormat.nSamplesPerSec / 10;
pData->pRing = CreateRingBuffer(wfexCaptureFormat.nBlockAlign, ulCapturedDataSize);
if(!pData->pRing)
goto failure;
pData->lWaveBuffersCommitted = 0;
// Create 4 Buffers of 50ms each
lBufferSize = wfexCaptureFormat.nAvgBytesPerSec / 20;
lBufferSize -= (lBufferSize % wfexCaptureFormat.nBlockAlign);
BufferData = calloc(4, lBufferSize);
if(!BufferData)
goto failure;
for(i = 0;i < 4;i++)
{
memset(&pData->WaveBuffer[i], 0, sizeof(WAVEHDR));
pData->WaveBuffer[i].dwBufferLength = lBufferSize;
pData->WaveBuffer[i].lpData = ((i==0) ? (LPSTR)BufferData :
(pData->WaveBuffer[i-1].lpData +
pData->WaveBuffer[i-1].dwBufferLength));
pData->WaveBuffer[i].dwFlags = 0;
pData->WaveBuffer[i].dwLoops = 0;
waveInPrepareHeader(pData->hWaveHandle.In, &pData->WaveBuffer[i], sizeof(WAVEHDR));
waveInAddBuffer(pData->hWaveHandle.In, &pData->WaveBuffer[i], sizeof(WAVEHDR));
InterlockedIncrement(&pData->lWaveBuffersCommitted);
}
pData->hWaveThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CaptureThreadProc, (LPVOID)pDevice, 0, &pData->ulWaveThreadID);
if (pData->hWaveThread == NULL)
goto failure;
pDevice->szDeviceName = strdup(CaptureDeviceList[lDeviceID]);
return ALC_NO_ERROR;
failure:
if(pData->hWaveThread)
CloseHandle(pData->hWaveThread);
if(BufferData)
{
for(i = 0;i < 4;i++)
waveInUnprepareHeader(pData->hWaveHandle.In, &pData->WaveBuffer[i], sizeof(WAVEHDR));
free(BufferData);
}
if(pData->pRing)
DestroyRingBuffer(pData->pRing);
if(pData->hWaveThreadEvent)
CloseHandle(pData->hWaveThreadEvent);
if(pData->hWaveHandle.In)
waveInClose(pData->hWaveHandle.In);
free(pData);
pDevice->ExtraData = NULL;
return ALC_INVALID_VALUE;
}
static void WinMMCloseCapture(ALCdevice *pDevice)
{
WinMMData *pData = (WinMMData*)pDevice->ExtraData;
void *buffer = NULL;
int i;
/* Tell the processing thread to quit and wait for it to do so. */
pData->bWaveShutdown = AL_TRUE;
PostThreadMessage(pData->ulWaveThreadID, WM_QUIT, 0, 0);
WaitForSingleObjectEx(pData->hWaveThreadEvent, 5000, FALSE);
/* Make sure capture is stopped and all pending buffers are flushed. */
waveInReset(pData->hWaveHandle.In);
CloseHandle(pData->hWaveThread);
pData->hWaveThread = 0;
// Release the wave buffers
for(i = 0;i < 4;i++)
{
waveInUnprepareHeader(pData->hWaveHandle.In, &pData->WaveBuffer[i], sizeof(WAVEHDR));
if(i == 0) buffer = pData->WaveBuffer[i].lpData;
pData->WaveBuffer[i].lpData = NULL;
}
free(buffer);
DestroyRingBuffer(pData->pRing);
pData->pRing = NULL;
// Close the Wave device
CloseHandle(pData->hWaveThreadEvent);
pData->hWaveThreadEvent = 0;
waveInClose(pData->hWaveHandle.In);
pData->hWaveHandle.In = 0;
free(pData);
pDevice->ExtraData = NULL;
}
static void WinMMStartCapture(ALCdevice *pDevice)
{
WinMMData *pData = (WinMMData*)pDevice->ExtraData;
waveInStart(pData->hWaveHandle.In);
}
static void WinMMStopCapture(ALCdevice *pDevice)
{
WinMMData *pData = (WinMMData*)pDevice->ExtraData;
waveInStop(pData->hWaveHandle.In);
}
static ALCenum WinMMCaptureSamples(ALCdevice *pDevice, ALCvoid *pBuffer, ALCuint lSamples)
{
WinMMData *pData = (WinMMData*)pDevice->ExtraData;
ReadRingBuffer(pData->pRing, pBuffer, lSamples);
return ALC_NO_ERROR;
}
static ALCuint WinMMAvailableSamples(ALCdevice *pDevice)
{
WinMMData *pData = (WinMMData*)pDevice->ExtraData;
return RingBufferSize(pData->pRing);
}
static const BackendFuncs WinMMFuncs = {
WinMMOpenPlayback,
WinMMClosePlayback,
WinMMResetPlayback,
WinMMStartPlayback,
WinMMStopPlayback,
WinMMOpenCapture,
WinMMCloseCapture,
WinMMStartCapture,
WinMMStopCapture,
WinMMCaptureSamples,
WinMMAvailableSamples
};
ALCboolean alcWinMMInit(BackendFuncs *FuncList)
{
*FuncList = WinMMFuncs;
return ALC_TRUE;
}
void alcWinMMDeinit()
{
ALuint lLoop;
for(lLoop = 0;lLoop < NumPlaybackDevices;lLoop++)
free(PlaybackDeviceList[lLoop]);
free(PlaybackDeviceList);
PlaybackDeviceList = NULL;
NumPlaybackDevices = 0;
for(lLoop = 0; lLoop < NumCaptureDevices; lLoop++)
free(CaptureDeviceList[lLoop]);
free(CaptureDeviceList);
CaptureDeviceList = NULL;
NumCaptureDevices = 0;
}
void alcWinMMProbe(enum DevProbe type)
{
ALuint i;
switch(type)
{
case ALL_DEVICE_PROBE:
ProbePlaybackDevices();
for(i = 0;i < NumPlaybackDevices;i++)
{
if(PlaybackDeviceList[i])
AppendAllDeviceList(PlaybackDeviceList[i]);
}
break;
case CAPTURE_DEVICE_PROBE:
ProbeCaptureDevices();
for(i = 0;i < NumCaptureDevices;i++)
{
if(CaptureDeviceList[i])
AppendCaptureDeviceList(CaptureDeviceList[i]);
}
break;
}
}