mirror of
https://github.com/mackron/miniaudio.git
synced 2026-05-05 22:54:03 +02:00
15da9bd7ff
A new callback called `onProp` has been added to
`ma_data_source_vtable`. This replaces the following callbacks:
onGetDataFormat
onGetCursor
onGetLength
onSetLooping
This new callback is for retrieving and setting various properties
relating to the data source. It takes a `prop` parameter which is an ID
for the property being handled, and a `void*` pointer for
property-specific data.
Typically onProp implementations would discriminate on the property type
using a switch. The example below shows how to handle the old callbacks:
switch (prop)
{
// Replaces onGetDataFormat (format/channels/rate).
case MA_DATA_SOURCE_GET_DATA_SOURCE:
{
ma_data_source_data_format* pDataFormat =
(ma_data_source_data_format*)pData;
pDataFormat->format = pCustomDataSource->format;
pDataFormat->channels = pCustomDataSource->channels;
pDataFormat->sampleRate = pCustomDataSource->sampleRate;
return MA_SUCCESS;
}
// Replaces onGetDataFormat (channel map)
case MA_DATA_SOURCE_GET_CHANNEL_MAP:
{
ma_channel_map_init_standard(
ma_standard_channel_map_default,
(ma_channel*)pData,
MA_MAX_CHANNELS,
pCustomDataSource->channels);
return MA_SUCCESS;
}
// Replaces onGetCursor
case MA_DATA_SOURCE_GET_CURSOR:
{
*((ma_uint64*)pData) = pCustomDataSource->cursor;
return MA_SUCCESS;
}
// Replaces onGetLength
case MA_DATA_SOURCE_GET_LENGTH:
{
*((ma_uint64*)pData) = pCustomDataSource->length;
return MA_SUCCESS;
}
// Replaces onSetLooping
case MA_DATA_SOURCE_SET_LOOPING:
{
pCustomDataSource->isLooping = *((ma_bool32*)pData);
return MA_SUCCESS;
}
// Mandatory when MA_DATA_SOURCE_SET_LOOPING is implemented.
case MA_DATA_SOURCE_GET_LOOPING:
{
*((ma_bool32*)pData) = pCustomDataSource->isLooping;
return MA_SUCCESS;
}
// Return MA_NOT_IMPLEMENTED for any ignored properties.
default: return MA_NOT_IMPLEMENTED;
}
Note how the format/channels/rate and channel map properties have been
split across two separate properties, `MA_DATA_SOURCE_GET_DATA_SOURCE`
and `MA_DATA_SOURCE_GET_CHANNEL_MAP`. Along with this change, the
channel map parameters have been removed from
`ma_data_source_get_data_format()` and a new function called
`ma_data_source_get_channel_map()` has been added.
New properties have also been added for handling ranges and loop points.
This allows the data source implementation itself to handle it rather
than miniaudio doing it at a higher level. Where this is useful is if
your data source is a wrapper around another data source and you want to
route ranges and loop points to the internal data source.
The reason for this change is that it allows for properties to be added
without having to break the build due to yet another callback being
added. It also hides away the more niche properties that the majority of
data sources do not care about. For example, rarely does a data source
need to handle the `onSetLooping` callback, yet every data source needed
to add a `NULL` entry to their vtables just for this one extremely niche
property.
See documentation for further details.
Tag: release-notes
Tag: api-change
525 lines
14 KiB
C
525 lines
14 KiB
C
#ifndef miniaudio_libopus_c
|
|
#define miniaudio_libopus_c
|
|
|
|
#include "miniaudio_libopus.h"
|
|
|
|
#if !defined(MA_NO_LIBOPUS)
|
|
#include <opusfile.h>
|
|
#endif
|
|
|
|
#include <string.h> /* For memset(). */
|
|
#include <assert.h>
|
|
|
|
static size_t ma_libopus_ds_sizeof(void)
|
|
{
|
|
return sizeof(ma_libopus);
|
|
}
|
|
|
|
static void ma_libopus_ds_uninit(ma_data_source* pDataSource)
|
|
{
|
|
ma_libopus_uninit((ma_libopus*)pDataSource);
|
|
}
|
|
|
|
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_prop(ma_data_source* pDataSource, int prop, void* pData)
|
|
{
|
|
ma_libopus* pOpus = (ma_libopus*)pDataSource;
|
|
|
|
switch (prop)
|
|
{
|
|
case MA_DATA_SOURCE_GET_DATA_FORMAT:
|
|
{
|
|
ma_data_source_data_format* pDataFormat = (ma_data_source_data_format*)pData;
|
|
|
|
return ma_libopus_get_data_format(pOpus, &pDataFormat->format, &pDataFormat->channels, &pDataFormat->sampleRate, NULL, 0);
|
|
}
|
|
|
|
case MA_DATA_SOURCE_GET_CHANNEL_MAP:
|
|
{
|
|
return ma_libopus_get_data_format(pOpus, NULL, NULL, NULL, (ma_channel*)pData, MA_MAX_CHANNELS);
|
|
}
|
|
|
|
case MA_DATA_SOURCE_GET_CURSOR:
|
|
{
|
|
return ma_libopus_get_cursor_in_pcm_frames(pOpus, (ma_uint64*)pData);
|
|
}
|
|
|
|
case MA_DATA_SOURCE_GET_LENGTH:
|
|
{
|
|
return ma_libopus_get_length_in_pcm_frames(pOpus, (ma_uint64*)pData);
|
|
}
|
|
|
|
default: break;
|
|
}
|
|
|
|
return MA_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
static ma_data_source_vtable ma_gDataSourceVTable_libopus =
|
|
{
|
|
ma_libopus_ds_sizeof,
|
|
ma_libopus_ds_uninit,
|
|
NULL, /* onCopy. Copying is not supported. */
|
|
ma_libopus_ds_read,
|
|
ma_libopus_ds_seek,
|
|
ma_libopus_ds_prop
|
|
};
|
|
|
|
|
|
#if !defined(MA_NO_LIBOPUS)
|
|
static int ma_libopus_of_callback__read(void* pUserData, unsigned char* pBufferOut, int bytesToRead)
|
|
{
|
|
ma_libopus* pOpus = (ma_libopus*)pUserData;
|
|
ma_result result;
|
|
size_t bytesRead;
|
|
|
|
result = pOpus->onRead(pOpus->pReadSeekTellUserData, (void*)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_SET;
|
|
} else if (whence == SEEK_END) {
|
|
origin = MA_SEEK_END;
|
|
} else {
|
|
origin = MA_SEEK_CUR;
|
|
}
|
|
|
|
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, const ma_allocation_callbacks* pAllocationCallbacks, ma_libopus* pOpus)
|
|
{
|
|
ma_result result;
|
|
ma_data_source_config dataSourceConfig;
|
|
|
|
if (pOpus == NULL) {
|
|
return MA_INVALID_ARGS;
|
|
}
|
|
|
|
memset(pOpus, 0, sizeof(*pOpus));
|
|
pOpus->allocationCallbacks = ma_allocation_callbacks_init_copy(pAllocationCallbacks);
|
|
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.pVTable = &ma_gDataSourceVTable_libopus;
|
|
|
|
result = ma_data_source_base_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, pAllocationCallbacks, 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 Opus 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. */
|
|
(void)ma_gDataSourceVTable_libopus;
|
|
return MA_NOT_IMPLEMENTED;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
MA_API void ma_libopus_uninit(ma_libopus* pOpus)
|
|
{
|
|
if (pOpus == NULL) {
|
|
return;
|
|
}
|
|
|
|
#if !defined(MA_NO_LIBOPUS)
|
|
{
|
|
op_free((OggOpusFile*)pOpus->of);
|
|
}
|
|
#else
|
|
{
|
|
/* libopus is disabled. Should never hit this since initialization would have failed. */
|
|
assert(MA_FALSE);
|
|
}
|
|
#endif
|
|
|
|
ma_data_source_base_uninit(&pOpus->ds);
|
|
}
|
|
|
|
MA_API ma_result ma_libopus_read_pcm_frames(ma_libopus* pOpus, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead)
|
|
{
|
|
if (pFramesRead != NULL) {
|
|
*pFramesRead = 0;
|
|
}
|
|
|
|
if (frameCount == 0) {
|
|
return MA_INVALID_ARGS;
|
|
}
|
|
|
|
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;
|
|
ma_uint64 framesToRead;
|
|
ma_uint64 framesRemaining;
|
|
|
|
framesRemaining = (frameCount - totalFramesRead);
|
|
framesToRead = 1024;
|
|
if (framesToRead > framesRemaining) {
|
|
framesToRead = framesRemaining;
|
|
}
|
|
|
|
if (format == ma_format_f32) {
|
|
libopusResult = op_read_float((OggOpusFile*)pOpus->of, (float* )ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels), (int)(framesToRead * channels), NULL);
|
|
} else {
|
|
libopusResult = op_read ((OggOpusFile*)pOpus->of, (opus_int16*)ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels), (int)(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;
|
|
}
|
|
|
|
if (result == MA_SUCCESS && totalFramesRead == 0) {
|
|
result = MA_AT_END;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
#else
|
|
{
|
|
/* libopus is disabled. Should never hit this since initialization would have failed. */
|
|
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((OggOpusFile*)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. */
|
|
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) {
|
|
memset(pChannelMap, 0, 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((OggOpusFile*)pOpus->of, -1);
|
|
|
|
if (pChannels != NULL) {
|
|
*pChannels = channels;
|
|
}
|
|
|
|
if (pSampleRate != NULL) {
|
|
*pSampleRate = 48000;
|
|
}
|
|
|
|
if (pChannelMap != NULL) {
|
|
ma_channel_map_init_standard(ma_standard_channel_map_vorbis, pChannelMap, channelMapCap, channels);
|
|
}
|
|
|
|
return MA_SUCCESS;
|
|
}
|
|
#else
|
|
{
|
|
/* libopus is disabled. Should never hit this since initialization would have failed. */
|
|
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((OggOpusFile*)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. */
|
|
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((OggOpusFile*)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. */
|
|
assert(MA_FALSE);
|
|
return MA_NOT_IMPLEMENTED;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
The code below defines the vtable that you'll plug into your `ma_decoder_config` object.
|
|
*/
|
|
#if !defined(MA_NO_LIBOPUS)
|
|
static void ma_decoding_backend_info__libopus(void* pUserData, ma_decoding_backend_info* pInfo)
|
|
{
|
|
(void)pUserData;
|
|
|
|
pInfo->pName = "Opus";
|
|
pInfo->pLibraryName = "libopus";
|
|
pInfo->pVendor = "Xiph.Org";
|
|
pInfo->encodingFormat = ma_encoding_format_opus;
|
|
}
|
|
|
|
static ma_result ma_decoding_backend_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_result result;
|
|
ma_libopus* pOpus;
|
|
|
|
(void)pUserData;
|
|
|
|
pOpus = (ma_libopus*)ma_malloc(sizeof(*pOpus), pAllocationCallbacks);
|
|
if (pOpus == NULL) {
|
|
return MA_OUT_OF_MEMORY;
|
|
}
|
|
|
|
result = ma_libopus_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pOpus);
|
|
if (result != MA_SUCCESS) {
|
|
ma_free(pOpus, pAllocationCallbacks);
|
|
return result;
|
|
}
|
|
|
|
*ppBackend = pOpus;
|
|
|
|
return MA_SUCCESS;
|
|
}
|
|
|
|
static void ma_decoding_backend_uninit__libopus(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks)
|
|
{
|
|
ma_libopus* pOpus = (ma_libopus*)pBackend;
|
|
|
|
(void)pUserData;
|
|
|
|
ma_libopus_uninit(pOpus);
|
|
ma_free(pOpus, pAllocationCallbacks);
|
|
}
|
|
|
|
static ma_decoding_backend_vtable ma_gDecodingBackendVTable_libopus =
|
|
{
|
|
ma_decoding_backend_info__libopus,
|
|
ma_decoding_backend_init__libopus,
|
|
NULL, /* onInitMemory() */
|
|
ma_decoding_backend_uninit__libopus
|
|
};
|
|
ma_decoding_backend_vtable* ma_decoding_backend_libopus = &ma_gDecodingBackendVTable_libopus;
|
|
#else
|
|
ma_decoding_backend_vtable* ma_decoding_backend_libopus = NULL;
|
|
#endif
|
|
|
|
MA_API ma_decoding_backend_vtable* ma_libopus_get_vtable(void)
|
|
{
|
|
return ma_decoding_backend_libopus;
|
|
}
|
|
|
|
#endif /* miniaudio_libopus_c */
|