# HG changeset patch # User Eric Wing # Date 1288223476 25200 # Node ID a8a8fe3749841f3680afdaecfd3bb2fc7ab972ac # Parent 01e39f9f58d58f11b845c81a0f788b3ff5b166aa Subversion era diff -r 01e39f9f58d5 -r a8a8fe374984 CircularQueue.c --- a/CircularQueue.c Wed Oct 27 16:50:19 2010 -0700 +++ b/CircularQueue.c Wed Oct 27 16:51:16 2010 -0700 @@ -266,6 +266,22 @@ fprintf(stderr, "\n"); } +unsigned int CircularQueueUnsignedInt_ValueAtIndex(CircularQueueUnsignedInt* queue, unsigned int the_index) +{ + unsigned int i; + if(NULL == queue) + { + return 0; + } + if(the_index >= queue->currentSize) + { + return 0; + } + i = (queue->headIndex + the_index) % queue->currentSize; +// fprintf(stderr, "%d\n", queue->internalQueue[i]); + return queue->internalQueue[i]; +} + /* * Implementation for void* version starts here. */ @@ -485,8 +501,6 @@ queue->tailIndex = 0; } -/* Not implemented for void* */ -/* void CircularQueueVoid_Print(CircularQueueVoid* queue) { unsigned int i; @@ -502,11 +516,25 @@ { i=0; } - fprintf(stderr, "%d ", queue->internalQueue[i]); + fprintf(stderr, "%x ", (unsigned int)queue->internalQueue[i]); } fprintf(stderr, "\n"); } -*/ + +void* CircularQueueVoid_ValueAtIndex(CircularQueueVoid* queue, unsigned int the_index) +{ + unsigned int i; + if(NULL == queue) + { + return NULL; + } + if(the_index >= queue->currentSize) + { + return NULL; + } + i = (queue->headIndex + the_index) % queue->currentSize; + // fprintf(stderr, "%d\n", queue->internalQueue[i]); + return queue->internalQueue[i]; +} - diff -r 01e39f9f58d5 -r a8a8fe374984 CircularQueue.h --- a/CircularQueue.h Wed Oct 27 16:50:19 2010 -0700 +++ b/CircularQueue.h Wed Oct 27 16:51:16 2010 -0700 @@ -93,6 +93,14 @@ * @endcode */ +/* This is a trick I picked up from Lua. Doing the typedef separately + * (and I guess before the definition) instead of a single + * entry: typedef struct {...} YourName; seems to allow me + * to use forward declarations. Doing it the other way (like SDL) + * seems to prevent me from using forward declarions as I get conflicting + * definition errors. I don't really understand why though. + */ +typedef struct CircularQueueUnsignedInt CircularQueueUnsignedInt; /** * This is the essentially the CircularQueue object. * This contains all the data associated with a CircularQueue instance. @@ -101,14 +109,14 @@ * other data types. * This should be considered an opaque data type. */ -typedef struct +struct CircularQueueUnsignedInt { unsigned int maxSize; /**< Max size of the queue. */ unsigned int currentSize; /**< Current number of entries in the queue. */ unsigned int headIndex; /**< The index of where the current head is. */ unsigned int tailIndex; /**< The index of where the current tail is. */ unsigned int* internalQueue; /**< The array representing the queue. */ -} CircularQueueUnsignedInt; +}; /** * This creates a new CircularQueue (for unsigned int) instance. @@ -266,7 +274,46 @@ */ void CircularQueueUnsignedInt_Print(CircularQueueUnsignedInt* queue); +/** + * This returns the element located at the specified index, + * where index=0 represents the head/front of the queue. + * + * @param queue The pointer to the CircularQueue instance. + * @param the_index The index of the element you want where 0 represents the + * head/front of the queue and Size-1 is the back. + * + * @return Returns the value located at the index on success, or 0 on failure. + * Be careful to not to confuse an error for a legitmate 0 value. + * Any index from 0 to Size-1 (where Size>0) will be a valid index. + * + * This example traverses through the whole queue and prints out each value. + * @code + * fprintf(stderr, "Queue: "); + * for(i=0;i0) will be a valid index. + * + * This example traverses through the whole queue and prints out each value. + * @code + * fprintf(stderr, "Queue: "); + * for(i=0;i +#endif + /* For malloc, bsearch, qsort */ #include @@ -160,7 +171,7 @@ #endif /************ END REMOVE ME (Don't need anymore) ********/ -static Uint8 ALmixer_Initialized = 0; +static SDL_bool ALmixer_Initialized = 0; /* This should be set correctly by Init */ static Uint32 ALmixer_Frequency_global = ALMIXER_DEFAULT_FREQUENCY; @@ -195,20 +206,65 @@ static const Uint16 SIGN_TYPE_8_BIT_FORMAT = AUDIO_S8; #endif + +/* This can be private instead of being in the header now that I moved + * ALmixer_Data inside here. + */ +typedef struct ALmixer_Buffer_Map ALmixer_Buffer_Map; + + +struct ALmixer_Data +{ + SDL_bool decoded_all; /* dictates different behaviors */ + Sint32 total_time; /* total playing time of sample (msec) */ + + Uint32 in_use; /* needed to prevent sharing for streams */ + SDL_bool eof; /* flag for eof, only used for streams */ + + Uint32 total_bytes; /* For predecoded */ + Uint32 loaded_bytes; /* For predecoded (for seek) */ + + Sound_Sample* sample; /* SDL_Sound provides the data */ + ALuint* buffer; /* array of OpenAL buffers (at least 1 for predecoded) */ + + /* Needed for streamed buffers */ + Uint32 max_queue_buffers; /* Max number of queue buffers */ + Uint32 num_startup_buffers; /* Number of ramp-up buffers */ + Uint32 num_buffers_in_use; /* number of buffers in use */ + + /* This stuff is for streamed buffers that require data access */ + ALmixer_Buffer_Map* buffer_map_list; /* translate ALbuffer to index + and holds pointer to copy of data for + data access */ + ALuint current_buffer; /* The current playing buffer */ + + /* Nvidia distribution refuses to recognize a simple buffer query command + * unlike all other distributions. It's forcing me to redo the code + * to accomodate this Nvidia flaw by making me maintain a "best guess" + * copy of what I think the buffer queue state looks like. + * A circular queue would a helpful data structure for this task, + * but I wanted to avoid making an additional header requirement, + * so I'm making it a void* + */ + void* circular_buffer_queue; + + +}; + static struct ALmixer_Channel { - Uint8 channel_in_use; - Uint8 callback_update; /* For streaming determination */ - Uint8 needs_stream; /* For streaming determination */ - Uint8 halted; - Uint8 paused; + SDL_bool channel_in_use; + SDL_bool callback_update; /* For streaming determination */ + SDL_bool needs_stream; /* For streaming determination */ + SDL_bool halted; + SDL_bool paused; ALuint alsource; ALmixer_Data* almixer_data; Sint32 loops; Sint32 expire_ticks; Uint32 start_time; - Uint8 fade_enabled; + SDL_bool fade_enabled; Uint32 fade_expire_ticks; Uint32 fade_start_time; ALfloat fade_inv_time; @@ -232,6 +288,14 @@ */ } *ALmixer_Channel_List = NULL; +struct ALmixer_Buffer_Map +{ + ALuint albuffer; + Sint32 index; /* might not need */ + Uint8* data; + Uint32 num_bytes; +}; + /* This will be used to find a channel if the user supplies a source */ typedef struct Source_Map { @@ -255,7 +319,7 @@ /* Compare by albuffer */ static int Compare_Buffer_Map(const void* a, const void* b) { - return ( ((Buffer_Map*)a)->albuffer - ((Buffer_Map*)b)->albuffer ); + return ( ((ALmixer_Buffer_Map*)a)->albuffer - ((ALmixer_Buffer_Map*)b)->albuffer ); } /* This is for the user defined callback via @@ -263,8 +327,8 @@ */ static void (*Channel_Done_Callback)(Sint32 channel, void* userdata) = NULL; static void* Channel_Done_Callback_Userdata = NULL; -static void (*Channel_Data_Callback)(Sint32 which_channel, Uint8* data, Uint32 num_bytes, Uint32 frequency, Uint8 channels, Uint8 bitdepth, Uint16 format, Uint8 decode_mode) = NULL; - +static void (*Channel_Data_Callback)(Sint32 which_channel, Uint8* data, Uint32 num_bytes, Uint32 frequency, Uint8 channels, Uint8 bit_depth, SDL_bool is_unsigned, SDL_bool decode_mode_is_predecoded, Uint32 length_in_msec, void* user_data) = NULL; +static void* Channel_Data_Callback_Userdata = NULL; /* I thought OpenAL seemed to lack an error number to string converter... * but I was wrong. Apparently they call it alGetString() which @@ -366,6 +430,7 @@ source, buffers_queued, buffers_processed); + } @@ -583,6 +648,60 @@ return AL_FORMAT_STEREO16; } + +/* This will compute the total playing time +* based upon the number of bytes and audio info. +* (In prinicple, it should compute the time for any given length) +*/ +static Uint32 Compute_Total_Time_Decomposed(Uint32 bytes_per_sample, Uint32 frequency, Uint8 channels, Uint32 total_bytes) +{ + double total_sec; + Uint32 total_msec; + Uint32 bytes_per_sec; + + if(0 == total_bytes) + { + return 0; + } + /* To compute Bytes per second, do + * samples_per_sec * bytes_per_sample * number_of_channels + */ + bytes_per_sec = frequency * bytes_per_sample * channels; + + /* Now to get total time (sec), do + * total_bytes / bytes_per_sec + */ + total_sec = total_bytes / (double)bytes_per_sec; + + /* Now convert seconds to milliseconds + * Add .5 to the float to do rounding before the final cast + */ + total_msec = (Uint32) ( (total_sec * 1000) + 0.5 ); + /* + fprintf(stderr, "freq=%d, bytes_per_sample=%d, channels=%d, total_msec=%d\n", frequency, bytes_per_sample, channels, total_msec); + */ + return total_msec; +} + +static Uint32 Compute_Total_Time(Sound_AudioInfo *info, Uint32 total_bytes) +{ + Uint32 bytes_per_sample; + + if(0 == total_bytes) + { + return 0; + } + /* SDL has a mask trick I was not aware of. Mask the upper bits + * of the format, and you get 8 or 16 which is the bits per sample. + * Divide by 8bits_per_bytes and you get bytes_per_sample + */ + bytes_per_sample = (Uint32) ((info->format & 0xFF) / 8); + + return Compute_Total_Time_Decomposed(bytes_per_sample, info->rate, info->channels, total_bytes); +} /* End Compute_Total_Time */ + + + /**************** REMOVED ****************************/ /* This was removed because I originally thought * OpenAL could return a pointer to the buffer data, @@ -728,15 +847,15 @@ Channel_Done_Callback(channel, Channel_Done_Callback_Userdata); } -static Sint32 LookUpBuffer(ALuint buffer, Buffer_Map* buffer_map_list, Uint32 num_items_in_list) +static Sint32 LookUpBuffer(ALuint buffer, ALmixer_Buffer_Map* buffer_map_list, Uint32 num_items_in_list) { /* Only the first value is used for the key */ - Buffer_Map key = { 0, 0, NULL, 0 }; - Buffer_Map* found_item = NULL; + ALmixer_Buffer_Map key = { 0, 0, NULL, 0 }; + ALmixer_Buffer_Map* found_item = NULL; key.albuffer = buffer; /* Use the ANSI C binary search feature (yea!) */ - found_item = (Buffer_Map*)bsearch(&key, buffer_map_list, num_items_in_list, sizeof(Buffer_Map), Compare_Buffer_Map); + found_item = (ALmixer_Buffer_Map*)bsearch(&key, buffer_map_list, num_items_in_list, sizeof(ALmixer_Buffer_Map), Compare_Buffer_Map); if(NULL == found_item) { ALmixer_SetError("Can't find buffer"); @@ -750,8 +869,30 @@ * Bit rate, stereo/mono (num chans), time in msec? * Precoded/streamed flag so user can plan for future data? */ -static void Invoke_Channel_Data_Callback(Sint32 which_channel, Uint8* data, Uint32 num_bytes, Uint32 frequency, Uint8 channels, Uint16 format, Uint8 decode_mode) -{ +/* + * channels: 1 for mono, 2 for stereo + * + */ +static void Invoke_Channel_Data_Callback(Sint32 which_channel, Uint8* data, Uint32 num_bytes, Uint32 frequency, Uint8 channels, Uint16 format, SDL_bool decode_mode_is_predecoded) +{ + SDL_bool is_unsigned; + Uint8 bits_per_sample = GetBitDepth(format); + Uint32 bytes_per_sample; + Uint32 length_in_msec; + + if(GetSignednessValue(format) == ALMIXER_UNSIGNED_VALUE) + { + is_unsigned = 1; + } + else + { + is_unsigned = 0; + } + + bytes_per_sample = (Uint32) (bits_per_sample / 8); + + length_in_msec = Compute_Total_Time_Decomposed(bytes_per_sample, frequency, channels, num_bytes); + /* fprintf(stderr, "%x %x %x %x, bytes=%d, whichchan=%d, freq=%d, channels=%d\n", data[0], data[1], data[2], data[3], num_bytes, channels, frequency, channels); */ @@ -759,7 +900,10 @@ { return; } - Channel_Data_Callback(which_channel, data, num_bytes, frequency, channels, GetBitDepth(format), format, decode_mode); + /* + * Channel_Data_Callback(which_channel, data, num_bytes, frequency, channels, GetBitDepth(format), format, decode_mode_is_predecoded); + */ + Channel_Data_Callback(which_channel, data, num_bytes, frequency, channels, bits_per_sample, is_unsigned, decode_mode_is_predecoded, length_in_msec, Channel_Data_Callback_Userdata); } static void Invoke_Predecoded_Channel_Data_Callback(Sint32 channel, ALmixer_Data* data) @@ -777,7 +921,7 @@ data->sample->desired.rate, data->sample->desired.channels, data->sample->desired.format, - ALMIXER_DECODE_ALL + SDL_TRUE ); } @@ -802,7 +946,7 @@ data->sample->desired.rate, data->sample->desired.channels, data->sample->desired.format, - ALMIXER_DECODE_STREAM + SDL_FALSE ); } @@ -909,48 +1053,6 @@ return 0; } -/* This will compute the total playing time - * based upon the number of bytes and audio info. - * (In prinicple, it should compute the time for any given length) - */ -static Uint32 Compute_Total_Time(Sound_AudioInfo *info, Uint32 total_bytes) -{ - Uint32 bytes_per_sec; - Uint32 bytes_per_sample; - double total_sec; - Uint32 total_msec; - - if(0 == total_bytes) - { - return 0; - } - /* SDL has a mask trick I was not aware of. Mask the upper bits - * of the format, and you get 8 or 16 which is the bits per sample. - * Divide by 8bits_per_bytes and you get bytes_per_sample - */ - bytes_per_sample = (Uint32) ((info->format & 0xFF) / 8); - /* To compute Bytes per second, do - * samples_per_sec * bytes_per_sample * number_of_channels - */ - bytes_per_sec = info->rate * bytes_per_sample * info->channels; - - /* Now to get total time (sec), do - * total_bytes / bytes_per_sec - */ - total_sec = total_bytes / (double)bytes_per_sec; - - /* Now convert seconds to milliseconds - * Add .5 to the float to do rounding before the final cast - */ - total_msec = (Uint32) ( (total_sec * 1000) + 0.5 ); - - fprintf(stderr, "%d\n", total_msec); - return total_msec; -} /* End Compute_Total_Time */ - - - - /* Because we have multiple queue buffers and OpenAL won't let * us access them, we need to keep copies of each buffer around */ @@ -2983,11 +3085,11 @@ alGetSourcef(ALmixer_Channel_List[channel].alsource, AL_MAX_GAIN, &value); ALmixer_Channel_List[channel].fade_end_volume = value; + fprintf(stderr, "MAX gain: %f\n", value); */ ALmixer_Channel_List[channel].fade_end_volume = ALmixer_Channel_List[channel].max_volume; - fprintf(stderr, "MAX gain: %f\n", value); /* Get the Min volume */ alGetSourcef(ALmixer_Channel_List[channel].alsource, AL_MIN_GAIN, &value); @@ -4439,10 +4541,17 @@ } if(buffers_still_queued > 0) { - /* + +#if 0 /* This triggers an error in OS X Core Audio. */ + alSourceUnqueueBuffers( + ALmixer_Channel_List[i].alsource, + 1, + ALmixer_Channel_List[i].almixer_data->buffer + ); +#else +/* fprintf(stderr, "In the Bob Aron section...about to clear source\n"); PrintQueueStatus(ALmixer_Channel_List[i].alsource); - */ - +*/ /* Rather than force unqueuing the buffer, let's see if * setting the buffer to none works (the OpenAL 1.0 * Reference Annotation suggests this should work). @@ -4452,6 +4561,7 @@ /* PrintQueueStatus(ALmixer_Channel_List[i].alsource); */ +#endif if((error = alGetError()) != AL_NO_ERROR) { fprintf(stderr, "Error with unqueue, after alSourceUnqueueBuffers, buffers_still_queued=%d, error is: %s", buffers_still_queued, @@ -4595,6 +4705,17 @@ Uint32 queue_ret_flag; Uint8 is_out_of_sync = 0; Uint32 my_queue_size = CircularQueueUnsignedInt_Size(ALmixer_Channel_List[i].almixer_data->circular_buffer_queue); + /* Ugh, I have to deal with signed/unsigned mismatch here. */ + ALint buffers_unplayed_int = buffers_still_queued - buffers_processed; + Uint32 unplayed_buffers; + if(buffers_unplayed_int < 0) + { + unplayed_buffers = 0; + } + else + { + unplayed_buffers = (Uint32)buffers_unplayed_int; + } /* fprintf(stderr, "Queue in processed check, before pop, buffers_processed=%d\n", buffers_processed); CircularQueueUnsignedInt_Print(ALmixer_Channel_List[i].almixer_data->circular_buffer_queue); @@ -4622,11 +4743,11 @@ #if 0 fprintf(stderr, "inside, Buffers processed=%d, Buffers queued=%d, my queue=%d\n", buffers_processed, buffers_still_queued, my_queue_size); - #endif - if(my_queue_size > (buffers_still_queued - buffers_processed)) + #endif + if(my_queue_size > unplayed_buffers) { is_out_of_sync = 1; - for(k=0; k<(my_queue_size - (buffers_still_queued - buffers_processed)); k++) + for(k=0; k<(my_queue_size - unplayed_buffers); k++) { queue_ret_flag = CircularQueueUnsignedInt_PopFront( ALmixer_Channel_List[i].almixer_data->circular_buffer_queue); @@ -4679,7 +4800,10 @@ } else { +/* fprintf(stderr, "53b, Notice/Warning:, OpenAL queue has been depleted.\n"); + PrintQueueStatus(ALmixer_Channel_List[i].alsource); +*/ /* In this case, we might either be in an underrun or finished with playback */ ALmixer_Channel_List[i].almixer_data->current_buffer = 0; } @@ -4732,11 +4856,11 @@ if(ALmixer_Channel_List[i].almixer_data->num_buffers_in_use < ALmixer_Channel_List[i].almixer_data->max_queue_buffers) { - /* +#if 0 fprintf(stderr, "Getting more data in NOT_EOF and num_buffers_in_use (%d) < max_queue (%d)\n", ALmixer_Channel_List[i].almixer_data->num_buffers_in_use, ALmixer_Channel_List[i].almixer_data->max_queue_buffers); - */ +#endif /* Going to add an unused packet. * Grab next packet */ bytes_returned = GetMoreData( @@ -4787,31 +4911,87 @@ /* Might want to check state */ /* In case the playback stopped, * we need to resume */ + #if 1 + /* Try not refetching the state here because I'm getting a duplicate + buffer playback (hiccup) */ alGetSourcei( ALmixer_Channel_List[i].alsource, AL_SOURCE_STATE, &state ); - if((error = alGetError()) != AL_NO_ERROR) - { - fprintf(stderr, "54Testing error: %s\n", - aluGetErrorString(error)); - } + if((error = alGetError()) != AL_NO_ERROR) + { + fprintf(stderr, "54bTesting error: %s\n", + aluGetErrorString(error)); + } + /* Get the number of buffers processed + */ + alGetSourcei( + ALmixer_Channel_List[i].alsource, + AL_BUFFERS_PROCESSED, + &buffers_processed + ); + if((error = alGetError()) != AL_NO_ERROR) + { + fprintf(stderr, "54cError, Can't get buffers_processed: %s\n", + aluGetErrorString(error)); + } +#endif if(AL_STOPPED == state) { /* Resuming in not eof, but nothing to buffer */ + + /* Okay, here's another lately discovered problem: + * I can't find it in the spec, but for at least some of the + * implementations, if I call play on a stopped source that + * has processed buffers, all those buffers get marked as unprocessed + * on alSourcePlay. So if I had a queue of 25 with 24 of the buffers + * processed, on resume, the earlier 24 buffers will get replayed, + * causing a "hiccup" like sound in the playback. + * To avoid this, I must unqueue all processed buffers before + * calling play. But to complicate things, I need to worry about resyncing + * the circular queue with this since I designed this thing + * with some correlation between the two. However, I might + * have already handled this, so I will try writing this code without + * syncing for now. + * There is currently an assumption that a buffer + * was queued above so I actually have something + * to play. + */ + ALint temp_count; +/* + fprintf(stderr, "STOPPED1, need to clear processed, status is:\n"); + PrintQueueStatus(ALmixer_Channel_List[i].alsource); +*/ + for(temp_count=0; temp_countnum_buffers_in_use, ALmixer_Channel_List[i].almixer_data->max_queue_buffers); - + */ alSourceQueueBuffers( ALmixer_Channel_List[i].alsource, 1, @@ -5056,24 +5238,83 @@ /* Might want to check state */ /* In case the playback stopped, * we need to resume */ + #if 1 + /* Try not refetching the state here because I'm getting a duplicate + buffer playback (hiccup) */ alGetSourcei( ALmixer_Channel_List[i].alsource, AL_SOURCE_STATE, &state ); - if((error = alGetError()) != AL_NO_ERROR) - { - fprintf(stderr, "57Testing error: %s\n", - aluGetErrorString(error)); - } + if((error = alGetError()) != AL_NO_ERROR) + { + fprintf(stderr, "57bTesting error: %s\n", + aluGetErrorString(error)); + } + /* Get the number of buffers processed + */ + alGetSourcei( + ALmixer_Channel_List[i].alsource, + AL_BUFFERS_PROCESSED, + &buffers_processed + ); + if((error = alGetError()) != AL_NO_ERROR) + { + fprintf(stderr, "57cError, Can't get buffers_processed: %s\n", + aluGetErrorString(error)); + } + #endif if(AL_STOPPED == state) { + /* fprintf(stderr, "Resuming in not eof\n"); - alSourcePlay(ALmixer_Channel_List[i].alsource); - if((error = alGetError()) != AL_NO_ERROR) - { - fprintf(stderr, "58Testing error: %s\n", - aluGetErrorString(error)); - } + */ + /* Okay, here's another lately discovered problem: + * I can't find it in the spec, but for at least some of the + * implementations, if I call play on a stopped source that + * has processed buffers, all those buffers get marked as unprocessed + * on alSourcePlay. So if I had a queue of 25 with 24 of the buffers + * processed, on resume, the earlier 24 buffers will get replayed, + * causing a "hiccup" like sound in the playback. + * To avoid this, I must unqueue all processed buffers before + * calling play. But to complicate things, I need to worry about resyncing + * the circular queue with this since I designed this thing + * with some correlation between the two. However, I might + * have already handled this, so I will try writing this code without + * syncing for now. + * There is currently an assumption that a buffer + * was queued above so I actually have something + * to play. + */ + ALint temp_count; +/* + fprintf(stderr, "STOPPED2, need to clear processed, status is:\n"); + PrintQueueStatus(ALmixer_Channel_List[i].alsource); +*/ + + for(temp_count=0; temp_countflags & SOUND_SAMPLEFLAG_ERROR) @@ -6793,7 +7036,7 @@ /* Create buffers for data access * Should be the same number as the number of queue buffers */ - ret_data->buffer_map_list = (Buffer_Map*)malloc( sizeof(Buffer_Map) * max_queue_buffers); + ret_data->buffer_map_list = (ALmixer_Buffer_Map*)malloc( sizeof(ALmixer_Buffer_Map) * max_queue_buffers); if(NULL == ret_data->buffer_map_list) { ALmixer_SetError("Out of Memory"); @@ -6845,14 +7088,14 @@ /* The Buffer_Map_List must be sorted by albuffer for binary searches */ - qsort(ret_data->buffer_map_list, max_queue_buffers, sizeof(Buffer_Map), Compare_Buffer_Map); + qsort(ret_data->buffer_map_list, max_queue_buffers, sizeof(ALmixer_Buffer_Map), Compare_Buffer_Map); } /* End if access_data==true */ } /* End of do stream */ } /* end of DECODE_STREAM */ /* User requested decode all (easy, nothing to figure out) */ - else if(ALMIXER_DECODE_ALL == decode_mode) + else if(SDL_TRUE == decode_mode_is_predecoded) { bytes_decoded = Sound_DecodeAll(sample); if(sample->flags & SOUND_SAMPLEFLAG_ERROR) @@ -6995,7 +7238,7 @@ * must specify it, so I had to bring it back. * Remember I must close the rwops if there is an error before NewSample() */ -ALmixer_Data* ALmixer_LoadSample_RW(SDL_RWops* rwops, const char* fileext, Uint32 buffersize, Uint8 decode_mode, Uint32 max_queue_buffers, Uint32 num_startup_buffers, Uint8 access_data) +ALmixer_Data* ALmixer_LoadSample_RW(SDL_RWops* rwops, const char* fileext, Uint32 buffersize, SDL_bool decode_mode_is_predecoded, Uint32 max_queue_buffers, Uint32 num_startup_buffers, SDL_bool access_data) { Sound_Sample* sample = NULL; Sound_AudioInfo target; @@ -7031,7 +7274,7 @@ return NULL; } - return( DoLoad(sample, buffersize, decode_mode, max_queue_buffers, num_startup_buffers, access_data)); + return( DoLoad(sample, buffersize, decode_mode_is_predecoded, max_queue_buffers, num_startup_buffers, access_data)); } @@ -7041,7 +7284,7 @@ * error checking and the fact that streamed/predecoded files * must be treated differently. */ -ALmixer_Data* ALmixer_LoadSample(const char* filename, Uint32 buffersize, Uint8 decode_mode, Uint32 max_queue_buffers, Uint32 num_startup_buffers, Uint8 access_data) +ALmixer_Data* ALmixer_LoadSample(const char* filename, Uint32 buffersize, SDL_bool decode_mode_is_predecoded, Uint32 max_queue_buffers, Uint32 num_startup_buffers, SDL_bool access_data) { Sound_Sample* sample = NULL; Sound_AudioInfo target; @@ -7135,23 +7378,41 @@ fprintf(stderr, "Correction test: Actual rate=%d, desired=%d, actual format=%d, desired format=%d\n", sample->actual.rate, sample->desired.rate, sample->actual.format, sample->desired.format); - return( DoLoad(sample, buffersize, decode_mode, max_queue_buffers, num_startup_buffers, access_data)); + return( DoLoad(sample, buffersize, decode_mode_is_predecoded, max_queue_buffers, num_startup_buffers, access_data)); } /* This is a back door for RAW samples or if you need the * AudioInfo field. Use at your own risk. */ -ALmixer_Data* ALmixer_LoadSample_RAW_RW(SDL_RWops* rwops, const char* fileext, Sound_AudioInfo* desired, Uint32 buffersize, Uint8 decode_mode, Uint32 max_queue_buffers, Uint32 num_startup_buffers, Uint8 access_data) +ALmixer_Data* ALmixer_LoadSample_RAW_RW(SDL_RWops* rwops, const char* fileext, ALmixer_AudioInfo* desired, Uint32 buffersize, SDL_bool decode_mode_is_predecoded, Uint32 max_queue_buffers, Uint32 num_startup_buffers, SDL_bool access_data) { Sound_Sample* sample = NULL; - sample = Sound_NewSample(rwops, fileext, desired, buffersize); + Sound_AudioInfo sound_desired; + /* Rather than copying the data from struct to struct, I could just + * cast the thing since the structs are meant to be identical. + * But if SDL_sound changes it's implementation, bad things + * will probably happen. (Or if I change my implementation and + * forget about the cast, same bad scenario.) Since this is a load + * function, performance of this is negligible. + */ + if(NULL == desired) + { + sample = Sound_NewSample(rwops, fileext, NULL, buffersize); + } + else + { + sound_desired.format = desired->format; + sound_desired.channels = desired->channels; + sound_desired.rate = desired->rate; + sample = Sound_NewSample(rwops, fileext, &sound_desired, buffersize); + } if(NULL == sample) { ALmixer_SetError(Sound_GetError()); return NULL; } - return( DoLoad(sample, buffersize, decode_mode, max_queue_buffers, num_startup_buffers, access_data)); + return( DoLoad(sample, buffersize, decode_mode_is_predecoded, max_queue_buffers, num_startup_buffers, access_data)); } @@ -7160,16 +7421,35 @@ /* This is a back door for RAW samples or if you need the * AudioInfo field. Use at your own risk. */ -ALmixer_Data* ALmixer_LoadSample_RAW(const char* filename, Sound_AudioInfo* desired, Uint32 buffersize, Uint8 decode_mode, Uint32 max_queue_buffers, Uint32 num_startup_buffers, Uint8 access_data) +ALmixer_Data* ALmixer_LoadSample_RAW(const char* filename, ALmixer_AudioInfo* desired, Uint32 buffersize, SDL_bool decode_mode_is_predecoded, Uint32 max_queue_buffers, Uint32 num_startup_buffers, SDL_bool access_data) { Sound_Sample* sample = NULL; - sample = Sound_NewSampleFromFile(filename, desired, buffersize); + Sound_AudioInfo sound_desired; + /* Rather than copying the data from struct to struct, I could just + * cast the thing since the structs are meant to be identical. + * But if SDL_sound changes it's implementation, bad things + * will probably happen. (Or if I change my implementation and + * forget about the cast, same bad scenario.) Since this is a load + * function, performance of this is negligible. + */ + if(NULL == desired) + { + sample = Sound_NewSampleFromFile(filename, NULL, buffersize); + } + else + { + sound_desired.format = desired->format; + sound_desired.channels = desired->channels; + sound_desired.rate = desired->rate; + sample = Sound_NewSampleFromFile(filename, &sound_desired, buffersize); + } + if(NULL == sample) { ALmixer_SetError(Sound_GetError()); return NULL; } - return( DoLoad(sample, buffersize, decode_mode, max_queue_buffers, num_startup_buffers, access_data)); + return( DoLoad(sample, buffersize, decode_mode_is_predecoded, max_queue_buffers, num_startup_buffers, access_data)); } @@ -7304,10 +7584,11 @@ } -void ALmixer_ChannelData(void (*channel_data)(Sint32 which_chan, Uint8* data, Uint32 num_bytes, Uint32 frequency, Uint8 channels, Uint8 bitdepth, Uint16 format, Uint8 decode_mode)) +void ALmixer_ChannelData(void (*channel_data)(Sint32 which_chan, Uint8* data, Uint32 num_bytes, Uint32 frequency, Uint8 channels, Uint8 bit_depth, SDL_bool is_unsigned, SDL_bool decode_mode_is_predecoded, Uint32 length_in_msec, void* user_data), void* user_data) { SDL_LockMutex(simple_lock); Channel_Data_Callback = channel_data; + Channel_Data_Callback_Userdata = user_data; SDL_UnlockMutex(simple_lock); } @@ -7709,7 +7990,16 @@ return retval; } - - - - +SDL_bool ALmixer_IsPredecoded(ALmixer_Data* data) +{ + if(NULL == data) + { + return SDL_FALSE; + } + return data->decoded_all; +} + + + + + diff -r 01e39f9f58d5 -r a8a8fe374984 SDL_ALmixer.h --- a/SDL_ALmixer.h Wed Oct 27 16:50:19 2010 -0700 +++ b/SDL_ALmixer.h Wed Oct 27 16:51:16 2010 -0700 @@ -23,19 +23,25 @@ #define _SDL_ALMIXER_H_ #include "SDL_types.h" +#include "SDL_rwops.h" +#include "SDL_error.h" +#include "SDL_version.h" /* -#include "SDL_rwops.h" #include "SDL_audio.h" #include "SDL_byteorder.h" */ -#include "SDL_version.h" /* #include "begin_code.h" */ +/* #include "SDL_sound.h" +*/ +/* Crap! altypes.h is missing from 1.1 #include "altypes.h" +*/ +#include "al.h" /* Set up for C function definitions, even when using C++ */ #ifdef __cplusplus @@ -81,56 +87,43 @@ /* Default startup buffers should be at least 1 */ #define ALMIXER_DEFAULT_STARTUP_BUFFERS 2 +/* #define ALMIXER_DECODE_STREAM 0 #define ALMIXER_DECODE_ALL 1 +*/ #define ALmixer_GetError SDL_GetError #define ALmixer_SetError SDL_SetError -typedef struct { - ALuint albuffer; - Sint32 index; /* might not need */ - Uint8* data; - Uint32 num_bytes; -} Buffer_Map; -typedef struct { - Uint8 decoded_all; /* dictates different behaviors */ - Sint32 total_time; /* total playing time of sample (msec) */ - - Uint32 in_use; /* needed to prevent sharing for streams */ - Uint8 eof; /* flag for eof, only used for streams */ - - Uint32 total_bytes; /* For predecoded */ - Uint32 loaded_bytes; /* For predecoded (for seek) */ - - Sound_Sample* sample; /* SDL_Sound provides the data */ - ALuint* buffer; /* array of OpenAL buffers (at least 1 for predecoded) */ +/* This is a trick I picked up from Lua. Doing the typedef separately +* (and I guess before the definition) instead of a single +* entry: typedef struct {...} YourName; seems to allow me +* to use forward declarations. Doing it the other way (like SDL) +* seems to prevent me from using forward declarions as I get conflicting +* definition errors. I don't really understand why though. +*/ +typedef struct ALmixer_Data ALmixer_Data; +typedef struct ALmixer_AudioInfo ALmixer_AudioInfo; - /* Needed for streamed buffers */ - Uint32 max_queue_buffers; /* Max number of queue buffers */ - Uint32 num_startup_buffers; /* Number of ramp-up buffers */ - Uint8 num_buffers_in_use; /* number of buffers in use */ - - /* This stuff is for streamed buffers that require data access */ - Buffer_Map* buffer_map_list; /* translate ALbuffer to index - and holds pointer to copy of data for - data access */ - ALuint current_buffer; /* The current playing buffer */ - - /* Nvidia distribution refuses to recognize a simple buffer query command - * unlike all other distributions. It's forcing me to redo the code - * to accomodate this Nvidia flaw by making me maintain a "best guess" - * copy of what I think the buffer queue state looks like. - * A circular queue would a helpful data structure for this task, - * but I wanted to avoid making an additional header requirement, - * so I'm making it a void* - */ - void* circular_buffer_queue; - - -} ALmixer_Data; +/** + * Equvialent to the Sound_AudioInfo struct in SDL_sound. + * Originally, I just used the Sound_AudioInfo directly, but + * I've been trying to reduce the header dependencies for this file. + * But more to the point, I've been interested in dealing with the + * WinMain override problem Josh faced when trying to use SDL components + * in an MFC app which didn't like losing control of WinMain. + * My theory is that if I can purge the header of any thing that + * #include's SDL_main.h, then this might work. + * So I am now introducing my own AudioInfo struct. + */ +struct ALmixer_AudioInfo +{ + Uint16 format; /**< Equivalent of SDL_AudioSpec.format. */ + Uint8 channels; /**< Number of sound channels. 1 == mono, 2 == stereo. */ + Uint32 rate; /**< Sample rate; frequency of sample points per second. */ +}; #if 0 @@ -174,37 +167,37 @@ extern DECLSPEC Sint32 SDLCALL ALmixer_Init_Mixer(Sint32 num_sources); extern DECLSPEC void SDLCALL ALmixer_Quit(); -extern DECLSPEC Uint8 SDLCALL ALmixer_IsInitialized(); +extern DECLSPEC SDL_bool SDLCALL ALmixer_IsInitialized(); extern DECLSPEC Uint32 SDLCALL ALmixer_GetFrequency(); extern DECLSPEC Sint32 SDLCALL ALmixer_AllocateChannels(Sint32 numchans); extern DECLSPEC Sint32 SDLCALL ALmixer_ReserveChannels(Sint32 num); -extern DECLSPEC ALmixer_Data * SDLCALL ALmixer_LoadSample_RW(SDL_RWops* rwops, const char* fileext, Uint32 buffersize, Uint8 decode_mode, Uint32 max_queue_buffers, Uint32 num_startup_buffers, Uint8 access_data); +extern DECLSPEC ALmixer_Data * SDLCALL ALmixer_LoadSample_RW(SDL_RWops* rwops, const char* fileext, Uint32 buffersize, SDL_bool decode_mode_is_predecoded, Uint32 max_queue_buffers, Uint32 num_startup_buffers, SDL_bool access_data); -#define ALmixer_LoadStream_RW(rwops,fileext,buffersize,max_queue_buffers,num_startup_buffers,access_data) ALmixer_LoadSample_RW(rwops,fileext,buffersize,ALMIXER_DECODE_STREAM, max_queue_buffers, num_startup_buffers,access_data) +#define ALmixer_LoadStream_RW(rwops,fileext,buffersize,max_queue_buffers,num_startup_buffers,access_data) ALmixer_LoadSample_RW(rwops,fileext,buffersize, SDL_FALSE, max_queue_buffers, num_startup_buffers,access_data) -#define ALmixer_LoadAll_RW(rwops,fileext,buffersize,access_data) ALmixer_LoadSample_RW(rwops,fileext,buffersize,ALMIXER_DECODE_ALL, 0, 0,access_data) +#define ALmixer_LoadAll_RW(rwops,fileext,buffersize,access_data) ALmixer_LoadSample_RW(rwops,fileext,buffersize, SDL_TRUE, 0, 0,access_data) -extern DECLSPEC ALmixer_Data * SDLCALL ALmixer_LoadSample(const char* filename, Uint32 buffersize, Uint8 decode_mode, Uint32 max_queue_buffers, Uint32 num_startup_buffers, Uint8 access_data); +extern DECLSPEC ALmixer_Data * SDLCALL ALmixer_LoadSample(const char* filename, Uint32 buffersize, SDL_bool decode_mode_is_predecoded, Uint32 max_queue_buffers, Uint32 num_startup_buffers, SDL_bool access_data); -#define ALmixer_LoadStream(filename,buffersize,max_queue_buffers,num_startup_buffers,access_data) ALmixer_LoadSample(filename,buffersize,ALMIXER_DECODE_STREAM, max_queue_buffers, num_startup_buffers,access_data) +#define ALmixer_LoadStream(filename,buffersize,max_queue_buffers,num_startup_buffers,access_data) ALmixer_LoadSample(filename,buffersize, SDL_FALSE, max_queue_buffers, num_startup_buffers,access_data) -#define ALmixer_LoadAll(filename,buffersize,access_data) ALmixer_LoadSample(filename,buffersize,ALMIXER_DECODE_ALL, 0, 0,access_data) +#define ALmixer_LoadAll(filename,buffersize,access_data) ALmixer_LoadSample(filename,buffersize, SDL_TRUE, 0, 0,access_data) -extern DECLSPEC ALmixer_Data * SDLCALL ALmixer_LoadSample_RAW_RW(SDL_RWops* rwops, const char* fileext, Sound_AudioInfo* desired, Uint32 buffersize, Uint8 decode_mode, Uint32 max_queue_buffers, Uint32 num_startup_buffers, Uint8 access_data); +extern DECLSPEC ALmixer_Data * SDLCALL ALmixer_LoadSample_RAW_RW(SDL_RWops* rwops, const char* fileext, ALmixer_AudioInfo* desired, Uint32 buffersize, SDL_bool decode_mode_is_predecoded, Uint32 max_queue_buffers, Uint32 num_startup_buffers, SDL_bool access_data); -#define ALmixer_LoadStream_RAW_RW(rwops,fileext,desired,buffersize,max_queue_buffers,num_startup_buffers,access_data) ALmixer_LoadSample_RAW_RW(rwops,fileext,desired,buffersize,ALMIXER_DECODE_STREAM, max_queue_buffers, num_startup_buffers,access_data) +#define ALmixer_LoadStream_RAW_RW(rwops,fileext,desired,buffersize,max_queue_buffers,num_startup_buffers,access_data) ALmixer_LoadSample_RAW_RW(rwops,fileext,desired,buffersize, SDL_FALSE, max_queue_buffers, num_startup_buffers,access_data) -#define ALmixer_LoadAll_RAW_RW(rwops,fileext,desired,buffersize,access_data) ALmixer_LoadSample_RAW_RW(rwops,fileext,desired,buffersize,ALMIXER_DECODE_ALL, 0, 0,access_data) +#define ALmixer_LoadAll_RAW_RW(rwops,fileext,desired,buffersize,access_data) ALmixer_LoadSample_RAW_RW(rwops,fileext,desired,buffersize, SDL_TRUE, 0, 0,access_data) -extern DECLSPEC ALmixer_Data * SDLCALL ALmixer_LoadSample_RAW(const char* filename, Sound_AudioInfo* desired, Uint32 buffersize, Uint8 decode_mode, Uint32 max_queue_buffers, Uint32 num_startup_buffers, Uint8 access_data); +extern DECLSPEC ALmixer_Data * SDLCALL ALmixer_LoadSample_RAW(const char* filename, ALmixer_AudioInfo* desired, Uint32 buffersize, SDL_bool decode_mode_is_predecoded, Uint32 max_queue_buffers, Uint32 num_startup_buffers, SDL_bool access_data); @@ -249,7 +242,82 @@ extern DECLSPEC Sint32 SDLCALL ALmixer_FindFreeChannel(Sint32 start_channel); extern DECLSPEC void SDLCALL ALmixer_ChannelFinished(void (*channel_finished)(Sint32 channel, void* userdata), void* userdata); + +/* extern DECLSPEC void SDLCALL ALmixer_ChannelData(void (*channel_data)(Sint32 which_chan, Uint8* data, Uint32 num_bytes, Uint32 frequency, Uint8 channels, Uint8 bitdepth, Uint16 format, Uint8 decode_mode)); +*/ +/** + * Audio data callback system. + * This is a callback function pointer that when set, will trigger a function + * anytime there is new data loaded for a sample. The appropriate load + * parameter must be set in order for a sample to appear here. + * Keep in mind the the current backend implementation must do an end run + * around OpenAL because OpenAL lacks support for this kind of thing. + * As such, buffers are copied at decode time, and there is no attempt to do + * fine grained timing syncronization. You will be provided the entire buffer + * that is decoded regardless of length. So if you predecoded the entire + * audio file, the entire data buffer will be provided in a single callback. + * If you stream the data, you will be getting chunk sizes that are the same as + * what you specified the decode size to be. Unfortunely, this means if you + * pick smaller buffers, you get finer detail at the expense/risk of buffer + * underruns. If you decode more data, you have to deal with the syncronization + * issues if you want to display the data during playback in something like an + * oscilloscope. + * + * @param which_chan The ALmixer channel that the data is currently playing on. + * @param data This is a pointer to the data buffer containing ALmixer's + * version of the decoded data. Consider this data as read-only. In the + * non-threaded backend, this data will persist until potentially the next call + * to Update(). Currently, data buffers are preallocated and not destroyed + * until FreeData() is called (though this behavior is subject to change), + * but the contents will change when the buffer needs to be reused for a + * future callback. The buffer reuse is tied to the amount of buffers that + * may be queued. + * But assuming I don't change this, this may allow for some optimization + * so you can try referencing data from these buffers without worrying + * about crashing. (You still need to be aware that the data could be + * modified behind the scenes on an Update().) + * + * The data type listed is an Unsigned 8-bit format, but the real data may + * not actually be this. Uint8 was chosen as a convenience. If you have + * a 16 bit format, you will want to cast the data and also divide the num_bytes + * by 2. Typically, data is either Sint16 or Uint8. This seems to be a + * convention audio people seem to follow though I'm not sure what the + * underlying reasons (if any) are for this. I suspect that there may be + * some nice alignment/conversion property if you need to cast from Uint8 + * to Sint16. + * + * @param num_bytes This is the total length of the data buffer. It presumes + * that this length is measured for Uint8. So if you have Sint16 data, you + * should divide num_bytes by two if you access the data as Sint16. + * + * @param frequency The frequency the data was decoded at. + * + * @param channels 1 for mono, 2 for stereo. + * + * @param bit_depth Bits per sample. This is expected to be 8 or 16. This + * number will tell you if you if you need to treat the data buffer as + * 16 bit or not. + * + * @param is_unsigned 1 if the data is unsigned, 0 if signed. Using this + * combined with bit_depth will tell you if you need to treat the data + * as Uint8, Sint8, Uint32, or Sint32. + * + * @param decode_mode_is_predecoded This is here to tell you if the data was totally + * predecoded or loaded as a stream. If predecoded, you will only get + * one data callback per playback instance. (This might also be true for + * looping the same sample...I don't remember how it was implemented. + * Maybe this should be fixed.) + * 0 (ALMIXER_DECODE_STREAM) for streamed. + * 1 (ALMIXER_DECODE_ALL) for predecoded. + * + * @param length_in_msec This returns the total length (time) of the data + * buffer in milliseconds. This could be computed yourself, but is provided + * as a convenince. + * + * + */ +extern DECLSPEC void SDLCALL ALmixer_ChannelData(void (*channel_data)(Sint32 which_chan, Uint8* data, Uint32 num_bytes, Uint32 frequency, Uint8 channels, Uint8 bit_depth, SDL_bool is_unsigned, SDL_bool decode_mode_is_predecoded, Uint32 length_in_msec, void* user_data), void* user_data); extern DECLSPEC Sint32 SDLCALL ALmixer_HaltChannel(Sint32 channel); @@ -322,6 +390,8 @@ #define ALmixer_CountTotalChannels() ALmixer_AllocateChannels(-1) #define ALmixer_CountReservedChannels() ALmixer_ReserveChannels(-1) +extern DECLSPEC SDL_bool SDLCALL ALmixer_IsPredecoded(ALmixer_Data* data); + /* For testing */