mirror of
				https://github.com/libsdl-org/SDL.git
				synced 2025-10-26 12:27:44 +00:00 
			
		
		
		
	Refactored audio conversion to reduce copying
More of the logic has been moved into SDL_AudioQueue, allowing data to be converted directly from the input buffer.
This commit is contained in:
		| @@ -789,13 +789,13 @@ extern DECLSPEC float SDLCALL SDL_GetAudioStreamFrequencyRatio(SDL_AudioStream * | |||||||
| extern DECLSPEC int SDLCALL SDL_SetAudioStreamFrequencyRatio(SDL_AudioStream *stream, float ratio); | extern DECLSPEC int SDLCALL SDL_SetAudioStreamFrequencyRatio(SDL_AudioStream *stream, float ratio); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Add data to be converted/resampled to the stream. |  * Add data to the stream. | ||||||
|  * |  * | ||||||
|  * This data must match the format/channels/samplerate specified in the latest |  * This data must match the format/channels/samplerate specified in the latest | ||||||
|  * call to SDL_SetAudioStreamFormat, or the format specified when creating the |  * call to SDL_SetAudioStreamFormat, or the format specified when creating the | ||||||
|  * stream if it hasn't been changed. |  * stream if it hasn't been changed. | ||||||
|  * |  * | ||||||
|  * Note that this call simply queues unconverted data for later. This is |  * Note that this call simply copies the unconverted data for later. This is | ||||||
|  * different than SDL2, where data was converted during the Put call and the |  * different than SDL2, where data was converted during the Put call and the | ||||||
|  * Get call would just dequeue the previously-converted data. |  * Get call would just dequeue the previously-converted data. | ||||||
|  * |  * | ||||||
|   | |||||||
| @@ -308,20 +308,10 @@ static int UpdateAudioStreamInputSpec(SDL_AudioStream *stream, const SDL_AudioSp | |||||||
|         return 0; |         return 0; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const size_t history_buffer_allocation = SDL_GetResamplerHistoryFrames() * SDL_AUDIO_FRAMESIZE(*spec); |     if (SDL_ResetAudioQueueHistory(stream->queue, SDL_GetResamplerHistoryFrames()) != 0) { | ||||||
|     Uint8 *history_buffer = stream->history_buffer; |         return -1; | ||||||
|  |  | ||||||
|     if (stream->history_buffer_allocation < history_buffer_allocation) { |  | ||||||
|         history_buffer = (Uint8 *) SDL_aligned_alloc(SDL_SIMDGetAlignment(), history_buffer_allocation); |  | ||||||
|         if (!history_buffer) { |  | ||||||
|             return -1; |  | ||||||
|         } |  | ||||||
|         SDL_aligned_free(stream->history_buffer); |  | ||||||
|         stream->history_buffer = history_buffer; |  | ||||||
|         stream->history_buffer_allocation = history_buffer_allocation; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     SDL_memset(history_buffer, SDL_GetSilenceValueForFormat(spec->format), history_buffer_allocation); |  | ||||||
|     SDL_copyp(&stream->input_spec, spec); |     SDL_copyp(&stream->input_spec, spec); | ||||||
|  |  | ||||||
|     return 0; |     return 0; | ||||||
| @@ -338,7 +328,7 @@ SDL_AudioStream *SDL_CreateAudioStream(const SDL_AudioSpec *src_spec, const SDL_ | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     retval->freq_ratio = 1.0f; |     retval->freq_ratio = 1.0f; | ||||||
|     retval->queue = SDL_CreateAudioQueue(4096); |     retval->queue = SDL_CreateAudioQueue(8192); | ||||||
|  |  | ||||||
|     if (!retval->queue) { |     if (!retval->queue) { | ||||||
|         SDL_free(retval); |         SDL_free(retval); | ||||||
| @@ -550,22 +540,12 @@ static int CheckAudioStreamIsFullySetup(SDL_AudioStream *stream) | |||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) | static int PutAudioStreamBuffer(SDL_AudioStream *stream, const void *buf, int len, SDL_ReleaseAudioBufferCallback callback, void* userdata) | ||||||
| { | { | ||||||
| #if DEBUG_AUDIOSTREAM | #if DEBUG_AUDIOSTREAM | ||||||
|     SDL_Log("AUDIOSTREAM: wants to put %d bytes", len); |     SDL_Log("AUDIOSTREAM: wants to put %d bytes", len); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|     if (!stream) { |  | ||||||
|         return SDL_InvalidParamError("stream"); |  | ||||||
|     } else if (!buf) { |  | ||||||
|         return SDL_InvalidParamError("buf"); |  | ||||||
|     } else if (len < 0) { |  | ||||||
|         return SDL_InvalidParamError("len"); |  | ||||||
|     } else if (len == 0) { |  | ||||||
|         return 0; // nothing to do. |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     SDL_LockMutex(stream->lock); |     SDL_LockMutex(stream->lock); | ||||||
|  |  | ||||||
|     if (CheckAudioStreamIsFullySetup(stream) != 0) { |     if (CheckAudioStreamIsFullySetup(stream) != 0) { | ||||||
| @@ -580,24 +560,13 @@ int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) | |||||||
|  |  | ||||||
|     SDL_AudioTrack* track = NULL; |     SDL_AudioTrack* track = NULL; | ||||||
|  |  | ||||||
|     // When copying in large amounts of data, try and do as much work as possible |     if (callback) { | ||||||
|     // outside of the stream lock, otherwise the output device is likely to be starved. |         track = SDL_CreateAudioTrack(stream->queue, &stream->src_spec, (Uint8 *)buf, len, len, callback, userdata); | ||||||
|     const int large_input_thresh = 1024 * 1024; |  | ||||||
|  |  | ||||||
|     if (len >= large_input_thresh) { |  | ||||||
|         SDL_AudioSpec src_spec; |  | ||||||
|         SDL_copyp(&src_spec, &stream->src_spec); |  | ||||||
|  |  | ||||||
|         SDL_UnlockMutex(stream->lock); |  | ||||||
|  |  | ||||||
|         size_t chunk_size = SDL_GetAudioQueueChunkSize(stream->queue); |  | ||||||
|         track = SDL_CreateChunkedAudioTrack(&src_spec, (const Uint8 *)buf, len, chunk_size); |  | ||||||
|  |  | ||||||
|         if (!track) { |         if (!track) { | ||||||
|  |             SDL_UnlockMutex(stream->lock); | ||||||
|             return -1; |             return -1; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         SDL_LockMutex(stream->lock); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const int prev_available = stream->put_callback ? SDL_GetAudioStreamAvailable(stream) : 0; |     const int prev_available = stream->put_callback ? SDL_GetAudioStreamAvailable(stream) : 0; | ||||||
| @@ -611,7 +580,6 @@ int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (retval == 0) { |     if (retval == 0) { | ||||||
|         stream->total_bytes_queued += len; |  | ||||||
|         if (stream->put_callback) { |         if (stream->put_callback) { | ||||||
|             const int newavail = SDL_GetAudioStreamAvailable(stream) - prev_available; |             const int newavail = SDL_GetAudioStreamAvailable(stream) - prev_available; | ||||||
|             stream->put_callback(stream->put_callback_userdata, stream, newavail, newavail); |             stream->put_callback(stream->put_callback_userdata, stream, newavail, newavail); | ||||||
| @@ -623,6 +591,49 @@ int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) | |||||||
|     return retval; |     return retval; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static void SDLCALL FreeAllocatedAudioBuffer(void *userdata, const void *buf, int len) | ||||||
|  | { | ||||||
|  |     SDL_free((void*) buf); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int SDL_PutAudioStreamData(SDL_AudioStream *stream, const void *buf, int len) | ||||||
|  | { | ||||||
|  |     if (!stream) { | ||||||
|  |         return SDL_InvalidParamError("stream"); | ||||||
|  |     } else if (!buf) { | ||||||
|  |         return SDL_InvalidParamError("buf"); | ||||||
|  |     } else if (len < 0) { | ||||||
|  |         return SDL_InvalidParamError("len"); | ||||||
|  |     } else if (len == 0) { | ||||||
|  |         return 0; // nothing to do. | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // When copying in large amounts of data, try and do as much work as possible | ||||||
|  |     // outside of the stream lock, otherwise the output device is likely to be starved. | ||||||
|  |     const int large_input_thresh = 64 * 1024; | ||||||
|  |  | ||||||
|  |     if (len >= large_input_thresh) { | ||||||
|  |         void* data = SDL_malloc(len); | ||||||
|  |  | ||||||
|  |         if (!data) { | ||||||
|  |             return -1; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         SDL_memcpy(data, buf, len); | ||||||
|  |         buf = data; | ||||||
|  |  | ||||||
|  |         int ret = PutAudioStreamBuffer(stream, buf, len, FreeAllocatedAudioBuffer, NULL); | ||||||
|  |  | ||||||
|  |         if (ret < 0) { | ||||||
|  |             SDL_free(data); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return PutAudioStreamBuffer(stream, buf, len, NULL, NULL); | ||||||
|  | } | ||||||
|  |  | ||||||
| int SDL_FlushAudioStream(SDL_AudioStream *stream) | int SDL_FlushAudioStream(SDL_AudioStream *stream) | ||||||
| { | { | ||||||
|     if (!stream) { |     if (!stream) { | ||||||
| @@ -655,31 +666,6 @@ static Uint8 *EnsureAudioStreamWorkBufferSize(SDL_AudioStream *stream, size_t ne | |||||||
|     return ptr; |     return ptr; | ||||||
| } | } | ||||||
|  |  | ||||||
| static void UpdateAudioStreamHistoryBuffer(SDL_AudioStream* stream, |  | ||||||
|     Uint8* input_buffer, int input_bytes, Uint8* left_padding, int padding_bytes) |  | ||||||
| { |  | ||||||
|     const int history_buffer_frames = SDL_GetResamplerHistoryFrames(); |  | ||||||
|  |  | ||||||
|     // Even if we aren't currently resampling, we always need to update the history buffer |  | ||||||
|     Uint8 *history_buffer = stream->history_buffer; |  | ||||||
|     int history_bytes = history_buffer_frames * SDL_AUDIO_FRAMESIZE(stream->input_spec); |  | ||||||
|  |  | ||||||
|     if (left_padding) { |  | ||||||
|         // Fill in the left padding using the history buffer |  | ||||||
|         SDL_assert(padding_bytes <= history_bytes); |  | ||||||
|         SDL_memcpy(left_padding, history_buffer + history_bytes - padding_bytes, padding_bytes); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Update the history buffer using the new input data |  | ||||||
|     if (input_bytes >= history_bytes) { |  | ||||||
|         SDL_memcpy(history_buffer, input_buffer + (input_bytes - history_bytes), history_bytes); |  | ||||||
|     } else { |  | ||||||
|         int preserve_bytes = history_bytes - input_bytes; |  | ||||||
|         SDL_memmove(history_buffer, history_buffer + input_bytes, preserve_bytes); |  | ||||||
|         SDL_memcpy(history_buffer + preserve_bytes, input_buffer, input_bytes); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static Sint64 NextAudioStreamIter(SDL_AudioStream* stream, void** inout_iter, | static Sint64 NextAudioStreamIter(SDL_AudioStream* stream, void** inout_iter, | ||||||
|     Sint64* inout_resample_offset, SDL_AudioSpec* out_spec, SDL_bool* out_flushed) |     Sint64* inout_resample_offset, SDL_AudioSpec* out_spec, SDL_bool* out_flushed) | ||||||
| { | { | ||||||
| @@ -777,7 +763,6 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou | |||||||
|  |  | ||||||
|     const SDL_AudioFormat src_format = src_spec->format; |     const SDL_AudioFormat src_format = src_spec->format; | ||||||
|     const int src_channels = src_spec->channels; |     const int src_channels = src_spec->channels; | ||||||
|     const int src_frame_size = SDL_AUDIO_FRAMESIZE(*src_spec); |  | ||||||
|  |  | ||||||
|     const SDL_AudioFormat dst_format = dst_spec->format; |     const SDL_AudioFormat dst_format = dst_spec->format; | ||||||
|     const int dst_channels = dst_spec->channels; |     const int dst_channels = dst_spec->channels; | ||||||
| @@ -793,34 +778,19 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou | |||||||
|  |  | ||||||
|     // Not resampling? It's an easy conversion (and maybe not even that!) |     // Not resampling? It's an easy conversion (and maybe not even that!) | ||||||
|     if (resample_rate == 0) { |     if (resample_rate == 0) { | ||||||
|         Uint8* input_buffer = NULL; |         Uint8* work_buffer = NULL; | ||||||
|  |  | ||||||
|         // If no conversion is happening, read straight into the output buffer. |         // Ensure we have enough scratch space for any conversions | ||||||
|         // Note, this is just to avoid extra copies. |         if ((src_format != dst_format) || (src_channels != dst_channels)) { | ||||||
|         // Some other formats may fit directly into the output buffer, but i'd rather process data in a SIMD-aligned buffer. |             work_buffer = EnsureAudioStreamWorkBufferSize(stream, output_frames * max_frame_size); | ||||||
|         if ((src_format == dst_format) && (src_channels == dst_channels)) { |  | ||||||
|             input_buffer = (Uint8 *)buf; |  | ||||||
|         } else { |  | ||||||
|             input_buffer = EnsureAudioStreamWorkBufferSize(stream, output_frames * max_frame_size); |  | ||||||
|  |  | ||||||
|             if (!input_buffer) { |             if (!work_buffer) { | ||||||
|                 return -1; |                 return -1; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const int input_bytes = output_frames * src_frame_size; |         if (SDL_ReadFromAudioQueue(stream->queue, buf, dst_format, dst_channels, 0, output_frames, 0, work_buffer) != buf) { | ||||||
|         if (SDL_ReadFromAudioQueue(stream->queue, input_buffer, input_bytes) != 0) { |             return SDL_SetError("Not enough data in queue"); | ||||||
|             SDL_assert(!"Not enough data in queue (read)"); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         stream->total_bytes_queued -= input_bytes; |  | ||||||
|  |  | ||||||
|         // Even if we aren't currently resampling, we always need to update the history buffer |  | ||||||
|         UpdateAudioStreamHistoryBuffer(stream, input_buffer, input_bytes, NULL, 0); |  | ||||||
|  |  | ||||||
|         // Convert the data, if necessary |  | ||||||
|         if (buf != input_buffer) { |  | ||||||
|             ConvertAudio(output_frames, input_buffer, src_format, src_channels, buf, dst_format, dst_channels, input_buffer); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return 0; |         return 0; | ||||||
| @@ -832,9 +802,10 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou | |||||||
|     // can require a different number of input_frames, depending on the resample_offset. |     // can require a different number of input_frames, depending on the resample_offset. | ||||||
|     // Infact, input_frames can sometimes even be zero when upsampling. |     // Infact, input_frames can sometimes even be zero when upsampling. | ||||||
|     const int input_frames = (int) SDL_GetResamplerInputFrames(output_frames, resample_rate, stream->resample_offset); |     const int input_frames = (int) SDL_GetResamplerInputFrames(output_frames, resample_rate, stream->resample_offset); | ||||||
|     const int input_bytes = input_frames * src_frame_size; |  | ||||||
|  |  | ||||||
|     const int resampler_padding_frames = SDL_GetResamplerPaddingFrames(resample_rate); |     const int padding_frames = SDL_GetResamplerPaddingFrames(resample_rate); | ||||||
|  |  | ||||||
|  |     const SDL_AudioFormat resample_format = SDL_AUDIO_F32; | ||||||
|  |  | ||||||
|     // If increasing channels, do it after resampling, since we'd just |     // If increasing channels, do it after resampling, since we'd just | ||||||
|     // do more work to resample duplicate channels. If we're decreasing, do |     // do more work to resample duplicate channels. If we're decreasing, do | ||||||
| @@ -843,7 +814,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou | |||||||
|     const int resample_channels = SDL_min(src_channels, dst_channels); |     const int resample_channels = SDL_min(src_channels, dst_channels); | ||||||
|  |  | ||||||
|     // The size of the frame used when resampling |     // The size of the frame used when resampling | ||||||
|     const int resample_frame_size = resample_channels * sizeof(float); |     const int resample_frame_size = SDL_AUDIO_BYTESIZE(resample_format) * resample_channels; | ||||||
|  |  | ||||||
|     // The main portion of the work_buffer can be used to store 3 things: |     // The main portion of the work_buffer can be used to store 3 things: | ||||||
|     // src_sample_frame_size * (left_padding+input_buffer+right_padding) |     // src_sample_frame_size * (left_padding+input_buffer+right_padding) | ||||||
| @@ -854,14 +825,14 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou | |||||||
|     //   resample_frame_size * output_frames |     //   resample_frame_size * output_frames | ||||||
|     // |     // | ||||||
|     // Note, ConvertAudio requires (num_frames * max_sample_frame_size) of scratch space |     // Note, ConvertAudio requires (num_frames * max_sample_frame_size) of scratch space | ||||||
|     const int work_buffer_frames = input_frames + (resampler_padding_frames * 2); |     const int work_buffer_frames = input_frames + (padding_frames * 2); | ||||||
|     int work_buffer_capacity = work_buffer_frames * max_frame_size; |     int work_buffer_capacity = work_buffer_frames * max_frame_size; | ||||||
|     int resample_buffer_offset = -1; |     int resample_buffer_offset = -1; | ||||||
|  |  | ||||||
|     // Check if we can resample directly into the output buffer. |     // Check if we can resample directly into the output buffer. | ||||||
|     // Note, this is just to avoid extra copies. |     // Note, this is just to avoid extra copies. | ||||||
|     // Some other formats may fit directly into the output buffer, but i'd rather process data in a SIMD-aligned buffer. |     // Some other formats may fit directly into the output buffer, but i'd rather process data in a SIMD-aligned buffer. | ||||||
|     if ((dst_format != SDL_AUDIO_F32) || (dst_channels != resample_channels)) { |     if ((dst_format != resample_format) || (dst_channels != resample_channels)) { | ||||||
|         // Allocate space for converting the resampled output to the destination format |         // Allocate space for converting the resampled output to the destination format | ||||||
|         int resample_convert_bytes = output_frames * max_frame_size; |         int resample_convert_bytes = output_frames * max_frame_size; | ||||||
|         work_buffer_capacity = SDL_max(work_buffer_capacity, resample_convert_bytes); |         work_buffer_capacity = SDL_max(work_buffer_capacity, resample_convert_bytes); | ||||||
| @@ -883,45 +854,15 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou | |||||||
|         return -1; |         return -1; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const int padding_bytes = resampler_padding_frames * src_frame_size; |     const Uint8* input_buffer = SDL_ReadFromAudioQueue(stream->queue, | ||||||
|  |         NULL, resample_format, resample_channels, | ||||||
|  |         padding_frames, input_frames, padding_frames, work_buffer); | ||||||
|  |  | ||||||
|     Uint8* work_buffer_tail = work_buffer; |     if (!input_buffer) { | ||||||
|  |         return SDL_SetError("Not enough data in queue (resample)"); | ||||||
|     // Split the work_buffer into [left_padding][input_buffer][right_padding] |  | ||||||
|     Uint8* left_padding = work_buffer_tail; |  | ||||||
|     work_buffer_tail += padding_bytes; |  | ||||||
|  |  | ||||||
|     Uint8* input_buffer = work_buffer_tail; |  | ||||||
|     work_buffer_tail += input_bytes; |  | ||||||
|  |  | ||||||
|     Uint8* right_padding = work_buffer_tail; |  | ||||||
|     work_buffer_tail += padding_bytes; |  | ||||||
|  |  | ||||||
|     SDL_assert((work_buffer_tail - work_buffer) <= work_buffer_capacity); |  | ||||||
|  |  | ||||||
|     // Now read unconverted data from the queue into the work buffer to fulfill the request. |  | ||||||
|     if (SDL_ReadFromAudioQueue(stream->queue, input_buffer, input_bytes) != 0) { |  | ||||||
|         SDL_assert(!"Not enough data in queue (resample read)"); |  | ||||||
|     } |  | ||||||
|     stream->total_bytes_queued -= input_bytes; |  | ||||||
|  |  | ||||||
|     // Update the history buffer and fill in the left padding |  | ||||||
|     UpdateAudioStreamHistoryBuffer(stream, input_buffer, input_bytes, left_padding, padding_bytes); |  | ||||||
|  |  | ||||||
|     // Fill in the right padding by peeking into the input queue (missing data is filled with silence) |  | ||||||
|     if (SDL_PeekIntoAudioQueue(stream->queue, right_padding, padding_bytes) != 0) { |  | ||||||
|         SDL_assert(!"Not enough data in queue (resample peek)"); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     SDL_assert(work_buffer_frames == input_frames + (resampler_padding_frames * 2)); |     input_buffer += padding_frames * resample_frame_size; | ||||||
|  |  | ||||||
|     // Resampling! get the work buffer to float32 format, etc, in-place. |  | ||||||
|     ConvertAudio(work_buffer_frames, work_buffer, src_format, src_channels, work_buffer, SDL_AUDIO_F32, resample_channels, NULL); |  | ||||||
|  |  | ||||||
|     // Update the work_buffer pointers based on the new frame size |  | ||||||
|     input_buffer = work_buffer + ((input_buffer - work_buffer) / src_frame_size * resample_frame_size); |  | ||||||
|     work_buffer_tail = work_buffer + ((work_buffer_tail - work_buffer) / src_frame_size * resample_frame_size); |  | ||||||
|     SDL_assert((work_buffer_tail - work_buffer) <= work_buffer_capacity); |  | ||||||
|  |  | ||||||
|     // Decide where the resampled output goes |     // Decide where the resampled output goes | ||||||
|     void* resample_buffer = (resample_buffer_offset != -1) ? (work_buffer + resample_buffer_offset) : buf; |     void* resample_buffer = (resample_buffer_offset != -1) ? (work_buffer + resample_buffer_offset) : buf; | ||||||
| @@ -932,9 +873,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int ou | |||||||
|                   resample_rate, &stream->resample_offset); |                   resample_rate, &stream->resample_offset); | ||||||
|  |  | ||||||
|     // Convert to the final format, if necessary |     // Convert to the final format, if necessary | ||||||
|     if (buf != resample_buffer) { |     ConvertAudio(output_frames, resample_buffer, resample_format, resample_channels, buf, dst_format, dst_channels, work_buffer); | ||||||
|         ConvertAudio(output_frames, resample_buffer, SDL_AUDIO_F32, resample_channels, buf, dst_format, dst_channels, work_buffer); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
| @@ -1074,7 +1013,9 @@ int SDL_GetAudioStreamQueued(SDL_AudioStream *stream) | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     SDL_LockMutex(stream->lock); |     SDL_LockMutex(stream->lock); | ||||||
|     const Uint64 total = stream->total_bytes_queued; |  | ||||||
|  |     size_t total = SDL_GetAudioQueueQueued(stream->queue); | ||||||
|  |  | ||||||
|     SDL_UnlockMutex(stream->lock); |     SDL_UnlockMutex(stream->lock); | ||||||
|  |  | ||||||
|     // if this overflows an int, just clamp it to a maximum. |     // if this overflows an int, just clamp it to a maximum. | ||||||
| @@ -1092,7 +1033,6 @@ int SDL_ClearAudioStream(SDL_AudioStream *stream) | |||||||
|     SDL_ClearAudioQueue(stream->queue); |     SDL_ClearAudioQueue(stream->queue); | ||||||
|     SDL_zero(stream->input_spec); |     SDL_zero(stream->input_spec); | ||||||
|     stream->resample_offset = 0; |     stream->resample_offset = 0; | ||||||
|     stream->total_bytes_queued = 0; |  | ||||||
|  |  | ||||||
|     SDL_UnlockMutex(stream->lock); |     SDL_UnlockMutex(stream->lock); | ||||||
|     return 0; |     return 0; | ||||||
| @@ -1118,7 +1058,6 @@ void SDL_DestroyAudioStream(SDL_AudioStream *stream) | |||||||
|         SDL_UnbindAudioStream(stream); |         SDL_UnbindAudioStream(stream); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     SDL_aligned_free(stream->history_buffer); |  | ||||||
|     SDL_aligned_free(stream->work_buffer); |     SDL_aligned_free(stream->work_buffer); | ||||||
|     SDL_DestroyAudioQueue(stream->queue); |     SDL_DestroyAudioQueue(stream->queue); | ||||||
|     SDL_DestroyMutex(stream->lock); |     SDL_DestroyMutex(stream->lock); | ||||||
| @@ -1126,6 +1065,11 @@ void SDL_DestroyAudioStream(SDL_AudioStream *stream) | |||||||
|     SDL_free(stream); |     SDL_free(stream); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static void SDLCALL DontFreeThisAudioBuffer(void *userdata, const void *buf, int len) | ||||||
|  | { | ||||||
|  |     // We don't own the buffer, but know it will outlive the stream | ||||||
|  | } | ||||||
|  |  | ||||||
| int SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len, | int SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len, | ||||||
|                             const SDL_AudioSpec *dst_spec, Uint8 **dst_data, int *dst_len) |                             const SDL_AudioSpec *dst_spec, Uint8 **dst_data, int *dst_len) | ||||||
| { | { | ||||||
| @@ -1153,7 +1097,7 @@ int SDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data | |||||||
|  |  | ||||||
|     SDL_AudioStream *stream = SDL_CreateAudioStream(src_spec, dst_spec); |     SDL_AudioStream *stream = SDL_CreateAudioStream(src_spec, dst_spec); | ||||||
|     if (stream) { |     if (stream) { | ||||||
|         if ((SDL_PutAudioStreamData(stream, src_data, src_len) == 0) && (SDL_FlushAudioStream(stream) == 0)) { |         if ((PutAudioStreamBuffer(stream, src_data, src_len, DontFreeThisAudioBuffer, NULL) == 0) && (SDL_FlushAudioStream(stream) == 0)) { | ||||||
|             dstlen = SDL_GetAudioStreamAvailable(stream); |             dstlen = SDL_GetAudioStreamAvailable(stream); | ||||||
|             if (dstlen >= 0) { |             if (dstlen >= 0) { | ||||||
|                 dst = (Uint8 *)SDL_malloc(dstlen); |                 dst = (Uint8 *)SDL_malloc(dstlen); | ||||||
|   | |||||||
| @@ -21,253 +21,131 @@ | |||||||
| #include "SDL_internal.h" | #include "SDL_internal.h" | ||||||
|  |  | ||||||
| #include "SDL_audioqueue.h" | #include "SDL_audioqueue.h" | ||||||
|  | #include "SDL_sysaudio.h" | ||||||
|  |  | ||||||
| #define AUDIO_SPECS_EQUAL(x, y) (((x).format == (y).format) && ((x).channels == (y).channels) && ((x).freq == (y).freq)) | #define AUDIO_SPECS_EQUAL(x, y) (((x).format == (y).format) && ((x).channels == (y).channels) && ((x).freq == (y).freq)) | ||||||
|  |  | ||||||
|  | typedef struct SDL_MemoryPool SDL_MemoryPool; | ||||||
|  |  | ||||||
|  | struct SDL_MemoryPool | ||||||
|  | { | ||||||
|  |     void *free_blocks; | ||||||
|  |     size_t block_size; | ||||||
|  |     size_t num_free; | ||||||
|  |     size_t max_free; | ||||||
|  | }; | ||||||
|  |  | ||||||
| struct SDL_AudioTrack | struct SDL_AudioTrack | ||||||
| { | { | ||||||
|     SDL_AudioSpec spec; |     SDL_AudioSpec spec; | ||||||
|     SDL_bool flushed; |     SDL_bool flushed; | ||||||
|     SDL_AudioTrack *next; |     SDL_AudioTrack *next; | ||||||
|  |  | ||||||
|     size_t (*avail)(void *ctx); |     void *userdata; | ||||||
|     int (*write)(void *ctx, const Uint8 *buf, size_t len); |     SDL_ReleaseAudioBufferCallback callback; | ||||||
|     size_t (*read)(void *ctx, Uint8 *buf, size_t len, SDL_bool advance); |  | ||||||
|     void (*destroy)(void *ctx); |     Uint8 *data; | ||||||
|  |     size_t head; | ||||||
|  |     size_t tail; | ||||||
|  |     size_t capacity; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| struct SDL_AudioQueue | struct SDL_AudioQueue | ||||||
| { | { | ||||||
|     SDL_AudioTrack *head; |     SDL_AudioTrack *head; | ||||||
|     SDL_AudioTrack *tail; |     SDL_AudioTrack *tail; | ||||||
|     size_t chunk_size; |  | ||||||
|  |     Uint8 *history_buffer; | ||||||
|  |     size_t history_length; | ||||||
|  |     size_t history_capacity; | ||||||
|  |  | ||||||
|  |     SDL_MemoryPool track_pool; | ||||||
|  |     SDL_MemoryPool chunk_pool; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| typedef struct SDL_AudioChunk SDL_AudioChunk; | // Allocate a new block, avoiding checking for ones already in the pool | ||||||
|  | static void *AllocNewMemoryPoolBlock(const SDL_MemoryPool *pool) | ||||||
| struct SDL_AudioChunk |  | ||||||
| { | { | ||||||
|     SDL_AudioChunk *next; |     return SDL_malloc(pool->block_size); | ||||||
|     size_t head; |  | ||||||
|     size_t tail; |  | ||||||
|     Uint8 data[SDL_VARIABLE_LENGTH_ARRAY]; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| typedef struct SDL_ChunkedAudioTrack |  | ||||||
| { |  | ||||||
|     SDL_AudioTrack track; |  | ||||||
|  |  | ||||||
|     size_t chunk_size; |  | ||||||
|  |  | ||||||
|     SDL_AudioChunk *head; |  | ||||||
|     SDL_AudioChunk *tail; |  | ||||||
|     size_t queued_bytes; |  | ||||||
|  |  | ||||||
|     SDL_AudioChunk *free_chunks; |  | ||||||
|     size_t num_free_chunks; |  | ||||||
| } SDL_ChunkedAudioTrack; |  | ||||||
|  |  | ||||||
| static void DestroyAudioChunk(SDL_AudioChunk *chunk) |  | ||||||
| { |  | ||||||
|     SDL_free(chunk); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| static void DestroyAudioChunks(SDL_AudioChunk *chunk) | // Allocate a new block, first checking if there are any in the pool | ||||||
|  | static void *AllocMemoryPoolBlock(SDL_MemoryPool *pool) | ||||||
| { | { | ||||||
|     while (chunk) { |     if (pool->num_free == 0) { | ||||||
|         SDL_AudioChunk *next = chunk->next; |         return AllocNewMemoryPoolBlock(pool); | ||||||
|         DestroyAudioChunk(chunk); |  | ||||||
|         chunk = next; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static void ResetAudioChunk(SDL_AudioChunk *chunk) |  | ||||||
| { |  | ||||||
|     chunk->next = NULL; |  | ||||||
|     chunk->head = 0; |  | ||||||
|     chunk->tail = 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static SDL_AudioChunk *CreateAudioChunk(size_t chunk_size) |  | ||||||
| { |  | ||||||
|     SDL_AudioChunk *chunk = (SDL_AudioChunk *)SDL_malloc(sizeof(*chunk) + chunk_size); |  | ||||||
|  |  | ||||||
|     if (!chunk) { |  | ||||||
|         return NULL; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     ResetAudioChunk(chunk); |     void *block = pool->free_blocks; | ||||||
|  |     pool->free_blocks = *(void **)block; | ||||||
|     return chunk; |     --pool->num_free; | ||||||
|  |     return block; | ||||||
| } | } | ||||||
|  |  | ||||||
| static void DestroyAudioTrackChunk(SDL_ChunkedAudioTrack *track, SDL_AudioChunk *chunk) | // Free a block, or add it to the pool if there's room | ||||||
|  | static void FreeMemoryPoolBlock(SDL_MemoryPool *pool, void *block) | ||||||
| { | { | ||||||
|     // Keeping a list of free chunks reduces memory allocations, |     if (pool->num_free < pool->max_free) { | ||||||
|     // But also increases the amount of work to perform when freeing the track. |         *(void **)block = pool->free_blocks; | ||||||
|     const size_t max_free_bytes = 64 * 1024; |         pool->free_blocks = block; | ||||||
|  |         ++pool->num_free; | ||||||
|     if (track->chunk_size * track->num_free_chunks < max_free_bytes) { |  | ||||||
|         chunk->next = track->free_chunks; |  | ||||||
|         track->free_chunks = chunk; |  | ||||||
|         ++track->num_free_chunks; |  | ||||||
|     } else { |     } else { | ||||||
|         DestroyAudioChunk(chunk); |         SDL_free(block); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| static SDL_AudioChunk *CreateAudioTrackChunk(SDL_ChunkedAudioTrack *track) | // Destroy a pool and all of its blocks | ||||||
|  | static void DestroyMemoryPool(SDL_MemoryPool *pool) | ||||||
| { | { | ||||||
|     if (track->num_free_chunks > 0) { |     void *block = pool->free_blocks; | ||||||
|         SDL_AudioChunk *chunk = track->free_chunks; |     pool->free_blocks = NULL; | ||||||
|  |     pool->num_free = 0; | ||||||
|  |  | ||||||
|         track->free_chunks = chunk->next; |     while (block) { | ||||||
|         --track->num_free_chunks; |         void *next = *(void **)block; | ||||||
|  |         SDL_free(block); | ||||||
|         ResetAudioChunk(chunk); |         block = next; | ||||||
|  |  | ||||||
|         return chunk; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return CreateAudioChunk(track->chunk_size); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| static size_t AvailChunkedAudioTrack(void *ctx) | // Keeping a list of free chunks reduces memory allocations, | ||||||
|  | // But also increases the amount of work to perform when freeing the track. | ||||||
|  | static void InitMemoryPool(SDL_MemoryPool *pool, size_t block_size, size_t max_free) | ||||||
| { | { | ||||||
|     SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)ctx; |     SDL_zerop(pool); | ||||||
|  |  | ||||||
|     return track->queued_bytes; |     SDL_assert(block_size >= sizeof(void *)); | ||||||
|  |     pool->block_size = block_size; | ||||||
|  |     pool->max_free = max_free; | ||||||
| } | } | ||||||
|  |  | ||||||
| static int WriteToChunkedAudioTrack(void *ctx, const Uint8 *data, size_t len) | // Allocates a number of blocks and adds them to the pool | ||||||
|  | static int ReserveMemoryPoolBlocks(SDL_MemoryPool *pool, size_t num_blocks) | ||||||
| { | { | ||||||
|     SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)ctx; |     for (; num_blocks; --num_blocks) { | ||||||
|  |         void *block = AllocNewMemoryPoolBlock(pool); | ||||||
|  |  | ||||||
|     SDL_AudioChunk *chunk = track->tail; |         if (block == NULL) { | ||||||
|  |             return SDL_OutOfMemory(); | ||||||
|     // Handle the first chunk |  | ||||||
|     if (!chunk) { |  | ||||||
|         chunk = CreateAudioTrackChunk(track); |  | ||||||
|  |  | ||||||
|         if (!chunk) { |  | ||||||
|             return -1; |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         SDL_assert((track->head == NULL) && (track->tail == NULL) && (track->queued_bytes == 0)); |         *(void **)block = pool->free_blocks; | ||||||
|         track->head = chunk; |         pool->free_blocks = block; | ||||||
|         track->tail = chunk; |         ++pool->num_free; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     size_t total = 0; |  | ||||||
|     size_t old_tail = chunk->tail; |  | ||||||
|     size_t chunk_size = track->chunk_size; |  | ||||||
|  |  | ||||||
|     while (chunk) { |  | ||||||
|         size_t to_write = chunk_size - chunk->tail; |  | ||||||
|         to_write = SDL_min(to_write, len - total); |  | ||||||
|         SDL_memcpy(&chunk->data[chunk->tail], &data[total], to_write); |  | ||||||
|         total += to_write; |  | ||||||
|  |  | ||||||
|         chunk->tail += to_write; |  | ||||||
|  |  | ||||||
|         if (total == len) { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         SDL_AudioChunk *next = CreateAudioTrackChunk(track); |  | ||||||
|         chunk->next = next; |  | ||||||
|         chunk = next; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Roll back the changes if we couldn't write all the data |  | ||||||
|     if (!chunk) { |  | ||||||
|         chunk = track->tail; |  | ||||||
|  |  | ||||||
|         SDL_AudioChunk *next = chunk->next; |  | ||||||
|         chunk->next = NULL; |  | ||||||
|         chunk->tail = old_tail; |  | ||||||
|  |  | ||||||
|         DestroyAudioChunks(next); |  | ||||||
|  |  | ||||||
|         return -1; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     track->tail = chunk; |  | ||||||
|     track->queued_bytes += total; |  | ||||||
|  |  | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| static size_t ReadFromChunkedAudioTrack(void *ctx, Uint8 *data, size_t len, SDL_bool advance) | void SDL_DestroyAudioQueue(SDL_AudioQueue *queue) | ||||||
| { | { | ||||||
|     SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)ctx; |     SDL_ClearAudioQueue(queue); | ||||||
|     SDL_AudioChunk *chunk = track->head; |  | ||||||
|  |  | ||||||
|     size_t total = 0; |     DestroyMemoryPool(&queue->track_pool); | ||||||
|     size_t head = 0; |     DestroyMemoryPool(&queue->chunk_pool); | ||||||
|  |     SDL_aligned_free(queue->history_buffer); | ||||||
|  |  | ||||||
|     while (chunk) { |     SDL_free(queue); | ||||||
|         head = chunk->head; |  | ||||||
|  |  | ||||||
|         size_t to_read = chunk->tail - head; |  | ||||||
|         to_read = SDL_min(to_read, len - total); |  | ||||||
|         SDL_memcpy(&data[total], &chunk->data[head], to_read); |  | ||||||
|         total += to_read; |  | ||||||
|  |  | ||||||
|         SDL_AudioChunk *next = chunk->next; |  | ||||||
|  |  | ||||||
|         if (total == len) { |  | ||||||
|             head += to_read; |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (advance) { |  | ||||||
|             DestroyAudioTrackChunk(track, chunk); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         chunk = next; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (advance) { |  | ||||||
|         if (chunk) { |  | ||||||
|             chunk->head = head; |  | ||||||
|             track->head = chunk; |  | ||||||
|         } else { |  | ||||||
|             track->head = NULL; |  | ||||||
|             track->tail = NULL; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         track->queued_bytes -= total; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return total; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static void DestroyChunkedAudioTrack(void *ctx) |  | ||||||
| { |  | ||||||
|     SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)ctx; |  | ||||||
|     DestroyAudioChunks(track->head); |  | ||||||
|     DestroyAudioChunks(track->free_chunks); |  | ||||||
|     SDL_free(track); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static SDL_AudioTrack *CreateChunkedAudioTrack(const SDL_AudioSpec *spec, size_t chunk_size) |  | ||||||
| { |  | ||||||
|     SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)SDL_calloc(1, sizeof(*track)); |  | ||||||
|  |  | ||||||
|     if (!track) { |  | ||||||
|         return NULL; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     SDL_copyp(&track->track.spec, spec); |  | ||||||
|     track->track.avail = AvailChunkedAudioTrack; |  | ||||||
|     track->track.write = WriteToChunkedAudioTrack; |  | ||||||
|     track->track.read = ReadFromChunkedAudioTrack; |  | ||||||
|     track->track.destroy = DestroyChunkedAudioTrack; |  | ||||||
|  |  | ||||||
|     track->chunk_size = chunk_size; |  | ||||||
|  |  | ||||||
|     return &track->track; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size) | SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size) | ||||||
| @@ -278,35 +156,42 @@ SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size) | |||||||
|         return NULL; |         return NULL; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     queue->chunk_size = chunk_size; |     InitMemoryPool(&queue->track_pool, sizeof(SDL_AudioTrack), 8); | ||||||
|  |     InitMemoryPool(&queue->chunk_pool, chunk_size, 4); | ||||||
|  |  | ||||||
|  |     if (ReserveMemoryPoolBlocks(&queue->track_pool, 2) != 0) { | ||||||
|  |         SDL_DestroyAudioQueue(queue); | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return queue; |     return queue; | ||||||
| } | } | ||||||
|  |  | ||||||
| void SDL_DestroyAudioQueue(SDL_AudioQueue *queue) | static void DestroyAudioTrack(SDL_AudioQueue *queue, SDL_AudioTrack *track) | ||||||
| { | { | ||||||
|     SDL_ClearAudioQueue(queue); |     track->callback(track->userdata, track->data, (int)track->capacity); | ||||||
|  |  | ||||||
|     SDL_free(queue); |     FreeMemoryPoolBlock(&queue->track_pool, track); | ||||||
| } | } | ||||||
|  |  | ||||||
| void SDL_ClearAudioQueue(SDL_AudioQueue *queue) | void SDL_ClearAudioQueue(SDL_AudioQueue *queue) | ||||||
| { | { | ||||||
|     SDL_AudioTrack *track = queue->head; |     SDL_AudioTrack *track = queue->head; | ||||||
|  |  | ||||||
|     queue->head = NULL; |     queue->head = NULL; | ||||||
|     queue->tail = NULL; |     queue->tail = NULL; | ||||||
|  |     queue->history_length = 0; | ||||||
|  |  | ||||||
|     while (track) { |     while (track) { | ||||||
|         SDL_AudioTrack *next = track->next; |         SDL_AudioTrack *next = track->next; | ||||||
|         track->destroy(track); |         DestroyAudioTrack(queue, track); | ||||||
|         track = next; |         track = next; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| static void SDL_FlushAudioTrack(SDL_AudioTrack *track) | static void FlushAudioTrack(SDL_AudioTrack *track) | ||||||
| { | { | ||||||
|     track->flushed = SDL_TRUE; |     track->flushed = SDL_TRUE; | ||||||
|     track->write = NULL; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void SDL_FlushAudioQueue(SDL_AudioQueue *queue) | void SDL_FlushAudioQueue(SDL_AudioQueue *queue) | ||||||
| @@ -314,7 +199,7 @@ void SDL_FlushAudioQueue(SDL_AudioQueue *queue) | |||||||
|     SDL_AudioTrack *track = queue->tail; |     SDL_AudioTrack *track = queue->tail; | ||||||
|  |  | ||||||
|     if (track) { |     if (track) { | ||||||
|         SDL_FlushAudioTrack(track); |         FlushAudioTrack(track); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -326,7 +211,7 @@ void SDL_PopAudioQueueHead(SDL_AudioQueue *queue) | |||||||
|         SDL_bool flushed = track->flushed; |         SDL_bool flushed = track->flushed; | ||||||
|  |  | ||||||
|         SDL_AudioTrack *next = track->next; |         SDL_AudioTrack *next = track->next; | ||||||
|         track->destroy(track); |         DestroyAudioTrack(queue, track); | ||||||
|         track = next; |         track = next; | ||||||
|  |  | ||||||
|         if (flushed) { |         if (flushed) { | ||||||
| @@ -335,27 +220,59 @@ void SDL_PopAudioQueueHead(SDL_AudioQueue *queue) | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     queue->head = track; |     queue->head = track; | ||||||
|  |     queue->history_length = 0; | ||||||
|  |  | ||||||
|     if (!track) { |     if (!track) { | ||||||
|         queue->tail = NULL; |         queue->tail = NULL; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| size_t SDL_GetAudioQueueChunkSize(SDL_AudioQueue *queue) | SDL_AudioTrack *SDL_CreateAudioTrack( | ||||||
|  |     SDL_AudioQueue *queue, const SDL_AudioSpec *spec, | ||||||
|  |     Uint8 *data, size_t len, size_t capacity, | ||||||
|  |     SDL_ReleaseAudioBufferCallback callback, void *userdata) | ||||||
| { | { | ||||||
|     return queue->chunk_size; |     SDL_AudioTrack *track = AllocMemoryPoolBlock(&queue->track_pool); | ||||||
| } |  | ||||||
|  |  | ||||||
| SDL_AudioTrack *SDL_CreateChunkedAudioTrack(const SDL_AudioSpec *spec, const Uint8 *data, size_t len, size_t chunk_size) |  | ||||||
| { |  | ||||||
|     SDL_AudioTrack *track = CreateChunkedAudioTrack(spec, chunk_size); |  | ||||||
|  |  | ||||||
|     if (!track) { |     if (!track) { | ||||||
|         return NULL; |         return NULL; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (track->write(track, data, len) != 0) { |     SDL_zerop(track); | ||||||
|         track->destroy(track); |     SDL_copyp(&track->spec, spec); | ||||||
|  |  | ||||||
|  |     track->userdata = userdata; | ||||||
|  |     track->callback = callback; | ||||||
|  |     track->data = data; | ||||||
|  |     track->head = 0; | ||||||
|  |     track->tail = len; | ||||||
|  |     track->capacity = capacity; | ||||||
|  |  | ||||||
|  |     return track; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void SDLCALL FreeChunkedAudioBuffer(void *userdata, const void *buf, int len) | ||||||
|  | { | ||||||
|  |     SDL_AudioQueue *queue = userdata; | ||||||
|  |  | ||||||
|  |     FreeMemoryPoolBlock(&queue->chunk_pool, (void *)buf); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static SDL_AudioTrack *CreateChunkedAudioTrack(SDL_AudioQueue *queue, const SDL_AudioSpec *spec) | ||||||
|  | { | ||||||
|  |     void *chunk = AllocMemoryPoolBlock(&queue->chunk_pool); | ||||||
|  |  | ||||||
|  |     if (!chunk) { | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     size_t capacity = queue->chunk_pool.block_size; | ||||||
|  |     capacity -= capacity % SDL_AUDIO_FRAMESIZE(*spec); | ||||||
|  |  | ||||||
|  |     SDL_AudioTrack *track = SDL_CreateAudioTrack(queue, spec, chunk, 0, capacity, FreeChunkedAudioBuffer, queue); | ||||||
|  |  | ||||||
|  |     if (!track) { | ||||||
|  |         FreeMemoryPoolBlock(&queue->chunk_pool, chunk); | ||||||
|         return NULL; |         return NULL; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -369,7 +286,7 @@ void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track) | |||||||
|     if (tail) { |     if (tail) { | ||||||
|         // If the spec has changed, make sure to flush the previous track |         // If the spec has changed, make sure to flush the previous track | ||||||
|         if (!AUDIO_SPECS_EQUAL(tail->spec, track->spec)) { |         if (!AUDIO_SPECS_EQUAL(tail->spec, track->spec)) { | ||||||
|             SDL_FlushAudioTrack(tail); |             FlushAudioTrack(tail); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         tail->next = track; |         tail->next = track; | ||||||
| @@ -380,6 +297,19 @@ void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track) | |||||||
|     queue->tail = track; |     queue->tail = track; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static size_t WriteToAudioTrack(SDL_AudioTrack *track, const Uint8 *data, size_t len) | ||||||
|  | { | ||||||
|  |     if (track->flushed || track->tail >= track->capacity) { | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     len = SDL_min(len, track->capacity - track->tail); | ||||||
|  |     SDL_memcpy(&track->data[track->tail], data, len); | ||||||
|  |     track->tail += len; | ||||||
|  |  | ||||||
|  |     return len; | ||||||
|  | } | ||||||
|  |  | ||||||
| int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const Uint8 *data, size_t len) | int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const Uint8 *data, size_t len) | ||||||
| { | { | ||||||
|     if (len == 0) { |     if (len == 0) { | ||||||
| @@ -388,29 +318,43 @@ int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, cons | |||||||
|  |  | ||||||
|     SDL_AudioTrack *track = queue->tail; |     SDL_AudioTrack *track = queue->tail; | ||||||
|  |  | ||||||
|     if ((track) && !AUDIO_SPECS_EQUAL(track->spec, *spec)) { |     if (track) { | ||||||
|         SDL_FlushAudioTrack(track); |         if (!AUDIO_SPECS_EQUAL(track->spec, *spec)) { | ||||||
|  |             FlushAudioTrack(track); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         SDL_assert(!queue->head); | ||||||
|  |         track = CreateChunkedAudioTrack(queue, spec); | ||||||
|  |  | ||||||
|  |         if (!track) { | ||||||
|  |             return -1; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         queue->head = track; | ||||||
|  |         queue->tail = track; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if ((!track) || (!track->write)) { |     for (;;) { | ||||||
|         SDL_AudioTrack *new_track = CreateChunkedAudioTrack(spec, queue->chunk_size); |         size_t written = WriteToAudioTrack(track, data, len); | ||||||
|  |         data += written; | ||||||
|  |         len -= written; | ||||||
|  |  | ||||||
|  |         if (len == 0) { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         SDL_AudioTrack *new_track = CreateChunkedAudioTrack(queue, spec); | ||||||
|  |  | ||||||
|         if (!new_track) { |         if (!new_track) { | ||||||
|             return -1; |             return -1; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (track) { |         track->next = new_track; | ||||||
|             track->next = new_track; |  | ||||||
|         } else { |  | ||||||
|             queue->head = new_track; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         queue->tail = new_track; |         queue->tail = new_track; | ||||||
|  |  | ||||||
|         track = new_track; |         track = new_track; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return track->write(track, data, len); |     return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue) | void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue) | ||||||
| @@ -432,7 +376,7 @@ size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_Audi | |||||||
|         SDL_AudioTrack *track = iter; |         SDL_AudioTrack *track = iter; | ||||||
|         iter = iter->next; |         iter = iter->next; | ||||||
|  |  | ||||||
|         size_t avail = track->avail(track); |         size_t avail = track->tail - track->head; | ||||||
|  |  | ||||||
|         if (avail >= SDL_SIZE_MAX - queued_bytes) { |         if (avail >= SDL_SIZE_MAX - queued_bytes) { | ||||||
|             queued_bytes = SDL_SIZE_MAX; |             queued_bytes = SDL_SIZE_MAX; | ||||||
| @@ -454,61 +398,244 @@ size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_Audi | |||||||
|     return queued_bytes; |     return queued_bytes; | ||||||
| } | } | ||||||
|  |  | ||||||
| int SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len) | static const Uint8 *PeekIntoAudioQueuePast(SDL_AudioQueue *queue, Uint8 *data, size_t len) | ||||||
| { | { | ||||||
|     size_t total = 0; |  | ||||||
|     SDL_AudioTrack *track = queue->head; |     SDL_AudioTrack *track = queue->head; | ||||||
|  |  | ||||||
|     for (;;) { |     if (track->head >= len) { | ||||||
|         if (!track) { |         return &track->data[track->head - len]; | ||||||
|             return SDL_SetError("Reading past end of queue"); |     } | ||||||
|         } |  | ||||||
|  |  | ||||||
|         total += track->read(track, &data[total], len - total, SDL_TRUE); |     size_t past = len - track->head; | ||||||
|  |  | ||||||
|  |     if (past > queue->history_length) { | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     SDL_memcpy(data, &queue->history_buffer[queue->history_length - past], past); | ||||||
|  |     SDL_memcpy(&data[past], track->data, track->head); | ||||||
|  |  | ||||||
|  |     return data; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void UpdateAudioQueueHistory(SDL_AudioQueue *queue, | ||||||
|  |                                     const Uint8 *data, size_t len) | ||||||
|  | { | ||||||
|  |     Uint8 *history_buffer = queue->history_buffer; | ||||||
|  |     size_t history_bytes = queue->history_length; | ||||||
|  |  | ||||||
|  |     if (len >= history_bytes) { | ||||||
|  |         SDL_memcpy(history_buffer, &data[len - history_bytes], history_bytes); | ||||||
|  |     } else { | ||||||
|  |         size_t preserve = history_bytes - len; | ||||||
|  |         SDL_memmove(history_buffer, &history_buffer[len], preserve); | ||||||
|  |         SDL_memcpy(&history_buffer[preserve], data, len); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static const Uint8 *ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len) | ||||||
|  | { | ||||||
|  |     SDL_AudioTrack *track = queue->head; | ||||||
|  |  | ||||||
|  |     if (track->tail - track->head >= len) { | ||||||
|  |         const Uint8 *ptr = &track->data[track->head]; | ||||||
|  |         track->head += len; | ||||||
|  |         return ptr; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     size_t total = 0; | ||||||
|  |  | ||||||
|  |     for (;;) { | ||||||
|  |         size_t avail = SDL_min(len - total, track->tail - track->head); | ||||||
|  |         SDL_memcpy(&data[total], &track->data[track->head], avail); | ||||||
|  |         track->head += avail; | ||||||
|  |         total += avail; | ||||||
|  |  | ||||||
|         if (total == len) { |         if (total == len) { | ||||||
|             return 0; |             break; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (track->flushed) { |         if (track->flushed) { | ||||||
|             return SDL_SetError("Reading past end of flushed track"); |             SDL_SetError("Reading past end of flushed track"); | ||||||
|  |             return NULL; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         SDL_AudioTrack *next = track->next; |         SDL_AudioTrack *next = track->next; | ||||||
|  |  | ||||||
|         if (!next) { |         if (!next) { | ||||||
|             return SDL_SetError("Reading past end of incomplete track"); |             SDL_SetError("Reading past end of incomplete track"); | ||||||
|  |             return NULL; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         UpdateAudioQueueHistory(queue, track->data, track->tail); | ||||||
|  |  | ||||||
|         queue->head = next; |         queue->head = next; | ||||||
|  |         DestroyAudioTrack(queue, track); | ||||||
|         track->destroy(track); |  | ||||||
|         track = next; |         track = next; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     return data; | ||||||
| } | } | ||||||
|  |  | ||||||
| int SDL_PeekIntoAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len) | static const Uint8 *PeekIntoAudioQueueFuture(SDL_AudioQueue *queue, Uint8 *data, size_t len) | ||||||
| { | { | ||||||
|     size_t total = 0; |  | ||||||
|     SDL_AudioTrack *track = queue->head; |     SDL_AudioTrack *track = queue->head; | ||||||
|  |  | ||||||
|     for (;;) { |     if (track->tail - track->head >= len) { | ||||||
|         if (!track) { |         return &track->data[track->head]; | ||||||
|             return SDL_SetError("Peeking past end of queue"); |     } | ||||||
|         } |  | ||||||
|  |  | ||||||
|         total += track->read(track, &data[total], len - total, SDL_FALSE); |     size_t total = 0; | ||||||
|  |  | ||||||
|  |     for (;;) { | ||||||
|  |         size_t avail = SDL_min(len - total, track->tail - track->head); | ||||||
|  |         SDL_memcpy(&data[total], &track->data[track->head], avail); | ||||||
|  |         total += avail; | ||||||
|  |  | ||||||
|         if (total == len) { |         if (total == len) { | ||||||
|             return 0; |             break; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (track->flushed) { |         if (track->flushed) { | ||||||
|             // If we have run out of data, fill the rest with silence. |             // If we have run out of data, fill the rest with silence. | ||||||
|             SDL_memset(&data[total], SDL_GetSilenceValueForFormat(track->spec.format), len - total); |             SDL_memset(&data[total], SDL_GetSilenceValueForFormat(track->spec.format), len - total); | ||||||
|             return 0; |             break; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         track = track->next; |         track = track->next; | ||||||
|  |  | ||||||
|  |         if (!track) { | ||||||
|  |             SDL_SetError("Peeking past end of incomplete track"); | ||||||
|  |             return NULL; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     return data; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, | ||||||
|  |                                     Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels, | ||||||
|  |                                     int past_frames, int present_frames, int future_frames, | ||||||
|  |                                     Uint8 *scratch) | ||||||
|  | { | ||||||
|  |     SDL_AudioTrack *track = queue->head; | ||||||
|  |  | ||||||
|  |     if (!track) { | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     SDL_AudioFormat src_format = track->spec.format; | ||||||
|  |     SDL_AudioFormat src_channels = track->spec.channels; | ||||||
|  |  | ||||||
|  |     size_t src_frame_size = SDL_AUDIO_BYTESIZE(src_format) * src_channels; | ||||||
|  |     size_t dst_frame_size = SDL_AUDIO_BYTESIZE(dst_format) * dst_channels; | ||||||
|  |  | ||||||
|  |     size_t src_past_bytes = past_frames * src_frame_size; | ||||||
|  |     size_t src_present_bytes = present_frames * src_frame_size; | ||||||
|  |     size_t src_future_bytes = future_frames * src_frame_size; | ||||||
|  |  | ||||||
|  |     size_t dst_past_bytes = past_frames * dst_frame_size; | ||||||
|  |     size_t dst_present_bytes = present_frames * dst_frame_size; | ||||||
|  |     size_t dst_future_bytes = future_frames * dst_frame_size; | ||||||
|  |  | ||||||
|  |     SDL_bool convert = (src_format != dst_format) || (src_channels != dst_channels); | ||||||
|  |  | ||||||
|  |     if (convert && !dst) { | ||||||
|  |         // The user didn't ask for the data to be copied, but we need to convert it, so store it in the scratch buffer | ||||||
|  |         dst = scratch; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Can we get all of the data straight from this track? | ||||||
|  |     if ((track->head >= src_past_bytes) && ((track->tail - track->head) >= (src_present_bytes + src_future_bytes))) { | ||||||
|  |         const Uint8 *ptr = &track->data[track->head - src_past_bytes]; | ||||||
|  |         track->head += src_present_bytes; | ||||||
|  |  | ||||||
|  |         // Do we still need to copy/convert the data? | ||||||
|  |         if (dst) { | ||||||
|  |             ConvertAudio(past_frames + present_frames + future_frames, ptr, | ||||||
|  |                          src_format, src_channels, dst, dst_format, dst_channels, scratch); | ||||||
|  |             ptr = dst; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return ptr; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (!dst) { | ||||||
|  |         // The user didn't ask for the data to be copied, but we need to, so store it in the scratch buffer | ||||||
|  |         dst = scratch; | ||||||
|  |     } else if (!convert) { | ||||||
|  |         // We are only copying, not converting, so copy straight into the dst buffer | ||||||
|  |         scratch = dst; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Uint8 *ptr = dst; | ||||||
|  |  | ||||||
|  |     if (src_past_bytes) { | ||||||
|  |         ConvertAudio(past_frames, PeekIntoAudioQueuePast(queue, scratch, src_past_bytes), src_format, src_channels, dst, dst_format, dst_channels, scratch); | ||||||
|  |         dst += dst_past_bytes; | ||||||
|  |         scratch += dst_past_bytes; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (src_present_bytes) { | ||||||
|  |         ConvertAudio(present_frames, ReadFromAudioQueue(queue, scratch, src_present_bytes), src_format, src_channels, dst, dst_format, dst_channels, scratch); | ||||||
|  |         dst += dst_present_bytes; | ||||||
|  |         scratch += dst_present_bytes; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (src_future_bytes) { | ||||||
|  |         ConvertAudio(future_frames, PeekIntoAudioQueueFuture(queue, scratch, src_future_bytes), src_format, src_channels, dst, dst_format, dst_channels, scratch); | ||||||
|  |         dst += dst_future_bytes; | ||||||
|  |         scratch += dst_future_bytes; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return ptr; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | size_t SDL_GetAudioQueueQueued(SDL_AudioQueue *queue) | ||||||
|  | { | ||||||
|  |     size_t total = 0; | ||||||
|  |     void *iter = SDL_BeginAudioQueueIter(queue); | ||||||
|  |  | ||||||
|  |     while (iter) { | ||||||
|  |         SDL_AudioSpec src_spec; | ||||||
|  |         SDL_bool flushed; | ||||||
|  |  | ||||||
|  |         size_t avail = SDL_NextAudioQueueIter(queue, &iter, &src_spec, &flushed); | ||||||
|  |  | ||||||
|  |         if (avail >= SDL_SIZE_MAX - total) { | ||||||
|  |             total = SDL_SIZE_MAX; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         total += avail; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return total; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int SDL_ResetAudioQueueHistory(SDL_AudioQueue *queue, int num_frames) | ||||||
|  | { | ||||||
|  |     SDL_AudioTrack *track = queue->head; | ||||||
|  |  | ||||||
|  |     if (!track) { | ||||||
|  |         return -1; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     size_t length = num_frames * SDL_AUDIO_FRAMESIZE(track->spec); | ||||||
|  |     Uint8 *history_buffer = queue->history_buffer; | ||||||
|  |  | ||||||
|  |     if (queue->history_capacity < length) { | ||||||
|  |         history_buffer = SDL_aligned_alloc(SDL_SIMDGetAlignment(), length); | ||||||
|  |         if (!history_buffer) { | ||||||
|  |             return -1; | ||||||
|  |         } | ||||||
|  |         SDL_aligned_free(queue->history_buffer); | ||||||
|  |         queue->history_buffer = history_buffer; | ||||||
|  |         queue->history_capacity = length; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     queue->history_length = length; | ||||||
|  |     SDL_memset(history_buffer, SDL_GetSilenceValueForFormat(track->spec.format), length); | ||||||
|  |  | ||||||
|  |     return 0; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,6 +25,8 @@ | |||||||
|  |  | ||||||
| // Internal functions used by SDL_AudioStream for queueing audio. | // Internal functions used by SDL_AudioStream for queueing audio. | ||||||
|  |  | ||||||
|  | typedef void(SDLCALL *SDL_ReleaseAudioBufferCallback)(void *userdata, const void *buffer, int buflen); | ||||||
|  |  | ||||||
| typedef struct SDL_AudioQueue SDL_AudioQueue; | typedef struct SDL_AudioQueue SDL_AudioQueue; | ||||||
| typedef struct SDL_AudioTrack SDL_AudioTrack; | typedef struct SDL_AudioTrack SDL_AudioTrack; | ||||||
|  |  | ||||||
| @@ -44,16 +46,14 @@ void SDL_FlushAudioQueue(SDL_AudioQueue *queue); | |||||||
| // REQUIRES: The head track must exist, and must have been flushed | // REQUIRES: The head track must exist, and must have been flushed | ||||||
| void SDL_PopAudioQueueHead(SDL_AudioQueue *queue); | void SDL_PopAudioQueueHead(SDL_AudioQueue *queue); | ||||||
|  |  | ||||||
| // Get the chunk size, mostly for use with SDL_CreateChunkedAudioTrack |  | ||||||
| // This can be called from any thread |  | ||||||
| size_t SDL_GetAudioQueueChunkSize(SDL_AudioQueue *queue); |  | ||||||
|  |  | ||||||
| // Write data to the end of queue | // Write data to the end of queue | ||||||
| // REQUIRES: If the spec has changed, the last track must have been flushed | // REQUIRES: If the spec has changed, the last track must have been flushed | ||||||
| int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const Uint8 *data, size_t len); | int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const Uint8 *data, size_t len); | ||||||
|  |  | ||||||
| // Create a track without needing to hold any locks | // Create a track where the input data is owned by the caller | ||||||
| SDL_AudioTrack *SDL_CreateChunkedAudioTrack(const SDL_AudioSpec *spec, const Uint8 *data, size_t len, size_t chunk_size); | SDL_AudioTrack *SDL_CreateAudioTrack(SDL_AudioQueue *queue, | ||||||
|  |                                      const SDL_AudioSpec *spec, Uint8 *data, size_t len, size_t capacity, | ||||||
|  |                                      SDL_ReleaseAudioBufferCallback callback, void *userdata); | ||||||
|  |  | ||||||
| // Add a track to the end of the queue | // Add a track to the end of the queue | ||||||
| // REQUIRES: `track != NULL` | // REQUIRES: `track != NULL` | ||||||
| @@ -66,12 +66,14 @@ void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue); | |||||||
| // REQUIRES: `*inout_iter != NULL` (a valid iterator) | // REQUIRES: `*inout_iter != NULL` (a valid iterator) | ||||||
| size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, SDL_bool *out_flushed); | size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, SDL_bool *out_flushed); | ||||||
|  |  | ||||||
| // Read data from the start of the queue | const Uint8 *SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, | ||||||
| // REQUIRES: There must be enough data in the queue |                                     Uint8 *dst, SDL_AudioFormat dst_format, int dst_channels, | ||||||
| int SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len); |                                     int past_frames, int present_frames, int future_frames, | ||||||
|  |                                     Uint8 *scratch); | ||||||
|  |  | ||||||
| // Peek into the start of the queue | // Get the total number of bytes currently queued | ||||||
| // REQUIRES: There must be enough data in the queue, unless it has been flushed, in which case missing data is filled with silence. | size_t SDL_GetAudioQueueQueued(SDL_AudioQueue *queue); | ||||||
| int SDL_PeekIntoAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len); |  | ||||||
|  | int SDL_ResetAudioQueueHistory(SDL_AudioQueue *queue, int num_frames); | ||||||
|  |  | ||||||
| #endif // SDL_audioqueue_h_ | #endif // SDL_audioqueue_h_ | ||||||
|   | |||||||
| @@ -190,7 +190,6 @@ struct SDL_AudioStream | |||||||
|     float freq_ratio; |     float freq_ratio; | ||||||
|  |  | ||||||
|     struct SDL_AudioQueue* queue; |     struct SDL_AudioQueue* queue; | ||||||
|     Uint64 total_bytes_queued; |  | ||||||
|  |  | ||||||
|     SDL_AudioSpec input_spec; // The spec of input data currently being processed |     SDL_AudioSpec input_spec; // The spec of input data currently being processed | ||||||
|     Sint64 resample_offset; |     Sint64 resample_offset; | ||||||
| @@ -198,9 +197,6 @@ struct SDL_AudioStream | |||||||
|     Uint8 *work_buffer;    // used for scratch space during data conversion/resampling. |     Uint8 *work_buffer;    // used for scratch space during data conversion/resampling. | ||||||
|     size_t work_buffer_allocation; |     size_t work_buffer_allocation; | ||||||
|  |  | ||||||
|     Uint8 *history_buffer;  // history for left padding and future sample rate changes. |  | ||||||
|     size_t history_buffer_allocation; |  | ||||||
|  |  | ||||||
|     SDL_bool simplified;  // SDL_TRUE if created via SDL_OpenAudioDeviceStream |     SDL_bool simplified;  // SDL_TRUE if created via SDL_OpenAudioDeviceStream | ||||||
|  |  | ||||||
|     SDL_LogicalAudioDevice *bound_device; |     SDL_LogicalAudioDevice *bound_device; | ||||||
|   | |||||||
| @@ -10,8 +10,6 @@ | |||||||
|   freely. |   freely. | ||||||
| */ | */ | ||||||
|  |  | ||||||
| /* !!! FIXME: this code is not up to standards for SDL3 test apps. Someone should improve this. */ |  | ||||||
|  |  | ||||||
| #include <SDL3/SDL.h> | #include <SDL3/SDL.h> | ||||||
| #include <SDL3/SDL_main.h> | #include <SDL3/SDL_main.h> | ||||||
| #include <SDL3/SDL_test.h> | #include <SDL3/SDL_test.h> | ||||||
| @@ -117,6 +115,7 @@ static void queue_audio() | |||||||
|  |  | ||||||
|     SDL_Log("Converting audio from %i to %i", spec.freq, new_spec.freq); |     SDL_Log("Converting audio from %i to %i", spec.freq, new_spec.freq); | ||||||
|  |  | ||||||
|  |     /* You shouldn't actually use SDL_ConvertAudioSamples like this (just put the data straight into the stream and let it handle conversion) */ | ||||||
|     retval = retval ? retval : SDL_ConvertAudioSamples(&spec, audio_buf, audio_len, &new_spec, &new_data, &new_len); |     retval = retval ? retval : SDL_ConvertAudioSamples(&spec, audio_buf, audio_len, &new_spec, &new_data, &new_len); | ||||||
|     retval = retval ? retval : SDL_SetAudioStreamFormat(stream, &new_spec, NULL); |     retval = retval ? retval : SDL_SetAudioStreamFormat(stream, &new_spec, NULL); | ||||||
|     retval = retval ? retval : SDL_PutAudioStreamData(stream, new_data, new_len); |     retval = retval ? retval : SDL_PutAudioStreamData(stream, new_data, new_len); | ||||||
| @@ -207,6 +206,7 @@ static void loop(void) | |||||||
|     SDL_Event e; |     SDL_Event e; | ||||||
|     SDL_FPoint p; |     SDL_FPoint p; | ||||||
|     SDL_AudioSpec src_spec, dst_spec; |     SDL_AudioSpec src_spec, dst_spec; | ||||||
|  |     int queued_bytes = 0; | ||||||
|     int available_bytes = 0; |     int available_bytes = 0; | ||||||
|     float available_seconds = 0; |     float available_seconds = 0; | ||||||
|  |  | ||||||
| @@ -294,6 +294,8 @@ static void loop(void) | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     queued_bytes = SDL_GetAudioStreamQueued(stream); | ||||||
|  |  | ||||||
|     for (i = 0; i < state->num_windows; i++) { |     for (i = 0; i < state->num_windows; i++) { | ||||||
|         int draw_y = 0; |         int draw_y = 0; | ||||||
|         SDL_Renderer* rend = state->renderers[i]; |         SDL_Renderer* rend = state->renderers[i]; | ||||||
| @@ -326,6 +328,9 @@ static void loop(void) | |||||||
|         draw_textf(rend, 0, draw_y, "Available: %4.2f (%i bytes)", available_seconds, available_bytes); |         draw_textf(rend, 0, draw_y, "Available: %4.2f (%i bytes)", available_seconds, available_bytes); | ||||||
|         draw_y += FONT_LINE_HEIGHT; |         draw_y += FONT_LINE_HEIGHT; | ||||||
|  |  | ||||||
|  |         draw_textf(rend, 0, draw_y, "Queued: %i bytes", queued_bytes); | ||||||
|  |         draw_y += FONT_LINE_HEIGHT; | ||||||
|  |  | ||||||
|         SDL_LockAudioStream(stream); |         SDL_LockAudioStream(stream); | ||||||
|  |  | ||||||
|         draw_textf(rend, 0, draw_y, "Get Callback: %i/%i bytes, %2i ms ago", |         draw_textf(rend, 0, draw_y, "Get Callback: %i/%i bytes, %2i ms ago", | ||||||
|   | |||||||
| @@ -821,11 +821,6 @@ static double sine_wave_sample(const Sint64 idx, const Sint64 rate, const Sint64 | |||||||
|   return SDL_sin(((double)(idx * freq % rate)) / ((double)rate) * (SDL_PI_D * 2) + phase); |   return SDL_sin(((double)(idx * freq % rate)) / ((double)rate) * (SDL_PI_D * 2) + phase); | ||||||
| } | } | ||||||
|  |  | ||||||
| static void free_audio_buffer(void* userdata, const void* buf, int len) |  | ||||||
| { |  | ||||||
|   SDL_free((void*) buf); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /* Split the data into randomly sized chunks */ | /* Split the data into randomly sized chunks */ | ||||||
| static int put_audio_data_split(SDL_AudioStream* stream, const void* buf, int len) | static int put_audio_data_split(SDL_AudioStream* stream, const void* buf, int len) | ||||||
| { | { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Brick
					Brick