mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-23 08:44:04 +02:00
Add support for partial processing to nodes.
This includes a leading trimming node to act as a test.
This commit is contained in:
@@ -0,0 +1,102 @@
|
|||||||
|
|
||||||
|
#include "ma_ltrim_node.h"
|
||||||
|
|
||||||
|
MA_API ma_ltrim_node_config ma_ltrim_node_config_init(ma_uint32 channels, float threshold)
|
||||||
|
{
|
||||||
|
ma_ltrim_node_config config;
|
||||||
|
|
||||||
|
MA_ZERO_OBJECT(&config);
|
||||||
|
config.nodeConfig = ma_node_config_init(); /* Input and output channels will be set in ma_ltrim_node_init(). */
|
||||||
|
config.channels = channels;
|
||||||
|
config.threshold = threshold;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void ma_ltrim_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut)
|
||||||
|
{
|
||||||
|
ma_ltrim_node* pTrimNode = (ma_ltrim_node*)pNode;
|
||||||
|
ma_uint32 framesProcessedIn = 0;
|
||||||
|
ma_uint32 framesProcessedOut = 0;
|
||||||
|
ma_uint32 channelCount = ma_node_get_input_channels(pNode, 0);
|
||||||
|
|
||||||
|
/*
|
||||||
|
If we haven't yet found the start, skip over every input sample until we find a frame outside
|
||||||
|
of the threshold.
|
||||||
|
*/
|
||||||
|
if (pTrimNode->foundStart == MA_FALSE) {
|
||||||
|
while (framesProcessedIn < *pFrameCountIn) {
|
||||||
|
ma_uint32 iChannel = 0;
|
||||||
|
for (iChannel = 0; iChannel < channelCount; iChannel += 1) {
|
||||||
|
float sample = ppFramesIn[0][framesProcessedIn*channelCount + iChannel];
|
||||||
|
if (sample < -pTrimNode->threshold || sample > pTrimNode->threshold) {
|
||||||
|
pTrimNode->foundStart = MA_TRUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pTrimNode->foundStart) {
|
||||||
|
break; /* The start has been found. Get out of this loop and finish off processing. */
|
||||||
|
} else {
|
||||||
|
framesProcessedIn += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If there's anything left, just copy it over. */
|
||||||
|
framesProcessedOut = ma_min(*pFrameCountOut, *pFrameCountIn - framesProcessedIn);
|
||||||
|
ma_copy_pcm_frames(ppFramesOut[0], &ppFramesIn[0][framesProcessedIn], framesProcessedOut, ma_format_f32, channelCount);
|
||||||
|
|
||||||
|
framesProcessedIn += framesProcessedOut;
|
||||||
|
|
||||||
|
/* We always "process" every input frame, but we may only done a partial output. */
|
||||||
|
*pFrameCountIn = framesProcessedIn;
|
||||||
|
*pFrameCountOut = framesProcessedOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ma_node_vtable g_ma_ltrim_node_vtable =
|
||||||
|
{
|
||||||
|
ma_ltrim_node_process_pcm_frames,
|
||||||
|
NULL,
|
||||||
|
1, /* 1 input channel. */
|
||||||
|
1, /* 1 output channel. */
|
||||||
|
MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES
|
||||||
|
};
|
||||||
|
|
||||||
|
MA_API ma_result ma_ltrim_node_init(ma_node_graph* pNodeGraph, const ma_ltrim_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_ltrim_node* pTrimNode)
|
||||||
|
{
|
||||||
|
ma_result result;
|
||||||
|
ma_node_config baseConfig;
|
||||||
|
|
||||||
|
if (pTrimNode == NULL) {
|
||||||
|
return MA_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
MA_ZERO_OBJECT(pTrimNode);
|
||||||
|
|
||||||
|
if (pConfig == NULL) {
|
||||||
|
return MA_INVALID_ARGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
pTrimNode->threshold = pConfig->threshold;
|
||||||
|
pTrimNode->foundStart = MA_FALSE;
|
||||||
|
|
||||||
|
baseConfig = pConfig->nodeConfig;
|
||||||
|
baseConfig.vtable = &g_ma_ltrim_node_vtable;
|
||||||
|
baseConfig.pInputChannels = &pConfig->channels;
|
||||||
|
baseConfig.pOutputChannels = &pConfig->channels;
|
||||||
|
|
||||||
|
result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pTrimNode->baseNode);
|
||||||
|
if (result != MA_SUCCESS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MA_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
MA_API void ma_ltrim_node_uninit(ma_ltrim_node* pTrimNode, const ma_allocation_callbacks* pAllocationCallbacks)
|
||||||
|
{
|
||||||
|
/* The base node is always uninitialized first. */
|
||||||
|
ma_node_uninit(pTrimNode, pAllocationCallbacks);
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/* Include ma_ltrim_node.h after miniaudio.h */
|
||||||
|
#ifndef ma_ltrim_node_h
|
||||||
|
#define ma_ltrim_node_h
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
The trim node has one input and one output.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
ma_node_config nodeConfig;
|
||||||
|
ma_uint32 channels;
|
||||||
|
float threshold;
|
||||||
|
} ma_ltrim_node_config;
|
||||||
|
|
||||||
|
MA_API ma_ltrim_node_config ma_ltrim_node_config_init(ma_uint32 channels, float threshold);
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
ma_node_base baseNode;
|
||||||
|
float threshold;
|
||||||
|
ma_bool32 foundStart;
|
||||||
|
} ma_ltrim_node;
|
||||||
|
|
||||||
|
MA_API ma_result ma_ltrim_node_init(ma_node_graph* pNodeGraph, const ma_ltrim_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_ltrim_node* pTrimNode);
|
||||||
|
MA_API void ma_ltrim_node_uninit(ma_ltrim_node* pTrimNode, const ma_allocation_callbacks* pAllocationCallbacks);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif /* ma_ltrim_node_h */
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
#define MINIAUDIO_IMPLEMENTATION
|
||||||
|
#include "../../../miniaudio.h"
|
||||||
|
#include "ma_ltrim_node.c"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#define DEVICE_FORMAT ma_format_f32 /* Must always be f32 for this example because the node graph system only works with this. */
|
||||||
|
#define DEVICE_CHANNELS 0 /* The input file will determine the channel count. */
|
||||||
|
#define DEVICE_SAMPLE_RATE 0 /* The input file will determine the sample rate. */
|
||||||
|
|
||||||
|
static ma_decoder g_decoder; /* The decoder that we'll read data from. */
|
||||||
|
static ma_data_source_node g_dataSupplyNode; /* The node that will sit at the root level. Will be reading data from g_dataSupply. */
|
||||||
|
static ma_ltrim_node g_trimNode; /* The trim node. */
|
||||||
|
static ma_node_graph g_nodeGraph; /* The main node graph that we'll be feeding data through. */
|
||||||
|
|
||||||
|
void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
|
||||||
|
{
|
||||||
|
(void)pInput;
|
||||||
|
(void)pDevice;
|
||||||
|
|
||||||
|
/* All we need to do is read from the node graph. */
|
||||||
|
ma_node_graph_read_pcm_frames(&g_nodeGraph, pOutput, frameCount, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
ma_result result;
|
||||||
|
ma_decoder_config decoderConfig;
|
||||||
|
ma_device_config deviceConfig;
|
||||||
|
ma_device device;
|
||||||
|
ma_node_graph_config nodeGraphConfig;
|
||||||
|
ma_ltrim_node_config trimNodeConfig;
|
||||||
|
ma_data_source_node_config dataSupplyNodeConfig;
|
||||||
|
|
||||||
|
if (argc < 1) {
|
||||||
|
printf("No input file.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Decoder. */
|
||||||
|
decoderConfig = ma_decoder_config_init(DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE);
|
||||||
|
|
||||||
|
result = ma_decoder_init_file(argv[1], &decoderConfig, &g_decoder);
|
||||||
|
if (result != MA_SUCCESS) {
|
||||||
|
printf("Failed to load decoder.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Device. */
|
||||||
|
deviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||||
|
deviceConfig.playback.pDeviceID = NULL;
|
||||||
|
deviceConfig.playback.format = g_decoder.outputFormat;
|
||||||
|
deviceConfig.playback.channels = g_decoder.outputChannels;
|
||||||
|
deviceConfig.sampleRate = g_decoder.outputSampleRate;
|
||||||
|
deviceConfig.dataCallback = data_callback;
|
||||||
|
result = ma_device_init(NULL, &deviceConfig, &device);
|
||||||
|
if (result != MA_SUCCESS) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Node graph. */
|
||||||
|
nodeGraphConfig = ma_node_graph_config_init(device.playback.channels);
|
||||||
|
|
||||||
|
result = ma_node_graph_init(&nodeGraphConfig, NULL, &g_nodeGraph);
|
||||||
|
if (result != MA_SUCCESS) {
|
||||||
|
printf("Failed to initialize node graph.");
|
||||||
|
goto done0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Trimmer. Attached straight to the endpoint. Input will be the data source node. */
|
||||||
|
trimNodeConfig = ma_ltrim_node_config_init(device.playback.channels, 0);
|
||||||
|
|
||||||
|
result = ma_ltrim_node_init(&g_nodeGraph, &trimNodeConfig, NULL, &g_trimNode);
|
||||||
|
if (result != MA_SUCCESS) {
|
||||||
|
printf("Failed to initialize ltrim node.");
|
||||||
|
goto done1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ma_node_attach_output_bus(&g_trimNode, 0, ma_node_graph_get_endpoint(&g_nodeGraph), 0);
|
||||||
|
|
||||||
|
|
||||||
|
/* Data supply. */
|
||||||
|
dataSupplyNodeConfig = ma_data_source_node_config_init(&g_decoder);
|
||||||
|
|
||||||
|
result = ma_data_source_node_init(&g_nodeGraph, &dataSupplyNodeConfig, NULL, &g_dataSupplyNode);
|
||||||
|
if (result != MA_SUCCESS) {
|
||||||
|
printf("Failed to initialize data source node.");
|
||||||
|
goto done2;
|
||||||
|
}
|
||||||
|
|
||||||
|
ma_node_attach_output_bus(&g_dataSupplyNode, 0, &g_trimNode, 0);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Now we just start the device and wait for the user to terminate the program. */
|
||||||
|
ma_device_start(&device);
|
||||||
|
|
||||||
|
printf("Press Enter to quit...\n");
|
||||||
|
getchar();
|
||||||
|
|
||||||
|
/* It's important that we stop the device first or else we'll uninitialize the graph from under the device. */
|
||||||
|
ma_device_stop(&device);
|
||||||
|
|
||||||
|
|
||||||
|
/*done3:*/ ma_data_source_node_uninit(&g_dataSupplyNode, NULL);
|
||||||
|
done2: ma_ltrim_node_uninit(&g_trimNode, NULL);
|
||||||
|
done1: ma_node_graph_uninit(&g_nodeGraph, NULL);
|
||||||
|
done0: ma_device_uninit(&device);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+133
-124
@@ -28,7 +28,7 @@ audio device. The high level API is good for those who have complex mixing and e
|
|||||||
|
|
||||||
|
|
||||||
1.1. Low Level API
|
1.1. Low Level API
|
||||||
-----------------
|
------------------
|
||||||
The low level API gives you access to the raw audio data of an audio device. It supports playback,
|
The low level API gives you access to the raw audio data of an audio device. It supports playback,
|
||||||
capture, full-duplex and loopback (WASAPI only). You can enumerate over devices to determine which
|
capture, full-duplex and loopback (WASAPI only). You can enumerate over devices to determine which
|
||||||
physical device(s) you want to connect to.
|
physical device(s) you want to connect to.
|
||||||
@@ -67466,149 +67466,158 @@ static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusInde
|
|||||||
/* Getting here means we need to do another round of processing. */
|
/* Getting here means we need to do another round of processing. */
|
||||||
pNodeBase->cachedFrameCountOut = 0;
|
pNodeBase->cachedFrameCountOut = 0;
|
||||||
|
|
||||||
/*
|
for (;;) {
|
||||||
We need to prepare our output frame pointers for processing. In the same iteration we need
|
frameCountOut = 0;
|
||||||
to mark every output bus as unread so that future calls to this function for different buses
|
|
||||||
for the current time period don't pull in data when they should instead be reading from cache.
|
|
||||||
*/
|
|
||||||
for (iOutputBus = 0; iOutputBus < outputBusCount; iOutputBus += 1) {
|
|
||||||
ma_node_output_bus_set_has_read(&pNodeBase->pOutputBuses[iOutputBus], MA_FALSE); /* <-- This is what tells the next calls to this function for other output buses for this time period to read from cache instead of pulling in more data. */
|
|
||||||
ppFramesOut[iOutputBus] = ma_node_get_cached_output_ptr(pNode, iOutputBus);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We only need to read from input buses if there isn't already some data in the cache. */
|
|
||||||
if (pNodeBase->cachedFrameCountIn == 0) {
|
|
||||||
ma_uint32 maxFramesReadIn = 0;
|
|
||||||
|
|
||||||
/* Here is where we pull in data from the input buses. This is what will trigger an advance in time. */
|
|
||||||
for (iInputBus = 0; iInputBus < inputBusCount; iInputBus += 1) {
|
|
||||||
ma_uint32 framesRead;
|
|
||||||
|
|
||||||
/* The first thing to do is get the offset within our bulk allocation to store this input data. */
|
|
||||||
ppFramesIn[iInputBus] = ma_node_get_cached_input_ptr(pNode, iInputBus);
|
|
||||||
|
|
||||||
/* Once we've determined our destination pointer we can read. Note that we must inspect the number of frames read and fill any leftovers with silence for safety. */
|
|
||||||
result = ma_node_input_bus_read_pcm_frames(pNodeBase, &pNodeBase->pInputBuses[iInputBus], ppFramesIn[iInputBus], framesToProcessIn, &framesRead, globalTime);
|
|
||||||
if (result != MA_SUCCESS) {
|
|
||||||
/* It doesn't really matter if we fail because we'll just fill with silence. */
|
|
||||||
framesRead = 0; /* Just for safety, but I don't think it's really needed. */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: Minor optimization opportunity here. If no frames were read and the buffer is already filled with silence, no need to re-silence it. */
|
|
||||||
/* Any leftover frames need to silenced for safety. */
|
|
||||||
if (framesRead < framesToProcessIn) {
|
|
||||||
ma_silence_pcm_frames(ppFramesIn[iInputBus] + (framesRead * ma_node_get_input_channels(pNodeBase, iInputBus)), (framesToProcessIn - framesRead), ma_format_f32, ma_node_get_input_channels(pNodeBase, iInputBus));
|
|
||||||
}
|
|
||||||
|
|
||||||
maxFramesReadIn = ma_max(maxFramesReadIn, framesRead);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This was a fresh load of input data so reset our consumption counter. */
|
|
||||||
pNodeBase->consumedFrameCountIn = 0;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
We don't want to keep processing if there's nothing to process, so set the number of cached
|
We need to prepare our output frame pointers for processing. In the same iteration we need
|
||||||
input frames to the maximum number we read from each attachment (the lesser will be padded
|
to mark every output bus as unread so that future calls to this function for different buses
|
||||||
with silence). If we didn't read anything, this will be set to 0 and the entire buffer will
|
for the current time period don't pull in data when they should instead be reading from cache.
|
||||||
have been assigned to silence. This being equal to 0 is an important property for us because
|
|
||||||
it allows us to detect when NULL can be passed into the processing callback for the input
|
|
||||||
buffer for the purpose of continuous processing.
|
|
||||||
*/
|
*/
|
||||||
pNodeBase->cachedFrameCountIn = (ma_uint16)maxFramesReadIn;
|
for (iOutputBus = 0; iOutputBus < outputBusCount; iOutputBus += 1) {
|
||||||
} else {
|
ma_node_output_bus_set_has_read(&pNodeBase->pOutputBuses[iOutputBus], MA_FALSE); /* <-- This is what tells the next calls to this function for other output buses for this time period to read from cache instead of pulling in more data. */
|
||||||
/* We don't need to read anything, but we do need to prepare our input frame pointers. */
|
ppFramesOut[iOutputBus] = ma_node_get_cached_output_ptr(pNode, iOutputBus);
|
||||||
for (iInputBus = 0; iInputBus < inputBusCount; iInputBus += 1) {
|
|
||||||
ppFramesIn[iInputBus] = ma_node_get_cached_input_ptr(pNode, iInputBus) + (pNodeBase->consumedFrameCountIn * ma_node_get_input_channels(pNodeBase, iInputBus));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/* We only need to read from input buses if there isn't already some data in the cache. */
|
||||||
At this point we have our input data so now we need to do some processing. Sneaky little
|
if (pNodeBase->cachedFrameCountIn == 0) {
|
||||||
optimization here - we can set the pointer to the output buffer for this output bus so
|
ma_uint32 maxFramesReadIn = 0;
|
||||||
that the final copy into the output buffer is done directly by onProcess().
|
|
||||||
*/
|
|
||||||
if (pFramesOut != NULL) {
|
|
||||||
ppFramesOut[outputBusIndex] = pFramesOut;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/* Here is where we pull in data from the input buses. This is what will trigger an advance in time. */
|
||||||
|
for (iInputBus = 0; iInputBus < inputBusCount; iInputBus += 1) {
|
||||||
|
ma_uint32 framesRead;
|
||||||
|
|
||||||
/* Give the processing function the entire capacity of the output buffer. */
|
/* The first thing to do is get the offset within our bulk allocation to store this input data. */
|
||||||
frameCountOut = framesToProcessOut;
|
ppFramesIn[iInputBus] = ma_node_get_cached_input_ptr(pNode, iInputBus);
|
||||||
|
|
||||||
/*
|
/* Once we've determined our destination pointer we can read. Note that we must inspect the number of frames read and fill any leftovers with silence for safety. */
|
||||||
We need to treat nodes with continuous processing a little differently. For these ones,
|
result = ma_node_input_bus_read_pcm_frames(pNodeBase, &pNodeBase->pInputBuses[iInputBus], ppFramesIn[iInputBus], framesToProcessIn, &framesRead, globalTime);
|
||||||
we always want to fire the callback with the requested number of frames, regardless of
|
if (result != MA_SUCCESS) {
|
||||||
pNodeBase->cachedFrameCountIn, which could be 0. Also, we want to check if we can pass
|
/* It doesn't really matter if we fail because we'll just fill with silence. */
|
||||||
in NULL for the input buffer to the callback.
|
framesRead = 0; /* Just for safety, but I don't think it's really needed. */
|
||||||
*/
|
}
|
||||||
if ((pNodeBase->vtable->flags & MA_NODE_FLAG_CONTINUOUS_PROCESSING) != 0) {
|
|
||||||
/* We're using continuous processing. Make sure we specify the whole frame count at all times. */
|
|
||||||
frameCountIn = framesToProcessIn; /* Give the processing function as much input data as we've got in the buffer, including any silenced padding from short reads. */
|
|
||||||
|
|
||||||
if ((pNodeBase->vtable->flags & MA_NODE_FLAG_ALLOW_NULL_INPUT) != 0 && pNodeBase->consumedFrameCountIn == 0 && pNodeBase->cachedFrameCountIn == 0) {
|
/* TODO: Minor optimization opportunity here. If no frames were read and the buffer is already filled with silence, no need to re-silence it. */
|
||||||
consumeNullInput = MA_TRUE;
|
/* Any leftover frames need to silenced for safety. */
|
||||||
|
if (framesRead < framesToProcessIn) {
|
||||||
|
ma_silence_pcm_frames(ppFramesIn[iInputBus] + (framesRead * ma_node_get_input_channels(pNodeBase, iInputBus)), (framesToProcessIn - framesRead), ma_format_f32, ma_node_get_input_channels(pNodeBase, iInputBus));
|
||||||
|
}
|
||||||
|
|
||||||
|
maxFramesReadIn = ma_max(maxFramesReadIn, framesRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This was a fresh load of input data so reset our consumption counter. */
|
||||||
|
pNodeBase->consumedFrameCountIn = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
We don't want to keep processing if there's nothing to process, so set the number of cached
|
||||||
|
input frames to the maximum number we read from each attachment (the lesser will be padded
|
||||||
|
with silence). If we didn't read anything, this will be set to 0 and the entire buffer will
|
||||||
|
have been assigned to silence. This being equal to 0 is an important property for us because
|
||||||
|
it allows us to detect when NULL can be passed into the processing callback for the input
|
||||||
|
buffer for the purpose of continuous processing.
|
||||||
|
*/
|
||||||
|
pNodeBase->cachedFrameCountIn = (ma_uint16)maxFramesReadIn;
|
||||||
} else {
|
} else {
|
||||||
|
/* We don't need to read anything, but we do need to prepare our input frame pointers. */
|
||||||
|
for (iInputBus = 0; iInputBus < inputBusCount; iInputBus += 1) {
|
||||||
|
ppFramesIn[iInputBus] = ma_node_get_cached_input_ptr(pNode, iInputBus) + (pNodeBase->consumedFrameCountIn * ma_node_get_input_channels(pNodeBase, iInputBus));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
At this point we have our input data so now we need to do some processing. Sneaky little
|
||||||
|
optimization here - we can set the pointer to the output buffer for this output bus so
|
||||||
|
that the final copy into the output buffer is done directly by onProcess().
|
||||||
|
*/
|
||||||
|
if (pFramesOut != NULL) {
|
||||||
|
ppFramesOut[outputBusIndex] = ma_offset_pcm_frames_ptr_f32(pFramesOut, pNodeBase->cachedFrameCountOut, ma_node_get_output_channels(pNode, outputBusIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Give the processing function the entire capacity of the output buffer. */
|
||||||
|
frameCountOut = (framesToProcessOut - pNodeBase->cachedFrameCountOut);
|
||||||
|
|
||||||
|
/*
|
||||||
|
We need to treat nodes with continuous processing a little differently. For these ones,
|
||||||
|
we always want to fire the callback with the requested number of frames, regardless of
|
||||||
|
pNodeBase->cachedFrameCountIn, which could be 0. Also, we want to check if we can pass
|
||||||
|
in NULL for the input buffer to the callback.
|
||||||
|
*/
|
||||||
|
if ((pNodeBase->vtable->flags & MA_NODE_FLAG_CONTINUOUS_PROCESSING) != 0) {
|
||||||
|
/* We're using continuous processing. Make sure we specify the whole frame count at all times. */
|
||||||
|
frameCountIn = framesToProcessIn; /* Give the processing function as much input data as we've got in the buffer, including any silenced padding from short reads. */
|
||||||
|
|
||||||
|
if ((pNodeBase->vtable->flags & MA_NODE_FLAG_ALLOW_NULL_INPUT) != 0 && pNodeBase->consumedFrameCountIn == 0 && pNodeBase->cachedFrameCountIn == 0) {
|
||||||
|
consumeNullInput = MA_TRUE;
|
||||||
|
} else {
|
||||||
|
consumeNullInput = MA_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Since we're using continuous processing we're always passing in a full frame count
|
||||||
|
regardless of how much input data was read. If this is greater than what we read as
|
||||||
|
input, we'll end up with an underflow. We instead need to make sure our cached frame
|
||||||
|
count is set to the number of frames we'll be passing to the data callback. Not
|
||||||
|
doing this will result in an underflow when we "consume" the cached data later on.
|
||||||
|
|
||||||
|
Note that this check needs to be done after the "consumeNullInput" check above because
|
||||||
|
we use the property of cachedFrameCountIn being 0 to determine whether or not we
|
||||||
|
should be passing in a null pointer to the processing callback for when the node is
|
||||||
|
configured with MA_NODE_FLAG_ALLOW_NULL_INPUT.
|
||||||
|
*/
|
||||||
|
if (pNodeBase->cachedFrameCountIn < (ma_uint16)frameCountIn) {
|
||||||
|
pNodeBase->cachedFrameCountIn = (ma_uint16)frameCountIn;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
frameCountIn = pNodeBase->cachedFrameCountIn; /* Give the processing function as much valid input data as we've got. */
|
||||||
consumeNullInput = MA_FALSE;
|
consumeNullInput = MA_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Since we're using continuous processing we're always passing in a full frame count
|
Process data slightly differently depending on whether or not we're consuming NULL
|
||||||
regardless of how much input data was read. If this is greater than what we read as
|
input (checked just above).
|
||||||
input, we'll end up with an underflow. We instead need to make sure our cached frame
|
|
||||||
count is set to the number of frames we'll be passing to the data callback. Not
|
|
||||||
doing this will result in an underflow when we "consume" the cached data later on.
|
|
||||||
|
|
||||||
Note that this check needs to be done after the "consumeNullInput" check above because
|
|
||||||
we use the property of cachedFrameCountIn being 0 to determine whether or not we
|
|
||||||
should be passing in a null pointer to the processing callback for when the node is
|
|
||||||
configured with MA_NODE_FLAG_ALLOW_NULL_INPUT.
|
|
||||||
*/
|
*/
|
||||||
if (pNodeBase->cachedFrameCountIn < (ma_uint16)frameCountIn) {
|
if (consumeNullInput) {
|
||||||
pNodeBase->cachedFrameCountIn = (ma_uint16)frameCountIn;
|
ma_node_process_pcm_frames_internal(pNode, NULL, &frameCountIn, ppFramesOut, &frameCountOut);
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
We want to skip processing if there's no input data, but we can only do that safely if
|
||||||
|
we know that there is no chance of any output frames being produced. If continuous
|
||||||
|
processing is being used, this won't be a problem because the input frame count will
|
||||||
|
always be non-0. However, if continuous processing is *not* enabled and input and output
|
||||||
|
data is processed at different rates, we still need to process that last input frame
|
||||||
|
because there could be a few excess output frames needing to be produced from cached
|
||||||
|
data. The `MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES` flag is used as the indicator for
|
||||||
|
determining whether or not we need to process the node even when there are no input
|
||||||
|
frames available right now.
|
||||||
|
*/
|
||||||
|
if (frameCountIn > 0 || (pNodeBase->vtable->flags & MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES) != 0) {
|
||||||
|
ma_node_process_pcm_frames_internal(pNode, (const float**)ppFramesIn, &frameCountIn, ppFramesOut, &frameCountOut); /* From GCC: expected 'const float **' but argument is of type 'float **'. Shouldn't this be implicit? Excplicit cast to silence the warning. */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
frameCountIn = pNodeBase->cachedFrameCountIn; /* Give the processing function as much valid input data as we've got. */
|
|
||||||
consumeNullInput = MA_FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Process data slightly differently depending on whether or not we're consuming NULL
|
|
||||||
input (checked just above).
|
|
||||||
*/
|
|
||||||
if (consumeNullInput) {
|
|
||||||
ma_node_process_pcm_frames_internal(pNode, NULL, &frameCountIn, ppFramesOut, &frameCountOut);
|
|
||||||
} else {
|
|
||||||
/*
|
/*
|
||||||
We want to skip processing if there's no input data, but we can only do that safely if
|
Thanks to our sneaky optimization above we don't need to do any data copying directly into
|
||||||
we know that there is no chance of any output frames being produced. If continuous
|
the output buffer - the onProcess() callback just did that for us. We do, however, need to
|
||||||
processing is being used, this won't be a problem because the input frame count will
|
apply the number of input and output frames that were processed. Note that due to continuous
|
||||||
always be non-0. However, if continuous processing is *not* enabled and input and output
|
processing above, we need to do explicit checks here. If we just consumed a NULL input
|
||||||
data is processed at different rates, we still need to process that last input frame
|
buffer it means that no actual input data was processed from the internal buffers and we
|
||||||
because there could be a few excess output frames needing to be produced from cached
|
don't want to be modifying any counters.
|
||||||
data. The `MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES` flag is used as the indicator for
|
|
||||||
determining whether or not we need to process the node even when there are no input
|
|
||||||
frames available right now.
|
|
||||||
*/
|
*/
|
||||||
if (frameCountIn > 0 || (pNodeBase->vtable->flags & MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES) != 0) {
|
if (consumeNullInput == MA_FALSE) {
|
||||||
ma_node_process_pcm_frames_internal(pNode, (const float**)ppFramesIn, &frameCountIn, ppFramesOut, &frameCountOut); /* From GCC: expected 'const float **' but argument is of type 'float **'. Shouldn't this be implicit? Excplicit cast to silence the warning. */
|
pNodeBase->consumedFrameCountIn += (ma_uint16)frameCountIn;
|
||||||
|
pNodeBase->cachedFrameCountIn -= (ma_uint16)frameCountIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The cached output frame count is always equal to what we just read. */
|
||||||
|
pNodeBase->cachedFrameCountOut += (ma_uint16)frameCountOut;
|
||||||
|
|
||||||
|
/* If we couldn't process any data, we're done. The loop needs to be terminated here or else we'll get stuck in a loop. */
|
||||||
|
if (pNodeBase->cachedFrameCountOut == framesToProcessOut || (frameCountOut == 0 && frameCountIn == 0)) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Thanks to our sneaky optimization above we don't need to do any data copying directly into
|
|
||||||
the output buffer - the onProcess() callback just did that for us. We do, however, need to
|
|
||||||
apply the number of input and output frames that were processed. Note that due to continuous
|
|
||||||
processing above, we need to do explicit checks here. If we just consumed a NULL input
|
|
||||||
buffer it means that no actual input data was processed from the internal buffers and we
|
|
||||||
don't want to be modifying any counters.
|
|
||||||
*/
|
|
||||||
if (consumeNullInput == MA_FALSE) {
|
|
||||||
pNodeBase->consumedFrameCountIn += (ma_uint16)frameCountIn;
|
|
||||||
pNodeBase->cachedFrameCountIn -= (ma_uint16)frameCountIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The cached output frame count is always equal to what we just read. */
|
|
||||||
pNodeBase->cachedFrameCountOut = (ma_uint16)frameCountOut;
|
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
We're not needing to read anything from the input buffer so just read directly from our
|
We're not needing to read anything from the input buffer so just read directly from our
|
||||||
@@ -87790,7 +87799,7 @@ There are many breaking API changes in this release.
|
|||||||
have been removed. Instead you should set the encodingFormat variable in the decoder config.
|
have been removed. Instead you should set the encodingFormat variable in the decoder config.
|
||||||
- ma_decoder_get_length_in_pcm_frames() has been updated to return a result code and output the
|
- ma_decoder_get_length_in_pcm_frames() has been updated to return a result code and output the
|
||||||
length via an output parameter. This makes it consistent with data sources.
|
length via an output parameter. This makes it consistent with data sources.
|
||||||
- ma_decoder_read_pcm_frames() has been update to return a result code and output the number of
|
- ma_decoder_read_pcm_frames() has been updated to return a result code and output the number of
|
||||||
frames read via an output parameter.
|
frames read via an output parameter.
|
||||||
- Allocation callbacks must now implement the onRealloc() callback. Previously miniaudio would
|
- Allocation callbacks must now implement the onRealloc() callback. Previously miniaudio would
|
||||||
emulate this in terms of malloc and free, but for simplicity it is now required that allocation
|
emulate this in terms of malloc and free, but for simplicity it is now required that allocation
|
||||||
|
|||||||
Reference in New Issue
Block a user