-
Here are the flags converted to events and better window message handling (and a run through my code beautifier). Still needs proper error checking/handling. See if this still skips:
Code:
/* gcc main.c -lwinmm -std=c99 -pedantic */
#include <windows.h>
#include <stdbool.h>
#include <stdio.h>
#define MAXBUFFSIZE 44100
#define WM_PLYBCKERR (WM_USER+1)
#define WM_ENDPLAY (WM_USER+2)
/* Declare Windows procedure */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);
void ErrorBox(HWND hwnd, const char *msg)
{
MessageBox(hwnd, msg, "Error", MB_OK | MB_ICONSTOP);
}
typedef struct _wave_struct
{
WAVEFORMATEX wsFormat;
PBYTE FileData;
LPWAVEHDR Hdrs;
unsigned int HdrC;
} WAVE_STRUCT, *PWAVE_STRUCT;
typedef struct _wave_file_hdr
{
char wfhSign[4];
unsigned int wfhChnkSize;
char wfhFormat[4];
char wfhSubChnk1ID[4];
unsigned int wfhSubChnk1Size;
unsigned short wfhAudFormat;
unsigned short wfhChannels;
unsigned int wfhSampPerSec;
unsigned int wfhAvgBytesPerSec;
unsigned short wfhBlockAlign;
unsigned short wfhBitsPerSamp;
char wfhSubChnk2Sign[4];
unsigned int wfhSubChnk2Size;
} WAVE_FILE_HDR, *PWAVE_FILE_HDR;
typedef enum
{
PlayState_Exit,
PlayState_Pause,
PlayState_Play,
PlayState_RewindPlay,
} PlayState;
typedef struct _thr_data
{
HANDLE Thr;
WAVE_STRUCT ws;
HWAVEOUT wOut;
char *wLoc;
HWND hwndPar;
ULONG ID;
PlayState ps;
CRITICAL_SECTION csps;
HANDLE evPlayStateChange,
evData;
} THREAD_DATA, *PTHREAD_DATA;
bool LoadWaveFile(PWAVE_STRUCT ws, const char *Loc)
{
if (!ws || !Loc)
{
SetLastError(ERROR_INVALID_PARAMETER);
return false;
}
WAVE_FILE_HDR Head = {0};
DWORD Read = 0, i = 0;
PBYTE wPoint = 0;
HANDLE wFile = CreateFile(Loc, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
if (wFile == INVALID_HANDLE_VALUE)
return false;
if (!ReadFile(wFile, &Head, sizeof(Head), &Read, NULL))
{
CloseHandle(wFile);
return false;
}
if (Head.wfhSign[0] != 'R' || Head.wfhSign[1] != 'I' ||
Head.wfhSign[2] != 'F' || Head.wfhSign[3] != 'F')
{
CloseHandle(wFile);
SetLastError(ERROR_NOT_SUPPORTED);
return false;
}
if (Head.wfhFormat[0] != 'W' || Head.wfhFormat[1] != 'A' ||
Head.wfhFormat[2] != 'V' || Head.wfhFormat[3] != 'E')
{
CloseHandle(wFile);
SetLastError(ERROR_NOT_SUPPORTED);
return false;
}
if (Head.wfhAudFormat != WAVE_FORMAT_PCM)
{
CloseHandle(wFile);
SetLastError(ERROR_NOT_SUPPORTED);
return false;
}
Read = 1 + (Head.wfhChnkSize-36) / MAXBUFFSIZE;
ws->Hdrs = (WAVEHDR*)malloc(Read * sizeof(WAVEHDR) +
(Head.wfhChnkSize - 36));
if (!ws->Hdrs)
{
CloseHandle(wFile);
SetLastError(ERROR_OUTOFMEMORY);
return false;
}
ZeroMemory(ws->Hdrs, Read * sizeof(WAVEHDR) + (Head.wfhChnkSize - 36));
ws->FileData = (PBYTE)ws->Hdrs+Read*sizeof(WAVEHDR);
ws->HdrC = Read;
ReadFile(wFile, ws->FileData, Head.wfhChnkSize - 36, &Read, NULL);
if (Read != (Head.wfhChnkSize - 36))
{
free(ws->Hdrs);
CloseHandle(wFile);
free(ws->FileData);
return false;
}
ws->wsFormat.wFormatTag = WAVE_FORMAT_PCM;
ws->wsFormat.nChannels = Head.wfhChannels;
ws->wsFormat.nSamplesPerSec = Head.wfhSampPerSec;
ws->wsFormat.nAvgBytesPerSec = Head.wfhAvgBytesPerSec;
ws->wsFormat.nBlockAlign = Head.wfhBlockAlign;
ws->wsFormat.wBitsPerSample = Head.wfhBitsPerSamp;
ws->wsFormat.cbSize = 0;
CloseHandle(wFile);
wPoint = ws->FileData;
for (i = 0; i < ws->HdrC - 1; ++i)
{
ws->Hdrs[i].lpData = (LPSTR)wPoint;
ws->Hdrs[i].dwBufferLength = MAXBUFFSIZE;
wPoint += MAXBUFFSIZE;
}
ws->Hdrs[ws->HdrC-1].lpData = (LPSTR)wPoint;
ws->Hdrs[ws->HdrC-1].dwBufferLength = (Head.wfhChnkSize-36) % MAXBUFFSIZE;
SetLastError(ERROR_SUCCESS);
return true;
}
void CALLBACK waveyProc(HWAVEOUT hwo, UINT uMsg, DWORD dwInstance,
DWORD Param1, DWORD Param2)
{
if (uMsg == WOM_DONE)
{
PTHREAD_DATA pThrDat = (PTHREAD_DATA)dwInstance;
SetEvent(pThrDat->evData);
}
return;
}
DWORD WINAPI PlayThr(void *param)
{
PTHREAD_DATA pThrDat = (PTHREAD_DATA)param;
MMRESULT mRes = MMSYSERR_NOERROR;
unsigned int Pos = 0;
bool bPaused = false;
if (!LoadWaveFile(&pThrDat->ws, pThrDat->wLoc))
{
PostMessage(pThrDat->hwndPar, WM_PLYBCKERR, 0, GetLastError());
return 0;
}
mRes = waveOutOpen(&pThrDat->wOut, WAVE_MAPPER, &pThrDat->ws.wsFormat,
(DWORD_PTR)waveyProc, (DWORD_PTR)pThrDat,
CALLBACK_FUNCTION);
if (mRes != MMSYSERR_NOERROR)
{
PostMessage(pThrDat->hwndPar, WM_PLYBCKERR, 0, mRes);
free(pThrDat->ws.Hdrs);
return 0;
}
for (Pos = 0; Pos < pThrDat->ws.HdrC; ++Pos)
waveOutPrepareHeader(pThrDat->wOut,
&pThrDat->ws.Hdrs[Pos],
sizeof(WAVEHDR));
# define numWaitObjects 2
const HANDLE waitObjects[numWaitObjects] =
{
pThrDat->evPlayStateChange,
pThrDat->evData,
};
enum
{
WO_PLAYSTATE_CHANGE = WAIT_OBJECT_0 + 0,
WO_MORE_DATA = WAIT_OBJECT_0 + 1,
};
Pos = 0;
SetEvent(pThrDat->evData);
for (;;)
{
DWORD status = WaitForMultipleObjects(numWaitObjects, waitObjects,
FALSE, INFINITE);
if (status == WO_MORE_DATA)
{
if (!bPaused && Pos < pThrDat->ws.HdrC)
{
waveOutWrite(pThrDat->wOut,
&pThrDat->ws.Hdrs[Pos],
sizeof(WAVEHDR));
++Pos;
}
}
else if (status == WO_PLAYSTATE_CHANGE)
{
PlayState nextState;
EnterCriticalSection(&pThrDat->csps);
nextState = pThrDat->ps;
LeaveCriticalSection(&pThrDat->csps);
if (nextState == PlayState_Exit)
{
break;
}
else if (nextState == PlayState_Pause)
{
bPaused = true;
waveOutPause(pThrDat->wOut);
}
else if (nextState == PlayState_Play ||
nextState == PlayState_RewindPlay)
{
if (bPaused)
waveOutRestart(pThrDat->wOut);
bPaused = false;
if (nextState == PlayState_RewindPlay)
{
waveOutReset(pThrDat->wOut);
Pos = 0;
SetEvent(pThrDat->evData);
}
}
}
}
for (Pos = 0; Pos < pThrDat->ws.HdrC; ++Pos)
waveOutUnprepareHeader(pThrDat->wOut,
&pThrDat->ws.Hdrs[Pos],
sizeof(WAVEHDR));
waveOutClose(pThrDat->wOut);
free(pThrDat->ws.Hdrs);
PostMessage(pThrDat->hwndPar, WM_ENDPLAY, 0, 0);
return 0;
}
THREAD_DATA ThrData = {0};
char szClassName[ ] = "WavePlayer";
char *wLoc = NULL;
int WINAPI WinMain (HINSTANCE hThisInstance,
HINSTANCE hPrevInstance,
LPSTR lpszArgument,
int nFunsterStil)
{
HWND hwnd;
MSG messages;
WNDCLASSEX wincl;
if (!lstrlen(lpszArgument))
{
ErrorBox(NULL,"No sound file specified!");
return 0;
}
else
{
wLoc = lpszArgument;
}
wincl.hInstance = hThisInstance;
wincl.lpszClassName = szClassName;
wincl.lpfnWndProc = WindowProcedure;
wincl.style = CS_DBLCLKS;
wincl.cbSize = sizeof(WNDCLASSEX);
wincl.hIcon = LoadIcon (NULL,IDI_APPLICATION);
wincl.hIconSm = LoadIcon (NULL,IDI_APPLICATION);
wincl.hCursor = LoadCursor (NULL,IDC_ARROW);
wincl.lpszMenuName = NULL;
wincl.cbClsExtra = 0;
wincl.cbWndExtra = 0;
wincl.hbrBackground = (HBRUSH)COLOR_BACKGROUND;
if (!RegisterClassEx(&wincl))
return 0;
hwnd = CreateWindowEx(0, szClassName, "Wavey",
WS_OVERLAPPEDWINDOW|WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
400, 400,
NULL, NULL, hThisInstance, NULL);
while (GetMessage(&messages, NULL, 0, 0))
{
TranslateMessage(&messages);
DispatchMessage(&messages);
}
return messages.wParam;
}
/* This function is called by the Windows function DispatchMessage() */
LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
ThrData.hwndPar = hwnd;
ThrData.wLoc = wLoc;
ThrData.ps = PlayState_Play;
InitializeCriticalSection(&ThrData.csps);
ThrData.evPlayStateChange = CreateEvent(0, FALSE, FALSE, 0);
ThrData.evData = CreateEvent(0, FALSE, FALSE, 0);
ThrData.Thr = CreateThread(NULL, 0, PlayThr, &ThrData, 0, &ThrData.ID);
if (!ThrData.Thr)
{
ErrorBox(hwnd,"Unable to initialize playback thread!");
PostQuitMessage(0);
}
break;
case WM_LBUTTONDOWN:
EnterCriticalSection(&ThrData.csps);
if (ThrData.ps != PlayState_Pause)
ThrData.ps = PlayState_Pause;
else
ThrData.ps = PlayState_Play;
LeaveCriticalSection(&ThrData.csps);
SetEvent(ThrData.evPlayStateChange);
break;
case WM_RBUTTONDOWN:
EnterCriticalSection(&ThrData.csps);
ThrData.ps = PlayState_RewindPlay;
LeaveCriticalSection(&ThrData.csps);
SetEvent(ThrData.evPlayStateChange);
break;
case WM_PLYBCKERR:
{
char err[512];
sprintf(err, "Playback Error:\n(0x%X)", lParam);
ErrorBox(hwnd, err);
CloseHandle(ThrData.Thr);
break;
}
case WM_ENDPLAY:
CloseWindow(hwnd);
break;
case WM_DESTROY:
EnterCriticalSection(&ThrData.csps);
ThrData.ps = PlayState_Exit;
LeaveCriticalSection(&ThrData.csps);
SetEvent(ThrData.evPlayStateChange);
WaitForSingleObject(ThrData.Thr, INFINITE);
CloseHandle(ThrData.Thr);
CloseHandle(ThrData.evData);
CloseHandle(ThrData.evPlayStateChange);
DeleteCriticalSection(&ThrData.csps);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return ERROR_SUCCESS;
}
Wait Functions (Windows)
Event Objects (Windows)
gg
-
I tested and compiled the code. It didn't make any difference. the sound was still choppy. Here's the example code mine was based off that plays with no sound glitches:
Code:
#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
// some good values for block size and count
#define BLOCK_SIZE 8192
#define BLOCK_COUNT 5
// module level variables
CRITICAL_SECTION waveCriticalSection;
WAVEHDR* waveBlocks;
volatile int waveFreeBlockCount;
int waveCurrentBlock;
// function prototypes
void CALLBACK waveOutProc(HWAVEOUT hWaveOut,UINT uMsg,DWORD dwInstance,DWORD dwParam1,DWORD dwParam2){
// pointer to free block counter
int *freeBlockCounter = (int*)dwInstance;
// ignore calls that occur due to openining and closing the device.
if(uMsg!=WOM_DONE) return;
EnterCriticalSection(&waveCriticalSection);
(*freeBlockCounter)++;
LeaveCriticalSection(&waveCriticalSection);
return;
}
WAVEHDR* allocateBlocks(int size,int count){
char *buffer;
int i;
WAVEHDR* blocks;
DWORD totalBufferSize = (size+sizeof(WAVEHDR))*count;
// allocate memory for the entire set in one go
buffer = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,totalBufferSize);
if(!buffer){
printf("Memory allocation error\n");
ExitProcess(0);
}
// and set up the pointers to each bit
blocks = (WAVEHDR*)buffer;
buffer += sizeof(WAVEHDR)*count;
for(i=0;i<count;i++){
blocks[i].dwBufferLength = size;
blocks[i].lpData = buffer;
buffer += size;
}
return blocks;
}
void freeBlocks(WAVEHDR* blockArray){
// and this is why allocateBlocks works the way it does
HeapFree(GetProcessHeap(),0,blockArray);
return;
}
void writeAudio(HWAVEOUT hWaveOut,char *data,int size){
WAVEHDR* current;
int remain;
current = &waveBlocks[waveCurrentBlock];
while(size>0){
// first make sure the header we're going to use is unprepared
if(current->dwFlags & WHDR_PREPARED) waveOutUnprepareHeader(hWaveOut,current,sizeof(WAVEHDR));
if(size < (int)(BLOCK_SIZE - current->dwUser)){
memcpy(current->lpData + current->dwUser,data,size);
current->dwUser += size;
break;
}
remain = BLOCK_SIZE-current->dwUser;
memcpy(current->lpData+current->dwUser,data,remain);
size -= remain;
data += remain;
current->dwBufferLength = BLOCK_SIZE;
waveOutPrepareHeader(hWaveOut,current,sizeof(WAVEHDR));
waveOutWrite(hWaveOut,current,sizeof(WAVEHDR));
EnterCriticalSection(&waveCriticalSection);
waveFreeBlockCount--;
LeaveCriticalSection(&waveCriticalSection);
// wait for a block to become free
while(!waveFreeBlockCount) Sleep(10);
// point to the next block
waveCurrentBlock++;
waveCurrentBlock %= BLOCK_COUNT;
current = &waveBlocks[waveCurrentBlock];
current->dwUser = 0;
}
return;
}
int main(int argc, char* argv[]){
HWAVEOUT hWaveOut; // device handle
HANDLE hFile; // file handle
WAVEFORMATEX wfx; // look this up in your documentation
char buffer[1024]; // intermediate buffer for reading
int i;
//quick argument check
if(argc != 2) {
printf("usage: %s <filename>\n",argv[0]);
return 0;
}
// initialise the module variables
waveBlocks = allocateBlocks(BLOCK_SIZE, BLOCK_COUNT);
waveFreeBlockCount = BLOCK_COUNT;
waveCurrentBlock = 0;
InitializeCriticalSection(&waveCriticalSection);
// try and open the file
hFile = CreateFile(argv[1],GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL);
if(hFile == INVALID_HANDLE_VALUE){
printf("%s: unable to open file '%s'\n",argv[0],argv[1]);
return 0;
}
// set up the WAVEFORMATEX structure.
wfx.nSamplesPerSec = 44100; // sample rate
wfx.wBitsPerSample = 16; // sample size
wfx.nChannels= 2; // channel
wfx.cbSize = 0; // size of _extra_ info
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nBlockAlign = (wfx.wBitsPerSample * wfx.nChannels) >> 3;
wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec;
/*
* try to open the default wave device. WAVE_MAPPER is
* a constant defined in mmsystem.h, it always points to the
* default wave device on the system (some people have 2 or
* more sound cards).
*/
if(waveOutOpen(&hWaveOut,WAVE_MAPPER,&wfx,(DWORD_PTR)waveOutProc,(DWORD_PTR)&waveFreeBlockCount,CALLBACK_FUNCTION) != MMSYSERR_NOERROR){
printf("%s: unable to open wave mapper device\n",argv[0]);
return 0;
}
// playback loop
while(1){
DWORD readBytes;
if(!ReadFile(hFile,buffer,sizeof(buffer),&readBytes,NULL)) break;
if(!readBytes) break;
if(readBytes < sizeof(buffer)){
printf("at end of buffer\n");
memset(buffer+readBytes,0,sizeof(buffer)-readBytes);
printf("after memcpy\n");
}
writeAudio(hWaveOut,buffer,sizeof(buffer));
}
// wait for all blocks to complete
while(waveFreeBlockCount < BLOCK_COUNT) Sleep(10);
// unprepare any blocks that are still prepared
for(i=0;i<waveFreeBlockCount;i++) if(waveBlocks[i].dwFlags & WHDR_PREPARED) waveOutUnprepareHeader(hWaveOut,&waveBlocks[i],sizeof(WAVEHDR));
DeleteCriticalSection(&waveCriticalSection);
freeBlocks(waveBlocks);
waveOutClose(hWaveOut);
CloseHandle(hFile);
return 0;
}
This example is a console app. It works but our other codes' sound has drop-outs.
-
Ah - well they are trying to call waveOutWrite() multiple times in order to keep up to BLOCK_COUNT blocks queued up in the system. Your code and mine only kept one block queued.
Here is a modification to my version to achieve the same effect:
Code:
...
typedef struct _thr_data
{
HANDLE Thr;
WAVE_STRUCT ws;
HWAVEOUT wOut;
char *wLoc;
HWND hwndPar;
ULONG ID;
PlayState ps;
CRITICAL_SECTION csps;
HANDLE evPlayStateChange,
evData;
LONG queuedBlocks;
} THREAD_DATA, *PTHREAD_DATA;
#define MAX_QUEUED_BLOCKS 5
...
void CALLBACK waveyProc(HWAVEOUT hwo, UINT uMsg, DWORD dwInstance,
DWORD Param1, DWORD Param2)
{
if (uMsg == WOM_DONE)
{
PTHREAD_DATA pThrDat = (PTHREAD_DATA)dwInstance;
InterlockedDecrement(&pThrDat->queuedBlocks);
SetEvent(pThrDat->evData);
}
return;
}
...
DWORD WINAPI PlayThr(void *param)
{
...
if (status == WO_MORE_DATA)
{
if (!bPaused)
{
LONG queuedBlocks = InterlockedExchangeAdd(&pThrDat->queuedBlocks, 0);
while (Pos < pThrDat->ws.HdrC &&
queuedBlocks <= MAX_QUEUED_BLOCKS)
{
waveOutWrite(pThrDat->wOut,
&pThrDat->ws.Hdrs[Pos],
sizeof(WAVEHDR));
++Pos;
++queuedBlocks;
InterlockedIncrement(&pThrDat->queuedBlocks);
}
}
}
...
if (nextState == PlayState_RewindPlay)
{
waveOutReset(pThrDat->wOut);
Pos = 0;
InterlockedExchange(&pThrDat->queuedBlocks, 0);
SetEvent(pThrDat->evData);
}
...
The Interlocked functions provide safe mult-threaded access without having to use a critical section. Modified source attached.
gg
-
You were right the code works. Thanks.