Created
February 10, 2019 01:15
-
-
Save Philanatidae/ea716a35c981ace60bd640126162661c to your computer and use it in GitHub Desktop.
Example of using stb_vorbis with PortAudio (C++11)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| 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