diff options
author | Chris Robinson <[email protected]> | 2012-01-01 12:36:41 -0800 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2012-01-01 12:36:41 -0800 |
commit | 466cac328f15f3538c895208bfe7ea14f3923ffc (patch) | |
tree | 4111b4502b76146cad46bdf2634b0f11e6ee68a8 /examples/alstream.c | |
parent | 06caac261976e42f08faeebe4ec921de3200b85f (diff) |
Add an example program that streams audio using ffmpeg
Diffstat (limited to 'examples/alstream.c')
-rw-r--r-- | examples/alstream.c | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/examples/alstream.c b/examples/alstream.c new file mode 100644 index 00000000..f25ca0be --- /dev/null +++ b/examples/alstream.c @@ -0,0 +1,331 @@ +/* + * OpenAL Audio Stream Example + * + * Copyright (c) 2011 by Chris Robinson <[email protected]> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* This file contains a relatively simple streaming audio player. */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> +#include <assert.h> + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" + +#include "alhelpers.h" +#include "alffmpeg.h" + + +/* Define the number of buffers and buffer size (in samples) to use. 4 buffers + * with 8192 samples each gives a nice per-chunk size, and lets the queue last + * for almost 3/4ths of a second for a 44.1khz stream. */ +#define NUM_BUFFERS 4 +#define BUFFER_SIZE 8192 + +typedef struct StreamPlayer { + /* These are the buffers and source to play out through OpenAL with */ + ALuint buffers[NUM_BUFFERS]; + ALuint source; + + /* Handles for the audio stream */ + FilePtr file; + StreamPtr stream; + + /* A temporary data buffer for readAVAudioData to write to and pass to + * OpenAL with */ + ALbyte *data; + ALsizei datasize; + + /* The format of the output stream */ + ALenum format; + ALenum channels; + ALenum type; + ALuint rate; +} StreamPlayer; + +static StreamPlayer *NewPlayer(void); +static void DeletePlayer(StreamPlayer *player); +static int OpenPlayerFile(StreamPlayer *player, const char *filename); +static void ClosePlayerFile(StreamPlayer *player); +static int StartPlayer(StreamPlayer *player); +static int UpdatePlayer(StreamPlayer *player); + +/* Creates a new player object, and allocates the needed OpenAL source and + * buffer objects. Error checking is simplified for the purposes of this + * example, and will cause an abort if needed. */ +static StreamPlayer *NewPlayer(void) +{ + StreamPlayer *player; + + player = malloc(sizeof(*player)); + assert(player != NULL); + + memset(player, 0, sizeof(*player)); + + /* Generate the buffers and source */ + alGenBuffers(NUM_BUFFERS, player->buffers); + assert(alGetError() == AL_NO_ERROR && "Could not create buffers"); + + alGenSources(1, &player->source); + assert(alGetError() == AL_NO_ERROR && "Could not create source"); + + /* Set parameters so mono sources play out the front-center speaker and + * won't distance attenuate. */ + alSource3i(player->source, AL_POSITION, 0, 0, -1); + alSourcei(player->source, AL_SOURCE_RELATIVE, AL_TRUE); + alSourcei(player->source, AL_ROLLOFF_FACTOR, 0); + assert(alGetError() == AL_NO_ERROR && "Could not set source parameters"); + + return player; +} + +/* Destroys a player object, deleting the source and buffers. No error handling + * since these calls shouldn't fail with a properly-made player object. */ +static void DeletePlayer(StreamPlayer *player) +{ + ClosePlayerFile(player); + + alDeleteSources(1, &player->source); + alDeleteBuffers(NUM_BUFFERS, player->buffers); + if(alGetError() != AL_NO_ERROR) + fprintf(stderr, "Failed to delete object IDs\n"); + + memset(player, 0, sizeof(*player)); + free(player); +} + + +/* Opens the first audio stream of the named file. If a file is already open, + * it will be closed first. */ +static int OpenPlayerFile(StreamPlayer *player, const char *filename) +{ + ClosePlayerFile(player); + + /* Open the file and get the first stream from it */ + player->file = openAVFile(filename); + player->stream = getAVAudioStream(player->file, 0); + if(!player->stream) + { + fprintf(stderr, "Could not open audio in %s\n", filename); + goto error; + } + + /* Get the stream format, and figure out the OpenAL format */ + if(getAVAudioInfo(player->stream, &player->rate, &player->channels, + &player->type) != 0) + { + fprintf(stderr, "Error getting audio info for %s\n", filename); + goto error; + } + + player->format = GetFormat(player->channels, player->type); + if(player->format == 0) + { + fprintf(stderr, "Unsupported format (%s, %s) for %s\n", + ChannelsName(player->channels), TypeName(player->type), + filename); + goto error; + } + + /* Allocate enough space for the temp buffer, given the format */ + player->datasize = FramesToBytes(BUFFER_SIZE, player->channels, + player->type); + player->data = malloc(player->datasize); + if(player->data == NULL) + { + fprintf(stderr, "Error allocating %d bytes\n", player->datasize); + goto error; + } + + return 1; + +error: + closeAVFile(player->file); + player->file = NULL; + player->stream = NULL; + player->datasize = 0; + + return 0; +} + +/* Closes the audio file stream */ +static void ClosePlayerFile(StreamPlayer *player) +{ + closeAVFile(player->file); + player->file = NULL; + player->stream = NULL; + + free(player->data); + player->data = NULL; + player->datasize = 0; +} + + +/* Prebuffers some audio from the file, and starts playing the source */ +static int StartPlayer(StreamPlayer *player) +{ + size_t i, got; + + /* Rewind the source position and clear the buffer queue */ + alSourceRewind(player->source); + alSourcei(player->source, AL_BUFFER, 0); + + /* Fill the buffer queue */ + for(i = 0;i < NUM_BUFFERS;i++) + { + /* Get some data to give it to the buffer */ + got = readAVAudioData(player->stream, player->data, player->datasize); + if(got == 0) break; + + alBufferData(player->buffers[i], player->format, player->data, got, + player->rate); + } + if(alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "Error buffering for playback\n"); + return 0; + } + + /* Now queue and start playback! */ + alSourceQueueBuffers(player->source, i, player->buffers); + alSourcePlay(player->source); + if(alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "Error starting playback\n"); + return 0; + } + + return 1; +} + +static int UpdatePlayer(StreamPlayer *player) +{ + ALint processed, state; + + /* Get relevant source info */ + alGetSourcei(player->source, AL_SOURCE_STATE, &state); + alGetSourcei(player->source, AL_BUFFERS_PROCESSED, &processed); + if(alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "Error checking source state\n"); + return 0; + } + + /* Unqueue and handle each processed buffer */ + while(processed > 0) + { + ALuint bufid; + size_t got; + + alSourceUnqueueBuffers(player->source, 1, &bufid); + processed--; + + /* Read the next chunk of data, refill the buffer, and queue it + * back on the source */ + got = readAVAudioData(player->stream, player->data, player->datasize); + if(got > 0) + { + alBufferData(bufid, player->format, player->data, got, + player->rate); + alSourceQueueBuffers(player->source, 1, &bufid); + } + if(alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "Error buffering data\n"); + return 0; + } + } + + /* Make sure the source hasn't underrun */ + if(state != AL_PLAYING && state != AL_PAUSED) + { + ALint queued; + + /* If no buffers are queued, playback is finished */ + alGetSourcei(player->source, AL_BUFFERS_QUEUED, &queued); + if(queued == 0) + return 0; + + alSourcePlay(player->source); + if(alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "Error restarting playback\n"); + return 0; + } + } + + return 1; +} + + +int main(int argc, char **argv) +{ + StreamPlayer *player; + int i; + + /* Print out usage if no file was specified */ + if(argc < 2) + { + fprintf(stderr, "Usage: %s <filenames...>\n", argv[0]); + return 1; + } + + if(InitAL() != 0) + return 1; + + player = NewPlayer(); + + /* Play each file listed on the command line */ + for(i = 1;i < argc;i++) + { + if(!OpenPlayerFile(player, argv[i])) + continue; + + fprintf(stderr, "Playing %s (%s, %s, %dhz)\n", argv[i], + TypeName(player->type), ChannelsName(player->channels), + player->rate); + + if(!StartPlayer(player)) + { + ClosePlayerFile(player); + continue; + } + + while(UpdatePlayer(player)) + Sleep(10); + + /* All done with this file. Close it and go to the next */ + ClosePlayerFile(player); + } + fprintf(stderr, "Done.\n"); + + /* All files done. Delete the player, and close OpenAL */ + DeletePlayer(player); + player = NULL; + + CloseAL(); + + return 0; +} |