Skip to content

Instantly share code, notes, and snippets.

@Philanatidae
Created February 10, 2019 01:15
Show Gist options
  • Select an option

  • Save Philanatidae/ea716a35c981ace60bd640126162661c to your computer and use it in GitHub Desktop.

Select an option

Save Philanatidae/ea716a35c981ace60bd640126162661c to your computer and use it in GitHub Desktop.
Example of using stb_vorbis with PortAudio (C++11)
/*
This is a simple example of playing an OGG file/stream with stb_vorbis
and PortAudio. This was my first time experimenting with stb_vorbis and
PortAudio. I wrote up this example if anybody else had trouble integrating
the two libraries.
This example uses C++11 since the project I am integrating PortAudio into is
C++11. I use `unique_ptr` and lambda functions, but they are not requirements.
Since this was a quick example I have very little error checking. If you
are basing an implementation based on this example, I highly recommend
you implement error checking correctly.
*/
#define STB_VORBIS_HEADER_ONLY
#include <stb_vorbis.c> // Depends on stb_vorbis
#include <portaudio.h> // Depends on PortAudio
#include <memory>
#include <cstdlib>
/*
A struct to hold info about the OGG stream
so that we can access it during the PortAudio callback.
*/
struct oggPaData {
stb_vorbis* ogg_stream; // stb_vorbis stream
stb_vorbis_info ogg_info; // Info about the stb_vorbis stream
};
int main(int argc, char** argv) {
// Initialize PortAudio
if(Pa_Initialize() != paNoError) {
exit(EXIT_FAILURE);
}
std::atexit((void(*))Pa_Terminate);
// Load an OGG stream into memory via stb_vorbis. In this example I used
// `stb_vorbis_open_filename`, but the other functions work as well.
int err_vorbis; // Hold error code for stb_vorbis
std::unique_ptr<stb_vorbis, void(*)(stb_vorbis* ptr)> ogg_stream(stb_vorbis_open_filename("path/to/file.ogg"),
[](stb_vorbis* ptr) -> void {
stb_vorbis_close(ptr); // Custom deleter
});
// Create userdata. This data is on the stack. In an actual
// implementation you could hold the PortAudio stream and
// userdata in a parent object so that the userdata does not
// get deallocated.
oggPaData data;
data.ogg_stream = ogg_stream.get();
data.ogg_info = stb_vorbis_get_info(ogg_stream.get());
PaStream* rawStream;// Temporary variable for the PortAudio stream
// Open the PortAudio stream (default)
if(Pa_OpenDefaultStream(&rawStream,
0, // No input channels
data->ogg_info.channels, // Output channels will be the channel count of the OGG stream
paFloat32, // Use floats (0-1) for the buffer
data->ogg_info.sample_rate, // Sample rate will be the sample rate of the OGG stream
paFramesPerBufferUnspecified, // Let PortAudio decide the amount of frames per buffer
// Callback function as a lambda
[](const void* inputBuffer,
void* outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void* userData) -> int {
oggPaData* data = (oggPaData*)userData;
float* out = (float*)outputBuffer;
// stb_vorbis_get_samples_float_interleaved
// PortAudio's buffer has the channel data interleaved. We can pass
// in the framesPerBuffer that PortAudio has decided to use, and
// stb_vorbis will grab that many frames and place them into the buffer.
// stb_vorbis_get_samples_float_interleaved returns the actual number of
// frames that were buffered.
if((unsigned long)stb_vorbis_get_samples_float_interleaved(data->ogg_stream,
data->ogg_info.channels,
out,
framesPerBuffer)
< framesPerBuffer) {
// Signal that the stream is completed if the actual number of frames
// buffered is less than the frames requested. I'm not 100% sure if this
// is the correct way to go about this, another option would be to store
// the total frames buffered and compare that to the total sample size
// of the stream. This method seems to work though.
//
// paComplete signals that the stream was completely buffered. PortAudio
// will continue playback and automatically stop the stream when these
// frames have been played.
return paComplete;
}
return paContinue; // Continue buffering the stream
},
&data) != paNoError) {
exit(EXIT_FAILURE);
}
// Place the PortAudio stream into a unique_ptr.
std::unique_ptr<PaStream, void(*)(PaStream* ptr)> audio_stream(rawStream,
[](PaStream* ptr) -> void {
Pa_CloseStream(ptr); // Custom deleter
});
// Start the stream
if(Pa_StartStream(audio_stream.get()) != paNoError) {
exit(EXIT_FAILURE);
}
// Sleep for the duration of the OGG stream.
// Pa_Sleep is not accurate, but for testing it is good enough.
Pa_Sleep((long)(stb_vorbis_stream_length_in_seconds(ogg_steam.get())*1000));
// Stop the stream (the stream may already be stopped since the callback
// function signaled the end of the stream).
if(Pa_StopStream(audio_stream.get()) != paNoError) {
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment