/** * 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 #include #include #include #include #include "alMain.h" #include "alu.h" #include "threads.h" #ifndef WAVE_FORMAT_IEEE_FLOAT #define WAVE_FORMAT_IEEE_FLOAT 0x0003 #endif typedef struct { // MMSYSTEM Device volatile ALboolean killNow; HANDLE WaveThreadEvent; HANDLE WaveThread; DWORD WaveThreadID; volatile LONG WaveBuffersCommitted; WAVEHDR WaveBuffer[4]; union { HWAVEIN In; HWAVEOUT Out; } WaveHandle; WAVEFORMATEX Format; RingBuffer *Ring; } WinMMData; static vector_al_string PlaybackDevices; static vector_al_string CaptureDevices; static void ProbePlaybackDevices(void) { al_string *iter, *end; ALuint numdevs; ALuint i; iter = VECTOR_ITER_BEGIN(PlaybackDevices); end = VECTOR_ITER_END(PlaybackDevices); for(;iter != end;iter++) AL_STRING_DEINIT(*iter); VECTOR_RESIZE(PlaybackDevices, 0); numdevs = waveOutGetNumDevs(); VECTOR_RESERVE(PlaybackDevices, numdevs); for(i = 0;i < numdevs;i++) { WAVEOUTCAPSW WaveCaps; al_string dname; AL_STRING_INIT(dname); if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR) { ALuint count = 0; do { al_string_copy_wcstr(&dname, WaveCaps.szPname); if(count != 0) { char str[64]; snprintf(str, sizeof(str), " #%d", count+1); al_string_append_cstr(&dname, str); } count++; iter = VECTOR_ITER_BEGIN(PlaybackDevices); end = VECTOR_ITER_END(PlaybackDevices); for(;iter != end;iter++) { if(al_string_cmp(*iter, dname) == 0) break; } } while(iter != end); TRACE("Got device \"%s\", ID %u\n", al_string_get_cstr(dname), i); } VECTOR_PUSH_BACK(PlaybackDevices, dname); } } static void ProbeCaptureDevices(void) { al_string *iter, *end; ALuint numdevs; ALuint i; iter = VECTOR_ITER_BEGIN(CaptureDevices); end = VECTOR_ITER_END(CaptureDevices); for(;iter != end;iter++) AL_STRING_DEINIT(*iter); VECTOR_RESIZE(CaptureDevices, 0); numdevs = waveInGetNumDevs(); VECTOR_RESERVE(CaptureDevices, numdevs); for(i = 0;i < numdevs;i++) { WAVEINCAPSW WaveCaps; al_string dname; AL_STRING_INIT(dname); if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR) { ALuint count = 0; do { al_string_copy_wcstr(&dname, WaveCaps.szPname); if(count != 0) { char str[64]; snprintf(str, sizeof(str), " #%d", count+1); al_string_append_cstr(&dname, str); } count++; iter = VECTOR_ITER_BEGIN(CaptureDevices); end = VECTOR_ITER_END(CaptureDevices); for(;iter != end;iter++) { if(al_string_cmp(*iter, dname) == 0) break; } } while(iter != end); TRACE("Got device \"%s\", ID %u\n", al_string_get_cstr(dname), i); } VECTOR_PUSH_BACK(CaptureDevices, dname); } } /* 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 UNUSED(device), UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR UNUSED(param2)) { ALCdevice *Device = (ALCdevice*)instance; WinMMData *data = Device->ExtraData; if(msg != WOM_DONE) return; InterlockedDecrement(&data->WaveBuffersCommitted); PostThreadMessage(data->WaveThreadID, msg, 0, param1); } /* PlaybackThreadProc Used by "MMSYSTEM" Device. Called when a WaveOut buffer has used up its audio data. */ FORCE_ALIGN static DWORD WINAPI PlaybackThreadProc(LPVOID param) { ALCdevice *Device = (ALCdevice*)param; WinMMData *data = Device->ExtraData; LPWAVEHDR WaveHdr; ALuint FrameSize; MSG msg; FrameSize = FrameSizeFromDevFmt(Device->FmtChans, Device->FmtType); SetRTPriority(); althrd_setname(althrd_current(), MIXER_THREAD_NAME); while(GetMessage(&msg, NULL, 0, 0)) { if(msg.message != WOM_DONE) continue; if(data->killNow) { if(data->WaveBuffersCommitted == 0) break; continue; } WaveHdr = ((LPWAVEHDR)msg.lParam); aluMixData(Device, WaveHdr->lpData, WaveHdr->dwBufferLength/FrameSize); // Send buffer back to play more data waveOutWrite(data->WaveHandle.Out, WaveHdr, sizeof(WAVEHDR)); InterlockedIncrement(&data->WaveBuffersCommitted); } // Signal Wave Thread completed event if(data->WaveThreadEvent) SetEvent(data->WaveThreadEvent); 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 UNUSED(device), UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR UNUSED(param2)) { ALCdevice *Device = (ALCdevice*)instance; WinMMData *data = Device->ExtraData; if(msg != WIM_DATA) return; InterlockedDecrement(&data->WaveBuffersCommitted); PostThreadMessage(data->WaveThreadID, msg, 0, param1); } /* CaptureThreadProc Used by "MMSYSTEM" Device. Called when a WaveIn buffer had been filled with new audio data. */ static DWORD WINAPI CaptureThreadProc(LPVOID param) { ALCdevice *Device = (ALCdevice*)param; WinMMData *data = Device->ExtraData; LPWAVEHDR WaveHdr; ALuint FrameSize; MSG msg; FrameSize = FrameSizeFromDevFmt(Device->FmtChans, Device->FmtType); althrd_setname(althrd_current(), "alsoft-record"); 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(data->killNow) break; WaveHdr = ((LPWAVEHDR)msg.lParam); WriteRingBuffer(data->Ring, (ALubyte*)WaveHdr->lpData, WaveHdr->dwBytesRecorded/FrameSize); // Send buffer back to capture more data waveInAddBuffer(data->WaveHandle.In, WaveHdr, sizeof(WAVEHDR)); InterlockedIncrement(&data->WaveBuffersCommitted); } // Signal Wave Thread completed event if(data->WaveThreadEvent) SetEvent(data->WaveThreadEvent); ExitThread(0); return 0; } static ALCenum WinMMOpenPlayback(ALCdevice *Device, const ALCchar *deviceName) { WinMMData *data = NULL; const al_string *iter, *end; UINT DeviceID = 0u-1u; MMRESULT res; if(VECTOR_SIZE(PlaybackDevices) == 0) ProbePlaybackDevices(); // Find the Device ID matching the deviceName if valid iter = VECTOR_ITER_BEGIN(PlaybackDevices); end = VECTOR_ITER_END(PlaybackDevices); for(;iter != end;iter++) { if(!al_string_empty(*iter) && (!deviceName || al_string_cmp_cstr(*iter, deviceName) == 0)) { DeviceID = (UINT)(iter - VECTOR_ITER_BEGIN(PlaybackDevices)); break; } } if(DeviceID == 0u-1u) return ALC_INVALID_VALUE; data = calloc(1, sizeof(*data)); if(!data) return ALC_OUT_OF_MEMORY; Device->ExtraData = data; retry_open: memset(&data->Format, 0, sizeof(WAVEFORMATEX)); if(Device->FmtType == DevFmtFloat) { data->Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; data->Format.wBitsPerSample = 32; } else { data->Format.wFormatTag = WAVE_FORMAT_PCM; if(Device->FmtType == DevFmtUByte || Device->FmtType == DevFmtByte) data->Format.wBitsPerSample = 8; else data->Format.wBitsPerSample = 16; } data->Format.nChannels = ((Device->FmtChans == DevFmtMono) ? 1 : 2); data->Format.nBlockAlign = data->Format.wBitsPerSample * data->Format.nChannels / 8; data->Format.nSamplesPerSec = Device->Frequency; data->Format.nAvgBytesPerSec = data->Format.nSamplesPerSec * data->Format.nBlockAlign; data->Format.cbSize = 0; if((res=waveOutOpen(&data->WaveHandle.Out, DeviceID, &data->Format, (DWORD_PTR)&WaveOutProc, (DWORD_PTR)Device, CALLBACK_FUNCTION)) != MMSYSERR_NOERROR) { if(Device->FmtType == DevFmtFloat) { Device->FmtType = DevFmtShort; goto retry_open; } ERR("waveOutOpen failed: %u\n", res); goto failure; } data->WaveThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if(data->WaveThreadEvent == NULL) { ERR("CreateEvent failed: %lu\n", GetLastError()); goto failure; } al_string_copy(&Device->DeviceName, VECTOR_ELEM(PlaybackDevices, DeviceID)); return ALC_NO_ERROR; failure: if(data->WaveThreadEvent) CloseHandle(data->WaveThreadEvent); if(data->WaveHandle.Out) waveOutClose(data->WaveHandle.Out); free(data); Device->ExtraData = NULL; return ALC_INVALID_VALUE; } static void WinMMClosePlayback(ALCdevice *device) { WinMMData *data = (WinMMData*)device->ExtraData; // Close the Wave device CloseHandle(data->WaveThreadEvent); data->WaveThreadEvent = 0; waveOutClose(data->WaveHandle.Out); data->WaveHandle.Out = 0; free(data); device->ExtraData = NULL; } static ALCboolean WinMMResetPlayback(ALCdevice *device) { WinMMData *data = (WinMMData*)device->ExtraData; device->UpdateSize = (ALuint)((ALuint64)device->UpdateSize * data->Format.nSamplesPerSec / device->Frequency); device->UpdateSize = (device->UpdateSize*device->NumUpdates + 3) / 4; device->NumUpdates = 4; device->Frequency = data->Format.nSamplesPerSec; if(data->Format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT) { if(data->Format.wBitsPerSample == 32) device->FmtType = DevFmtFloat; else { ERR("Unhandled IEEE float sample depth: %d\n", data->Format.wBitsPerSample); return ALC_FALSE; } } else if(data->Format.wFormatTag == WAVE_FORMAT_PCM) { if(data->Format.wBitsPerSample == 16) device->FmtType = DevFmtShort; else if(data->Format.wBitsPerSample == 8) device->FmtType = DevFmtUByte; else { ERR("Unhandled PCM sample depth: %d\n", data->Format.wBitsPerSample); return ALC_FALSE; } } else { ERR("Unhandled format tag: 0x%04x\n", data->Format.wFormatTag); return ALC_FALSE; } if(data->Format.nChannels == 2) device->FmtChans = DevFmtStereo; else if(data->Format.nChannels == 1) device->FmtChans = DevFmtMono; else { ERR("Unhandled channel count: %d\n", data->Format.nChannels); return ALC_FALSE; } SetDefaultWFXChannelOrder(device); return ALC_TRUE; } static ALCboolean WinMMStartPlayback(ALCdevice *device) { WinMMData *data = (WinMMData*)device->ExtraData; ALbyte *BufferData; ALint BufferSize; ALuint i; data->WaveThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PlaybackThreadProc, (LPVOID)device, 0, &data->WaveThreadID); if(data->WaveThread == NULL) return ALC_FALSE; data->WaveBuffersCommitted = 0; // Create 4 Buffers BufferSize = device->UpdateSize*device->NumUpdates / 4; BufferSize *= FrameSizeFromDevFmt(device->FmtChans, device->FmtType); BufferData = calloc(4, BufferSize); for(i = 0;i < 4;i++) { memset(&data->WaveBuffer[i], 0, sizeof(WAVEHDR)); data->WaveBuffer[i].dwBufferLength = BufferSize; data->WaveBuffer[i].lpData = ((i==0) ? (LPSTR)BufferData : (data->WaveBuffer[i-1].lpData + data->WaveBuffer[i-1].dwBufferLength)); waveOutPrepareHeader(data->WaveHandle.Out, &data->WaveBuffer[i], sizeof(WAVEHDR)); waveOutWrite(data->WaveHandle.Out, &data->WaveBuffer[i], sizeof(WAVEHDR)); InterlockedIncrement(&data->WaveBuffersCommitted); } return ALC_TRUE; } static void WinMMStopPlayback(ALCdevice *device) { WinMMData *data = (WinMMData*)device->ExtraData; void *buffer = NULL; int i; if(data->WaveThread == NULL) return; // Set flag to stop processing headers data->killNow = AL_TRUE; // Wait for signal that Wave Thread has been destroyed WaitForSingleObjectEx(data->WaveThreadEvent, 5000, FALSE); CloseHandle(data->WaveThread); data->WaveThread = 0; data->killNow = AL_FALSE; // Release the wave buffers for(i = 0;i < 4;i++) { waveOutUnprepareHeader(data->WaveHandle.Out, &data->WaveBuffer[i], sizeof(WAVEHDR)); if(i == 0) buffer = data->WaveBuffer[i].lpData; data->WaveBuffer[i].lpData = NULL; } free(buffer); } static ALCenum WinMMOpenCapture(ALCdevice *Device, const ALCchar *deviceName) { const al_string *iter, *end; ALbyte *BufferData = NULL; DWORD CapturedDataSize; WinMMData *data = NULL; UINT DeviceID = 0u-1u; ALint BufferSize; MMRESULT res; ALuint i; if(VECTOR_SIZE(CaptureDevices) == 0) ProbeCaptureDevices(); // Find the Device ID matching the deviceName if valid iter = VECTOR_ITER_BEGIN(CaptureDevices); end = VECTOR_ITER_END(CaptureDevices); for(;iter != end;iter++) { if(!al_string_empty(*iter) && (!deviceName || al_string_cmp_cstr(*iter, deviceName) == 0)) { DeviceID = (UINT)(iter - VECTOR_ITER_BEGIN(CaptureDevices)); break; } } if(DeviceID == 0u-1u) return ALC_INVALID_VALUE; switch(Device->FmtChans) { case DevFmtMono: case DevFmtStereo: break; case DevFmtQuad: case DevFmtX51: case DevFmtX51Side: case DevFmtX61: case DevFmtX71: return ALC_INVALID_ENUM; } switch(Device->FmtType) { case DevFmtUByte: case DevFmtShort: case DevFmtInt: case DevFmtFloat: break; case DevFmtByte: case DevFmtUShort: case DevFmtUInt: return ALC_INVALID_ENUM; } data = calloc(1, sizeof(*data)); if(!data) return ALC_OUT_OF_MEMORY; Device->ExtraData = data; memset(&data->Format, 0, sizeof(WAVEFORMATEX)); data->Format.wFormatTag = ((Device->FmtType == DevFmtFloat) ? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM); data->Format.nChannels = ChannelsFromDevFmt(Device->FmtChans); data->Format.wBitsPerSample = BytesFromDevFmt(Device->FmtType) * 8; data->Format.nBlockAlign = data->Format.wBitsPerSample * data->Format.nChannels / 8; data->Format.nSamplesPerSec = Device->Frequency; data->Format.nAvgBytesPerSec = data->Format.nSamplesPerSec * data->Format.nBlockAlign; data->Format.cbSize = 0; if((res=waveInOpen(&data->WaveHandle.In, DeviceID, &data->Format, (DWORD_PTR)&WaveInProc, (DWORD_PTR)Device, CALLBACK_FUNCTION)) != MMSYSERR_NOERROR) { ERR("waveInOpen failed: %u\n", res); goto failure; } data->WaveThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if(data->WaveThreadEvent == NULL) { ERR("CreateEvent failed: %lu\n", GetLastError()); goto failure; } // Allocate circular memory buffer for the captured audio CapturedDataSize = Device->UpdateSize*Device->NumUpdates; // Make sure circular buffer is at least 100ms in size if(CapturedDataSize < (data->Format.nSamplesPerSec / 10)) CapturedDataSize = data->Format.nSamplesPerSec / 10; data->Ring = CreateRingBuffer(data->Format.nBlockAlign, CapturedDataSize); if(!data->Ring) goto failure; data->WaveBuffersCommitted = 0; // Create 4 Buffers of 50ms each BufferSize = data->Format.nAvgBytesPerSec / 20; BufferSize -= (BufferSize % data->Format.nBlockAlign); BufferData = calloc(4, BufferSize); if(!BufferData) goto failure; for(i = 0;i < 4;i++) { memset(&data->WaveBuffer[i], 0, sizeof(WAVEHDR)); data->WaveBuffer[i].dwBufferLength = BufferSize; data->WaveBuffer[i].lpData = ((i==0) ? (LPSTR)BufferData : (data->WaveBuffer[i-1].lpData + data->WaveBuffer[i-1].dwBufferLength)); data->WaveBuffer[i].dwFlags = 0; data->WaveBuffer[i].dwLoops = 0; waveInPrepareHeader(data->WaveHandle.In, &data->WaveBuffer[i], sizeof(WAVEHDR)); waveInAddBuffer(data->WaveHandle.In, &data->WaveBuffer[i], sizeof(WAVEHDR)); InterlockedIncrement(&data->WaveBuffersCommitted); } data->WaveThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CaptureThreadProc, (LPVOID)Device, 0, &data->WaveThreadID); if (data->WaveThread == NULL) goto failure; al_string_copy(&Device->DeviceName, VECTOR_ELEM(CaptureDevices, DeviceID)); return ALC_NO_ERROR; failure: if(data->WaveThread) CloseHandle(data->WaveThread); if(BufferData) { for(i = 0;i < 4;i++) waveInUnprepareHeader(data->WaveHandle.In, &data->WaveBuffer[i], sizeof(WAVEHDR)); free(BufferData); } if(data->Ring) DestroyRingBuffer(data->Ring); if(data->WaveThreadEvent) CloseHandle(data->WaveThreadEvent); if(data->WaveHandle.In) waveInClose(data->WaveHandle.In); free(data); Device->ExtraData = NULL; return ALC_INVALID_VALUE; } static void WinMMCloseCapture(ALCdevice *Device) { WinMMData *data = (WinMMData*)Device->ExtraData; void *buffer = NULL; int i; /* Tell the processing thread to quit and wait for it to do so. */ data->killNow = AL_TRUE; PostThreadMessage(data->WaveThreadID, WM_QUIT, 0, 0); WaitForSingleObjectEx(data->WaveThreadEvent, 5000, FALSE); /* Make sure capture is stopped and all pending buffers are flushed. */ waveInReset(data->WaveHandle.In); CloseHandle(data->WaveThread); data->WaveThread = 0; // Release the wave buffers for(i = 0;i < 4;i++) { waveInUnprepareHeader(data->WaveHandle.In, &data->WaveBuffer[i], sizeof(WAVEHDR)); if(i == 0) buffer = data->WaveBuffer[i].lpData; data->WaveBuffer[i].lpData = NULL; } free(buffer); DestroyRingBuffer(data->Ring); data->Ring = NULL; // Close the Wave device CloseHandle(data->WaveThreadEvent); data->WaveThreadEvent = 0; waveInClose(data->WaveHandle.In); data->WaveHandle.In = 0; free(data); Device->ExtraData = NULL; } static void WinMMStartCapture(ALCdevice *Device) { WinMMData *data = (WinMMData*)Device->ExtraData; waveInStart(data->WaveHandle.In); } static void WinMMStopCapture(ALCdevice *Device) { WinMMData *data = (WinMMData*)Device->ExtraData; waveInStop(data->WaveHandle.In); } static ALCenum WinMMCaptureSamples(ALCdevice *Device, ALCvoid *Buffer, ALCuint Samples) { WinMMData *data = (WinMMData*)Device->ExtraData; ReadRingBuffer(data->Ring, Buffer, Samples); return ALC_NO_ERROR; } static ALCuint WinMMAvailableSamples(ALCdevice *Device) { WinMMData *data = (WinMMData*)Device->ExtraData; return RingBufferSize(data->Ring); } static const BackendFuncs WinMMFuncs = { WinMMOpenPlayback, WinMMClosePlayback, WinMMResetPlayback, WinMMStartPlayback, WinMMStopPlayback, WinMMOpenCapture, WinMMCloseCapture, WinMMStartCapture, WinMMStopCapture, WinMMCaptureSamples, WinMMAvailableSamples, ALCdevice_GetLatencyDefault }; ALCboolean alcWinMMInit(BackendFuncs *FuncList) { VECTOR_INIT(PlaybackDevices); VECTOR_INIT(CaptureDevices); *FuncList = WinMMFuncs; return ALC_TRUE; } void alcWinMMDeinit() { al_string *iter, *end; iter = VECTOR_ITER_BEGIN(PlaybackDevices); end = VECTOR_ITER_END(PlaybackDevices); for(;iter != end;iter++) AL_STRING_DEINIT(*iter); VECTOR_DEINIT(PlaybackDevices); iter = VECTOR_ITER_BEGIN(CaptureDevices); end = VECTOR_ITER_END(CaptureDevices); for(;iter != end;iter++) AL_STRING_DEINIT(*iter); VECTOR_DEINIT(CaptureDevices); } void alcWinMMProbe(enum DevProbe type) { const al_string *iter, *end; switch(type) { case ALL_DEVICE_PROBE: ProbePlaybackDevices(); iter = VECTOR_ITER_BEGIN(PlaybackDevices); end = VECTOR_ITER_END(PlaybackDevices); for(;iter != end;iter++) { if(!al_string_empty(*iter)) AppendAllDevicesList(al_string_get_cstr(*iter)); } break; case CAPTURE_DEVICE_PROBE: ProbeCaptureDevices(); iter = VECTOR_ITER_BEGIN(CaptureDevices); end = VECTOR_ITER_END(CaptureDevices); for(;iter != end;iter++) { if(!al_string_empty(*iter)) AppendCaptureDeviceList(al_string_get_cstr(*iter)); } break; } }