diff --git a/examples/custom_decoder.c b/examples/custom_decoder.c new file mode 100644 index 00000000..c3fb9099 --- /dev/null +++ b/examples/custom_decoder.c @@ -0,0 +1,1205 @@ +/* +Demonstrates how to implement a custom decoder. + +This example implements two custom decoders: + + * Vorbis via libvorbis + * Opus via libopus + +A custom decoder must implement a data source. In this example, the libvorbis data source is called +`ma_libvorbis` and the Opus data source is called `ma_libopus`. These two objects are compatible +with the `ma_data_source` APIs and can be taken straight from this example and used in real code. + +The custom decoding data sources (`ma_libvorbis` and `ma_libopus` in this example) are connected to +the decoder via the decoder config (`ma_decoder_config`). You need to implement a vtable for each +of your custom decoders. See `ma_decoding_backend_vtable` for the functions you need to implement. +The `onInitFile`, `onInitFileW` and `onInitMemory` functions are optional. +*/ +#define MA_NO_VORBIS /* Disable the built-in Vorbis decoder to ensure the libvorbis decoder is picked. */ +#define MA_NO_OPUS /* Disable the (not yet implemented) built-in Opus decoder to ensure the libopus decoder is picked. */ +#define MINIAUDIO_IMPLEMENTATION +#include "../miniaudio.h" + +#include + + +#if !defined(MA_NO_LIBVORBIS) +#define OV_EXCLUDE_STATIC_CALLBACKS +#include +#endif + +typedef struct +{ + ma_data_source_base ds; /* The libvorbis decoder can be used independently as a data source. */ + ma_read_proc onRead; + ma_seek_proc onSeek; + ma_tell_proc onTell; + void* pReadSeekTellUserData; + ma_format format; /* Will be either f32 or s16. */ +#if !defined(MA_NO_LIBVORBIS) + OggVorbis_File vf; +#endif +} ma_libvorbis; + +MA_API ma_result ma_libvorbis_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libvorbis* pVorbis); +MA_API ma_result ma_libvorbis_init_file(const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libvorbis* pVorbis); +MA_API void ma_libvorbis_uninit(ma_libvorbis* pVorbis, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_libvorbis_read_pcm_frames(ma_libvorbis* pVorbis, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); +MA_API ma_result ma_libvorbis_seek_to_pcm_frame(ma_libvorbis* pVorbis, ma_uint64 frameIndex); +MA_API ma_result ma_libvorbis_get_data_format(ma_libvorbis* pVorbis, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_libvorbis_get_cursor_in_pcm_frames(ma_libvorbis* pVorbis, ma_uint64* pCursor); +MA_API ma_result ma_libvorbis_get_length_in_pcm_frames(ma_libvorbis* pVorbis, ma_uint64* pLength); + + +static ma_result ma_libvorbis_ds_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + return ma_libvorbis_read_pcm_frames((ma_libvorbis*)pDataSource, pFramesOut, frameCount, pFramesRead); +} + +static ma_result ma_libvorbis_ds_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) +{ + return ma_libvorbis_seek_to_pcm_frame((ma_libvorbis*)pDataSource, frameIndex); +} + +static ma_result ma_libvorbis_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +{ + return ma_libvorbis_get_data_format((ma_libvorbis*)pDataSource, pFormat, pChannels, pSampleRate, NULL, 0); +} + +static ma_result ma_libvorbis_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) +{ + return ma_libvorbis_get_cursor_in_pcm_frames((ma_libvorbis*)pDataSource, pCursor); +} + +static ma_result ma_libvorbis_ds_get_length(ma_data_source* pDataSource, ma_uint64* pLength) +{ + return ma_libvorbis_get_length_in_pcm_frames((ma_libvorbis*)pDataSource, pLength); +} + +static ma_data_source_vtable g_ma_libvorbis_ds_vtable = +{ + ma_libvorbis_ds_read, + ma_libvorbis_ds_seek, + NULL, /* onMap() */ + NULL, /* onUnmap() */ + ma_libvorbis_ds_get_data_format, + ma_libvorbis_ds_get_cursor, + ma_libvorbis_ds_get_length +}; + + +#if !defined(MA_NO_LIBVORBIS) +static size_t ma_libvorbis_vf_callback__read(void* pBufferOut, size_t size, size_t count, void* pUserData) +{ + ma_libvorbis* pVorbis = (ma_libvorbis*)pUserData; + ma_result result; + size_t bytesToRead; + size_t bytesRead; + + bytesToRead = size * count; + result = pVorbis->onRead(pVorbis->pReadSeekTellUserData, pBufferOut, bytesToRead, &bytesRead); + + return bytesRead / size; +} + +static int ma_libvorbis_vf_callback__seek(void* pUserData, ogg_int64_t offset, int whence) +{ + ma_libvorbis* pVorbis = (ma_libvorbis*)pUserData; + ma_result result; + ma_seek_origin origin; + + if (whence == SEEK_SET) { + origin = ma_seek_origin_start; + } else if (whence == SEEK_END) { + origin = ma_seek_origin_end; + } else { + origin = ma_seek_origin_current; + } + + result = pVorbis->onSeek(pVorbis->pReadSeekTellUserData, offset, origin); + if (result != MA_SUCCESS) { + return -1; + } + + return 0; +} + +static long ma_libvorbis_vf_callback__tell(void* pUserData) +{ + ma_libvorbis* pVorbis = (ma_libvorbis*)pUserData; + ma_result result; + ma_int64 cursor; + + result = pVorbis->onTell(pVorbis->pReadSeekTellUserData, &cursor); + if (result != MA_SUCCESS) { + return -1; + } + + return (long)cursor; +} +#endif + +static ma_result ma_libvorbis_init_internal(const ma_decoding_backend_config* pConfig, ma_libvorbis* pVorbis) +{ + ma_result result; + ma_data_source_config dataSourceConfig; + + if (pVorbis == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pVorbis); + pVorbis->format = ma_format_f32; /* f32 by default. */ + + if (pConfig != NULL && (pConfig->preferredFormat == ma_format_f32 || pConfig->preferredFormat == ma_format_s16)) { + pVorbis->format = pConfig->preferredFormat; + } else { + /* Getting here means something other than f32 and s16 was specified. Just leave this unset to use the default format. */ + } + + dataSourceConfig = ma_data_source_config_init(); + dataSourceConfig.vtable = &g_ma_libvorbis_ds_vtable; + + result = ma_data_source_init(&dataSourceConfig, &pVorbis->ds); + if (result != MA_SUCCESS) { + return result; /* Failed to initialize the base data source. */ + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_libvorbis_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libvorbis* pVorbis) +{ + ma_result result; + + (void)pAllocationCallbacks; /* Can't seem to find a way to configure memory allocations in libvorbis. */ + + result = ma_libvorbis_init_internal(pConfig, pVorbis); + if (result != MA_SUCCESS) { + return result; + } + + if (onRead == NULL || onSeek == NULL) { + return MA_INVALID_ARGS; /* onRead and onSeek are mandatory. */ + } + + pVorbis->onRead = onRead; + pVorbis->onSeek = onSeek; + pVorbis->onTell = onTell; + pVorbis->pReadSeekTellUserData = pReadSeekTellUserData; + + #if !defined(MA_NO_LIBVORBIS) + { + int libvorbisResult; + ov_callbacks libvorbisCallbacks; + + /* We can now initialize the vorbis decoder. This must be done after we've set up the callbacks. */ + libvorbisCallbacks.read_func = ma_libvorbis_vf_callback__read; + libvorbisCallbacks.seek_func = ma_libvorbis_vf_callback__seek; + libvorbisCallbacks.close_func = NULL; + libvorbisCallbacks.tell_func = ma_libvorbis_vf_callback__tell; + + libvorbisResult = ov_open_callbacks(pVorbis, &pVorbis->vf, NULL, 0, libvorbisCallbacks); + if (libvorbisResult < 0) { + return MA_INVALID_FILE; + } + + return MA_SUCCESS; + } + #else + { + /* libvorbis is disabled. */ + return MA_NOT_IMPLEMENTED; + } + #endif +} + +MA_API ma_result ma_libvorbis_init_file(const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libvorbis* pVorbis) +{ + ma_result result; + + (void)pAllocationCallbacks; /* Can't seem to find a way to configure memory allocations in libvorbis. */ + + result = ma_libvorbis_init_internal(pConfig, pVorbis); + if (result != MA_SUCCESS) { + return result; + } + + #if !defined(MA_NO_LIBVORBIS) + { + int libvorbisResult; + + libvorbisResult = ov_fopen(pFilePath, &pVorbis->vf); + if (libvorbisResult < 0) { + return MA_INVALID_FILE; + } + + return MA_SUCCESS; + } + #else + { + /* libvorbis is disabled. */ + (void)pFilePath; + return MA_NOT_IMPLEMENTED; + } + #endif +} + +MA_API void ma_libvorbis_uninit(ma_libvorbis* pVorbis, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pVorbis == NULL) { + return; + } + + (void)pAllocationCallbacks; + + #if !defined(MA_NO_LIBVORBIS) + { + ov_clear(&pVorbis->vf); + } + #else + { + /* libvorbis is disabled. Should never hit this since initialization would have failed. */ + MA_ASSERT(MA_FALSE); + } + #endif + + ma_data_source_uninit(&pVorbis->ds); +} + +MA_API ma_result ma_libvorbis_read_pcm_frames(ma_libvorbis* pVorbis, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + if (pVorbis == NULL) { + return MA_INVALID_ARGS; + } + + #if !defined(MA_NO_LIBVORBIS) + { + /* We always use floating point format. */ + ma_result result = MA_SUCCESS; /* Must be initialized to MA_SUCCESS. */ + ma_uint64 totalFramesRead; + ma_format format; + ma_uint32 channels; + + ma_libvorbis_get_data_format(pVorbis, &format, &channels, NULL, NULL, 0); + + totalFramesRead = 0; + while (totalFramesRead < frameCount) { + long libvorbisResult; + int framesToRead; + ma_uint64 framesRemaining; + + framesRemaining = (frameCount - totalFramesRead); + framesToRead = 1024; + if (framesToRead > framesRemaining) { + framesToRead = (int)framesRemaining; + } + + if (format == ma_format_f32) { + float** ppFramesF32; + + libvorbisResult = ov_read_float(&pVorbis->vf, &ppFramesF32, framesToRead, NULL); + if (libvorbisResult < 0) { + result = MA_ERROR; /* Error while decoding. */ + break; + } else { + /* Frames need to be interleaved. */ + ma_interleave_pcm_frames(format, channels, libvorbisResult, ppFramesF32, ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels)); + totalFramesRead += libvorbisResult; + + if (libvorbisResult == 0) { + result = MA_AT_END; + break; + } + } + } else { + libvorbisResult = ov_read(&pVorbis->vf, ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels), framesToRead * ma_get_bytes_per_frame(format, channels), 0, 2, 1, NULL); + if (libvorbisResult < 0) { + result = MA_ERROR; /* Error while decoding. */ + break; + } else { + /* Conveniently, there's no need to interleaving when using ov_read(). I'm not sure why ov_read_float() is different in that regard... */ + totalFramesRead += libvorbisResult / ma_get_bytes_per_frame(format, channels); + + if (libvorbisResult == 0) { + result = MA_AT_END; + break; + } + } + } + } + + if (pFramesRead != NULL) { + *pFramesRead = totalFramesRead; + } + + return result; + } + #else + { + /* libvorbis is disabled. Should never hit this since initialization would have failed. */ + MA_ASSERT(MA_FALSE); + + (void)pFramesOut; + (void)frameCount; + (void)pFramesRead; + + return MA_NOT_IMPLEMENTED; + } + #endif +} + +MA_API ma_result ma_libvorbis_seek_to_pcm_frame(ma_libvorbis* pVorbis, ma_uint64 frameIndex) +{ + if (pVorbis == NULL) { + return MA_INVALID_ARGS; + } + + #if !defined(MA_NO_LIBVORBIS) + { + int libvorbisResult = ov_pcm_seek(&pVorbis->vf, (ogg_int64_t)frameIndex); + if (libvorbisResult != 0) { + if (libvorbisResult == OV_ENOSEEK) { + return MA_INVALID_OPERATION; /* Not seekable. */ + } else if (libvorbisResult == OV_EINVAL) { + return MA_INVALID_ARGS; + } else { + return MA_ERROR; + } + } + + return MA_SUCCESS; + } + #else + { + /* libvorbis is disabled. Should never hit this since initialization would have failed. */ + MA_ASSERT(MA_FALSE); + + (void)frameIndex; + + return MA_NOT_IMPLEMENTED; + } + #endif +} + +MA_API ma_result ma_libvorbis_get_data_format(ma_libvorbis* pVorbis, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + /* Defaults for safety. */ + if (pFormat != NULL) { + *pFormat = ma_format_unknown; + } + if (pChannels != NULL) { + *pChannels = 0; + } + if (pSampleRate != NULL) { + *pSampleRate = 0; + } + if (pChannelMap != NULL) { + MA_ZERO_MEMORY(pChannelMap, sizeof(*pChannelMap) * channelMapCap); + } + + if (pVorbis == NULL) { + return MA_INVALID_OPERATION; + } + + if (pFormat != NULL) { + *pFormat = pVorbis->format; + } + + #if !defined(MA_NO_LIBVORBIS) + { + vorbis_info* pInfo = ov_info(&pVorbis->vf, 0); + if (pInfo == NULL) { + return MA_INVALID_OPERATION; + } + + if (pChannels != NULL) { + *pChannels = pInfo->channels; + } + + if (pSampleRate != NULL) { + *pSampleRate = pInfo->rate; + } + + if (pChannelMap != NULL) { + ma_get_standard_channel_map(ma_standard_channel_map_vorbis, (ma_uint32)ma_min(pInfo->channels, channelMapCap), pChannelMap); + } + + return MA_SUCCESS; + } + #else + { + /* libvorbis is disabled. Should never hit this since initialization would have failed. */ + MA_ASSERT(MA_FALSE); + return MA_NOT_IMPLEMENTED; + } + #endif +} + +MA_API ma_result ma_libvorbis_get_cursor_in_pcm_frames(ma_libvorbis* pVorbis, ma_uint64* pCursor) +{ + if (pCursor == NULL) { + return MA_INVALID_ARGS; + } + + *pCursor = 0; /* Safety. */ + + if (pVorbis == NULL) { + return MA_INVALID_ARGS; + } + + #if !defined(MA_NO_LIBVORBIS) + { + ogg_int64_t offset = ov_pcm_tell(&pVorbis->vf); + if (offset < 0) { + return MA_INVALID_FILE; + } + + *pCursor = (ma_uint64)offset; + + return MA_SUCCESS; + } + #else + { + /* libvorbis is disabled. Should never hit this since initialization would have failed. */ + MA_ASSERT(MA_FALSE); + return MA_NOT_IMPLEMENTED; + } + #endif +} + +MA_API ma_result ma_libvorbis_get_length_in_pcm_frames(ma_libvorbis* pVorbis, ma_uint64* pLength) +{ + if (pLength == NULL) { + return MA_INVALID_ARGS; + } + + *pLength = 0; /* Safety. */ + + if (pVorbis == NULL) { + return MA_INVALID_ARGS; + } + + #if !defined(MA_NO_LIBVORBIS) + { + /* I don't know how to reliably retrieve the length in frames using libvorbis, so returning 0 for now. */ + *pLength = 0; + + return MA_SUCCESS; + } + #else + { + /* libvorbis is disabled. Should never hit this since initialization would have failed. */ + MA_ASSERT(MA_FALSE); + return MA_NOT_IMPLEMENTED; + } + #endif +} + + + +#if !defined(MA_NO_LIBOPUS) +#include +#endif + +typedef struct +{ + ma_data_source_base ds; /* The libopus decoder can be used independently as a data source. */ + ma_read_proc onRead; + ma_seek_proc onSeek; + ma_tell_proc onTell; + void* pReadSeekTellUserData; + ma_format format; /* Will be either f32 or s16. */ +#if !defined(MA_NO_LIBOPUS) + OggOpusFile* of; +#endif +} ma_libopus; + +MA_API ma_result ma_libopus_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libopus* pVorbis); +MA_API ma_result ma_libopus_init_file(const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libopus* pVorbis); +MA_API void ma_libopus_uninit(ma_libopus* pVorbis, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_libopus_read_pcm_frames(ma_libopus* pVorbis, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); +MA_API ma_result ma_libopus_seek_to_pcm_frame(ma_libopus* pVorbis, ma_uint64 frameIndex); +MA_API ma_result ma_libopus_get_data_format(ma_libopus* pVorbis, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_libopus_get_cursor_in_pcm_frames(ma_libopus* pVorbis, ma_uint64* pCursor); +MA_API ma_result ma_libopus_get_length_in_pcm_frames(ma_libopus* pVorbis, ma_uint64* pLength); + + +static ma_result ma_libopus_ds_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + return ma_libopus_read_pcm_frames((ma_libopus*)pDataSource, pFramesOut, frameCount, pFramesRead); +} + +static ma_result ma_libopus_ds_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) +{ + return ma_libopus_seek_to_pcm_frame((ma_libopus*)pDataSource, frameIndex); +} + +static ma_result ma_libopus_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +{ + return ma_libopus_get_data_format((ma_libopus*)pDataSource, pFormat, pChannels, pSampleRate, NULL, 0); +} + +static ma_result ma_libopus_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) +{ + return ma_libopus_get_cursor_in_pcm_frames((ma_libopus*)pDataSource, pCursor); +} + +static ma_result ma_libopus_ds_get_length(ma_data_source* pDataSource, ma_uint64* pLength) +{ + return ma_libopus_get_length_in_pcm_frames((ma_libopus*)pDataSource, pLength); +} + +static ma_data_source_vtable g_ma_libopus_ds_vtable = +{ + ma_libopus_ds_read, + ma_libopus_ds_seek, + NULL, /* onMap() */ + NULL, /* onUnmap() */ + ma_libopus_ds_get_data_format, + ma_libopus_ds_get_cursor, + ma_libopus_ds_get_length +}; + + +#if !defined(MA_NO_LIBOPUS) +static int ma_libopus_of_callback__read(void* pUserData, void* pBufferOut, int bytesToRead) +{ + ma_libopus* pOpus = (ma_libopus*)pUserData; + ma_result result; + size_t bytesRead; + + result = pOpus->onRead(pOpus->pReadSeekTellUserData, pBufferOut, bytesToRead, &bytesRead); + + if (result != MA_SUCCESS) { + return -1; + } + + return (int)bytesRead; +} + +static int ma_libopus_of_callback__seek(void* pUserData, ogg_int64_t offset, int whence) +{ + ma_libopus* pOpus = (ma_libopus*)pUserData; + ma_result result; + ma_seek_origin origin; + + if (whence == SEEK_SET) { + origin = ma_seek_origin_start; + } else if (whence == SEEK_END) { + origin = ma_seek_origin_end; + } else { + origin = ma_seek_origin_current; + } + + result = pOpus->onSeek(pOpus->pReadSeekTellUserData, offset, origin); + if (result != MA_SUCCESS) { + return -1; + } + + return 0; +} + +static opus_int64 ma_libopus_of_callback__tell(void* pUserData) +{ + ma_libopus* pOpus = (ma_libopus*)pUserData; + ma_result result; + ma_int64 cursor; + + if (pOpus->onTell == NULL) { + return -1; + } + + result = pOpus->onTell(pOpus->pReadSeekTellUserData, &cursor); + if (result != MA_SUCCESS) { + return -1; + } + + return cursor; +} +#endif + +static ma_result ma_libopus_init_internal(const ma_decoding_backend_config* pConfig, ma_libopus* pOpus) +{ + ma_result result; + ma_data_source_config dataSourceConfig; + + if (pOpus == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pOpus); + pOpus->format = ma_format_f32; /* f32 by default. */ + + if (pConfig != NULL && (pConfig->preferredFormat == ma_format_f32 || pConfig->preferredFormat == ma_format_s16)) { + pOpus->format = pConfig->preferredFormat; + } else { + /* Getting here means something other than f32 and s16 was specified. Just leave this unset to use the default format. */ + } + + dataSourceConfig = ma_data_source_config_init(); + dataSourceConfig.vtable = &g_ma_libopus_ds_vtable; + + result = ma_data_source_init(&dataSourceConfig, &pOpus->ds); + if (result != MA_SUCCESS) { + return result; /* Failed to initialize the base data source. */ + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_libopus_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libopus* pOpus) +{ + ma_result result; + + (void)pAllocationCallbacks; /* Can't seem to find a way to configure memory allocations in libopus. */ + + result = ma_libopus_init_internal(pConfig, pOpus); + if (result != MA_SUCCESS) { + return result; + } + + if (onRead == NULL || onSeek == NULL) { + return MA_INVALID_ARGS; /* onRead and onSeek are mandatory. */ + } + + pOpus->onRead = onRead; + pOpus->onSeek = onSeek; + pOpus->onTell = onTell; + pOpus->pReadSeekTellUserData = pReadSeekTellUserData; + + #if !defined(MA_NO_LIBOPUS) + { + int libopusResult; + OpusFileCallbacks libopusCallbacks; + + /* We can now initialize the vorbis decoder. This must be done after we've set up the callbacks. */ + libopusCallbacks.read = ma_libopus_of_callback__read; + libopusCallbacks.seek = ma_libopus_of_callback__seek; + libopusCallbacks.close = NULL; + libopusCallbacks.tell = ma_libopus_of_callback__tell; + + pOpus->of = op_open_callbacks(pOpus, &libopusCallbacks, NULL, 0, &libopusResult); + if (pOpus->of == NULL) { + return MA_INVALID_FILE; + } + + return MA_SUCCESS; + } + #else + { + /* libopus is disabled. */ + return MA_NOT_IMPLEMENTED; + } + #endif +} + +MA_API ma_result ma_libopus_init_file(const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libopus* pOpus) +{ + ma_result result; + + (void)pAllocationCallbacks; /* Can't seem to find a way to configure memory allocations in libopus. */ + + result = ma_libopus_init_internal(pConfig, pOpus); + if (result != MA_SUCCESS) { + return result; + } + + #if !defined(MA_NO_LIBOPUS) + { + int libopusResult; + + pOpus->of = op_open_file(pFilePath, &libopusResult); + if (pOpus->of == NULL) { + return MA_INVALID_FILE; + } + + return MA_SUCCESS; + } + #else + { + /* libopus is disabled. */ + (void)pFilePath; + return MA_NOT_IMPLEMENTED; + } + #endif +} + +MA_API void ma_libopus_uninit(ma_libopus* pOpus, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pOpus == NULL) { + return; + } + + (void)pAllocationCallbacks; + + #if !defined(MA_NO_LIBOPUS) + { + op_free(pOpus->of); + } + #else + { + /* libopus is disabled. Should never hit this since initialization would have failed. */ + MA_ASSERT(MA_FALSE); + } + #endif + + ma_data_source_uninit(&pOpus->ds); +} + +MA_API ma_result ma_libopus_read_pcm_frames(ma_libopus* pOpus, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + if (pOpus == NULL) { + return MA_INVALID_ARGS; + } + + #if !defined(MA_NO_LIBOPUS) + { + /* We always use floating point format. */ + ma_result result = MA_SUCCESS; /* Must be initialized to MA_SUCCESS. */ + ma_uint64 totalFramesRead; + ma_format format; + ma_uint32 channels; + + ma_libopus_get_data_format(pOpus, &format, &channels, NULL, NULL, 0); + + totalFramesRead = 0; + while (totalFramesRead < frameCount) { + long libopusResult; + int framesToRead; + ma_uint64 framesRemaining; + + framesRemaining = (frameCount - totalFramesRead); + framesToRead = 1024; + if (framesToRead > framesRemaining) { + framesToRead = (int)framesRemaining; + } + + if (format == ma_format_f32) { + libopusResult = op_read_float(pOpus->of, ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels), framesToRead * channels, NULL); + } else { + libopusResult = op_read (pOpus->of, ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels), framesToRead * channels, NULL); + } + + if (libopusResult < 0) { + result = MA_ERROR; /* Error while decoding. */ + break; + } else { + totalFramesRead += libopusResult; + + if (libopusResult == 0) { + result = MA_AT_END; + break; + } + } + } + + if (pFramesRead != NULL) { + *pFramesRead = totalFramesRead; + } + + return result; + } + #else + { + /* libopus is disabled. Should never hit this since initialization would have failed. */ + MA_ASSERT(MA_FALSE); + + (void)pFramesOut; + (void)frameCount; + (void)pFramesRead; + + return MA_NOT_IMPLEMENTED; + } + #endif +} + +MA_API ma_result ma_libopus_seek_to_pcm_frame(ma_libopus* pOpus, ma_uint64 frameIndex) +{ + if (pOpus == NULL) { + return MA_INVALID_ARGS; + } + + #if !defined(MA_NO_LIBOPUS) + { + int libopusResult = op_pcm_seek(pOpus->of, (ogg_int64_t)frameIndex); + if (libopusResult != 0) { + if (libopusResult == OP_ENOSEEK) { + return MA_INVALID_OPERATION; /* Not seekable. */ + } else if (libopusResult == OP_EINVAL) { + return MA_INVALID_ARGS; + } else { + return MA_ERROR; + } + } + + return MA_SUCCESS; + } + #else + { + /* libopus is disabled. Should never hit this since initialization would have failed. */ + MA_ASSERT(MA_FALSE); + + (void)frameIndex; + + return MA_NOT_IMPLEMENTED; + } + #endif +} + +MA_API ma_result ma_libopus_get_data_format(ma_libopus* pOpus, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + /* Defaults for safety. */ + if (pFormat != NULL) { + *pFormat = ma_format_unknown; + } + if (pChannels != NULL) { + *pChannels = 0; + } + if (pSampleRate != NULL) { + *pSampleRate = 0; + } + if (pChannelMap != NULL) { + MA_ZERO_MEMORY(pChannelMap, sizeof(*pChannelMap) * channelMapCap); + } + + if (pOpus == NULL) { + return MA_INVALID_OPERATION; + } + + if (pFormat != NULL) { + *pFormat = pOpus->format; + } + + #if !defined(MA_NO_LIBOPUS) + { + ma_uint32 channels = op_channel_count(pOpus->of, -1); + + if (pChannels != NULL) { + *pChannels = channels; + } + + if (pSampleRate != NULL) { + *pSampleRate = 48000; + } + + if (pChannelMap != NULL) { + ma_get_standard_channel_map(ma_standard_channel_map_vorbis, (ma_uint32)ma_min(channels, channelMapCap), pChannelMap); + } + + return MA_SUCCESS; + } + #else + { + /* libopus is disabled. Should never hit this since initialization would have failed. */ + MA_ASSERT(MA_FALSE); + return MA_NOT_IMPLEMENTED; + } + #endif +} + +MA_API ma_result ma_libopus_get_cursor_in_pcm_frames(ma_libopus* pOpus, ma_uint64* pCursor) +{ + if (pCursor == NULL) { + return MA_INVALID_ARGS; + } + + *pCursor = 0; /* Safety. */ + + if (pOpus == NULL) { + return MA_INVALID_ARGS; + } + + #if !defined(MA_NO_LIBOPUS) + { + ogg_int64_t offset = op_pcm_tell(pOpus->of); + if (offset < 0) { + return MA_INVALID_FILE; + } + + *pCursor = (ma_uint64)offset; + + return MA_SUCCESS; + } + #else + { + /* libopus is disabled. Should never hit this since initialization would have failed. */ + MA_ASSERT(MA_FALSE); + return MA_NOT_IMPLEMENTED; + } + #endif +} + +MA_API ma_result ma_libopus_get_length_in_pcm_frames(ma_libopus* pOpus, ma_uint64* pLength) +{ + if (pLength == NULL) { + return MA_INVALID_ARGS; + } + + *pLength = 0; /* Safety. */ + + if (pOpus == NULL) { + return MA_INVALID_ARGS; + } + + #if !defined(MA_NO_LIBOPUS) + { + ogg_int64_t length = op_pcm_total(pOpus->of, -1); + if (length < 0) { + return MA_ERROR; + } + + *pLength = (ma_uint64)length; + + return MA_SUCCESS; + } + #else + { + /* libopus is disabled. Should never hit this since initialization would have failed. */ + MA_ASSERT(MA_FALSE); + return MA_NOT_IMPLEMENTED; + } + #endif +} + + +/* +In this example we're going to be implementing our custom decoders as an extension to the ma_decoder +object. We declare our decoding backends after the ma_decoder object which allows us to avoid a +memory allocation. There are many ways to manage the backend objects so use whatever works best for +your particular scenario. +*/ +typedef struct +{ + ma_decoder base; /* Must be the first member so we can cast between ma_decoder_ex and ma_decoder. */ + union + { + ma_libvorbis libvorbis; + ma_libopus libopus; + } backends; +} ma_decoder_ex; + +static ma_result ma_decoder_ex_init__libvorbis(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) +{ + ma_decoder_ex* pDecoderEx = (ma_decoder_ex*)pUserData; + ma_result result; + + /* + NOTE: We don't need to allocate any memory for the libvorbis object here because our backend + data is just extended off the ma_decoder object (ma_decode_ex) which is passed in as a + parameter to this function. We therefore need only cast to ma_decoder_ex and reference data + directly from that structure. + */ + *ppBackend = (ma_data_source*)&pDecoderEx->backends.libvorbis; + + result = ma_libvorbis_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, &pDecoderEx->backends.libvorbis); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + +static ma_result ma_decoder_ex_init_file__libvorbis(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) +{ + ma_decoder_ex* pDecoderEx = (ma_decoder_ex*)pUserData; + ma_result result; + + *ppBackend = (ma_data_source*)&pDecoderEx->backends.libvorbis; + + result = ma_libvorbis_init_file(pFilePath, pConfig, pAllocationCallbacks, &pDecoderEx->backends.libvorbis); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + +static void ma_decoder_ex_uninit__libvorbis(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_libvorbis* pVorbis = (ma_libvorbis*)pBackend; + ma_libvorbis_uninit(pVorbis, pAllocationCallbacks); + + /* No need to free the pVorbis object because it is sitting in the containing ma_decoder_ex object. */ + + (void)pUserData; +} + +static ma_result ma_decoder_ex_get_channel_map__libvorbis(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap) +{ + ma_libvorbis* pVorbis = (ma_libvorbis*)pBackend; + + (void)pUserData; + + return ma_libvorbis_get_data_format(pVorbis, NULL, NULL, NULL, pChannelMap, channelMapCap); +} + +static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_libvorbis = +{ + ma_decoder_ex_init__libvorbis, + ma_decoder_ex_init_file__libvorbis, + NULL, /* onInitFileW() */ + NULL, /* onInitMemory() */ + ma_decoder_ex_uninit__libvorbis, + ma_decoder_ex_get_channel_map__libvorbis +}; + + + +static ma_result ma_decoder_ex_init__libopus(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) +{ + ma_decoder_ex* pDecoderEx = (ma_decoder_ex*)pUserData; + ma_result result; + + /* + NOTE: We don't need to allocate any memory for the libopus object here because our backend + data is just extended off the ma_decoder object (ma_decode_ex) which is passed in as a + parameter to this function. We therefore need only cast to ma_decoder_ex and reference data + directly from that structure. + */ + *ppBackend = (ma_data_source*)&pDecoderEx->backends.libopus; + + result = ma_libopus_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, &pDecoderEx->backends.libopus); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + +static ma_result ma_decoder_ex_init_file__libopus(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) +{ + ma_decoder_ex* pDecoderEx = (ma_decoder_ex*)pUserData; + ma_result result; + + *ppBackend = (ma_data_source*)&pDecoderEx->backends.libopus; + + result = ma_libopus_init_file(pFilePath, pConfig, pAllocationCallbacks, &pDecoderEx->backends.libopus); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + +static void ma_decoder_ex_uninit__libopus(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_libopus* pOpus = (ma_libopus*)pBackend; + ma_libopus_uninit(pOpus, pAllocationCallbacks); + + /* No need to free the pOpus object because it is sitting in the containing ma_decoder_ex object. */ + + (void)pUserData; +} + +static ma_result ma_decoder_ex_get_channel_map__libopus(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap) +{ + ma_libopus* pOpus = (ma_libopus*)pBackend; + + (void)pUserData; + + return ma_libopus_get_data_format(pOpus, NULL, NULL, NULL, pChannelMap, channelMapCap); +} + +static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_libopus = +{ + ma_decoder_ex_init__libopus, + ma_decoder_ex_init_file__libopus, + NULL, /* onInitFileW() */ + NULL, /* onInitMemory() */ + ma_decoder_ex_uninit__libopus, + ma_decoder_ex_get_channel_map__libopus +}; + + + + +void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) +{ + ma_data_source* pDataSource = (ma_data_source*)pDevice->pUserData; + if (pDataSource == NULL) { + return; + } + + ma_data_source_read_pcm_frames(pDataSource, pOutput, frameCount, NULL, MA_TRUE); + + (void)pInput; +} + +int main(int argc, char** argv) +{ + ma_result result; + ma_decoder_config decoderConfig; + ma_decoder_ex decoder; + ma_device_config deviceConfig; + ma_device device; + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + + /* + Add your custom backend vtables here. The order in the array defines the order of priority. The + vtables will be passed in via the decoder config. + */ + ma_decoding_backend_vtable* pCustomBackendVTables[] = + { + &g_ma_decoding_backend_vtable_libvorbis, + &g_ma_decoding_backend_vtable_libopus + }; + + + if (argc < 2) { + printf("No input file.\n"); + return -1; + } + + + /* Initialize the decoder. */ + decoderConfig = ma_decoder_config_init_default(); + decoderConfig.pCustomBackendUserData = &decoder; /* In this example our backend objects are contained within a ma_decoder_ex object to avoid a malloc. Our vtables need to know about this. */ + decoderConfig.ppCustomBackendVTables = pCustomBackendVTables; + decoderConfig.customBackendVTableCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]); + + result = ma_decoder_init_file(argv[1], &decoderConfig, &decoder.base); + if (result != MA_SUCCESS) { + printf("Failed to initialize decoder."); + return -1; + } + + + /* Initialize the device. */ + result = ma_data_source_get_data_format(&decoder, &format, &channels, &sampleRate); + if (result != MA_SUCCESS) { + printf("Failed to retrieve decoder data format."); + ma_decoder_uninit(&decoder.base); + return -1; + } + + deviceConfig = ma_device_config_init(ma_device_type_playback); + deviceConfig.playback.format = format; + deviceConfig.playback.channels = channels; + deviceConfig.sampleRate = sampleRate; + deviceConfig.dataCallback = data_callback; + deviceConfig.pUserData = &decoder; + + if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) { + printf("Failed to open playback device.\n"); + ma_decoder_uninit(&decoder.base); + return -1; + } + + if (ma_device_start(&device) != MA_SUCCESS) { + printf("Failed to start playback device.\n"); + ma_device_uninit(&device); + ma_decoder_uninit(&decoder.base); + return -1; + } + + printf("Press Enter to quit..."); + getchar(); + + ma_device_uninit(&device); + ma_decoder_uninit(&decoder.base); + + return 0; +} \ No newline at end of file diff --git a/miniaudio.h b/miniaudio.h index 37cbe43c..65de00a1 100644 --- a/miniaudio.h +++ b/miniaudio.h @@ -6064,6 +6064,11 @@ MA_API ma_result ma_default_vfs_init(ma_default_vfs* pVFS, const ma_allocation_c +typedef ma_result (* ma_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead); +typedef ma_result (* ma_seek_proc)(void* pUserData, ma_int64 offset, ma_seek_origin origin); +typedef ma_result (* ma_tell_proc)(void* pUserData, ma_int64* pCursor); + + #if !defined(MA_NO_DECODING) || !defined(MA_NO_ENCODING) typedef enum @@ -6084,9 +6089,33 @@ you do your own synchronization. #ifndef MA_NO_DECODING typedef struct ma_decoder ma_decoder; -typedef size_t (* ma_decoder_read_proc) (ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead); /* Returns the number of bytes read. */ -typedef ma_bool32 (* ma_decoder_seek_proc) (ma_decoder* pDecoder, int byteOffset, ma_seek_origin origin); /* Origin will never be ma_seek_origin_end. */ -typedef ma_uint64 (* ma_decoder_read_pcm_frames_proc) (ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount); /* Returns the number of frames read. Output data is in internal format. */ + +typedef struct +{ + ma_format preferredFormat; +} ma_decoding_backend_config; + +MA_API ma_decoding_backend_config ma_decoding_backend_config_init(ma_format preferredFormat); + + +typedef struct +{ + ma_result (* onInit )(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); + ma_result (* onInitFile )(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ + ma_result (* onInitFileW )(void* pUserData, const wchar_t* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ + ma_result (* onInitMemory )(void* pUserData, const void* pData, size_t dataSize, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ + void (* onUninit )(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks); + ma_result (* onGetChannelMap)(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap); +} ma_decoding_backend_vtable; + + +/* TODO: Convert read and seek to be consistent with the VFS API (ma_result return value, bytes read moved to an output parameter). */ +typedef size_t (* ma_decoder_read_proc)(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead); /* Returns the number of bytes read. */ +typedef ma_bool32 (* ma_decoder_seek_proc)(ma_decoder* pDecoder, ma_int64 byteOffset, ma_seek_origin origin); +typedef ma_result (* ma_decoder_tell_proc)(ma_decoder* pDecoder, ma_int64* pCursor); + +/* TODO: Remove these when internal decoders are transferred over to the new backend system. */ +typedef ma_uint64 (* ma_decoder_read_pcm_frames_proc) (ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount); /* Returns the number of frames read. Output data is in internal format. */ typedef ma_result (* ma_decoder_seek_to_pcm_frame_proc) (ma_decoder* pDecoder, ma_uint64 frameIndex); typedef ma_result (* ma_decoder_uninit_proc) (ma_decoder* pDecoder); typedef ma_uint64 (* ma_decoder_get_length_in_pcm_frames_proc)(ma_decoder* pDecoder); @@ -6112,15 +6141,21 @@ typedef struct } speex; } resampling; ma_allocation_callbacks allocationCallbacks; + ma_decoding_backend_vtable** ppCustomBackendVTables; + ma_uint32 customBackendVTableCount; + void* pCustomBackendUserData; } ma_decoder_config; struct ma_decoder { ma_data_source_base ds; + ma_data_source* pBackend; /* The decoding backend we'll be pulling data from. */ + const ma_decoding_backend_vtable* pBackendVTable; /* The vtable for the decoding backend. This needs to be stored so we can access the onUninit() callback. */ + void* pBackendUserData; ma_decoder_read_proc onRead; ma_decoder_seek_proc onSeek; + ma_decoder_tell_proc onTell; void* pUserData; - ma_uint64 readPointerInBytes; /* In internal encoded data. */ ma_uint64 readPointerInPCMFrames; /* In output sample rate. Used for keeping track of how many frames are available for decoding. */ ma_format internalFormat; ma_uint32 internalChannels; @@ -6150,10 +6185,11 @@ struct ma_decoder size_t dataSize; size_t currentReadPos; } memory; /* Only used for decoders that were opened against a block of memory. */ - } backend; + } data; }; MA_API ma_decoder_config ma_decoder_config_init(ma_format outputFormat, ma_uint32 outputChannels, ma_uint32 outputSampleRate); +MA_API ma_decoder_config ma_decoder_config_init_default(); MA_API ma_result ma_decoder_init(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder); MA_API ma_result ma_decoder_init_wav(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder); @@ -44776,7 +44812,7 @@ MA_API ma_result ma_vfs_open_and_read_file_w(ma_vfs* pVFS, const wchar_t* pFileP } -#if defined(MA_WIN32) && defined(MA_WIN32_DESKTOP) +#if defined(MA_WIN32) && defined(MA_WIN32_DESKTOP) && !defined(MA_NO_WIN32_FILEIO) static void ma_default_vfs__get_open_settings_win32(ma_uint32 openMode, DWORD* pDesiredAccess, DWORD* pShareMode, DWORD* pCreationDisposition) { *pDesiredAccess = 0; @@ -45142,24 +45178,33 @@ static ma_result ma_default_vfs_write__stdio(ma_vfs* pVFS, ma_vfs_file file, con static ma_result ma_default_vfs_seek__stdio(ma_vfs* pVFS, ma_vfs_file file, ma_int64 offset, ma_seek_origin origin) { int result; + int whence; MA_ASSERT(file != NULL); (void)pVFS; + if (origin == ma_seek_origin_start) { + whence = SEEK_SET; + } else if (origin == ma_seek_origin_end) { + whence = SEEK_END; + } else { + whence = SEEK_CUR; + } + #if defined(_WIN32) #if defined(_MSC_VER) && _MSC_VER > 1200 - result = _fseeki64((FILE*)file, offset, origin); + result = _fseeki64((FILE*)file, offset, whence); #else /* No _fseeki64() so restrict to 31 bits. */ if (origin > 0x7FFFFFFF) { return MA_OUT_OF_RANGE; } - result = fseek((FILE*)file, (int)offset, origin); + result = fseek((FILE*)file, (int)offset, whence); #endif #else - result = fseek((FILE*)file, (long int)offset, origin); + result = fseek((FILE*)file, (long int)offset, whence); #endif if (result != 0) { return MA_ERROR; @@ -45235,7 +45280,7 @@ static ma_result ma_default_vfs_open(ma_vfs* pVFS, const char* pFilePath, ma_uin return MA_INVALID_ARGS; } -#if defined(MA_WIN32) && defined(MA_WIN32_DESKTOP) +#if defined(MA_WIN32) && defined(MA_WIN32_DESKTOP) && !defined(MA_NO_WIN32_FILEIO) return ma_default_vfs_open__win32(pVFS, pFilePath, openMode, pFile); #else return ma_default_vfs_open__stdio(pVFS, pFilePath, openMode, pFile); @@ -45254,7 +45299,7 @@ static ma_result ma_default_vfs_open_w(ma_vfs* pVFS, const wchar_t* pFilePath, m return MA_INVALID_ARGS; } -#if defined(MA_WIN32) && defined(MA_WIN32_DESKTOP) +#if defined(MA_WIN32) && defined(MA_WIN32_DESKTOP) && !defined(MA_NO_WIN32_FILEIO) return ma_default_vfs_open_w__win32(pVFS, pFilePath, openMode, pFile); #else return ma_default_vfs_open_w__stdio(pVFS, pFilePath, openMode, pFile); @@ -45267,7 +45312,7 @@ static ma_result ma_default_vfs_close(ma_vfs* pVFS, ma_vfs_file file) return MA_INVALID_ARGS; } -#if defined(MA_WIN32) && defined(MA_WIN32_DESKTOP) +#if defined(MA_WIN32) && defined(MA_WIN32_DESKTOP) && !defined(MA_NO_WIN32_FILEIO) return ma_default_vfs_close__win32(pVFS, file); #else return ma_default_vfs_close__stdio(pVFS, file); @@ -45284,7 +45329,7 @@ static ma_result ma_default_vfs_read(ma_vfs* pVFS, ma_vfs_file file, void* pDst, return MA_INVALID_ARGS; } -#if defined(MA_WIN32) && defined(MA_WIN32_DESKTOP) +#if defined(MA_WIN32) && defined(MA_WIN32_DESKTOP) && !defined(MA_NO_WIN32_FILEIO) return ma_default_vfs_read__win32(pVFS, file, pDst, sizeInBytes, pBytesRead); #else return ma_default_vfs_read__stdio(pVFS, file, pDst, sizeInBytes, pBytesRead); @@ -45301,7 +45346,7 @@ static ma_result ma_default_vfs_write(ma_vfs* pVFS, ma_vfs_file file, const void return MA_INVALID_ARGS; } -#if defined(MA_WIN32) && defined(MA_WIN32_DESKTOP) +#if defined(MA_WIN32) && defined(MA_WIN32_DESKTOP) && !defined(MA_NO_WIN32_FILEIO) return ma_default_vfs_write__win32(pVFS, file, pSrc, sizeInBytes, pBytesWritten); #else return ma_default_vfs_write__stdio(pVFS, file, pSrc, sizeInBytes, pBytesWritten); @@ -45314,7 +45359,7 @@ static ma_result ma_default_vfs_seek(ma_vfs* pVFS, ma_vfs_file file, ma_int64 of return MA_INVALID_ARGS; } -#if defined(MA_WIN32) && defined(MA_WIN32_DESKTOP) +#if defined(MA_WIN32) && defined(MA_WIN32_DESKTOP) && !defined(MA_NO_WIN32_FILEIO) return ma_default_vfs_seek__win32(pVFS, file, offset, origin); #else return ma_default_vfs_seek__stdio(pVFS, file, offset, origin); @@ -45333,7 +45378,7 @@ static ma_result ma_default_vfs_tell(ma_vfs* pVFS, ma_vfs_file file, ma_int64* p return MA_INVALID_ARGS; } -#if defined(MA_WIN32) && defined(MA_WIN32_DESKTOP) +#if defined(MA_WIN32) && defined(MA_WIN32_DESKTOP) && !defined(MA_NO_WIN32_FILEIO) return ma_default_vfs_tell__win32(pVFS, file, pCursor); #else return ma_default_vfs_tell__stdio(pVFS, file, pCursor); @@ -45352,7 +45397,7 @@ static ma_result ma_default_vfs_info(ma_vfs* pVFS, ma_vfs_file file, ma_file_inf return MA_INVALID_ARGS; } -#if defined(MA_WIN32) && defined(MA_WIN32_DESKTOP) +#if defined(MA_WIN32) && defined(MA_WIN32_DESKTOP) && !defined(MA_NO_WIN32_FILEIO) return ma_default_vfs_info__win32(pVFS, file, pInfo); #else return ma_default_vfs_info__stdio(pVFS, file, pInfo); @@ -46449,20 +46494,23 @@ Decoding **************************************************************************************************************************************************************/ #ifndef MA_NO_DECODING -static size_t ma_decoder_read_bytes(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead) +static ma_result ma_decoder_read_bytes(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead) { size_t bytesRead; - MA_ASSERT(pDecoder != NULL); + MA_ASSERT(pDecoder != NULL); MA_ASSERT(pBufferOut != NULL); bytesRead = pDecoder->onRead(pDecoder, pBufferOut, bytesToRead); - pDecoder->readPointerInBytes += bytesRead; - return bytesRead; + if (pBytesRead != NULL) { + *pBytesRead = bytesRead; + } + + return MA_SUCCESS; } -static ma_bool32 ma_decoder_seek_bytes(ma_decoder* pDecoder, int byteOffset, ma_seek_origin origin) +static ma_result ma_decoder_seek_bytes(ma_decoder* pDecoder, ma_int64 byteOffset, ma_seek_origin origin) { ma_bool32 wasSuccessful; @@ -46470,14 +46518,32 @@ static ma_bool32 ma_decoder_seek_bytes(ma_decoder* pDecoder, int byteOffset, ma_ wasSuccessful = pDecoder->onSeek(pDecoder, byteOffset, origin); if (wasSuccessful) { - if (origin == ma_seek_origin_start) { - pDecoder->readPointerInBytes = (ma_uint64)byteOffset; - } else { - pDecoder->readPointerInBytes += byteOffset; - } + return MA_SUCCESS; + } else { + return MA_ERROR; + } +} + +static ma_result ma_decoder_tell_bytes(ma_decoder* pDecoder, ma_int64* pCursor) +{ + MA_ASSERT(pDecoder != NULL); + + if (pDecoder->onTell == NULL) { + return MA_NOT_IMPLEMENTED; } - return wasSuccessful; + return pDecoder->onTell(pDecoder, pCursor); +} + + +MA_API ma_decoding_backend_config ma_decoding_backend_config_init(ma_format preferredFormat) +{ + ma_decoding_backend_config config; + + MA_ZERO_OBJECT(&config); + config.preferredFormat = preferredFormat; + + return config; } @@ -46497,6 +46563,11 @@ MA_API ma_decoder_config ma_decoder_config_init(ma_format outputFormat, ma_uint3 return config; } +MA_API ma_decoder_config ma_decoder_config_init_default() +{ + return ma_decoder_config_init(ma_format_unknown, 0, 0); +} + MA_API ma_decoder_config ma_decoder_config_init_copy(const ma_decoder_config* pConfig) { ma_decoder_config config; @@ -46570,6 +46641,88 @@ static ma_result ma_decoder__init_data_converter(ma_decoder* pDecoder, const ma_ return ma_data_converter_init(&converterConfig, &pDecoder->converter); } + +/* Custom decoders. */ +static ma_result ma_decoder_internal_on_read__custom(void* pUserData, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead) +{ + ma_decoder* pDecoder = (ma_decoder*)pUserData; + MA_ASSERT(pDecoder != NULL); + + return ma_decoder_read_bytes(pDecoder, pBufferOut, bytesToRead, pBytesRead); +} + +static ma_result ma_decoder_internal_on_seek__custom(void* pUserData, ma_int64 offset, ma_seek_origin origin) +{ + ma_decoder* pDecoder = (ma_decoder*)pUserData; + MA_ASSERT(pDecoder != NULL); + + return ma_decoder_seek_bytes(pDecoder, offset, origin); +} + +static ma_result ma_decoder_internal_on_tell__custom(void* pUserData, ma_int64* pCursor) +{ + ma_decoder* pDecoder = (ma_decoder*)pUserData; + MA_ASSERT(pDecoder != NULL); + + return ma_decoder_tell_bytes(pDecoder, pCursor); +} + +static ma_result ma_decoder_init_custom__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) +{ + ma_result result = MA_NO_BACKEND; + ma_data_source* pBackend; + ma_decoding_backend_config backendConfig; + size_t ivtable; + + MA_ASSERT(pConfig != NULL); + MA_ASSERT(pDecoder != NULL); + + (void)pConfig; + + if (pConfig->ppCustomBackendVTables == NULL) { + return MA_NO_BACKEND; + } + + backendConfig = ma_decoding_backend_config_init(pConfig->format); + + /* The order each backend is listed is what defines the priority. */ + for (ivtable = 0; ivtable < pConfig->customBackendVTableCount; ivtable += 1) { + const ma_decoding_backend_vtable* pVTable = pConfig->ppCustomBackendVTables[ivtable]; + if (pVTable != NULL && pVTable->onInit != NULL) { + result = pVTable->onInit(pConfig->pCustomBackendUserData, ma_decoder_internal_on_read__custom, ma_decoder_internal_on_seek__custom, ma_decoder_internal_on_tell__custom, pDecoder, &backendConfig, &pDecoder->allocationCallbacks, &pBackend); + if (result == MA_SUCCESS) { + /* We found our decoding backend. Now we just need to set up some stuff in the decoder. */ + pDecoder->pBackend = pBackend; + pDecoder->pBackendVTable = pVTable; + pDecoder->pBackendUserData = pConfig->pCustomBackendUserData; + + /* Internal format/channels/rate. */ + ma_data_source_get_data_format(pDecoder->pBackend, &pDecoder->internalFormat, &pDecoder->internalChannels, &pDecoder->internalSampleRate); + + /* Internal channel map. For now we need to use a separate vtable API for this, but later on we'll add this to ma_data_source_get_data_format(). */ + if (pVTable->onGetChannelMap == NULL || pVTable->onGetChannelMap(pDecoder->pBackendUserData, pDecoder->pBackend, pDecoder->internalChannelMap, ma_countof(pDecoder->internalChannelMap)) != MA_SUCCESS) { + /* Failed to retrieve the channel map. Assume default. */ + ma_get_standard_channel_map(ma_standard_channel_map_default, ma_min(pDecoder->internalChannels, ma_countof(pDecoder->internalChannelMap)), pDecoder->internalChannelMap); + } + + return MA_SUCCESS; + } else { + /* Initialization failed. Move on to the next one, but seek back to the start. */ + result = ma_decoder_seek_bytes(pDecoder, 0, ma_seek_origin_start); + if (result != MA_SUCCESS) { + return result; /* Failed to seek back to the start. */ + } + } + } else { + /* No onInit callback. */ + } + } + + /* Getting here means we couldn't find a backend. */ + return MA_NO_BACKEND; +} + + /* WAV */ #ifdef dr_wav_h #define MA_HAS_WAV @@ -46577,17 +46730,30 @@ static ma_result ma_decoder__init_data_converter(ma_decoder* pDecoder, const ma_ static size_t ma_decoder_internal_on_read__wav(void* pUserData, void* pBufferOut, size_t bytesToRead) { ma_decoder* pDecoder = (ma_decoder*)pUserData; + ma_result result; + size_t bytesRead; + MA_ASSERT(pDecoder != NULL); - return ma_decoder_read_bytes(pDecoder, pBufferOut, bytesToRead); + result = ma_decoder_read_bytes(pDecoder, pBufferOut, bytesToRead, &bytesRead); + (void)result; + + return bytesRead; } static drwav_bool32 ma_decoder_internal_on_seek__wav(void* pUserData, int offset, drwav_seek_origin origin) { ma_decoder* pDecoder = (ma_decoder*)pUserData; + ma_result result; + MA_ASSERT(pDecoder != NULL); - return ma_decoder_seek_bytes(pDecoder, offset, (origin == drwav_seek_origin_start) ? ma_seek_origin_start : ma_seek_origin_current); + result = ma_decoder_seek_bytes(pDecoder, offset, (origin == drwav_seek_origin_start) ? ma_seek_origin_start : ma_seek_origin_current); + if (result != MA_SUCCESS) { + return MA_FALSE; + } + + return MA_TRUE; } static ma_uint64 ma_decoder_internal_on_read_pcm_frames__wav(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount) @@ -46722,17 +46888,30 @@ static ma_result ma_decoder_init_wav__internal(const ma_decoder_config* pConfig, static size_t ma_decoder_internal_on_read__flac(void* pUserData, void* pBufferOut, size_t bytesToRead) { ma_decoder* pDecoder = (ma_decoder*)pUserData; + ma_result result; + size_t bytesRead; + MA_ASSERT(pDecoder != NULL); - return ma_decoder_read_bytes(pDecoder, pBufferOut, bytesToRead); + result = ma_decoder_read_bytes(pDecoder, pBufferOut, bytesToRead, &bytesRead); + (void)result; + + return bytesRead; } static drflac_bool32 ma_decoder_internal_on_seek__flac(void* pUserData, int offset, drflac_seek_origin origin) { ma_decoder* pDecoder = (ma_decoder*)pUserData; + ma_result result; + MA_ASSERT(pDecoder != NULL); - return ma_decoder_seek_bytes(pDecoder, offset, (origin == drflac_seek_origin_start) ? ma_seek_origin_start : ma_seek_origin_current); + result = ma_decoder_seek_bytes(pDecoder, offset, (origin == drflac_seek_origin_start) ? ma_seek_origin_start : ma_seek_origin_current); + if (result != MA_SUCCESS) { + return MA_FALSE; + } + + return MA_TRUE; } static ma_uint64 ma_decoder_internal_on_read_pcm_frames__flac(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount) @@ -46843,17 +47022,30 @@ static ma_result ma_decoder_init_flac__internal(const ma_decoder_config* pConfig static size_t ma_decoder_internal_on_read__mp3(void* pUserData, void* pBufferOut, size_t bytesToRead) { ma_decoder* pDecoder = (ma_decoder*)pUserData; + ma_result result; + size_t bytesRead; + MA_ASSERT(pDecoder != NULL); - return ma_decoder_read_bytes(pDecoder, pBufferOut, bytesToRead); + result = ma_decoder_read_bytes(pDecoder, pBufferOut, bytesToRead, &bytesRead); + (void)result; + + return bytesRead; } static drmp3_bool32 ma_decoder_internal_on_seek__mp3(void* pUserData, int offset, drmp3_seek_origin origin) { ma_decoder* pDecoder = (ma_decoder*)pUserData; + ma_result result; + MA_ASSERT(pDecoder != NULL); - return ma_decoder_seek_bytes(pDecoder, offset, (origin == drmp3_seek_origin_start) ? ma_seek_origin_start : ma_seek_origin_current); + result = ma_decoder_seek_bytes(pDecoder, offset, (origin == drmp3_seek_origin_start) ? ma_seek_origin_start : ma_seek_origin_current); + if (result != MA_SUCCESS) { + return MA_FALSE; + } + + return MA_TRUE; } static ma_uint64 ma_decoder_internal_on_read_pcm_frames__mp3(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount) @@ -46975,6 +47167,7 @@ static ma_uint64 ma_vorbis_decoder_read_pcm_frames(ma_vorbis_decoder* pVorbis, m { float* pFramesOutF; ma_uint64 totalFramesRead; + ma_result result; MA_ASSERT(pVorbis != NULL); MA_ASSERT(pDecoder != NULL); @@ -47050,12 +47243,12 @@ static ma_uint64 ma_vorbis_decoder_read_pcm_frames(ma_vorbis_decoder* pVorbis, m } /* Fill in a chunk. */ - bytesRead = ma_decoder_read_bytes(pDecoder, pVorbis->pData + pVorbis->dataSize, (pVorbis->dataCapacity - pVorbis->dataSize)); - if (bytesRead == 0) { + result = ma_decoder_read_bytes(pDecoder, pVorbis->pData + pVorbis->dataSize, (pVorbis->dataCapacity - pVorbis->dataSize), &bytesRead); + pVorbis->dataSize += bytesRead; + + if (result != MA_SUCCESS) { return totalFramesRead; /* Error reading more data. */ } - - pVorbis->dataSize += bytesRead; } } while (MA_TRUE); } @@ -47066,6 +47259,7 @@ static ma_uint64 ma_vorbis_decoder_read_pcm_frames(ma_vorbis_decoder* pVorbis, m static ma_result ma_vorbis_decoder_seek_to_pcm_frame(ma_vorbis_decoder* pVorbis, ma_decoder* pDecoder, ma_uint64 frameIndex) { float buffer[4096]; + ma_result result; MA_ASSERT(pVorbis != NULL); MA_ASSERT(pDecoder != NULL); @@ -47077,8 +47271,9 @@ static ma_result ma_vorbis_decoder_seek_to_pcm_frame(ma_vorbis_decoder* pVorbis, TODO: Use seeking logic documented for stb_vorbis_flush_pushdata(). */ - if (!ma_decoder_seek_bytes(pDecoder, 0, ma_seek_origin_start)) { - return MA_ERROR; + result = ma_decoder_seek_bytes(pDecoder, 0, ma_seek_origin_start); + if (result != MA_SUCCESS) { + return result; } stb_vorbis_flush_pushdata(pVorbis->pInternalVorbis); @@ -47148,6 +47343,7 @@ static ma_uint64 ma_decoder_internal_on_get_length_in_pcm_frames__vorbis(ma_deco static ma_result ma_decoder_init_vorbis__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) { + ma_result result; stb_vorbis* pInternalVorbis = NULL; size_t dataSize = 0; size_t dataCapacity = 0; @@ -47179,12 +47375,13 @@ static ma_result ma_decoder_init_vorbis__internal(const ma_decoder_config* pConf pData = pNewData; /* Fill in a chunk. */ - bytesRead = ma_decoder_read_bytes(pDecoder, pData + dataSize, (dataCapacity - dataSize)); - if (bytesRead == 0) { - return MA_ERROR; + result = ma_decoder_read_bytes(pDecoder, pData + dataSize, (dataCapacity - dataSize), &bytesRead); + dataSize += bytesRead; + + if (result != MA_SUCCESS) { + return result; } - dataSize += bytesRead; if (dataSize > INT_MAX) { return MA_ERROR; /* Too big. */ } @@ -47256,6 +47453,7 @@ static ma_result ma_decoder_init_vorbis__internal(const ma_decoder_config* pConf /* Raw */ static ma_uint64 ma_decoder_internal_on_read_pcm_frames__raw(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount) { + ma_result result; ma_uint32 bpf; ma_uint64 totalFramesRead; void* pRunningFramesOut; @@ -47276,11 +47474,12 @@ static ma_uint64 ma_decoder_internal_on_read_pcm_frames__raw(ma_decoder* pDecode } if (pFramesOut != NULL) { - framesReadThisIteration = ma_decoder_read_bytes(pDecoder, pRunningFramesOut, (size_t)framesToReadThisIteration * bpf) / bpf; /* Safe cast to size_t. */ + result = ma_decoder_read_bytes(pDecoder, pRunningFramesOut, (size_t)framesToReadThisIteration * bpf, &framesReadThisIteration); /* Safe cast to size_t. */ + framesReadThisIteration /= bpf; pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesReadThisIteration * bpf); } else { /* We'll first try seeking. If this fails it means the end was reached and we'll to do a read-and-discard slow path to get the exact amount. */ - if (ma_decoder_seek_bytes(pDecoder, (int)framesToReadThisIteration, ma_seek_origin_current)) { + if (ma_decoder_seek_bytes(pDecoder, (int)framesToReadThisIteration, ma_seek_origin_current) == MA_SUCCESS) { framesReadThisIteration = framesToReadThisIteration; } else { /* Slow path. Need to fall back to a read-and-discard. This is required so we can get the exact number of remaining. */ @@ -47295,10 +47494,11 @@ static ma_uint64 ma_decoder_internal_on_read_pcm_frames__raw(ma_decoder* pDecode framesToReadNow = bufferCap; } - framesReadNow = ma_decoder_read_bytes(pDecoder, buffer, (size_t)(framesToReadNow * bpf)) / bpf; /* Safe cast. */ + result = ma_decoder_read_bytes(pDecoder, buffer, (size_t)(framesToReadNow * bpf), &framesReadNow); /* Safe cast. */ + framesReadNow /= bpf; framesReadThisIteration += framesReadNow; - if (framesReadNow < framesToReadNow) { + if (result != MA_SUCCESS || framesReadNow < framesToReadNow) { break; /* The end has been reached. */ } } @@ -47317,7 +47517,7 @@ static ma_uint64 ma_decoder_internal_on_read_pcm_frames__raw(ma_decoder* pDecode static ma_result ma_decoder_internal_on_seek_to_pcm_frame__raw(ma_decoder* pDecoder, ma_uint64 frameIndex) { - ma_bool32 result = MA_FALSE; + ma_result result = MA_ERROR; ma_uint64 totalBytesToSeek; MA_ASSERT(pDecoder != NULL); @@ -47334,7 +47534,7 @@ static ma_result ma_decoder_internal_on_seek_to_pcm_frame__raw(ma_decoder* pDeco } else { /* Complex case. Start by doing a seek relative to the start. Then keep looping using offset seeking. */ result = ma_decoder_seek_bytes(pDecoder, 0x7FFFFFFF, ma_seek_origin_start); - if (result == MA_TRUE) { + if (result == MA_SUCCESS) { totalBytesToSeek -= 0x7FFFFFFF; while (totalBytesToSeek > 0) { @@ -47344,7 +47544,7 @@ static ma_result ma_decoder_internal_on_seek_to_pcm_frame__raw(ma_decoder* pDeco } result = ma_decoder_seek_bytes(pDecoder, (int)bytesToSeekThisIteration, ma_seek_origin_current); - if (result != MA_TRUE) { + if (result != MA_SUCCESS) { break; } @@ -47353,11 +47553,7 @@ static ma_result ma_decoder_internal_on_seek_to_pcm_frame__raw(ma_decoder* pDeco } } - if (result) { - return MA_SUCCESS; - } else { - return MA_ERROR; - } + return result; } static ma_result ma_decoder_internal_on_uninit__raw(ma_decoder* pDecoder) @@ -47467,7 +47663,7 @@ static ma_data_source_vtable g_ma_decoder_data_source_vtable = ma_decoder__data_source_on_get_length }; -static ma_result ma_decoder__preinit(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) +static ma_result ma_decoder__preinit(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, ma_decoder_tell_proc onTell, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) { ma_result result; ma_data_source_config dataSourceConfig; @@ -47494,6 +47690,7 @@ static ma_result ma_decoder__preinit(ma_decoder_read_proc onRead, ma_decoder_see pDecoder->onRead = onRead; pDecoder->onSeek = onSeek; + pDecoder->onTell = onTell; pDecoder->pUserData = pUserData; result = ma_decoder__init_allocation_callbacks(pConfig, pDecoder); @@ -47535,7 +47732,7 @@ MA_API ma_result ma_decoder_init_wav(ma_decoder_read_proc onRead, ma_decoder_see config = ma_decoder_config_init_copy(pConfig); - result = ma_decoder__preinit(onRead, onSeek, pUserData, &config, pDecoder); + result = ma_decoder__preinit(onRead, onSeek, NULL, pUserData, &config, pDecoder); if (result != MA_SUCCESS) { return result; } @@ -47564,7 +47761,7 @@ MA_API ma_result ma_decoder_init_flac(ma_decoder_read_proc onRead, ma_decoder_se config = ma_decoder_config_init_copy(pConfig); - result = ma_decoder__preinit(onRead, onSeek, pUserData, &config, pDecoder); + result = ma_decoder__preinit(onRead, onSeek, NULL, pUserData, &config, pDecoder); if (result != MA_SUCCESS) { return result; } @@ -47593,7 +47790,7 @@ MA_API ma_result ma_decoder_init_mp3(ma_decoder_read_proc onRead, ma_decoder_see config = ma_decoder_config_init_copy(pConfig); - result = ma_decoder__preinit(onRead, onSeek, pUserData, &config, pDecoder); + result = ma_decoder__preinit(onRead, onSeek, NULL, pUserData, &config, pDecoder); if (result != MA_SUCCESS) { return result; } @@ -47622,7 +47819,7 @@ MA_API ma_result ma_decoder_init_vorbis(ma_decoder_read_proc onRead, ma_decoder_ config = ma_decoder_config_init_copy(pConfig); - result = ma_decoder__preinit(onRead, onSeek, pUserData, &config, pDecoder); + result = ma_decoder__preinit(onRead, onSeek, NULL, pUserData, &config, pDecoder); if (result != MA_SUCCESS) { return result; } @@ -47650,7 +47847,7 @@ MA_API ma_result ma_decoder_init_raw(ma_decoder_read_proc onRead, ma_decoder_see config = ma_decoder_config_init_copy(pConfigOut); - result = ma_decoder__preinit(onRead, onSeek, pUserData, &config, pDecoder); + result = ma_decoder__preinit(onRead, onSeek, NULL, pUserData, &config, pDecoder); if (result != MA_SUCCESS) { return result; } @@ -47674,10 +47871,17 @@ static ma_result ma_decoder_init__internal(ma_decoder_read_proc onRead, ma_decod (void)onRead; (void)onSeek; (void)pUserData; - (void)pConfig; - (void)pDecoder; - /* We use trial and error to open a decoder. */ + /* + We use trial and error to open a decoder. We prioritize custom decoders so that if they + implement the same encoding format they take priority over the built-in decoders. + */ + if (result != MA_SUCCESS) { + result = ma_decoder_init_custom__internal(pConfig, pDecoder); + if (result != MA_SUCCESS) { + onSeek(pDecoder, 0, ma_seek_origin_start); + } + } #ifdef MA_HAS_WAV if (result != MA_SUCCESS) { @@ -47726,7 +47930,7 @@ MA_API ma_result ma_decoder_init(ma_decoder_read_proc onRead, ma_decoder_seek_pr config = ma_decoder_config_init_copy(pConfig); - result = ma_decoder__preinit(onRead, onSeek, pUserData, &config, pDecoder); + result = ma_decoder__preinit(onRead, onSeek, NULL, pUserData, &config, pDecoder); if (result != MA_SUCCESS) { return result; } @@ -47739,50 +47943,72 @@ static size_t ma_decoder__on_read_memory(ma_decoder* pDecoder, void* pBufferOut, { size_t bytesRemaining; - MA_ASSERT(pDecoder->backend.memory.dataSize >= pDecoder->backend.memory.currentReadPos); + MA_ASSERT(pDecoder->data.memory.dataSize >= pDecoder->data.memory.currentReadPos); - bytesRemaining = pDecoder->backend.memory.dataSize - pDecoder->backend.memory.currentReadPos; + bytesRemaining = pDecoder->data.memory.dataSize - pDecoder->data.memory.currentReadPos; if (bytesToRead > bytesRemaining) { bytesToRead = bytesRemaining; } if (bytesToRead > 0) { - MA_COPY_MEMORY(pBufferOut, pDecoder->backend.memory.pData + pDecoder->backend.memory.currentReadPos, bytesToRead); - pDecoder->backend.memory.currentReadPos += bytesToRead; + MA_COPY_MEMORY(pBufferOut, pDecoder->data.memory.pData + pDecoder->data.memory.currentReadPos, bytesToRead); + pDecoder->data.memory.currentReadPos += bytesToRead; } return bytesToRead; } -static ma_bool32 ma_decoder__on_seek_memory(ma_decoder* pDecoder, int byteOffset, ma_seek_origin origin) +static ma_bool32 ma_decoder__on_seek_memory(ma_decoder* pDecoder, ma_int64 byteOffset, ma_seek_origin origin) { if (origin == ma_seek_origin_current) { if (byteOffset > 0) { - if (pDecoder->backend.memory.currentReadPos + byteOffset > pDecoder->backend.memory.dataSize) { - byteOffset = (int)(pDecoder->backend.memory.dataSize - pDecoder->backend.memory.currentReadPos); /* Trying to seek too far forward. */ + if (pDecoder->data.memory.currentReadPos + byteOffset > pDecoder->data.memory.dataSize) { + byteOffset = (ma_int64)(pDecoder->data.memory.dataSize - pDecoder->data.memory.currentReadPos); /* Trying to seek too far forward. */ } } else { - if (pDecoder->backend.memory.currentReadPos < (size_t)-byteOffset) { - byteOffset = -(int)pDecoder->backend.memory.currentReadPos; /* Trying to seek too far backwards. */ + if (pDecoder->data.memory.currentReadPos < (size_t)-byteOffset) { + byteOffset = -(ma_int64)pDecoder->data.memory.currentReadPos; /* Trying to seek too far backwards. */ } } /* This will never underflow thanks to the clamps above. */ - pDecoder->backend.memory.currentReadPos += byteOffset; + pDecoder->data.memory.currentReadPos += byteOffset; } else { - if ((ma_uint32)byteOffset <= pDecoder->backend.memory.dataSize) { - pDecoder->backend.memory.currentReadPos = byteOffset; + if (origin == ma_seek_origin_end) { + if (byteOffset < 0) { + byteOffset = -byteOffset; + } + + if (byteOffset > (ma_int64)pDecoder->data.memory.dataSize) { + pDecoder->data.memory.currentReadPos = 0; /* Trying to seek too far back. */ + } else { + pDecoder->data.memory.currentReadPos = pDecoder->data.memory.dataSize - byteOffset; + } } else { - pDecoder->backend.memory.currentReadPos = pDecoder->backend.memory.dataSize; /* Trying to seek too far forward. */ + if ((size_t)byteOffset <= pDecoder->data.memory.dataSize) { + pDecoder->data.memory.currentReadPos = byteOffset; + } else { + pDecoder->data.memory.currentReadPos = pDecoder->data.memory.dataSize; /* Trying to seek too far forward. */ + } } } return MA_TRUE; } +static ma_result ma_decoder__on_tell_memory(ma_decoder* pDecoder, ma_int64* pCursor) +{ + MA_ASSERT(pDecoder != NULL); + MA_ASSERT(pCursor != NULL); + + *pCursor = (ma_int64)pDecoder->data.memory.currentReadPos; + + return MA_SUCCESS; +} + static ma_result ma_decoder__preinit_memory(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) { - ma_result result = ma_decoder__preinit(ma_decoder__on_read_memory, ma_decoder__on_seek_memory, NULL, pConfig, pDecoder); + ma_result result = ma_decoder__preinit(ma_decoder__on_read_memory, ma_decoder__on_seek_memory, ma_decoder__on_tell_memory, NULL, pConfig, pDecoder); if (result != MA_SUCCESS) { return result; } @@ -47791,9 +48017,9 @@ static ma_result ma_decoder__preinit_memory(const void* pData, size_t dataSize, return MA_INVALID_ARGS; } - pDecoder->backend.memory.pData = (const ma_uint8*)pData; - pDecoder->backend.memory.dataSize = dataSize; - pDecoder->backend.memory.currentReadPos = 0; + pDecoder->data.memory.pData = (const ma_uint8*)pData; + pDecoder->data.memory.dataSize = dataSize; + pDecoder->data.memory.currentReadPos = 0; (void)pConfig; return MA_SUCCESS; @@ -48133,18 +48359,18 @@ static size_t ma_decoder__on_read_vfs(ma_decoder* pDecoder, void* pBufferOut, si MA_ASSERT(pDecoder != NULL); MA_ASSERT(pBufferOut != NULL); - ma_vfs_or_default_read(pDecoder->backend.vfs.pVFS, pDecoder->backend.vfs.file, pBufferOut, bytesToRead, &bytesRead); + ma_vfs_or_default_read(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.file, pBufferOut, bytesToRead, &bytesRead); return bytesRead; } -static ma_bool32 ma_decoder__on_seek_vfs(ma_decoder* pDecoder, int offset, ma_seek_origin origin) +static ma_bool32 ma_decoder__on_seek_vfs(ma_decoder* pDecoder, ma_int64 offset, ma_seek_origin origin) { ma_result result; MA_ASSERT(pDecoder != NULL); - result = ma_vfs_or_default_seek(pDecoder->backend.vfs.pVFS, pDecoder->backend.vfs.file, offset, origin); + result = ma_vfs_or_default_seek(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.file, offset, origin); if (result != MA_SUCCESS) { return MA_FALSE; } @@ -48152,12 +48378,19 @@ static ma_bool32 ma_decoder__on_seek_vfs(ma_decoder* pDecoder, int offset, ma_se return MA_TRUE; } +static ma_result ma_decoder__on_tell_vfs(ma_decoder* pDecoder, ma_int64* pCursor) +{ + MA_ASSERT(pDecoder != NULL); + + return ma_vfs_or_default_tell(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.file, pCursor); +} + static ma_result ma_decoder__preinit_vfs(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) { ma_result result; ma_vfs_file file; - result = ma_decoder__preinit(ma_decoder__on_read_vfs, ma_decoder__on_seek_vfs, NULL, pConfig, pDecoder); + result = ma_decoder__preinit(ma_decoder__on_read_vfs, ma_decoder__on_seek_vfs, ma_decoder__on_tell_vfs, NULL, pConfig, pDecoder); if (result != MA_SUCCESS) { return result; } @@ -48171,8 +48404,8 @@ static ma_result ma_decoder__preinit_vfs(ma_vfs* pVFS, const char* pFilePath, co return result; } - pDecoder->backend.vfs.pVFS = pVFS; - pDecoder->backend.vfs.file = file; + pDecoder->data.vfs.pVFS = pVFS; + pDecoder->data.vfs.file = file; return MA_SUCCESS; } @@ -48223,8 +48456,8 @@ MA_API ma_result ma_decoder_init_vfs(ma_vfs* pVFS, const char* pFilePath, const } if (result != MA_SUCCESS) { - if (pDecoder->backend.vfs.file != NULL) { /* <-- Will be reset to NULL if ma_decoder_uninit() is called in one of the steps above which allows us to avoid a double close of the file. */ - ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); + if (pDecoder->data.vfs.file != NULL) { /* <-- Will be reset to NULL if ma_decoder_uninit() is called in one of the steps above which allows us to avoid a double close of the file. */ + ma_vfs_or_default_close(pVFS, pDecoder->data.vfs.file); } return result; @@ -48251,7 +48484,7 @@ MA_API ma_result ma_decoder_init_vfs_wav(ma_vfs* pVFS, const char* pFilePath, co } if (result != MA_SUCCESS) { - ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->data.vfs.file); } return result; @@ -48282,7 +48515,7 @@ MA_API ma_result ma_decoder_init_vfs_flac(ma_vfs* pVFS, const char* pFilePath, c } if (result != MA_SUCCESS) { - ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->data.vfs.file); } return result; @@ -48313,7 +48546,7 @@ MA_API ma_result ma_decoder_init_vfs_mp3(ma_vfs* pVFS, const char* pFilePath, co } if (result != MA_SUCCESS) { - ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->data.vfs.file); } return result; @@ -48344,7 +48577,7 @@ MA_API ma_result ma_decoder_init_vfs_vorbis(ma_vfs* pVFS, const char* pFilePath, } if (result != MA_SUCCESS) { - ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->data.vfs.file); } return result; @@ -48364,7 +48597,7 @@ static ma_result ma_decoder__preinit_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePat ma_result result; ma_vfs_file file; - result = ma_decoder__preinit(ma_decoder__on_read_vfs, ma_decoder__on_seek_vfs, NULL, pConfig, pDecoder); + result = ma_decoder__preinit(ma_decoder__on_read_vfs, ma_decoder__on_seek_vfs, ma_decoder__on_tell_vfs, NULL, pConfig, pDecoder); if (result != MA_SUCCESS) { return result; } @@ -48378,8 +48611,8 @@ static ma_result ma_decoder__preinit_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePat return result; } - pDecoder->backend.vfs.pVFS = pVFS; - pDecoder->backend.vfs.file = file; + pDecoder->data.vfs.pVFS = pVFS; + pDecoder->data.vfs.file = file; return MA_SUCCESS; } @@ -48430,7 +48663,7 @@ MA_API ma_result ma_decoder_init_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePath, c } if (result != MA_SUCCESS) { - ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->data.vfs.file); return result; } @@ -48455,7 +48688,7 @@ MA_API ma_result ma_decoder_init_vfs_wav_w(ma_vfs* pVFS, const wchar_t* pFilePat } if (result != MA_SUCCESS) { - ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->data.vfs.file); } return result; @@ -48486,7 +48719,7 @@ MA_API ma_result ma_decoder_init_vfs_flac_w(ma_vfs* pVFS, const wchar_t* pFilePa } if (result != MA_SUCCESS) { - ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->data.vfs.file); } return result; @@ -48517,7 +48750,7 @@ MA_API ma_result ma_decoder_init_vfs_mp3_w(ma_vfs* pVFS, const wchar_t* pFilePat } if (result != MA_SUCCESS) { - ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->data.vfs.file); } return result; @@ -48548,7 +48781,7 @@ MA_API ma_result ma_decoder_init_vfs_vorbis_w(ma_vfs* pVFS, const wchar_t* pFile } if (result != MA_SUCCESS) { - ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->data.vfs.file); } return result; @@ -48621,13 +48854,20 @@ MA_API ma_result ma_decoder_uninit(ma_decoder* pDecoder) return MA_INVALID_ARGS; } + if (pDecoder->pBackend != NULL) { + if (pDecoder->pBackendVTable != NULL && pDecoder->pBackendVTable->onUninit != NULL) { + pDecoder->pBackendVTable->onUninit(pDecoder->pBackendUserData, pDecoder->pBackend, &pDecoder->allocationCallbacks); + } + } + + /* Legacy. */ if (pDecoder->onUninit) { pDecoder->onUninit(pDecoder); } if (pDecoder->onRead == ma_decoder__on_read_vfs) { - ma_vfs_or_default_close(pDecoder->backend.vfs.pVFS, pDecoder->backend.vfs.file); - pDecoder->backend.vfs.file = NULL; + ma_vfs_or_default_close(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.file); + pDecoder->data.vfs.file = NULL; } ma_data_converter_uninit(&pDecoder->converter); @@ -48659,8 +48899,16 @@ MA_API ma_uint64 ma_decoder_get_length_in_pcm_frames(ma_decoder* pDecoder) return 0; } - if (pDecoder->onGetLengthInPCMFrames) { - ma_uint64 nativeLengthInPCMFrames = pDecoder->onGetLengthInPCMFrames(pDecoder); + if (pDecoder->pBackend != NULL || pDecoder->onGetLengthInPCMFrames != NULL) { + ma_uint64 nativeLengthInPCMFrames; + + if (pDecoder->pBackend != NULL) { + ma_data_source_get_length_in_pcm_frames(pDecoder->pBackend, &nativeLengthInPCMFrames); + } else { + /* Legacy. */ + nativeLengthInPCMFrames = pDecoder->onGetLengthInPCMFrames(pDecoder); + } + if (pDecoder->internalSampleRate == pDecoder->outputSampleRate) { return nativeLengthInPCMFrames; } else { @@ -48682,20 +48930,30 @@ MA_API ma_uint64 ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesO return 0; } - if (pDecoder->onReadPCMFrames == NULL) { + if (pDecoder->pBackend == NULL && pDecoder->onReadPCMFrames == NULL) { return 0; } /* Fast path. */ if (pDecoder->converter.isPassthrough) { - totalFramesReadOut = pDecoder->onReadPCMFrames(pDecoder, pFramesOut, frameCount); + if (pDecoder->pBackend != NULL) { + result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pFramesOut, frameCount, &totalFramesReadOut, MA_FALSE); + } else { + /* Legacy. */ + totalFramesReadOut = pDecoder->onReadPCMFrames(pDecoder, pFramesOut, frameCount); + } } else { /* Getting here means we need to do data conversion. If we're seeking forward and are _not_ doing resampling we can run this in a fast path. If we're doing resampling we need to run through each sample because we need to ensure it's internal cache is updated. */ if (pFramesOut == NULL && pDecoder->converter.hasResampler == MA_FALSE) { - totalFramesReadOut = pDecoder->onReadPCMFrames(pDecoder, NULL, frameCount); /* All decoder backends must support passing in NULL for the output buffer. */ + if (pDecoder->pBackend != NULL) { + result = ma_data_source_read_pcm_frames(pDecoder->pBackend, NULL, frameCount, &totalFramesReadOut, MA_FALSE); + } else { + /* Legacy. */ + totalFramesReadOut = pDecoder->onReadPCMFrames(pDecoder, NULL, frameCount); /* All decoder backends must support passing in NULL for the output buffer. */ + } } else { /* Slow path. Need to run everything through the data converter. */ totalFramesReadOut = 0; @@ -48723,7 +48981,13 @@ MA_API ma_uint64 ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesO } if (requiredInputFrameCount > 0) { - framesReadThisIterationIn = pDecoder->onReadPCMFrames(pDecoder, pIntermediaryBuffer, framesToReadThisIterationIn); + if (pDecoder->pBackend != NULL) { + result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pIntermediaryBuffer, framesToReadThisIterationIn, &framesReadThisIterationIn, MA_FALSE); + } else { + /* Legacy. */ + framesReadThisIterationIn = pDecoder->onReadPCMFrames(pDecoder, pIntermediaryBuffer, framesToReadThisIterationIn); + } + totalFramesReadIn += framesReadThisIterationIn; } else { framesReadThisIterationIn = 0; @@ -48763,7 +49027,7 @@ MA_API ma_result ma_decoder_seek_to_pcm_frame(ma_decoder* pDecoder, ma_uint64 fr return MA_INVALID_ARGS; } - if (pDecoder->onSeekToPCMFrame) { + if (pDecoder->pBackend != NULL || pDecoder->onSeekToPCMFrame != NULL) { ma_result result; ma_uint64 internalFrameIndex; if (pDecoder->internalSampleRate == pDecoder->outputSampleRate) { @@ -48772,7 +49036,13 @@ MA_API ma_result ma_decoder_seek_to_pcm_frame(ma_decoder* pDecoder, ma_uint64 fr internalFrameIndex = ma_calculate_frame_count_after_resampling(pDecoder->internalSampleRate, pDecoder->outputSampleRate, frameIndex); } - result = pDecoder->onSeekToPCMFrame(pDecoder, internalFrameIndex); + if (pDecoder->pBackend != NULL) { + result = ma_data_source_seek_to_pcm_frame(pDecoder->pBackend, internalFrameIndex); + } else { + /* Legacy. */ + result = pDecoder->onSeekToPCMFrame(pDecoder, internalFrameIndex); + } + if (result == MA_SUCCESS) { pDecoder->readPointerInPCMFrames = frameIndex; }