Thread: Audio Player Thread

  1. #16
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    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

  2. #17
    Registered User
    Join Date
    Nov 2010
    Posts
    13
    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.

  3. #18
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    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

  4. #19
    Registered User
    Join Date
    Nov 2010
    Posts
    13
    You were right the code works. Thanks.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Built simple audio player without gui
    By sauronnikko in forum C++ Programming
    Replies: 6
    Last Post: 02-01-2011, 01:09 AM
  2. Terminating secondary thread from another thread
    By wssoh85 in forum C++ Programming
    Replies: 13
    Last Post: 12-19-2008, 05:14 AM
  3. Thread Prog in C language (seg fault)
    By kumars in forum C Programming
    Replies: 22
    Last Post: 10-09-2008, 01:17 PM
  4. My first python project =)
    By Desolation in forum Tech Board
    Replies: 14
    Last Post: 06-26-2007, 10:52 PM
  5. Calling a Thread with a Function Pointer.
    By ScrollMaster in forum Windows Programming
    Replies: 6
    Last Post: 06-10-2006, 08:56 AM

Tags for this Thread