libdragon
Data Structures | Macros | Typedefs | Functions
samplebuffer.h File Reference

Sample buffer. More...

Go to the source code of this file.

Data Structures

struct  samplebuffer_t
 

Macros

#define SAMPLES_BPS_SHIFT(buf)   ((buf)->ptr_and_flags & 3)
 
#define SAMPLES_PTR(buf)   (void*)((buf)->ptr_and_flags ^ SAMPLES_BPS_SHIFT(buf))
 
#define SAMPLES_PTR_MAKE(ptr, bps)   ((sample_ptr_t)(ptr) | (bps))
 

Typedefs

typedef uint32_t sample_ptr_t
 

Functions

void samplebuffer_init (samplebuffer_t *buf, uint8_t *uncached_mem, int size)
 
void samplebuffer_set_bps (samplebuffer_t *buf, int bps)
 Configure the bit width of the samples stored in the buffer. More...
 
void samplebuffer_set_waveform (samplebuffer_t *buf, WaveformRead read, void *ctx)
 
void * samplebuffer_get (samplebuffer_t *buf, int wpos, int *wlen)
 Get a pointer to specific set of samples in the buffer (zero-copy). More...
 
void * samplebuffer_append (samplebuffer_t *buf, int wlen)
 Append samples into the buffer (zero-copy). More...
 
void samplebuffer_discard (samplebuffer_t *buf, int wpos)
 
void samplebuffer_flush (samplebuffer_t *buf)
 
void samplebuffer_close (samplebuffer_t *buf)
 

Detailed Description

Sample buffer.


Data Structure Documentation

◆ samplebuffer_t

struct samplebuffer_t

samplebuffer_t is a circular buffer of samples. It is used by the mixer to store and cache the samples required for playback on each channel. The mixer creates a sample buffer for each initialized channel. The size of the buffers is calculated for optimal playback, and might grow depending on channel usage (what waveforms are played on each channel).

The mixer follows a "pull" architecture. During mixer_poll, it will call samplebuffer_get() to extract samples from the buffer. If the required samples are not available, the sample buffer will callback the waveform decoder to produce more samples, through the WaveformRead API. The waveform read function will push samples into the buffer via samplebuffer_append, so that they become available for the mixer. The decoder can be configured with samplebuffer_set_decoder.

The current implementation of samplebuffer does not achieve full zero copy, because when the buffer is full, it is flushed and samples that need to be preserved (that is, already in the buffer but not yet played back) are copied back at the beginning of the buffer with the CPU. This limitation exists because the RSP ucode (rsp_audio.S) isn't currently able to "wrap around" in the sample buffer. In future, this limitation could be lifted to achieve full zero copy.

The sample buffer tries to always stay 8-byte aligned to simplify operations of decoders that might need to use DMA transfers (either PI DMA or RSP DMA). To guarantee this property, WaveformRead must collaborate by decoding the requested number of samples. If WaveformRead decodes a different number of samples, the alignment might be lost. Moreover, it always guarantees that the buffer has the same 2-byte phase of the waveforms (that is, odd samples of the waveforms are stored at odd addresses in memory); this is the minimal property required by dma_read (libdragon's optimized PI DMA transfer for unaligned addresses).

In general, the sample buffer assumes that the contained data is committed to physical memory, not just CPU cache. It is responsibility of the client to flush DMA cache (via data_cache_writeback) if samples are written via CPU.

Data Fields
sample_ptr_t ptr_and_flags

Tagged pointer to the actual buffer. Lower bits contain bit-per-shift.

int size

Size of the buffer (in samples).

int wpos

Absolute position in the waveform of the first sample in the sample buffer (the sample at index 0). It keeps track of which part of the waveform this sample buffer contains.

int widx

Write pointer in the sample buffer (expressed as index of samples). Since sample buffers are always filled from index 0, it is also the number of samples stored in the buffer.

int ridx

Read pointer in the sample buffer (expressed as index of samples). It remembers which sample was last read. Assuming a forward streaming, it is used by the sample buffer to discard unused samples when not needed anymore.

WaveformRead wv_read

wv_read is invoked by samplebuffer_get whenever more samples are requested by the mixer. See WaveformRead for more information.

void * wv_ctx

wv_ctx is the opaque pointer to pass as context to decoder functions.

Macro Definition Documentation

◆ SAMPLES_BPS_SHIFT

#define SAMPLES_BPS_SHIFT (   buf)    ((buf)->ptr_and_flags & 3)

SAMPLES_BPS_SHIFT extracts the byte-per-sample information from a sample_ptr_t. Byte-per-sample is encoded as shift value, so the actual number of bits is 1 << BPS. Valid shift values are 0, 1, 2 (which corresponds to 1, 2 or 4 bytes per sample).

◆ SAMPLES_PTR

#define SAMPLES_PTR (   buf)    (void*)((buf)->ptr_and_flags ^ SAMPLES_BPS_SHIFT(buf))

SAMPLES_PTR extract the raw void* to the sample array. The size of array is not encoded in the tagged pointer. Notice that it is implemented with a XOR because on MIPS it's faster than using a reverse mask.

◆ SAMPLES_PTR_MAKE

#define SAMPLES_PTR_MAKE (   ptr,
  bps 
)    ((sample_ptr_t)(ptr) | (bps))

SAMPLES_PTR_MAKE create a tagged pointer, given a pointer to an array of samples and a byte-per-sample value (encoded as shift value).

Typedef Documentation

◆ sample_ptr_t

typedef uint32_t sample_ptr_t

Tagged pointer to an array of samples. It contains both the void* sample pointer, and byte-per-sample information (encoded as shift value).

Function Documentation

◆ samplebuffer_init()

void samplebuffer_init ( samplebuffer_t buf,
uint8_t *  uncached_mem,
int  size 
)

Initialize the sample buffer by binding it to the specified memory buffer.

The sample buffer is guaranteed to be 8-bytes aligned, so the specified memory buffer must follow this constraint. Moreover, the buffer must be in the uncached segment and not loaded in any CPU cacheline. It is strongly advised to allocate the buffer via malloc_uncached, that takes care of these constraints.

Parameters
[in]bufSample buffer
[in]uncached_memMemory buffer to use. Must be 8-byte aligned, and in the uncached segment.
[in]sizeSize of the memory buffer, in bytes.

◆ samplebuffer_set_bps()

void samplebuffer_set_bps ( samplebuffer_t buf,
int  bps 
)

Configure the bit width of the samples stored in the buffer.

Valid values for "bps" are 1, 2, or 4: 1 can be used for 8-bit mono samples, 2 for either 8-bit interleaved stereo or 16-bit mono, and 4 for 16-bit interleaved stereo.

Parameters
[in]bufSample buffer
[in]bpsBytes per sample.

◆ samplebuffer_set_waveform()

void samplebuffer_set_waveform ( samplebuffer_t buf,
WaveformRead  read,
void *  ctx 
)

Connect a waveform reader callback to this sample buffer. The waveform will be use to produce samples whenever they are required by the mixer as playback progresses.

"read" is the main decoding function, that is invoked to produce a specified number of samples. Normally, the function is invoked by samplebuffer_get, whenever the mixer requests more samples. See WaveformRead for more information.

Parameters
[in]bufSample buffer
[in]readWaveform reading function, that produces samples.
[in]ctxOpaque context that will be passed to the read function.

◆ samplebuffer_get()

void * samplebuffer_get ( samplebuffer_t buf,
int  wpos,
int *  wlen 
)

Get a pointer to specific set of samples in the buffer (zero-copy).

"wpos" is the absolute waveform position of the first sample that the caller needs access to. "wlen" is the number of requested samples.

The function returns a pointer within the sample buffer where the samples should be read, and optionally changes "wlen" with the maximum number of samples that can be read. "wlen" is always less or equal to the requested value.

If the samples are available in the buffer, they will be returned immediately. Otherwise, if the samplebuffer has a sample decoder registered via samplebuffer_set_decoder, the decoder "read" function is called once to produce the samples.

If "wlen" is changed with a value less than "wlen", it means that not all samples were available in the buffer and it was not possible to generate more, so the caller should not loop calling this function, but rather use what was obtained and possibly pad with silence.

Parameters
[in]bufSample buffer
[in]wposAbsolute waveform position of the first samples to return.
[in,out]wlenNumber of samples to return. After return, it is modified with the actual number of samples that have been returned.
Returns
Pointer to samples.

◆ samplebuffer_append()

void * samplebuffer_append ( samplebuffer_t buf,
int  wlen 
)

Append samples into the buffer (zero-copy).

"wlen" is the number of samples that the caller will append.

The function returns a pointer within the sample buffer where the samples should be written. The samples to be written to physical memory, not just CPU cache, and to enforce this, the function returns a pointer in the uncached segment. Most of the times, we expect samples to be generated or manipulated via RSP/DMA anyway.

The function is meant only to "append" samples, as in add samples that are consecutive within the waveform to the ones already stored in the sample buffer. This is necessary because samplebuffer_t can only store a single range of samples of the waveform; there is no way to hold two disjoint ranges.

For instance, if the sample buffer currently contains 50 samples starting from position 100 in the waverform, the next call to samplebuffer_append will append samples starting at 150.

If required, samplebuffer_append will discard older samples to make space for the new ones, through samplebuffer_discard. It will only discard samples that come before the "wpos" specified in the last samplebuffer_get call, so to make sure that nothing required for playback is discarded. If there is not enough space in the buffer, it will assert.

Parameters
[in]bufSample buffer
[in]wlenNumber of samples to append.
Returns
Pointer to the area where new samples can be written.

◆ samplebuffer_discard()

void samplebuffer_discard ( samplebuffer_t buf,
int  wpos 
)

Discard all samples from the buffer that come before a specified absolute waveform position.

This function can be used to discard samples that are not needed anymore in the sample buffer. "wpos" specifies the absolute position of the first sample that should be kept: all samples that come before will be discarded. This function will silently do nothing if there are no samples to discard.

Parameters
[in]bufSample buffer
[in]wposAbsolute waveform position of the first sample that must be kept.

◆ samplebuffer_flush()

void samplebuffer_flush ( samplebuffer_t buf)

Flush (reset) the sample buffer to empty status, discarding all samples.

Parameters
[in]bufSample buffer.

◆ samplebuffer_close()

void samplebuffer_close ( samplebuffer_t buf)

Close the sample buffer.

After calling close, the sample buffer must be initialized again before using it.

Parameters
[in]bufSample buffer.