|
ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
|
đ Lock-free circular buffers for audio/video More...
Files | |
| file | ringbuffer.c |
| đŻ Lock-free circular buffer for audio streaming with atomic operations | |
| file | ringbuffer.h |
| Lock-Free Ring Buffer and Frame Buffer Management. | |
Data Structures | |
| struct | ringbuffer_t |
| Lock-free ring buffer structure. More... | |
| struct | audio_ring_buffer |
| Audio ring buffer for real-time audio streaming. More... | |
| struct | frame_t |
| Frame structure that stores both data and actual size. More... | |
| struct | multi_source_frame_t |
| Multi-source frame structure for multi-user support. More... | |
| struct | framebuffer_t |
| Frame buffer structure for managing video frames. More... | |
Typedefs | |
| typedef struct audio_ring_buffer | audio_ring_buffer_t |
| Audio ring buffer for real-time audio streaming. | |
Functions | |
| ringbuffer_t * | ringbuffer_create (size_t element_size, size_t capacity) |
| Create a new ring buffer. | |
| void | ringbuffer_destroy (ringbuffer_t *rb) |
| Destroy a ring buffer and free its memory. | |
| bool | ringbuffer_write (ringbuffer_t *rb, const void *data) |
| Try to write an element to the ring buffer (non-blocking) | |
| bool | ringbuffer_read (ringbuffer_t *rb, void *data) |
| Try to read an element from the ring buffer (non-blocking) | |
| bool | ringbuffer_peek (ringbuffer_t *rb, void *data) |
| Peek at the next element without removing it. | |
| size_t | ringbuffer_size (const ringbuffer_t *rb) |
| Get current number of elements in the buffer. | |
| bool | ringbuffer_is_empty (const ringbuffer_t *rb) |
| Check if buffer is empty. | |
| bool | ringbuffer_is_full (const ringbuffer_t *rb) |
| Check if buffer is full. | |
| void | ringbuffer_clear (ringbuffer_t *rb) |
| Clear all elements from the buffer. | |
| framebuffer_t * | framebuffer_create (size_t capacity) |
| Create a frame buffer for ASCII frames. | |
| framebuffer_t * | framebuffer_create_multi (size_t capacity) |
| Create a multi-source frame buffer for multi-user support. | |
| void | framebuffer_destroy (framebuffer_t *fb) |
| Destroy a frame buffer. | |
| bool | framebuffer_write_frame (framebuffer_t *fb, const char *frame_data, size_t frame_size) |
| Write a frame to the buffer. | |
| bool | framebuffer_read_frame (framebuffer_t *fb, frame_t *frame) |
| Read a frame from the buffer. | |
| void | framebuffer_clear (framebuffer_t *fb) |
| Clear all frames from the buffer, freeing their data. | |
| bool | framebuffer_write_multi_frame (framebuffer_t *fb, const char *frame_data, size_t frame_size, uint32_t source_client_id, uint32_t frame_sequence, uint32_t timestamp) |
| Write a multi-source frame to the buffer (for multi-user support) | |
| bool | framebuffer_read_multi_frame (framebuffer_t *fb, multi_source_frame_t *frame) |
| Read a multi-source frame from the buffer. | |
| bool | framebuffer_peek_latest_multi_frame (framebuffer_t *fb, multi_source_frame_t *frame) |
| Peek at the latest multi-source frame without removing it. | |
Audio Ring Buffer Constants | |
| #define | AUDIO_RING_BUFFER_SIZE 192000 |
| Audio ring buffer size in samples (192000 samples = 4 seconds @ 48kHz) | |
| #define | AUDIO_JITTER_BUFFER_THRESHOLD 960 |
| Jitter buffer threshold - samples needed before starting playback. | |
| #define | AUDIO_JITTER_LOW_WATER_MARK 480 |
| Low water mark - warn when available drops below this. | |
| #define | AUDIO_JITTER_HIGH_WATER_MARK (AUDIO_JITTER_BUFFER_THRESHOLD * 2) |
| High water mark - drop OLD samples when buffer exceeds this. | |
| #define | AUDIO_JITTER_TARGET_LEVEL (AUDIO_JITTER_BUFFER_THRESHOLD + 480) |
| Target buffer level after dropping old samples. | |
| #define | AUDIO_CROSSFADE_SAMPLES 256 |
| Crossfade duration in samples for smooth underrun recovery. | |
Frame Magic Numbers | |
| #define | FRAME_MAGIC 0xDEADBEEF |
| Magic number indicating a valid frame (0xDEADBEEF) | |
| #define | FRAME_FREED 0xFEEDFACE |
| Magic number indicating a freed frame (0xFEEDFACE) | |
đ Lock-free circular buffers for audio/video
This header provides lock-free ring buffer implementations for high-performance producer-consumer scenarios in ascii-chat. The system includes both generic lock-free ring buffers and specialized audio and frame buffers for media streaming.
The lock-free ring buffer uses atomic operations for:
Specialized audio ring buffer with:
Frame buffer system for ASCII video frames:
Welcome! Let's talk about the ring bufferâone of the coolest data structures in ascii-chat. If you've ever wondered how to pass data between threads really fast without all the overhead of mutexes and locks, you're in the right place.
Picture this: you have one thread capturing video frames (the producer) and another thread encoding them (the consumer). How do you hand frames from one to the other? You could use a mutex-protected queue, but that means locking and unlocking for every single frame. Not ideal when you're doing this 30 times per second!
That's where the ring buffer shines. It's a circular buffer that uses atomic operations instead of locks, making it blazingly fast for the single-producer, single-consumer pattern. Think of it like a race track where the producer and consumer chase each other aroundâone writing, one reading, never quite catching up to each other.
You can find the implementation in lib/ringbuffer.c and lib/ringbuffer.h.
Ring buffers are designed from the ground up for speed. They use C11 atomics instead of mutexes, which means way less overhead when passing data between threads. The design is intentionally simpleâno complex algorithms here, just a circular buffer done right.
The implementation takes advantage of a clever trick: power-of-2 capacities allow us to use bitwise masking instead of slow modulo operations. This might sound like a small detail, but it makes index calculations 15-30 times faster!
We also have specialized versions for different use cases. Frame buffers handle ASCII art frames with metadata like magic numbers for corruption detection. Audio ring buffers include jitter buffering to smooth out network hiccups. Each one is tailored to its specific job.
Let's dive into how ring buffers actually work under the hood. The design is elegant in its simplicityâjust a few atomic variables and a chunk of memory, but combined in a way that achieves lock-free thread safety.
The core structure is pretty straightforward:
The key insight here is that head and tail are atomic variables. The producer writes at head and advances it, while the consumer reads from tail and advances it. Since each position is only accessed by one thread, we don't need locksâjust atomic operations to make sure reads and writes are visible across threads.
The capacity_mask is the secret sauce for performance. When capacity is a power of 2, we can use head & capacity_mask instead of head % capacity. Bitwise AND is a single CPU instruction, while modulo takes 15-30 cycles. That adds up fast when you're doing it thousands of times per second!
The ring buffer uses atomic operations for thread-safe access without locks. Here's how the producer and consumer interact:
Write operation (producer):
When the producer wants to write data, it first checks if there's room in the buffer. If the buffer is full, we return falseâsimple as that. Otherwise, we calculate the write index using our fast bitwise mask trick, copy the data into place, and atomically advance the head.
Read operation (consumer):
The consumer side is the mirror image. We check if there's data available, calculate the read index the same way, copy data out, and atomically advance the tail.
The beauty of this design is that the producer and consumer can run completely independently. They never block each otherâthe worst case is one returns false if the buffer is full or empty, but that's a check we'd need anyway.
We have several flavors of ring buffer, each optimized for a specific use case. The generic ring buffer handles arbitrary element types, while frame and audio buffers are specialized for their domains.
The generic ring buffer is your go-to choice when you need to pass arbitrary data types between threads. It's simple, fast, and gets the job done.
Here's a quick example of using it:
Notice how the capacity gets rounded up to the next power of 2 automatically (256 is already a power of 2, so it stays as-is). If you ask for 100 elements, you'll get 128 insteadâa small price to pay for the performance benefits.
Frame buffers are specialized for ASCII art frames. They handle variable-sized frames and include metadata like magic numbers for corruption detection. This is crucial when frames are being passed between threadsâwe need to make sure we're not reading garbage if something goes wrong.
The frame structure looks like this:
The magic number serves as a sanity check. When you read a frame, you can verify that frame.magic == FRAME_MAGIC to make sure you got a valid frame and not corrupted memory. It's a simple check, but it can catch a lot of subtle bugs!
Here's how you'd use it in practice:
When you have multiple clients sending frames, you need to track which client sent what. That's where the multi-source frame buffer comes in. It's essentially the same as a regular frame buffer, but each frame includes metadata about its source.
The multi-source frame structure tracks the client ID, frame sequence number, and timestamp:
Using it is straightforward:
Audio ring buffers are a bit different from the others. They're designed for real-time audio playback, which means they need to handle network jitter gracefully. The buffer accumulates samples before starting playback, creating a "jitter buffer" that smooths out irregularities in network timing.
The audio buffer uses a fixed size tuned for audio needs:
Network audio packets don't arrive at perfectly regular intervals. Sometimes packets come early, sometimes they're delayed. Without buffering, you'd get audio crackling whenever a packet was late. The jitter buffer waits until it has enough samples to smooth out these timing variations before starting playback.
The threshold is set to about 46 milliseconds of audio. This is usually enough to handle typical network jitter without introducing noticeable latency. Once the buffer has reached this threshold, playback begins and the buffer continues to absorb timing variations.
Here's how it works in practice:
Note that the audio buffer uses a mutex. Unlike the generic ring buffer, this one can benefit from mutual exclusion since audio callbacks have strict timing requirements and we need to coordinate the jitter buffer logic.
Now let's look at the actual API you'll be working with. The functions are straightforward and follow a consistent pattern.
Creating a ring buffer is simpleâjust specify the element size and desired capacity:
Remember that the capacity gets rounded up to the next power of 2 for performance reasons. If you request 100 elements, you'll get 128. This usually isn't a problem, but it's good to be aware of.
The core operations are write, read, and peek. The query functions let you check the buffer state without modifying it.
All the write and read functions return false if the operation couldn't complete (buffer full or empty). This lets you decide what to doâmaybe drop the data, maybe wait and retry, maybe signal an error. It's up to you!
Let's talk numbers. The ring buffer isn't just fastâit's dramatically faster than traditional mutex-protected queues. Here's what the benchmarks show.
We compared the lock-free ring buffer against a mutex-protected queue in a contention-free scenario (producer and consumer on the same CPU core):
| Operation | Ring Buffer | Mutex Queue | Speedup |
|---|---|---|---|
| Write | 18 ns | 95 ns | 5.3x faster |
| Read | 20 ns | 102 ns | 5.1x faster |
| Write+Read pair | 38 ns | 197 ns | 5.2x faster |
These numbers are from an AMD Ryzen 9 5950X in a contention-free scenario. The ring buffer operations are so fast they're barely more expensive than a simple memory copy!
What happens when things get more intense? We also tested with high contention, where the producer and consumer are running on different CPU cores. This stresses the cache coherency protocol, but the ring buffer still comes out way ahead:
Under high contention:
Even when the CPU cores are fighting over cache lines, the ring buffer's lock-free design means it doesn't have to wait for mutexes to be released. The atomic operations are still much faster than lock acquisition.
Getting the capacity right is important for performance. Too small and you'll drop data. Too large and you're wasting memory. The ring buffer helps by optimizing capacity calculations automatically.
The ring buffer always rounds capacity up to the next power of 2. This lets us use bitwise AND instead of modulo for index calculations, which is dramatically faster.
Here's how it works:
The performance impact is significant:
Since we're calculating the index on every read and write, this optimization really adds up. It's one of those small details that makes a big difference when you're doing thousands of operations per second.
Choosing the right buffer size depends on what you're buffering. Let's look at some common scenarios:
Video frames (30 FPS):
For video, you want enough frames to survive brief CPU spikes and scheduling delays. We recommend at least 2 seconds worth (60 frames) as a minimum. That gives you enough cushion that a momentary slowdown won't cause frame drops. For more robust operation, 4 seconds (120 frames) is better. Going beyond 10 seconds (300 frames) doesn't usually help muchâby then you're just increasing latency.
Audio samples (48 kHz):
Audio buffering is a balancing act. You need enough buffer to absorb network jitter, but not so much that latency becomes noticeable. Our audio ring buffer uses:
These numbers are tuned for typical network conditions. If you're on a very jittery network, you might want to increase the jitter buffer threshold. If latency is more important than smoothness, you can decrease it.
This is important: ring buffers are only safe for the Single Producer, Single Consumer (SPSC) pattern. That means exactly one thread writing and exactly one thread reading. If you need multiple producers or consumers, you'll want to use packet_queue instead, which is mutex-protected.
The lock-free design relies on the fact that each position in the buffer is only accessed by one thread. The producer writes at head, the consumer reads from tail, and they never touch each other's positions. This is what makes the atomic operations sufficientâwe don't need locks because there's no contention.
Here's the correct pattern:
And here's what not to do:
If you try to have multiple producers writing at the same time, you'll get race conditions on the head pointer. Same problem with multiple consumers. The lock-free design only works when there's exactly one of each.
For multiple producers or consumers, use packet_queue insteadâit uses mutexes and can handle multiple writers and readers safely, though it won't be as fast.
Here are some tips we've learned from using ring buffers in ascii-chat:
Use power-of-2 capacities when possible
While the ring buffer will round up for you, if you know your requirements in advance, it's cleaner to request a power of 2 directly. That way there's no surprise about the actual capacity you get.
Handle full/empty cases gracefully
When ringbuffer_write() returns false, the buffer is full. When ringbuffer_read() returns false, it's empty. You need to decide what to do in each case:
For real-time video, dropping frames is usually fineâbetter to show the latest frame than to delay everything. For critical data, you might want to block until there's room. It depends on your use case.
Frame data is managed by buffer pool
Frame data is allocated from the buffer pool and automatically managed. Do NOT free frame data manually - the buffer pool handles cleanup when framebuffer_clear() or framebuffer_destroy() is called.
The buffer pool automatically reclaims frame memory when the buffer is cleared or destroyed. Calling SAFE_FREE() on frame data will cause double-free errors or corruption.
Validate magic numbers
Frame buffers include magic numbers for a reasonâthey help catch corruption. Always check that you got a valid frame:
This is a cheap sanity check that can save you from chasing weird bugs later. If you read garbage memory (maybe from a use-after-free or buffer overflow), the magic number won't match and you'll know immediately that something's wrong.
Size jitter buffers appropriately
For audio buffers, the jitter buffer size is a trade-off. Too small and you'll get audio crackling from underruns. Too large and you'll have noticeable latency. The sweet spot is usually about 2-3 times your typical network jitter.
Our default is 46 milliseconds, which works well for most network conditions. If you're on a very stable network (like a local connection), you could go smaller. If network jitter is severe, you might need to increase it, though you'll pay for it with latency.
| #define AUDIO_CROSSFADE_SAMPLES 256 |
#include <ringbuffer.h>
Crossfade duration in samples for smooth underrun recovery.
When recovering from an underrun, we fade in the audio over this many samples to prevent clicks and pops. 256 samples @ 48kHz = ~5.3ms
Definition at line 189 of file ringbuffer.h.
| #define AUDIO_JITTER_BUFFER_THRESHOLD 960 |
#include <ringbuffer.h>
Jitter buffer threshold - samples needed before starting playback.
This threshold determines how much audio must be buffered before playback starts. Too small = underruns and audio gaps when network packets arrive late Too large = excessive latency + AEC3 gets silence during fill period
For LAN with ~10ms network latency, we only need 1 Opus frame (20ms) of buffer. This minimizes end-to-end latency while still absorbing minor network jitter.
960 samples @ 48kHz = 20ms = 1 Opus frame
Definition at line 148 of file ringbuffer.h.
| #define AUDIO_JITTER_HIGH_WATER_MARK (AUDIO_JITTER_BUFFER_THRESHOLD * 2) |
#include <ringbuffer.h>
High water mark - drop OLD samples when buffer exceeds this.
When the playback buffer grows beyond this level, we have accumulated too much latency. We drop OLD samples to keep latency bounded. This prevents the "latency grows over time" problem where network bursts fill the buffer faster than playback can drain it.
Set to 2x jitter threshold = 40ms for LAN. This gives enough headroom for network jitter while preventing unbounded latency accumulation. 1920 samples @ 48kHz = 40ms = 2 Opus frames
Definition at line 172 of file ringbuffer.h.
| #define AUDIO_JITTER_LOW_WATER_MARK 480 |
#include <ringbuffer.h>
Low water mark - warn when available drops below this.
When playback buffer drops below this threshold, we log a warning but keep playing to avoid choppy audio. The jitter buffer should absorb network jitter without constantly pausing/resuming.
Set to 50% of threshold (10ms when threshold is 20ms). 480 samples @ 48kHz = 10ms = 0.5 Opus frames
Definition at line 159 of file ringbuffer.h.
| #define AUDIO_JITTER_TARGET_LEVEL (AUDIO_JITTER_BUFFER_THRESHOLD + 480) |
#include <ringbuffer.h>
Target buffer level after dropping old samples.
When we drop old samples due to exceeding HIGH_WATER_MARK, we bring the buffer back down to this level. Set slightly above jitter threshold to avoid immediately triggering underrun detection.
1440 samples @ 48kHz = 30ms = 1.5 Opus frames
Definition at line 182 of file ringbuffer.h.
| #define AUDIO_RING_BUFFER_SIZE 192000 |
#include <ringbuffer.h>
Audio ring buffer size in samples (192000 samples = 4 seconds @ 48kHz)
Sized to handle:
4 seconds provides headroom for mixer slowdowns and prevents overflow during processing spikes. The mixer reads adaptively to catch up.
Definition at line 135 of file ringbuffer.h.
| #define FRAME_FREED 0xFEEDFACE |
#include <ringbuffer.h>
Magic number indicating a freed frame (0xFEEDFACE)
Definition at line 423 of file ringbuffer.h.
| #define FRAME_MAGIC 0xDEADBEEF |
#include <ringbuffer.h>
Magic number indicating a valid frame (0xDEADBEEF)
Definition at line 421 of file ringbuffer.h.
| typedef struct audio_ring_buffer audio_ring_buffer_t |
#include <ringbuffer.h>
Audio ring buffer for real-time audio streaming.
Specialized ring buffer for audio samples with jitter buffering to compensate for network latency and packet timing variations. Uses mutex-protected operations for thread-safe audio access.
Features:
| void framebuffer_clear | ( | framebuffer_t * | fb | ) |
#include <ringbuffer.h>
Clear all frames from the buffer, freeing their data.
| fb | Frame buffer (must not be NULL) |
Removes all frames from the buffer and frees their associated data. This operation is thread-safe but should be used with caution if other threads may be reading frames.
Definition at line 323 of file ringbuffer.c.
References ringbuffer_t::buffer, buffer_pool_free(), ringbuffer_t::capacity, frame_t::data, multi_source_frame_t::data, ringbuffer_t::element_size, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, FRAME_FREED, FRAME_MAGIC, frame_t::magic, multi_source_frame_t::magic, framebuffer_t::mutex, mutex_lock, mutex_unlock, framebuffer_t::rb, ringbuffer_clear(), ringbuffer_read(), SAFE_MEMSET, SET_ERRNO, frame_t::size, and multi_source_frame_t::size.
Referenced by framebuffer_destroy().
| framebuffer_t * framebuffer_create | ( | size_t | capacity | ) |
#include <ringbuffer.h>
Create a frame buffer for ASCII frames.
| capacity | Number of frames to buffer |
Creates a new frame buffer for single-source frame scenarios. The capacity is automatically rounded up to the next power of 2 for optimal performance.
Definition at line 147 of file ringbuffer.c.
References ERROR_INVALID_PARAM, ERROR_MEMORY, ERROR_THREAD, framebuffer_t::mutex, mutex_destroy(), mutex_init(), framebuffer_t::rb, ringbuffer_create(), SAFE_CALLOC, SAFE_FREE, and SET_ERRNO.
| framebuffer_t * framebuffer_create_multi | ( | size_t | capacity | ) |
#include <ringbuffer.h>
Create a multi-source frame buffer for multi-user support.
| capacity | Number of frames to buffer |
Creates a new frame buffer for multi-client frame scenarios. Supports tracking frame sources, sequence numbers, and timestamps for proper multi-user frame display.
Definition at line 175 of file ringbuffer.c.
References ERROR_INVALID_PARAM, ERROR_THREAD, framebuffer_t::mutex, mutex_destroy(), mutex_init(), framebuffer_t::rb, ringbuffer_create(), SAFE_CALLOC, SAFE_FREE, and SET_ERRNO.
| void framebuffer_destroy | ( | framebuffer_t * | fb | ) |
#include <ringbuffer.h>
Destroy a frame buffer.
| fb | Frame buffer to destroy (must not be NULL) |
Frees all memory associated with the frame buffer, including any frames still in the buffer. The buffer must not be in use by any threads when this function is called.
Definition at line 203 of file ringbuffer.c.
References ERROR_INVALID_STATE, framebuffer_clear(), framebuffer_t::mutex, mutex_destroy(), framebuffer_t::rb, ringbuffer_destroy(), SAFE_FREE, and SET_ERRNO.
| bool framebuffer_peek_latest_multi_frame | ( | framebuffer_t * | fb, |
| multi_source_frame_t * | frame | ||
| ) |
#include <ringbuffer.h>
Peek at the latest multi-source frame without removing it.
| fb | Frame buffer (must not be NULL) |
| frame | Pointer to multi_source_frame_t struct to be filled (must not be NULL) |
Examines the latest multi-source frame without removing it from the buffer. Useful for previewing frames or checking frame metadata before reading.
Definition at line 453 of file ringbuffer.c.
References buffer_pool_alloc(), multi_source_frame_t::data, ERROR_INVALID_STATE, ERROR_MEMORY, FRAME_MAGIC, multi_source_frame_t::magic, framebuffer_t::mutex, mutex_lock, mutex_unlock, framebuffer_t::rb, ringbuffer_peek(), SAFE_MEMCPY, SET_ERRNO, multi_source_frame_t::size, and multi_source_frame_t::source_client_id.
| bool framebuffer_read_frame | ( | framebuffer_t * | fb, |
| frame_t * | frame | ||
| ) |
#include <ringbuffer.h>
Read a frame from the buffer.
| fb | Frame buffer (must not be NULL) |
| frame | Pointer to frame_t struct to be filled (must not be NULL) |
Reads a single-source frame from the buffer. The frame data pointer points to data owned by the buffer until the frame is cleared or the buffer is destroyed.
Definition at line 276 of file ringbuffer.c.
References frame_t::data, ERROR_INVALID_STATE, FRAME_FREED, FRAME_MAGIC, frame_t::magic, framebuffer_t::mutex, mutex_lock, mutex_unlock, framebuffer_t::rb, ringbuffer_read(), SAFE_FREE, SET_ERRNO, and frame_t::size.
| bool framebuffer_read_multi_frame | ( | framebuffer_t * | fb, |
| multi_source_frame_t * | frame | ||
| ) |
#include <ringbuffer.h>
Read a multi-source frame from the buffer.
| fb | Frame buffer (must not be NULL) |
| frame | Pointer to multi_source_frame_t struct to be filled (must not be NULL) |
Reads a multi-source frame from the buffer along with its metadata. The frame data pointer points to data owned by the buffer until the frame is cleared or the buffer is destroyed.
Definition at line 418 of file ringbuffer.c.
References multi_source_frame_t::data, ERROR_INVALID_STATE, FRAME_MAGIC, multi_source_frame_t::magic, framebuffer_t::mutex, mutex_lock, mutex_unlock, framebuffer_t::rb, ringbuffer_read(), SET_ERRNO, multi_source_frame_t::size, and multi_source_frame_t::source_client_id.
| bool framebuffer_write_frame | ( | framebuffer_t * | fb, |
| const char * | frame_data, | ||
| size_t | frame_size | ||
| ) |
#include <ringbuffer.h>
Write a frame to the buffer.
| fb | Frame buffer (must not be NULL) |
| frame_data | Frame data (must not be NULL) |
| frame_size | Actual size of frame data in bytes |
Writes a single-source frame to the buffer. The frame data is copied into the buffer, so the caller retains ownership of the original data.
Definition at line 222 of file ringbuffer.c.
References buffer_pool_alloc(), buffer_pool_free(), ringbuffer_t::capacity, frame_t::data, ERROR_INVALID_PARAM, ERROR_INVALID_STATE, ERROR_MEMORY, FRAME_FREED, FRAME_MAGIC, frame_t::magic, framebuffer_t::mutex, mutex_lock, mutex_unlock, framebuffer_t::rb, ringbuffer_read(), ringbuffer_size(), ringbuffer_write(), SAFE_MEMCPY, SET_ERRNO, and frame_t::size.
| bool framebuffer_write_multi_frame | ( | framebuffer_t * | fb, |
| const char * | frame_data, | ||
| size_t | frame_size, | ||
| uint32_t | source_client_id, | ||
| uint32_t | frame_sequence, | ||
| uint32_t | timestamp | ||
| ) |
#include <ringbuffer.h>
Write a multi-source frame to the buffer (for multi-user support)
| fb | Frame buffer (must not be NULL) |
| frame_data | Frame data (must not be NULL) |
| frame_size | Actual size of frame data in bytes |
| source_client_id | Client ID that sent this frame |
| frame_sequence | Frame sequence number for ordering |
| timestamp | Timestamp when frame was captured |
Writes a multi-source frame to the buffer with associated metadata. The frame data is copied into the buffer, so the caller retains ownership of the original data.
Definition at line 379 of file ringbuffer.c.
References buffer_pool_alloc(), buffer_pool_free(), ERROR_MEMORY, FRAME_MAGIC, log_debug, multi_source_frame_t::magic, framebuffer_t::mutex, mutex_lock, mutex_unlock, framebuffer_t::rb, ringbuffer_write(), SAFE_MEMCPY, and SET_ERRNO.
| void ringbuffer_clear | ( | ringbuffer_t * | rb | ) |
#include <ringbuffer.h>
Clear all elements from the buffer.
| rb | Ring buffer (must not be NULL) |
Removes all elements from the buffer and resets it to empty state. This operation is not atomic with respect to concurrent read/write operations - use with caution in multi-threaded scenarios.
Definition at line 134 of file ringbuffer.c.
References ringbuffer_t::head, ringbuffer_t::size, and ringbuffer_t::tail.
Referenced by framebuffer_clear().
| ringbuffer_t * ringbuffer_create | ( | size_t | element_size, |
| size_t | capacity | ||
| ) |
#include <ringbuffer.h>
Create a new ring buffer.
| element_size | Size of each element in bytes |
| capacity | Number of elements (will be rounded up to power of 2) |
Creates a new lock-free ring buffer with the specified element size and capacity. The capacity is automatically rounded up to the next power of 2 to enable fast bit masking optimizations.
Definition at line 28 of file ringbuffer.c.
References ringbuffer_t::buffer, ringbuffer_t::capacity, ringbuffer_t::capacity_mask, ringbuffer_t::element_size, ERROR_INVALID_PARAM, ringbuffer_t::head, ringbuffer_t::is_power_of_two, SAFE_CALLOC, SET_ERRNO, ringbuffer_t::size, and ringbuffer_t::tail.
Referenced by acip_webrtc_transport_create(), framebuffer_create(), and framebuffer_create_multi().
| void ringbuffer_destroy | ( | ringbuffer_t * | rb | ) |
#include <ringbuffer.h>
Destroy a ring buffer and free its memory.
| rb | Ring buffer to destroy (must not be NULL) |
Frees all memory associated with the ring buffer. The buffer must not be in use by any threads when this function is called.
Definition at line 54 of file ringbuffer.c.
References ringbuffer_t::buffer, and SAFE_FREE.
Referenced by acip_webrtc_transport_create(), and framebuffer_destroy().
| bool ringbuffer_is_empty | ( | const ringbuffer_t * | rb | ) |
#include <ringbuffer.h>
Check if buffer is empty.
| rb | Ring buffer (must not be NULL) |
Performs a fast check to determine if the buffer contains any elements. More efficient than checking if size() == 0.
Definition at line 126 of file ringbuffer.c.
References ringbuffer_size().
| bool ringbuffer_is_full | ( | const ringbuffer_t * | rb | ) |
#include <ringbuffer.h>
Check if buffer is full.
| rb | Ring buffer (must not be NULL) |
Performs a fast check to determine if the buffer has reached capacity. More efficient than checking if size() == capacity.
Definition at line 130 of file ringbuffer.c.
References ringbuffer_t::capacity, and ringbuffer_size().
| bool ringbuffer_peek | ( | ringbuffer_t * | rb, |
| void * | data | ||
| ) |
#include <ringbuffer.h>
Peek at the next element without removing it.
| rb | Ring buffer (must not be NULL) |
| data | Buffer to peek into (must not be NULL, must be element_size bytes) |
Examines the next element without removing it from the buffer. This is useful for previewing data before committing to reading it.
Definition at line 105 of file ringbuffer.c.
References ringbuffer_t::buffer, ringbuffer_t::element_size, SAFE_MEMCPY, ringbuffer_t::size, and ringbuffer_t::tail.
Referenced by framebuffer_peek_latest_multi_frame().
| bool ringbuffer_read | ( | ringbuffer_t * | rb, |
| void * | data | ||
| ) |
#include <ringbuffer.h>
Try to read an element from the ring buffer (non-blocking)
| rb | Ring buffer (must not be NULL) |
| data | Buffer to read into (must not be NULL, must be element_size bytes) |
Attempts to read an element from the ring buffer. This is a non-blocking operation that uses lock-free atomic operations. Returns false immediately if the buffer is empty.
Definition at line 83 of file ringbuffer.c.
References ringbuffer_t::buffer, ringbuffer_t::capacity_mask, ringbuffer_t::element_size, SAFE_MEMCPY, ringbuffer_t::size, and ringbuffer_t::tail.
Referenced by framebuffer_clear(), framebuffer_read_frame(), framebuffer_read_multi_frame(), and framebuffer_write_frame().
| size_t ringbuffer_size | ( | const ringbuffer_t * | rb | ) |
#include <ringbuffer.h>
Get current number of elements in the buffer.
| rb | Ring buffer (must not be NULL) |
Returns the current size of the buffer. This is a snapshot value that may change immediately after return due to concurrent operations.
Definition at line 122 of file ringbuffer.c.
References ringbuffer_t::size.
Referenced by framebuffer_write_frame(), ringbuffer_is_empty(), and ringbuffer_is_full().
| bool ringbuffer_write | ( | ringbuffer_t * | rb, |
| const void * | data | ||
| ) |
#include <ringbuffer.h>
Try to write an element to the ring buffer (non-blocking)
| rb | Ring buffer (must not be NULL) |
| data | Data to write (must not be NULL, must be element_size bytes) |
Attempts to write an element to the ring buffer. This is a non-blocking operation that uses lock-free atomic operations. Returns false immediately if the buffer is full.
Definition at line 61 of file ringbuffer.c.
References ringbuffer_t::buffer, ringbuffer_t::capacity, ringbuffer_t::capacity_mask, ringbuffer_t::element_size, ringbuffer_t::head, SAFE_MEMCPY, and ringbuffer_t::size.
Referenced by framebuffer_write_frame(), and framebuffer_write_multi_frame().