0
votes

I'm writing a porting of an emulator to SDL. There is a method, called at each frame, that passes a buffer with new audio samples for next frame.

I opened a device with SDL_OpenAudioDevice and at each frame the SDL callback method reproduces samples from audio buffer.

It works but the sound is not perfect, some tic, some metallic noise and so on.

Sound is 16 bit signed.

EDIT: Ok, I found a solution!

With the code of the opening post I was playing samples for next frame at the current frame in real time. It was wrong!

So, I implemented a circular buffer where I put samples for next frame that underlying code passes to me at each (current) frame.

In that buffer there are 2 pointers, one for read point and the other one for write point. SDL calls callback function when on its audio stream there are no more data to play; so when callback function is called I play audio samples from read point on the circular buffer then update the read pointer.

When underlying code gives me audio samples data for next frame I write them in the circular buffer at write point, then update the write pointer.

Read and write pointers are shifted by the amount of samples to be played at each frame.

Code updated, needs some adjustment when samplesPerFrame is not an int but it works ;-)

Circular buffer structure:

typedef struct circularBufferStruct
{
    short *buffer;
    int cells;
    short *readPoint;
    short *writePoint;
} circularBuffer;

This method is called at initialization:

int initialize_audio(int stereo)
{
    if (stereo)
        channel = 2;
    else
        channel = 1;

    // Check if sound is disabled
    if (sampleRate != 0)
    {
        // Initialize SDL Audio
        if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
        {
            SDL_Log("SDL fails to initialize audio subsystem!\n%s", SDL_GetError());
            return 1;
        }

        // Number of samples per frame
        samplesPerFrame = (double)sampleRate / (double)framesPerSecond * channel;

        audioSamplesSize = samplesPerFrame * bytesPerSample; // Bytes
        audioBufferSize = audioSamplesSize * 10; // Bytes

        // Set and clear circular buffer
        audioBuffer.buffer = malloc(audioBufferSize); // Bytes, must be a multiple of audioSamplesSize
        memset(audioBuffer.buffer, 0, audioBufferSize);

        audioBuffer.cells = (audioBufferSize) / sizeof(short); // Cells, not Bytes!
        audioBuffer.readPoint = audioBuffer.buffer;
        audioBuffer.writePoint = audioBuffer.readPoint + (short)samplesPerFrame;
    }
    else
        samplesPerFrame = 0;

    // First frame
    return samplesPerFrame;
}

This is the SDL method callback from want.callback:

void audioCallback(void *userdata, uint8_t *stream, int len)
{
    SDL_memset(stream, 0, len);

    if (audioSamplesSize == 0)
        return;

    if (len > audioSamplesSize)
    {
        len = audioSamplesSize;
    }

    SDL_MixAudioFormat(stream, (const Uint8 *)audioBuffer.readPoint, AUDIO_S16SYS, len, SDL_MIX_MAXVOLUME);
    audioBuffer.readPoint += (short)samplesPerFrame;

    if (audioBuffer.readPoint >= audioBuffer.buffer + audioBuffer.cells)
        audioBuffer.readPoint = audioBuffer.readPoint - audioBuffer.cells;
}

This method is called at each frame (after first pass we require only the amount of samples):

int update_audio(short *buffer)
{
    // Check if sound is disabled
    if (sampleRate != 0)
    {
        memcpy(audioBuffer.writePoint, buffer, audioSamplesSize); // Bytes
        audioBuffer.writePoint += (short)samplesPerFrame; // Cells

        if (audioBuffer.writePoint >= audioBuffer.buffer + audioBuffer.cells)
            audioBuffer.writePoint = audioBuffer.writePoint - audioBuffer.cells;

        if (firstTime)
        {
            // Set required audio specs
            want.freq = sampleRate;
            want.format = AUDIO_S16SYS;
            want.channels = channel;
            want.samples = samplesPerFrame / channel; // total samples divided by channel count
            want.padding = 0;
            want.callback = audioCallback;
            want.userdata = NULL;

            device = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(0, 0), 0, &want, &have, 0);
            SDL_PauseAudioDevice(device, 0);

            firstTime = 0;
        }
    }
    else
        samplesPerFrame = 0;

    // Next frame
    return samplesPerFrame;
}

I expect that this question/answer will be useful for others in the future because I didn't find almost nothing on the net for SDL Audio

1

1 Answers

0
votes

Ok, I found a solution!

With the code of the opening post I was playing samples for next frame at the current frame in real time. It was wrong!

So, I implemented a circular buffer where I put samples for next frame that underlying code passes to me at each (current) frame. From that buffer I read and write in different position, see opening post