Search code examples
caudiodynamic-memory-allocationstatic-memory-allocation

Using waveOutWrite with Dynamically Allocated Memory


I have been experimenting with sound using C.

I have found a piece of code that would allow me to generate PCM wave sound and output it through the sound card.

It worked pretty well for what it was.

Here's the code that works: (You need to link winmm for this to work)
(WARNING: It's quite loud.)

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <mmsystem.h>

//#pragma comment(lib, "winmm.lib")

int main()
{
    HWAVEOUT hWaveOut=0;
    WAVEFORMATEX wfx = {WAVE_FORMAT_PCM,1,8000,8000,1,8,0};
    //buffer to hold 1 minute of pcm wave data (8 bit, 8khz sample rate)
    char buffer[8000*60];
    int i;
    for (i=0;i<8000*60;i++){
        //generate pseudo-random sound.
        buffer[i] = (i*5&(i>>7)|i*3&(i*4>>10));
    }
    waveOutOpen(&hWaveOut, WAVE_MAPPER, &wfx, 0, 0, CALLBACK_NULL);
    WAVEHDR header = { buffer, sizeof(buffer), 0, 0, 0, 0, 0, 0 };
    waveOutPrepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
    waveOutWrite(hWaveOut, &header, sizeof(WAVEHDR));
    Sleep(60*1000);
    waveOutUnprepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
    waveOutClose(hWaveOut);
    return 0;
}

However, I wanted to parse and play .wav files using a similar method instead.

I have realised that while the code above works (where the buffer is statically allocated) Yet, when I allocate memory dynamically (using malloc for example), there's no sound output being played. I hear a very hoarse buzzing sound, which indicates that waveOutWrite() function has been executed, but there's no other sound being played.

Here's the example that doesn't work:

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <mmsystem.h>

//#pragma comment(lib, "winmm.lib")

int main()
{
    HWAVEOUT hWaveOut=0;
    WAVEFORMATEX wfx = {WAVE_FORMAT_PCM,1,8000,8000,1,8,0};
    //buffer to hold 1 minute of pcm wave data (8 bit, 8khz sample rate)
    char *buffer = malloc(sizeof(char)*8000*60);
    int i;
    for (i=0;i<8000*60;i++){
        //generate pseudo-random sound.
        buffer[i] = (i*5&(i>>7)|i*3&(i*4>>10));
    }
    waveOutOpen(&hWaveOut, WAVE_MAPPER, &wfx, 0, 0, CALLBACK_NULL);
    WAVEHDR header = { buffer, sizeof(buffer), 0, 0, 0, 0, 0, 0 };
    waveOutPrepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
    waveOutWrite(hWaveOut, &header, sizeof(WAVEHDR));
    Sleep(60*1000);
    waveOutUnprepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
    waveOutClose(hWaveOut);
    return 0;
}

Note: I have heard that there's a way to "lock" part of memory and I could use this to play audio buffer that has been dynamically allocated. If so, how would I do it?

I also have another little annoyance. I found out that if I start the program without headphones, the audio plays through my computer's speakers, which is expected, but when I plug in my headphones, the audio will still keep playing through computer's speakers. How would I adjust the code so that it swaps the playback device to default if the default playback device changes?

Thank you a lot for your time! You people are awesome.

Update:

I found out that Microsoft documentation uses functions GlobalAlloc(), HeapAlloc(), LocalAlloc() and VirtualAlloc() to allocate memory for wave buffers. The code below is taken from this example:

char *buffer = LocalAlloc(LMEM_FIXED,8000*60); //Doesn't work. Still no sound.

This is also taken from another example:

HANDLE hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, 8000*60);
char *buffer = GlobalLock(hData); //Also doesn't work, no sound.

Solution

  • The problem's here:

    WAVEHDR header = { buffer, sizeof(buffer), 0, 0, 0, 0, 0, 0 };
    

    Here, sizeof(buffer) is the size of the pointer to the buffer, because buffer is a char *. In your first example, buffer was an array of char, so sizeof(buffer) returned the size of the buffer.

    You should be providing the allocated size that you computed above. I would use a couple constants or variables to keep track of the size so you don't repeat the calculation. For example:

    //buffer to hold 1 minute of pcm wave data (8 bit, 8khz sample rate)
    const size_t samples_per_second = 8000;
    const size_t seconds = 60;
    const size_t size = samples_per_second * seconds;
    unsigned char *buffer = malloc(size);
    size_t i;
    for (i=0;i<size;i++){
        //generate pseudo-random sound.
        buffer[i] = (i*5&(i>>7)|i*3&(i*4>>10));
    }
    waveOutOpen(&hWaveOut, WAVE_MAPPER, &wfx, 0, 0, CALLBACK_NULL);
    WAVEHDR header = { buffer, size, 0, 0, 0, 0, 0, 0 };
    

    You might want to use unsigned char for your samples. I believe the WAV formats want unsigned values for 8-bit samples. (For other sizes, you want signed samples.)