# FrameContainerQueue Architecture for Frame Buffering in ApraPipes

> Explore the FrameContainerQueue architecture for frame buffering in ApraPipes. Discover its three-layer design for thread-safe, high-throughput frame movement between pipeline modules.

- Repository: [Apra Labs/aprapipes](https://github.com/apra-labs/aprapipes)
- Tags: architecture
- Published: 2026-02-25

---

**The FrameContainerQueue implements a three-layer buffering architecture—bounded buffer core, domain-specific wrapper, and adapter/strategy layer—that enables thread-safe, high-throughput frame movement between pipeline modules.**

The `FrameContainerQueue` is the central buffering component in the [apra-labs/aprapipes](https://github.com/apra-labs/aprapipes) repository, responsible for managing how video frames flow between processing stages. Understanding its architecture is essential for building reliable real-time video pipelines that handle back-pressure and concurrent access correctly.

## Three-Layer Architecture Overview

The FrameContainerQueue for frame buffering is built on three distinct layers that separate synchronization primitives from domain logic:

| Layer | Role | Implementation |
|-------|------|----------------|
| **Bounded Buffer** | Low-level, thread-safe circular buffer storing raw `frame_container` objects with capacity limits and synchronization. | `bounded_buffer` template in [`base/include/BoundBuffer.h`](https://github.com/apra-labs/aprapipes/blob/main/base/include/BoundBuffer.h) |
| **FrameContainerQueue** | Thin wrapper inheriting from `bounded_buffer<frame_container>` that exposes a video-pipeline API (`push`, `pop`, `try_push`, etc.). | [`base/include/FrameContainerQueue.h`](https://github.com/apra-labs/aprapipes/blob/main/base/include/FrameContainerQueue.h) and [`base/src/FrameContainerQueue.cpp`](https://github.com/apra-labs/aprapipes/blob/main/base/src/FrameContainerQueue.cpp) |
| **Queue Adapter & Push Strategies** | Flexible integration layer allowing different queue implementations and policies for when frames should be enqueued (always, try-push, or never). | `FrameContainerQueueAdapter` in [`base/include/FrameContainerQueue.h`](https://github.com/apra-labs/aprapipes/blob/main/base/include/FrameContainerQueue.h) and `QuePushStrategy` hierarchy in [`base/include/QuePushStrategy.h`](https://github.com/apra-labs/aprapipes/blob/main/base/include/QuePushStrategy.h) |

## Layer 1: Bounded Buffer Core

The foundation of the FrameContainerQueue is the `bounded_buffer` template defined in [`base/include/BoundBuffer.h`](https://github.com/apra-labs/aprapipes/blob/main/base/include/BoundBuffer.h). This class provides the thread-safe circular buffer that handles synchronization, capacity limits, and the *accept* flag used during pipeline start-up.

**Capacity and Back-Pressure**

The buffer is constructed with a fixed `capacity`. The `push` method blocks until space becomes available, providing natural back-pressure, while `try_push` returns `false` immediately when the buffer is full.

**Dual-Direction Pushes**

The buffer supports inserting elements at both ends:
- `push` inserts at the *front* (used for high-priority command frames)
- `push_back` appends at the *back* (standard data flow)
- `push_drop_oldest` maintains constant buffer size by discarding the oldest element when full

**Synchronization Primitives**

A `std::mutex` and two `std::condition_variable` instances (`m_not_empty`, `m_not_full`) guarantee safe concurrent access between producers and consumers. The `is_ready_to_accept` predicate also respects the `accept` flag that is cleared during `clear()` and set by `accept()`.

**Public API**

The bounded buffer exposes `pop`, `peek`, `clear`, `flush`, `size`, and `isFull` methods that preserve internal locking while providing standard queue operations.

## Layer 2: FrameContainerQueue Wrapper

`FrameContainerQueue` in [`base/include/FrameContainerQueue.h`](https://github.com/apra-labs/aprapipes/blob/main/base/include/FrameContainerQueue.h) is a thin wrapper that inherits from `bounded_buffer<frame_container>`. It does not add extra logic; instead, it provides the rest of the codebase with a **type-safe** name that conveys the intent of "frame buffering."

**Construction and Delegation**

The queue is constructed with a desired capacity (`FrameContainerQueue(size_t capacity)`). All methods—including `push`, `push_back`, `push_drop_oldest`, `pop`, `try_push`, `try_pop`, `peek`, `isFull`, `clear`, and `flush`—delegate directly to the base class, ensuring identical semantics to the bounded buffer.

**Public Inheritance**

Because the class inherits publicly from `bounded_buffer<frame_container>`, it can be used wherever the generic bounded buffer is expected, while the explicit `FrameContainerQueue` name improves code readability and maintainability.

Implementation details are located in [`base/src/FrameContainerQueue.cpp`](https://github.com/apra-labs/aprapipes/blob/main/base/src/FrameContainerQueue.cpp) (lines 4–65).

## Layer 3: Adapter and Push Strategies

The final layer provides flexible integration patterns that allow modules to plug in different queue implementations or mock objects, and to decide *when* a frame should be enqueued.

### FrameContainerQueueAdapter

The `FrameContainerQueueAdapter` class (defined in [`base/include/FrameContainerQueue.h`](https://github.com/apra-labs/aprapipes/blob/main/base/include/FrameContainerQueue.h), lines 28–55) holds a `boost::shared_ptr<FrameContainerQueue> mAdaptee`.

It overrides queue operations such as `push`, `pop`, `try_push`, `try_pop`, and `isFull` to forward calls to the underlying queue **after** evaluating a decision made by `should_push`.

The `PushType` enum (`DONT_PUSH`, `TRY_PUSH`, `MUST_PUSH`) allows derived adapters to customize when a frame should be enqueued—never, only if space is available, or always (blocking).

Hook methods (`on_failed_push`, `on_push_success`, `on_failed_pop`, `on_pop_success`) enable modules to react to push/pop outcomes without cluttering the core queue logic.

### QuePushStrategy

For scenarios requiring **multi-destination** pushes (such as broadcasting a frame to several downstream modules), the `QuePushStrategy` hierarchy in [`base/include/QuePushStrategy.h`](https://github.com/apra-labs/aprapipes/blob/main/base/include/QuePushStrategy.h) provides higher-level policies.

Strategies include:
- **BLOCKING**: Waits until space is available in the destination.
- **NON_BLOCKING_ANY**: Pushes to the first queue that can accept the frame.
- **NON_BLOCKING_ALL_OR_NONE**: Pushes only if **all** destinations can accept the frame; otherwise, drops.

The strategy maintains a map `mQueByModule` of destination IDs to `FrameContainerQueue` pointers, and its `push` method iterates according to the selected policy.

## Data Flow Through the Pipeline

Understanding how the FrameContainerQueue for frame buffering operates within the full pipeline context clarifies its role in the ApraPipes architecture.

1. **Module Creation** – Each `Module` constructs its own `FrameContainerQueue` with a capacity defined in the module’s properties (`_props.qlen`).

2. **Adapter Attachment** – Modules requiring custom push behavior (such as demuxers or transforms) create a `FrameContainerQueueAdapter`, call `adapt(otherQueue)`, and override `should_push` if needed.

3. **Producer Stage** – A module pushes frames into its queue via `queue->push(frame)` (blocking) or `queue->try_push(frame)` (non-blocking).

4. **Consumer Stage** – Downstream modules call `queue->pop()` (blocking) or `queue->try_pop()` (non-blocking) to retrieve frames.

5. **Broadcasting** – When a module broadcasts a frame to multiple downstream queues, it uses a `QuePushStrategy` instance that orchestrates the pushes according to the selected policy (blocking, any, or all-or-none).

This architecture guarantees **thread-safe buffering** with deterministic back-pressure, **extensibility** via adapters and push-strategies without modifying core queue code, and **clear separation** between low-level synchronization (`bounded_buffer`) and high-level pipeline semantics.

## Code Examples

The following examples demonstrate practical usage patterns for the FrameContainerQueue architecture.

```cpp
// 1️⃣ Create a queue with capacity for 30 frames
auto q = boost::make_shared<FrameContainerQueue>(30);

// 2️⃣ Simple push / pop (blocking)
frame_container f = readFrame();   // obtain a frame from some source
q->push(f);                       // blocks if the queue is full
frame_container out = q->pop();    // blocks until a frame is available

// 3️⃣ Non-blocking push – useful in a real-time source
if (!q->try_push(f)) {
    LOG_WARN << "Dropping frame because downstream is saturated\n";
}

// 4️⃣ Using an adapter to conditionally drop frames
class MyAdapter : public FrameContainerQueueAdapter {
protected:
    PushType should_push(frame_container) override {
        // e.g., drop every 10th frame
        static int cnt = 0;
        return (++cnt % 10 == 0) ? DONT_PUSH : MUST_PUSH;
    }
};
auto adapter = boost::make_shared<MyAdapter>();
adapter->adapt(q);                 // forward all calls to `q`

// 5️⃣ Broadcasting with a non-blocking "any" strategy
boost::shared_ptr<QuePushStrategy> strat =
    QuePushStrategy::getStrategy(QuePushStrategy::NON_BLOCKING_ANY, srcId);
strat->addQue(dstId1, q1);
strat->addQue(dstId2, q2);
strat->push(dstId1, f);   // pushes to the first queue that can accept

```

## Key Source Files

| File | Description | Link |
|------|-------------|------|
| [`base/include/BoundBuffer.h`](https://github.com/apra-labs/aprapipes/blob/main/base/include/BoundBuffer.h) | Generic thread-safe bounded buffer used by the queue. | [BoundBuffer.h](https://github.com/apra-labs/aprapipes/blob/main/base/include/BoundBuffer.h) |
| [`base/include/FrameContainerQueue.h`](https://github.com/apra-labs/aprapipes/blob/main/base/include/FrameContainerQueue.h) | Declaration of `FrameContainerQueue` and its adapter. | [FrameContainerQueue.h](https://github.com/apra-labs/aprapipes/blob/main/base/include/FrameContainerQueue.h) |
| [`base/src/FrameContainerQueue.cpp`](https://github.com/apra-labs/aprapipes/blob/main/base/src/FrameContainerQueue.cpp) | Implementation of the wrapper methods. | [FrameContainerQueue.cpp](https://github.com/apra-labs/aprapipes/blob/main/base/src/FrameContainerQueue.cpp) |
| [`base/include/QuePushStrategy.h`](https://github.com/apra-labs/aprapipes/blob/main/base/include/QuePushStrategy.h) | Push-strategy infrastructure for broadcasting frames. | [QuePushStrategy.h](https://github.com/apra-labs/aprapipes/blob/main/base/include/QuePushStrategy.h) |
| [`base/src/Module.cpp`](https://github.com/apra-labs/aprapipes/blob/main/base/src/Module.cpp) (relevant parts) | Shows how each module owns a `FrameContainerQueue`. | [Module.cpp](https://github.com/apra-labs/aprapipes/blob/main/base/src/Module.cpp) |

## Summary

- The **FrameContainerQueue** relies on a three-layer architecture: a generic `bounded_buffer` for thread-safe storage, a thin `FrameContainerQueue` wrapper for type safety, and an adapter/strategy layer for flexible integration.
- **Thread safety** is guaranteed by `std::mutex` and `std::condition_variable` in [`base/include/BoundBuffer.h`](https://github.com/apra-labs/aprapipes/blob/main/base/include/BoundBuffer.h), supporting both blocking and non-blocking operations.
- **Back-pressure handling** occurs naturally through blocking `push` calls, while `try_push` and `push_drop_oldest` offer alternatives for real-time scenarios.
- **Extensibility** comes from `FrameContainerQueueAdapter` and `QuePushStrategy`, allowing custom push logic and multi-destination broadcasting without modifying core queue code.
- **Key files** are located in [`base/include/BoundBuffer.h`](https://github.com/apra-labs/aprapipes/blob/main/base/include/BoundBuffer.h), [`base/include/FrameContainerQueue.h`](https://github.com/apra-labs/aprapipes/blob/main/base/include/FrameContainerQueue.h), and [`base/include/QuePushStrategy.h`](https://github.com/apra-labs/aprapipes/blob/main/base/include/QuePushStrategy.h).

## Frequently Asked Questions

### What is the difference between FrameContainerQueue and bounded_buffer?

`bounded_buffer` is a generic C++ template class that provides thread-safe circular buffer functionality for any data type, while `FrameContainerQueue` is a specific instantiation and thin wrapper that inherits from `bounded_buffer<frame_container>`. The wrapper provides a domain-specific name that clarifies intent within the ApraPipes video processing pipeline, though it delegates all operations directly to the base class.

### How does FrameContainerQueue handle back-pressure in real-time pipelines?

The queue handles back-pressure through its blocking `push` method, which waits on a `std::condition_variable` until space becomes available when the buffer reaches capacity. For real-time scenarios where blocking is unacceptable, the queue offers `try_push` for immediate failure feedback, and `push_drop_oldest` to maintain constant buffer size by discarding stale frames when new ones arrive.

### What is the purpose of FrameContainerQueueAdapter?

`FrameContainerQueueAdapter` provides a flexible integration layer that allows pipeline modules to customize enqueue behavior without modifying the core queue implementation. It holds a reference to a `FrameContainerQueue` and overrides push operations to evaluate a `should_push` predicate, enabling conditional frame dropping, and provides hooks like `on_failed_push` and `on_push_success` for modules to react to queue state changes.

### How does QuePushStrategy support multi-destination broadcasting?

`QuePushStrategy` implements higher-level policies for pushing frames to multiple downstream queues simultaneously, managing the complexity of partial failures. It supports strategies such as `BLOCKING` (wait for all destinations), `NON_BLOCKING_ANY` (push to first available queue), and `NON_BLOCKING_ALL_OR_NONE` (atomic push to all or drop), allowing modules to optimize for latency versus reliability when broadcasting frames.