Skip to content

Commit 0f6fa19

Browse files
committed
Add default input side API and docs
1 parent f6f7f32 commit 0f6fa19

5 files changed

Lines changed: 100 additions & 28 deletions

File tree

doc/asrc_task/asrc_task.rst

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ ASRC Task
44
Introduction
55
............
66

7-
The ASRC library provides a function call that operates on blocks of samples whereas typical XMOS audio IO libraries provide streaming audio one sample at a time. The ASRC task wraps up this and all of the other lower level APIs and required logic to provide a a simple to use and generic ASRC conversion block suitable for integration into practical designs. It is fully re-entrant permitting multiple instances within a project supporting multiple (or bi-directional) sample rate changes and clock domain bridges.
7+
The ASRC library provides a function call that operates on blocks of samples whereas typical XMOS audio IO libraries provide streaming audio one sample at a time. The ASRC task wraps up the core ASRC function with all of the other lower level APIs (eg. FIFO) and required logic. It provides a simple-to-use and generic ASRC conversion block suitable for integration into practical designs. It is fully re-entrant permitting multiple instances within a project supporting multiple (or bi-directional) sample rates and audio clock domain bridges.
8+
9+
Operation
10+
.........
811

912
The ASRC task handles bridging between two asynchronous audio sources. It has an input side and output side. The input samples are provided over a channel allowing the source to be placed on a different XCORE tile if needed. The output side sample interface is via an asynchronous FIFO meaning the consumer must reside on the same XCORE tile as the ASRC.
1013

@@ -13,10 +16,7 @@ The ASRC task handles bridging between two asynchronous audio sources. It has an
1316
:alt: ASRC Thread Usage
1417

1518

16-
Operation
17-
.........
18-
19-
Both input and output interfaces must specify the nominal sample rate and additionally the input must specify a channel count. The output channel count will be set to the same as the input channel count automatically. A timestamp indicating the time of the production of the input sample and the consumption of the output sample must also be supplied which allows the ASRC to calculate the rate and phase difference. Each time either the input or output nominal sample rate or the channel count changes the ASRC subsystem automatically re-configures itself and restarts with the new settings.
19+
Both input and output interfaces must specify the nominal sample rate required and additionally the input must specify a channel count. The output channel count will be set to the same as the input channel count automatically once the ASRC has automatically configured itself. A timestamp indicating the time of the production of the last input sample and the consumption of the first output sample must also be supplied which allows the ASRC to calculate the rate and phase difference. Each time either the input or output nominal sample rate or the channel count changes the ASRC subsystem automatically re-configures itself and restarts with the new settings.
2020

2121
The ASRC Task supports the following nominal sample rates for input and output:
2222

@@ -27,9 +27,9 @@ The ASRC Task supports the following nominal sample rates for input and output:
2727
- 176.4 kHz
2828
- 192 kHz
2929

30-
Because the required compute for multi-channel systems may exceed the performance limit of a single thread, the ASRC subsystem is able to make use of multiple threads in parallel to achieve the required conversion within the sample time period. It uses a dynamic fork and join architecture to share the ASRC workload across multiple threads each time a batch of samples is processed. The threads must all reside on the same tile as the ASRC task due to them sharing input and output buffers. The workload and buffer partitioning is dynamically computed by the ASRC task at startup and is constrained by the user at compile time to set maximum limits of both channel count and worker threads.
30+
Because the required compute for multi-channel systems may exceed the performance limit of a single thread, the ASRC subsystem is able to make use of multiple threads in parallel to achieve the required conversion within the sample time period. It uses a dynamic fork and join architecture to share the ASRC workload across multiple threads each time a batch of samples is processed. The threads must all reside on the same tile as the ASRC task due to them sharing input and output buffers. The workload and buffer partitioning is dynamically computed by the ASRC task at stream startup and is constrained by the user at compile time to set maximum limits of both channel count and worker threads.
3131

32-
The number of threads that are required depends on the required channel count and sample rates required. Higher sample rates require more MIPS. The amount of thread MHz (and consequently how many threads) required can be `roughly` calculated using the following formulae:
32+
The number of threads that are required depends on the required channel count and sample rates required. Higher sample rates require more MIPS. The amount of thread MHz (and consequently how many threads) required can be *roughly* calculated using the following formulae:
3333

3434
- Total thread MHz required for xcore.ai systems = 0.15 * Max channel count * (Max SR input kHz + Max SR output kHz)
3535
- Total thread MHz required for XCORE-200 systems = 0.3 * Max channel count * (Max SR input kHz + Max SR output kHz)
@@ -40,14 +40,14 @@ The difference between the performance requirement between the two architectures
4040

4141
- An eight channel system consisting of either 44.1kHz or 48kHz input with maximum output rate of 192kHz will require about (0.15 * (48 + 192) * 8) ~= 288 thread MHz. This can adequately be provided by four threads (assuming up to 8 active threads on an xcore.ai device with a 600MHz clock).
4242

43-
In reality the amount of thread MHz needed will be lower than the above formulae suggest since subsequent ASRC channels after the first can share some of the calculations resulting in about at 10% performance requirement reduction per additional channel per worker thread. Increasing the input frame size in the ASRC task may also reduce the MHz requirement a few % at the cost of larger buffers and a slight latency increase.
43+
In reality the amount of thread MHz needed will be lower than the above formulae suggest since subsequent ASRC channels after the first can share some of the calculations. This results in about at 10% performance requirement reduction per additional channel per worker thread. Increasing the input frame size in the ASRC task may also reduce the MHz requirement a few % at the cost of larger buffers and a slight latency increase.
4444

4545
.. warning::
4646
Exceeding the processing time available by specifying a channel count, input/output rates, number of worker threads or device clock speed may result in at best choppy audio or a blocked ASRC task if the overrun is persistent.
4747

4848
It is strongly recommended that you test the system for your desired channel count and input and output sample rates. An optional timing calculation and check is provided in the ASRC to allow characterisation at run-time which can be found in the `asrc_task.c` source code.
4949

50-
The low level ASRC processing function call API accepts a minimum input frame size of four whereas most XMOS audio interfaces provide a single sample period frame. The ASRC subsystem integrates a serial to block back to serial conversion to support this. The input side works by stealing cycles from the ASRC using an interrupt and notifies the main ASRC loop using a single channel end when a complete frame of double buffered is available. The ASRC output side is handled by the asynchronous FIFO which supports a block `put` with single sample `get` and thus provides serialisation intrinsically.
50+
The low level ASRC processing function call API accepts a minimum input frame size of four whereas most XMOS audio interfaces provide a single sample period frame. The ASRC subsystem integrates a serial to block back to serial conversion to support this. The input side works by stealing cycles from the ASRC using an interrupt and notifies the main ASRC loop using a single channel end when a complete frame of double buffered is available to process. The ASRC output side is handled by the asynchronous FIFO which supports a block `put` with single sample `get` and thus provides de-serialisation intrinsically.
5151

5252

5353
API & Usage
@@ -60,11 +60,13 @@ The ASRC Task consists of a forever loop task to which various data structures m
6060
- The length of the FIFO passed in above.
6161

6262

63-
In addition the following two functions must be declared in a user `C` file:
63+
In addition the following two functions may be declared in a user `C` file (note XC does not handle function pointers):
6464

6565
- The callback function from ASRC task which receives samples over a channel from the producer.
6666
- A callback initialisation function which registers the callback function into the `asrc_in_out_t` struct
6767

68+
If these are not defined, then a default receive implementation will be used which is matched with the send_asrc_input_samples_default() function on the user's producer side. This should be sufficient for typical usage.
69+
6870
An example of calling the ASRC task form and ``XC`` main function is provided below. Note use of `unsafe` permitting the compiler to allow shared memory structures to be accessed by more than one thread::
6971

7072
chan c_producer;
@@ -78,7 +80,7 @@ An example of calling the ASRC task form and ``XC`` main function is provided be
7880
asrc_in_out_t asrc_io = {{{0}}};
7981
asrc_in_out_t * unsafe asrc_io_ptr = &asrc_io;
8082
asynchronous_fifo_t * unsafe fifo = (asynchronous_fifo_t *)array;
81-
init_asrc_io_callback(asrc_io_ptr);
83+
setup_asrc_io_custom_callback(asrc_io_ptr); // Optional user rx function
8284

8385
par
8486
{
@@ -90,7 +92,7 @@ An example of calling the ASRC task form and ``XC`` main function is provided be
9092
} // unsafe region
9193

9294

93-
An example of the user `C` function for receiving the input samples is shown below along with the user callback registration function. The `receive_asrc_input_samples()` function must be as short as possible because it steals cycles from the ASRC task operation. Because this function is not called until the first channel word is received from the producer, the `chanend_in_word()` operations will happen straight away and not block::
95+
An example of the user-defined `C` function for receiving the input samples is shown below along with the user callback registration function. The `receive_asrc_input_samples()` function must be as short as possible because it steals cycles from the ASRC task operation. Because this function is not called until the first channel word is received from the producer, the `chanend_in_word()` operations will happen straight away and not block::
9496

9597
#include "asrc_task.h"
9698

@@ -118,8 +120,8 @@ An example of the user `C` function for receiving the input samples is shown bel
118120
}
119121

120122
// Register the above function for ASRC task
121-
void init_asrc_io_callback(asrc_in_out_t *asrc_io){
122-
asrc_io->asrc_task_produce_cb = receive_asrc_input_samples;
123+
void setup_asrc_io_custom_callback(asrc_in_out_t *asrc_io){
124+
init_asrc_io_callback(asrc_io, receive_asrc_input_samples);
123125
}
124126

125127

lib_src/src/asrc_task/asrc_task.c

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,28 @@ void reset_asrc_fifo_consumer(asynchronous_fifo_t * fifo){
192192
memset(fifo->buffer, 0, fifo->channel_count * fifo->max_fifo_depth * sizeof(int));
193193
}
194194

195-
// Handy type definition of the callback function.
196-
typedef unsigned (*asrc_task_produce_isr_cb_t)(chanend_t c_producer, asrc_in_out_t *asrc_io, unsigned *new_input_rate);
195+
// Default implementation of receive (called from ASRC) which receives samples and config over a channel. This is overridable.
196+
ASRC_TASK_ISR_CALLBACK_ATTR
197+
unsigned receive_asrc_input_samples_cb_default(chanend_t c_asrc_input, asrc_in_out_t *asrc_io, unsigned *new_input_rate){
198+
static unsigned asrc_in_counter = 0;
199+
200+
// Get format and timing data from channel
201+
*new_input_rate = chanend_in_word(c_asrc_input);
202+
asrc_io->input_timestamp = chanend_in_word(c_asrc_input);
203+
asrc_io->input_channel_count = chanend_in_word(c_asrc_input);
204+
205+
// Pack into array properly LRLRLRLR for 2ch or 123412341234 for 4ch etc.
206+
for(int i = 0; i < asrc_io->input_channel_count; i++){
207+
int idx = i + asrc_io->input_channel_count * asrc_in_counter;
208+
asrc_io->input_samples[asrc_io->input_write_idx][idx] = chanend_in_word(c_asrc_input);
209+
}
210+
211+
if(++asrc_in_counter == SRC_N_IN_SAMPLES){
212+
asrc_in_counter = 0;
213+
}
214+
215+
return asrc_in_counter;
216+
}
197217

198218
// Structure used for holding the vars needed for the ASRC_TASK receive_asrc_input_samples() callback.
199219
// This is needed because we can only pass a single pointer to an ISR.
@@ -391,8 +411,10 @@ DEFINE_INTERRUPT_PERMITTED(ASRC_ISR_GRP, void, asrc_processor,
391411

392412
// Wrapper to setup ISR->task signalling chanend and use ISR friendly call to function
393413
void asrc_task(chanend_t c_asrc_input, asrc_in_out_t *asrc_io, asynchronous_fifo_t *fifo, unsigned fifo_length){
394-
// Check callback is init'd.
395-
xassert(asrc_io->asrc_task_produce_cb != NULL);
414+
// Check callback is init'd. If not, use default implementation.
415+
if (asrc_io->asrc_task_produce_cb == NULL){
416+
asrc_io->asrc_task_produce_cb = receive_asrc_input_samples_cb_default;
417+
}
396418
// We use a single chanend to send the buffer IDX from the ISR of this task back to asrc task and sync
397419
chanend_t c_buff_idx = chanend_alloc();
398420
chanend_set_dest(c_buff_idx, c_buff_idx); // Loopback chanend to itself - we use this as a shallow event driven FIFO
@@ -402,3 +424,26 @@ void asrc_task(chanend_t c_asrc_input, asrc_in_out_t *asrc_io, asynchronous_fifo
402424
// Run the ASRC task with stack set aside for an ISR
403425
INTERRUPT_PERMITTED(asrc_processor)(c_asrc_input, asrc_io, c_buff_idx, fifo);
404426
}
427+
428+
// Register a custom rx function for ASRC task
429+
void init_asrc_io_callback(asrc_in_out_t *asrc_io, asrc_task_produce_isr_cb_t asrc_rx_fp){
430+
asrc_io->asrc_task_produce_cb = asrc_rx_fp;
431+
}
432+
433+
// Default send samples to ASRC function.
434+
void send_asrc_input_samples_default(chanend_t c_asrc_input,
435+
unsigned input_frequency,
436+
int32_t input_timestamp,
437+
unsigned input_channel_count,
438+
int32_t *input_samples){
439+
// Send format info
440+
chanend_out_word(c_asrc_input, input_frequency);
441+
chanend_out_word(c_asrc_input, input_timestamp);
442+
chanend_out_word(c_asrc_input, input_channel_count);
443+
444+
// Send samples
445+
for(int i = 0; i < input_channel_count; i++){
446+
chanend_out_word(c_asrc_input, input_samples[i]);
447+
}
448+
449+
}

lib_src/src/asrc_task/asrc_task.h

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ typedef struct asrc_in_out_t_{
116116

117117
}asrc_in_out_t;
118118

119+
#ifndef __XC__
120+
/**
121+
* Type definition of the callback function if a user version is required. May only be used from "C" (XC does not support function pointers).
122+
*/
123+
typedef unsigned (*asrc_task_produce_isr_cb_t)(chanend_t c_asrc_input, asrc_in_out_t *asrc_io, unsigned *new_input_rate);
124+
#endif
125+
119126

120127
/**
121128
* Main ASRC processor task. Runs forever waiting on new samples from the producer. Spawns up to MAX_ASRC_THREADS during ASRC processing.
@@ -136,7 +143,7 @@ void asrc_task(chanend c_asrc_input, asrc_in_out_t * UNSAFE asrc_io, asynchronou
136143
* \param fifo A pointer to the FIFO used for outputting samples from the ASRC task to the consumer.
137144
* \param samples A pointer to a whole output frame (all channels in a single sample period) to populate.
138145
* \param output_frequency The nominal output frequency. Used for detecting a sample rate change.
139-
* \param consume_timestamp The timestamp of the most recently consumed sample.
146+
* \param consume_timestamp The timestamp of the first consumed sample.
140147
*
141148
*/
142149
int pull_samples(asrc_in_out_t * UNSAFE asrc_io, asynchronous_fifo_t * UNSAFE fifo, int32_t * UNSAFE samples, uint32_t output_frequency, int32_t consume_timestamp);
@@ -149,19 +156,35 @@ int pull_samples(asrc_in_out_t * UNSAFE asrc_io, asynchronous_fifo_t * UNSAFE fi
149156
*/
150157
void reset_asrc_fifo_consumer(asynchronous_fifo_t * UNSAFE fifo);
151158

159+
#ifndef __XC__
152160
/**
153-
* Prototype that must be defined by the user to initialise the function pointer for the ASRC receive produced samples ISR.
154-
* Typical user function (where receive_asrc_input_samples() is the user defined rx function):
155-
* void init_asrc_io_callback(asrc_in_out_t *asrc_io){
156-
* asrc_io->asrc_task_produce_cb = receive_asrc_input_samples;
157-
* }
161+
* Prototype that can optionally be defined by the user to initialise the function pointer for the ASRC receive produced samples ISR.
162+
* If this is not called then receive_asrc_input_samples_cb_default() is used and the you may call send_asrc_input_samples_default()
163+
* from the application to send samples to the ASRC task.
158164
*
159165
* Must be called before running asrc_task()
160166
*
161167
* \param asrc_io A pointer to the structure used for holding ASRC IO and state.
168+
* \param asrc_rx_fp A pointer to the user asrc_receive_samples function. NOTE - This MUST be decorated by ASRC_TASK_ISR_CALLBACK_ATTR
169+
* to allow proper stack calculation by the compiler. See receive_asrc_input_samples_cb_default() in asrc_task.c
170+
* for an example of how to do this.
171+
*
172+
*/
173+
void init_asrc_io_callback(asrc_in_out_t * UNSAFE asrc_io, asrc_task_produce_isr_cb_t asrc_rx_fp);
174+
#endif
175+
176+
/**
177+
* If the init_asrc_io_callback() function is not called then a default implementation of the ASRC receive will be used.
178+
* This send function (called by the user producer side) mirrors the receive and can be used to push samples into the ASRC.
179+
*
180+
* \param c_asrc_input The chan end on the application producer side connecting to the ASRC task.
181+
* \param input_frequency The sample rate of the input stream (44100, 48000, ...).
182+
* \param input_timestamp The ref clock timestamp of latest received input sample.
183+
* \param input_channel_count The number of input audio channels (1, 2, 3 ...).
184+
* \param input_samples A pointer to the input samples array (channel 0, 1, ...).
162185
*
163186
*/
164-
void init_asrc_io_callback(asrc_in_out_t * UNSAFE asrc_io);
187+
void send_asrc_input_samples_default(chanend c_asrc_input, unsigned input_frequency, int32_t input_timestamp, unsigned input_channel_count, int32_t * UNSAFE input_samples);
165188

166189
/**@}*/ // END: addtogroup src_asrc_task
167190

tests/asrc_task_test/asrc_task_receive_samples.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,6 @@ unsigned receive_asrc_input_samples(chanend_t c_producer, asrc_in_out_t *asrc_io
2727
}
2828

2929
// Register the above function for ASRC task
30-
void init_asrc_io_callback(asrc_in_out_t *asrc_io){
31-
asrc_io->asrc_task_produce_cb = receive_asrc_input_samples;
30+
void setup_asrc_io_custom_callback(asrc_in_out_t *asrc_io){
31+
init_asrc_io_callback(asrc_io, receive_asrc_input_samples);
3232
}

tests/asrc_task_test/asrc_task_test.xc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ unsigned parse_cmd_line(unsigned commands[MAX_CMDS][CMD_LEN], unsigned &multi_to
149149
return (argc - 1) / CMD_LEN;
150150
}
151151

152+
extern void setup_asrc_io_custom_callback(asrc_in_out_t *unsafe asrc_io);
153+
152154
int main(unsigned argc, char * unsafe argv[argc])
153155
{
154156
chan c_producer;
@@ -164,7 +166,7 @@ int main(unsigned argc, char * unsafe argv[argc])
164166
asrc_in_out_t asrc_io = {{{0}}};
165167
asrc_in_out_t * unsafe asrc_io_ptr = &asrc_io;
166168
asynchronous_fifo_t * unsafe fifo = (asynchronous_fifo_t *)array;
167-
init_asrc_io_callback(asrc_io_ptr);
169+
setup_asrc_io_custom_callback(asrc_io_ptr);
168170

169171

170172
// Format is SR_IN, IN_CHANS, SR_OUT, POST_DELAY_MS

0 commit comments

Comments
 (0)