0
votes

I recently managed to get past the errors of using SDL for sound.

Now that it's running and I'm not running into errors, my program is only playing beeping noises instead of the file I've provided.

I want the program to play the .wav file I'm passing to the SDL_LoadWAV.

I've tried with two different .wav files of different length and size, and checked the header files to find comments and tips on what format is required for the SDL to play the .wav file, haven't gotten anywhere with either of it.

The myAudioCallback function is responsible for handling the SDL callback.

void myAudioCallback(void* userdata, Uint8* stream, int len) 
{
    AudioData* audio = (AudioData*)userdata;

if (audio->length == 0)
    return;

Uint32 length = (Uint32)len;
length = (length > audio->length ? audio->length : length); // if length is more than the audio length, then set length to be the audio.length, if not, set it to be the length passed to the function

std::cout << "Audio Length " << audio->length << std::endl;
std::cout << "Audio Position " << audio->position << std::endl;

SDL_memcpy(stream, audio->position, length); // audio callback is called by SDL, this ensures that the stream and data that is sent, is copied over to our struct, so we can use it and manipulate it

audio->position += length;
audio->length -= length; 
}

My loadAudio function is responsible for loading the audio file and saving information about the audio file to the various variables I've declared in the .h (see further down for my .h)

void mainEngineCW4::loadAudio() // this function is for the sole purpose of loading the .wav file 
{
    SDL_Init(SDL_INIT_AUDIO); // loads the SDL to initialise audio

char* audioFile = "backgroundmusic.wav"; // a char pointer for the file path

// LoadWAV loads the wav file, and by putting it in an if statement like this, we can simutaneously check if the result is null, meaning an error occured while loading it.
if (SDL_LoadWAV(audioFile, &wavSpec, &wavStart, &wavLength) == NULL)
    std::cerr << "Error: file could not be loaded as an audio file." << std::endl;
else
    std::cout << audioFile << " loaded" << std::endl; 
}

The playAudio function is responsible for loading the audio device and playing the audio through the Audio device

void mainEngineCW4::playAudio() // this function is for loading an audio device, and playing the audio through that device 
{
audio.position = wavStart; // define where we start in the audio file
audio.length = wavLength; // define the length of the audio file

wavSpec.callback = myAudioCallback; // the callback variable needs a function that its going to run to be able to call back the audio that is played. assigning the function name to the variable allows it to call that function when needed
wavSpec.userdata = &audio; // the userdata is the audio itself

audioDevice = SDL_OpenAudioDevice(NULL, 0, &wavSpec, NULL, SDL_AUDIO_ALLOW_ANY_CHANGE); // opens the audio device, also having it play the audio information found at memory address for wavspec
if (audioDevice == 0) {
    std::cerr << SDL_GetError() << std::endl;
    return; }

SDL_PauseAudioDevice(audioDevice, 0); // mildly confused by why they decided to call the function for starting to play audio for "PauseAudioDevice" but yeah. this plays audio. 
}

Here's my .h. I've defined myAudioCallback outside of the class, since SDL doesn't like the additional hidden parameter of a member function

struct AudioData
{
    Uint8* position;
    Uint32 length;
};

void myAudioCallback(void* userdata, Uint8* stream, int len);

class mainEngineCW4 :
    public BaseEngine
{

public:

    void loadAudio();
    void playAudio();
    void endAudio();

private:
    // variables and pointers for audio information
    AudioData audio;
    SDL_AudioSpec wavSpec;
    SDL_AudioDeviceID audioDevice;
    Uint8* wavStart;
    Uint32 wavLength;
};

I've removed the other functions and variables that are irrelevant to the issue I'm having

My problem is that I want my program to play the audio file I pass in, not just beeping noises. Any help is greatly appreciated

EDIT: I realised I'm crap at providing info and explanation to things, so I edited in more information, explanation and the header file. If there is anything else I can provide, please let me know

1
Can you get a simple example of audio with SDL from the docs or the internets (say, this one) working on your machine?metal
Apologies for the late reply, that was the tutorial I was mainly followingMarkus Andhøy

1 Answers

1
votes

With the help of a friend, I managed to fix the issue. It seems like SDL didn't like it when I passed

SDL_OpenAudioDevice(NULL, 0, &wavSpec, NULL, SDL_AUDIO_ALLOW_ANY_CHANGE);

so instead, I passed

SDL_OpenAudioDevice(NULL, 0, &wavSpec, NULL, 0);

instead. This made the file play nicely, as well as having additional variables for the length and position of the audio file.

Another issue I ran into even after I got the file playing, was that the beeping was still playing with the audio file. I wasn't able to fix this directly myself, instead, when I cleaned the solution the day after, the beeping was gone, and the only thing playing was the audio file.

I've attached the code that works below.

In addition to the struct I created in the .h

struct AudioData
{
    Uint8* position;
    Uint32 length;
};

Defining the audio_position and audio_length as a global variable also helped in copying over information in the audio callback function.

static Uint8* audio_position;
static Uint32 audio_length;
void myAudioCallback(void* userdata, Uint8* stream, int len)
{
    if (audio_length == 0)
        return;

    len = (len > audio_length ? audio_length : len); // if length is more than the audio length, then set length to be the audio.length, if not, set it to be the length passed to the function

    SDL_memcpy(stream, audio_position, len); // audio callback is called by SDL, this ensures that the stream and data that is sent, is copied over to our struct, so we can use it and manipulate it

    audio_position += len;
    audio_length -= len;
}

For the load audio, I made sure that I actually load all the information that would be considered "loading", including storing the AudioSpec callback function, and setting the length and position of the audio file.

void mainEngineCW4::loadAudio() // this function is for the sole purpose of loading the .wav file
{
    if (SDL_Init(SDL_INIT_AUDIO) < 0 || audioPlaying == true) // loads the SDL to initialise audio
        return;

    char* filePath = "backgroundmusic.wav"; // a char pointer for the file path

    // LoadWAV loads the wav file, and by putting it in an if statement like this, we can simutaneously check if the result is null, meaning an error occured while loading it.
    if (SDL_LoadWAV(filePath, &desiredSpec, &wavStart, &wavLength) == NULL)
        std::cerr << "Error: file could not be loaded as an audio file." << std::endl;
    else
        std::cout << filePath << " loaded" << std::endl;

    desiredSpec.callback = myAudioCallback; // the callback variable needs a function that its going to run to be able to call back the audio that is played. assigning the function name to the variable allows it to call that function when needed
    desiredSpec.userdata = &audioInfo; // the userdata is the audio itself

    audio_position = wavStart; // define where we start in the audio file
    audio_length = wavLength; // define the length of the audio file
}

I also added a boolean to the class, so that when this returns true, it means that the audio has already been playing or has already been loaded, as to ensure SDL won't play the same thing simultaneously.

void mainEngineCW4::playAudio() // this function is for loading an audio device, and playing the audio through that device
{
    if (audioPlaying == true)
        return;

    audioDevice = SDL_OpenAudioDevice(NULL, 0, &desiredSpec, NULL, 0); // opens the audio device, also having it play the audio information found at memory address for wavspec

    if (audioDevice == 0)
    {
        std::cerr << SDL_GetError() << std::endl;
        return;
    }

    SDL_PauseAudioDevice(audioDevice, 0); // mildly confused by why they decided to call the function for starting to play audio for "PauseAudioDevice" but yeah. this plays audio.
    audioPlaying = true;
}

void mainEngineCW4::endAudio()
{
    SDL_CloseAudioDevice(audioDevice);
    SDL_FreeWAV(wavStart);
    audioPlaying = false;
    }