From 998bd995bc6e49e8d7519ff2c7925e2ca17ff57b Mon Sep 17 00:00:00 2001 From: David Reid Date: Wed, 30 Jun 2021 20:42:43 +1000 Subject: [PATCH] Add libvorbis and libopus data sources to the extras folder. --- extras/miniaudio_libopus.h | 486 ++++++++++++++++++++++++++++++++++ extras/miniaudio_libvorbis.h | 495 +++++++++++++++++++++++++++++++++++ 2 files changed, 981 insertions(+) create mode 100644 extras/miniaudio_libopus.h create mode 100644 extras/miniaudio_libvorbis.h diff --git a/extras/miniaudio_libopus.h b/extras/miniaudio_libopus.h new file mode 100644 index 00000000..ede712c4 --- /dev/null +++ b/extras/miniaudio_libopus.h @@ -0,0 +1,486 @@ +/* +This implements a data source that decodes Vorbis streams via libopus + libopusfile + +This object can be plugged into any `ma_data_source_*()` API and can also be used as a custom +decoding backend. See the custom_decoder example. + +You need to include this file after miniaudio.h. +*/ +#ifndef miniaudio_libopus_h +#define miniaudio_libopus_h + +#ifdef __cplusplus +extern "C" { +#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); + +#ifdef __cplusplus +} +#endif +#endif + +#if defined(MINIAUDIO_IMPLEMENTATION) || defined(MA_IMPLEMENTATION) + +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 +} + +#endif diff --git a/extras/miniaudio_libvorbis.h b/extras/miniaudio_libvorbis.h new file mode 100644 index 00000000..25eaf094 --- /dev/null +++ b/extras/miniaudio_libvorbis.h @@ -0,0 +1,495 @@ +/* +This implements a data source that decodes Vorbis streams via libvorbis + libvorbisfile + +This object can be plugged into any `ma_data_source_*()` API and can also be used as a custom +decoding backend. See the custom_decoder example. + +You need to include this file after miniaudio.h. +*/ +#ifndef miniaudio_libvorbis_h +#define miniaudio_libvorbis_h + +#ifdef __cplusplus +extern "C" { +#endif + +#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); + +#ifdef __cplusplus +} +#endif +#endif + +#if defined(MINIAUDIO_IMPLEMENTATION) || defined(MA_IMPLEMENTATION) + +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 +} + +#endif