Choppy Audio with QueueAudio

I’m simply trying to play a sine wave, but all I hear is choppy sound, much like a ‘machine gun’ effect. Extract of relevant code (note I’m running with VSYNC renderer)

SDL_AudioSpec actual_audio_spec = {0};
  SDL_AudioSpec desired_audio_spec = {
    .freq = 48000,
    .format = AUDIO_S16LSB,
    .channels = 2,
    .samples = (48000 * (sizeof(i16) * 2)) / best_display_mode.refresh_rate
  };
  SDL_AudioDeviceID audio_dev = SDL_OpenAudioDevice(NULL, 0, &desired_audio_spec, &actual_audio_spec, 0);
  if (audio_dev == 0) {
    // TODO(Ryan): Logging
    return EXIT_FAILURE;
  }
  if (actual_audio_spec.format != desired_audio_spec.format) {
    // TODO(Ryan): Logging
    return EXIT_FAILURE;
  }

  u32 num_samples_to_fill_per_frame = actual_audio_spec.freq / 15;
  u32 target_bytes_to_write = num_samples_to_fill_per_frame * sizeof(i16) * 2;
  i16 audio_data[num_samples_to_fill_per_frame * 2];
  u32 audio_wave_pitch = 440; 
  r32 audio_wave_volume = 3000; 
  u32 audio_wave_period = actual_audio_spec.freq / audio_wave_pitch;
  r32 tsine = 0.0f;

  for (u32 sample_index = 0; sample_index < num_samples_to_fill_per_frame; ++sample_index) {
    r32 sine_value = sinf(tsine);
    i16 sample_value = (i16)(sine_value * audio_wave_volume);
    audio_data[sample_index++] = sample_value;
    audio_data[sample_index] = sample_value;

    tsine += 2.0f * M_PI * 1.0f / (r32)audio_wave_period;
  }
  SDL_QueueAudio(audio_dev, audio_data, target_bytes_to_write - SDL_GetQueuedAudioSize(audio_dev));
  SDL_PauseAudioDevice(audio_dev, 0);

  bool want_to_run = true;
  while (want_to_run) {
    SDL_Event event = {0};  
    while (SDL_PollEvent(&event)) {
      if (event.type == SDL_QUIT) {
        want_to_run = false; 
      }
    }

    for (u32 sample_index = 0; sample_index < num_samples_to_fill_per_frame; ++sample_index) {
      r32 sine_value = sinf(tsine);
      i16 sample_value = (i16)(sine_value * audio_wave_volume);
      audio_data[sample_index++] = sample_value;
      audio_data[sample_index] = sample_value;

      tsine += 2.0f * M_PI * 1.0f / (r32)audio_wave_period;
    }
    SDL_QueueAudio(audio_dev, audio_data, target_bytes_to_write - SDL_GetQueuedAudioSize(audio_dev));

    SDL_RenderPresent();
}

So a choppy sound would imply that you aren’t filling the queue quickly enough, i.e. it’s playing the samples then it runs out and you are getting silence, thus the stop-start effect.

If you are running with v-sync then you’ll only be queuing audio every 16.67ms (for 60fps) or 20 ms (for 50fps) so maybe that’s not quick enough?

So if you need to run the audio queuing in the main thread (it works perfectly fine in its own thread, in my experience) then you would need to queue up enough to cover the time between frames.

It seems to me that you are not handling the ‘cumulative phase’ tsine correctly. You are incrementing it for every sample generated but you are not queuing every sample to the audio device (you are queuing only target_bytes_to_write - SDL_GetQueuedAudioSize(audio_dev) bytes). As a result there may be a ‘phase jump’ between one block of queued audio and the next.

The way I would probably do it is to check whether there is already enough queued audio for the next ‘frame’ and if so not generate or queue any more. If there is insufficient queued audio for the next frame generate and queue a new block, but always queue the entire block so that the cumulative phase keeps in step.

Thank you both for your answers. A major cause of my issues was I was disregarding the fact I was using stereo sound, filling out only half of the samples:

for (u32 sample_index = 0; sample_index < num_samples_to_fill_per_frame; ++sample_index) { ... }
// CORRECTED TO
for (u32 sample_index = 0; sample_index < num_samples_to_fill_per_frame * 2; ++sample_index) { ... }

@oviano
For my current refresh rate of 60, the line: u32 num_samples_to_fill_per_frame = actual_audio_spec.freq / 15; will generate 4 frames worth of audio each block. I believe this is enough.

@rtrussell
Thank you, I see what you mean! I have changed the loop accordingly:

if (SDL_GetQueuedAudioSize(audio_dev) < target_bytes_to_write) {
  for (u32 sample_index = 0; sample_index < num_samples_to_fill_per_frame * 2; ++sample_index) { ... }
  SDL_QueueAudio(audio_dev, audio_data, target_bytes_to_write);
}

My sound is now smooth. However, I do notice that leaving it running about every 15 seconds the pitch of the sound changes.