diff --git a/applications/CMakeLists.txt b/applications/CMakeLists.txt
index 380abe6f31..e6d544dfc7 100644
--- a/applications/CMakeLists.txt
+++ b/applications/CMakeLists.txt
@@ -17,6 +17,12 @@
add_holohub_application(adv_networking_bench DEPENDS
OPERATORS advanced_network)
+add_holohub_application(adv_networking_media_player DEPENDS
+ OPERATORS advanced_network advanced_network_media_rx)
+
+add_holohub_application(adv_networking_media_sender DEPENDS
+ OPERATORS advanced_network advanced_network_media_tx)
+
add_holohub_application(aja_video_capture DEPENDS
OPERATORS aja_source)
diff --git a/applications/adv_networking_media_player/CMakeLists.txt b/applications/adv_networking_media_player/CMakeLists.txt
new file mode 100755
index 0000000000..97e06a0b11
--- /dev/null
+++ b/applications/adv_networking_media_player/CMakeLists.txt
@@ -0,0 +1,20 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Add subdirectories
+add_subdirectory(cpp)
+if(HOLOHUB_BUILD_PYTHON)
+ add_subdirectory(python)
+endif()
diff --git a/applications/adv_networking_media_player/README.md b/applications/adv_networking_media_player/README.md
new file mode 100755
index 0000000000..f0e6e330b6
--- /dev/null
+++ b/applications/adv_networking_media_player/README.md
@@ -0,0 +1,484 @@
+# Advanced Networking Media Player
+
+The Advanced Networking Media Player is a high-performance application for receiving and displaying media streams over advanced network infrastructure using NVIDIA's Rivermax SDK. This application demonstrates real-time media streaming capabilities with ultra-low latency and high throughput.
+
+## Overview
+
+This application showcases professional-grade media streaming over IP networks, utilizing NVIDIA's advanced networking technologies. It receives media streams using the SMPTE 2110 standard and can either display them in real-time or save them to disk for further processing.
+
+### Key Features
+
+- **High-Performance Streaming**: Receive media streams with minimal latency using Rivermax SDK
+- **SMPTE 2110 Compliance**: Industry-standard media over IP protocol support
+- **Flexible Output**: Choose between real-time visualization or file output
+- **GPU Acceleration**: Leverage GPUDirect for zero-copy operations
+- **Multiple Format Support**: RGB888, YUV420, NV12, and other common video formats
+- **Header-Data Split**: Optimized memory handling for improved performance
+
+### Application Architecture
+
+The Advanced Networking Media Player implements a sophisticated multi-layer architecture for high-performance media streaming with hardware acceleration and zero-copy optimizations.
+
+#### Complete Application Data Flow
+
+```mermaid
+graph TD
+ %% Network Hardware Layer
+ subgraph NET[" "]
+ direction TB
+ A["Network Interface
(ConnectX NIC)"] --> B["Rivermax Hardware
Acceleration"]
+ end
+
+ %% Advanced Network Manager Layer (includes RDK Services)
+ subgraph MGR[" "]
+ direction TB
+ C{{"RDK Service
(IPO/RTP Receiver)"}}
+ C -->|settings_type: ipo_receiver| D["IPO Receiver Service
(rmax_ipo_receiver)"]
+ C -->|settings_type: rtp_receiver| E["RTP Receiver Service
(rmax_rtp_receiver)"]
+ D --> F["Direct Memory Access
(DMA to Pre-allocated Regions)"]
+ E --> F
+ F --> G["Memory Regions
(Data_RX_CPU + Data_RX_GPU)"]
+ G --> H["RivermaxMgr
(Advanced Network Manager)"]
+ H --> I["Burst Assembly
(Packet Pointers Only)"]
+ I --> J["AnoBurstsQueue
(Pointer Distribution)"]
+ end
+
+ %% Media RX Operator Layer
+ subgraph OPS[" "]
+ direction TB
+ K["AdvNetworkMediaRxOp
(Packet-to-Frame Conversion)"]
+ K --> L{{"HDS Configuration
(Header-Data Split)"}}
+ L -->|hds: true| M["Headers: CPU Memory
Payloads: GPU Memory"]
+ L -->|hds: false| N["Headers+Payloads: CPU Memory
with RTP offset"]
+ M --> O["Frame Assembly
(Automatic Strategy Selection)"]
+ N --> O
+ O --> P{{"Output Format
Configuration"}}
+ P -->|output_format: video_buffer| Q["VideoBuffer Entity"]
+ P -->|output_format: tensor| R["Tensor Entity"]
+ end
+
+ %% Application Layer
+ subgraph APP[" "]
+ direction TB
+ S["Media Player Application
(Python/C++)"]
+ S --> T{{"Output Mode
Configuration"}}
+ T -->|visualize: true| U["HolovizOp
(Real-time Display)"]
+ T -->|write_to_file: true| V["FramesWriter
(File Output)"]
+ end
+
+ %% Layer Connections
+ B --> C
+ J --> K
+ Q --> S
+ R --> S
+
+ %% Layer Labels
+ NET -.-> |"Network Hardware Layer"| NET
+ MGR -.-> |"Advanced Network Manager
(includes RDK Services)"| MGR
+ OPS -.-> |"Media RX Operator"| OPS
+ APP -.-> |"Application Layer"| APP
+
+ %% Styling
+ classDef networkLayer fill:#e1f5fe,stroke:#01579b,stroke-width:2px
+ classDef managerLayer fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
+ classDef operatorLayer fill:#e8f5e8,stroke:#2e7d32,stroke-width:2px
+ classDef appLayer fill:#fff3e0,stroke:#ef6c00,stroke-width:2px
+ classDef configDecision fill:#f9f9f9,stroke:#333,stroke-width:2px
+ classDef hdsPath fill:#e8f5e8
+ classDef memoryOpt fill:#ffebee
+
+ %% Apply layer styling to subgraphs
+ class NET networkLayer
+ class MGR managerLayer
+ class OPS operatorLayer
+ class APP appLayer
+
+ %% Individual node styling
+ class A,B networkLayer
+ class C,D,E,F,G,H,I,J managerLayer
+ class K,O operatorLayer
+ class S,U,V appLayer
+ class L,P,T configDecision
+ class M,N hdsPath
+```
+
+#### Simplified Application Pipeline
+
+```
+┌─────────────────┐ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
+│ Network Hardware│ -> │ Advanced Network│ -> │ Media RX Operator│ -> │ Application │
+│ Layer │ │ Manager + RDK │ │ Layer │ │ Layer │
+└─────────────────┘ └─────────────────┘ └──────────────────┘ └─────────────────┘
+```
+
+#### Layered Architecture Overview
+
+The application implements a 4-layer architecture for high-performance media streaming, with clear separation of concerns:
+
+**🌐 Network Hardware Layer**
+- ConnectX NIC with Rivermax hardware acceleration
+- Direct memory access and hardware-based packet processing
+
+**🔄 Advanced Network Manager Layer**
+- **RDK Services Context**: IPO/RTP receivers run within this layer
+- Protocol handling and memory management via integrated RDK services
+- Pre-allocated memory regions (CPU + GPU)
+- Rivermax manager coordination and service orchestration
+- Burst assembly and packet pointer management
+- Queue distribution for operator consumption
+
+**🎬 Media RX Operator Layer**
+- `AdvNetworkMediaRxOp`: Packet-to-frame conversion
+- Automatic HDS detection and strategy optimization
+- Frame assembly with GPU acceleration
+
+**📱 Application Layer**
+- Media Player application (Python/C++)
+- Output configuration (visualization or file storage)
+- User interface and control logic
+
+**Key Features:**
+- **Automatic Optimization**: Each layer automatically configures for optimal performance
+- **Zero-Copy Operations**: GPU memory management minimizes data movement
+- **Error Recovery**: Robust handling across all layers
+- **Scalable Design**: Clean interfaces enable easy extension and testing
+
+## Requirements
+
+### Hardware Requirements
+- Linux system (x86_64 or aarch64)
+- NVIDIA NIC with ConnectX-6 or later chip
+- NVIDIA GPU (for visualization and GPU acceleration)
+- Sufficient network bandwidth for target media streams
+
+### Software Requirements
+- NVIDIA Rivermax SDK
+- NVIDIA GPU drivers
+- MOFED drivers (5.8-1.0.1.1 or later)
+- DOCA 2.7 or later (if using DOCA backend)
+- System tuning as described in the [High Performance Networking tutorial](../../tutorials/high_performance_networking/README.md)
+
+## Build Instructions
+
+### Build Docker Image
+
+Build the Docker image and application with Rivermax support:
+
+**C++ version:**
+```bash
+./holohub build adv_networking_media_player --build-args="--target rivermax" --configure-args="-D ANO_MGR:STRING=rivermax" --language cpp
+```
+
+**Python version:**
+```bash
+./holohub build adv_networking_media_player --build-args="--target rivermax" --configure-args="-D ANO_MGR:STRING=rivermax" --language python
+```
+
+### Launch Container
+
+Launch the Rivermax-enabled container:
+
+**C++ version:**
+```bash
+./holohub run-container adv_networking_media_player --build-args="--target rivermax" --docker-opts="-u root --privileged -e DISPLAY=:99 -v /opt/mellanox/rivermax/rivermax.lic:/opt/mellanox/rivermax/rivermax.lic -w /workspace/holohub/build/adv_networking_media_player/applications/adv_networking_media_player/cpp"
+```
+
+**Python version:**
+```bash
+./holohub run-container adv_networking_media_player --build-args="--target rivermax" --docker-opts="-u root --privileged -e DISPLAY=:99 -v /opt/mellanox/rivermax/rivermax.lic:/opt/mellanox/rivermax/rivermax.lic -w /workspace/holohub/build/adv_networking_media_player/applications/adv_networking_media_player/python"
+```
+
+## Running the Application
+
+### Prerequisites
+
+Before running, ensure your environment is properly configured:
+
+```bash
+# Update PYTHONPATH for Python applications
+# Note: Run this command from the container's working directory
+# (as set by -w flag in run-container command)
+export PYTHONPATH=${PYTHONPATH}:/opt/nvidia/holoscan/python/lib:$PWD/../../../python/lib:$PWD
+
+# Ensure proper system configuration (run as root if needed)
+# See High Performance Networking tutorial for system tuning
+```
+
+### Running on Headless Servers
+
+To run the application on a headless server and preview the video via VNC, start a virtual X server and VNC server:
+
+```bash
+# Start virtual X server on display :99
+Xvfb :99 -screen 0 1920x1080x24 -ac &
+
+# Start VNC server for remote access
+x11vnc -display :99 -forever -shared -rfbport 5900 -nopw -bg
+```
+
+After starting these services, you can connect to the server using any VNC client (e.g., TigerVNC, RealVNC) at `:5900` to view the video output.
+
+**Note**: The `-nopw` flag disables password authentication. For production environments, consider setting a password using the `-passwd` option for security.
+
+### C++ Application
+
+```bash
+./adv_networking_media_player adv_networking_media_player.yaml
+```
+
+### Python Application
+
+```bash
+python3 ./adv_networking_media_player.py adv_networking_media_player.yaml
+```
+
+## Configuration
+
+The application uses a YAML configuration file that defines the complete data flow pipeline. The configuration has three main sections:
+
+1. **Advanced Network Manager Configuration**: Network interfaces, memory regions, and RDK services
+2. **Media RX Operator Configuration**: Video format, frame dimensions, and output settings
+3. **Application Configuration**: Visualization and file output options
+
+> **📁 Example Configuration Files**:
+> - `applications/adv_networking_media_player/adv_networking_media_player.yaml`
+
+> **For detailed configuration parameter documentation**, see:
+> - [Advanced Network Operator Configuration](../../operators/advanced_network/README.md) - Network settings, memory regions, Rivermax RX/TX settings
+> - [Advanced Network Media RX Operator Configuration](../../operators/advanced_network_media/README.md) - HDS configuration, memory architecture, copy strategies, output formats
+
+### Quick Reference: Key Parameters That Must Match
+
+Critical parameters must be consistent across configuration sections to ensure proper operation:
+
+| Parameter Category | Section 1 | Section 2 | Example Values | Required Match |
+|-------------------|-----------|-----------|----------------|----------------|
+| **Video Format** | `advanced_network_media_rx.video_format` | `media_player_config.input_format` | RGB888 / rgb888 | ✓ Must be compatible |
+| **Interface** | `advanced_network_media_rx.interface_name` | `advanced_network.interfaces.address` | cc:00.1 | ✓ Must match exactly |
+| **Queue ID** | `advanced_network_media_rx.queue_id` | `advanced_network.interfaces.rx.queues.id` | 0 | ✓ Must match exactly |
+| **Memory Location** | `advanced_network_media_rx.memory_location` | Memory region types (`host`/`device`) | device | ✓ Should be consistent |
+| **HDS Mode** | `advanced_network_media_rx.hds` | Memory region configuration | true/false | ✓ Must align with regions |
+| **Frame Dimensions** | `advanced_network_media_rx.frame_width/height` | Sender configuration | 3840x2160 | ⚠️ Must match sender |
+
+> **⚠️ IMPORTANT: Configuration Parameter Consistency**
+>
+> Parameters across the three configuration sections must be consistent and properly matched:
+> - **Video Format Matching**: `video_format` (Media RX) must match `input_format` (Application)
+> - **Memory Buffer Sizing**: `buf_size` in memory regions depends on video format, resolution, and packet size
+> - RTP headers: ~20 bytes per packet (CPU memory region)
+> - Payload size: ~1440 bytes per packet for typical SMPTE 2110 streams (GPU memory region)
+> - Buffer count (`num_bufs`): Must accommodate all packets for multiple frames in flight
+> - **Memory Location**: `memory_location` (Media RX) should match the memory region types configured (`host` vs `device`)
+> - **HDS Configuration**: When `hds: true`, ensure both CPU and GPU memory regions are configured
+> - **Interface Matching**: `interface_name` (Media RX) must match the interface address/name in Advanced Network config
+>
+> Mismatched parameters will result in runtime errors or degraded performance.
+
+### Configuration File Structure
+
+The application configuration consists of three main sections:
+
+#### 1. Advanced Network Manager Configuration
+
+Configures network interfaces, memory regions, and Rivermax RX settings. See [Advanced Network Operator documentation](../../operators/advanced_network/README.md) for detailed parameter descriptions.
+
+```yaml
+advanced_network:
+ cfg:
+ version: 1
+ manager: "rivermax"
+ master_core: 6
+ debug: 1
+ log_level: "error"
+ memory_regions:
+ - name: "Data_RX_CPU"
+ kind: "host"
+ affinity: 0
+ access:
+ - local
+ num_bufs: 432000
+ buf_size: 20
+ - name: "Data_RX_GPU"
+ kind: "device"
+ affinity: 0
+ access:
+ - local
+ num_bufs: 432000
+ buf_size: 1440
+ interfaces:
+ - name: data1
+ address: cc:00.1
+ rx:
+ queues:
+ - name: "Data"
+ id: 0
+ cpu_core: "12"
+ batch_size: 4320
+ output_port: "bench_rx_out_1"
+ memory_regions:
+ - "Data_RX_CPU"
+ - "Data_RX_GPU"
+ rivermax_rx_settings:
+ settings_type: "ipo_receiver"
+ memory_registration: true
+ verbose: true
+ max_path_diff_us: 10000
+ ext_seq_num: true
+ sleep_between_operations_us: 0
+ local_ip_addresses:
+ - 2.1.0.12
+ source_ip_addresses:
+ - 2.1.0.12
+ destination_ip_addresses:
+ - 224.1.1.2
+ destination_ports:
+ - 50001
+ stats_report_interval_ms: 3000
+ send_packet_ext_info: true
+```
+
+**Key Rivermax RX Settings**:
+- `settings_type: "ipo_receiver"` - Uses IPO (Inline Packet Ordering) for high-throughput streams; alternatively use `"rtp_receiver"` for standard RTP
+- `memory_registration: true` - Registers memory with Rivermax for DMA operations
+- `local_ip_addresses` - Local interface IP addresses for receiving streams
+- `source_ip_addresses` - Expected source IP addresses (can match local for loopback testing)
+- `destination_ip_addresses` - Destination IP addresses (multicast: 224.0.0.0 - 239.255.255.255)
+- `destination_ports` - UDP ports to receive streams on
+- `ext_seq_num: true` - Enables extended sequence number support for SMPTE 2110
+- `send_packet_ext_info: true` - Provides extended packet information to operators
+
+**Memory Regions for HDS (Header-Data Split)**:
+- `Data_RX_CPU` (kind: host) - CPU memory for RTP headers (20 bytes per packet)
+- `Data_RX_GPU` (kind: device) - GPU memory for video payloads (1440 bytes per packet for 1080p RGB)
+
+#### 2. Media RX Operator Configuration
+
+Configures video format, frame dimensions, HDS, and output settings. See [Advanced Network Media RX Operator documentation](../../operators/advanced_network_media/README.md) for detailed parameter descriptions.
+
+```yaml
+advanced_network_media_rx:
+ interface_name: cc:00.1 # Must match Advanced Network interface address
+ queue_id: 0 # Must match Advanced Network queue ID
+ video_format: RGB888 # Video pixel format (RGB888, YUV420, NV12)
+ frame_width: 1920 # Frame width in pixels (must match sender)
+ frame_height: 1080 # Frame height in pixels (must match sender)
+ bit_depth: 8 # Color bit depth (8, 10, 12, 16)
+ hds: true # Enable Header-Data Split for optimal GPU performance
+ output_format: tensor # Output as tensor (alternative: video_buffer)
+ memory_location: device # Process in GPU memory (alternative: host)
+```
+
+**Key Media RX Operator Settings**:
+- `interface_name` - Must match the interface address in Advanced Network configuration
+- `queue_id` - Must match the queue ID in Advanced Network RX queue configuration
+- `video_format` - Must be compatible with sender format and `media_player_config.input_format`
+- `frame_width` / `frame_height` - Must match sender configuration (example shows 1080p: 1920x1080)
+- `hds: true` - Enables Header-Data Split: headers in CPU, payloads in GPU (requires both CPU and GPU memory regions)
+- `output_format: tensor` - Optimized for GPU post-processing; use `video_buffer` for compatibility with standard operators
+- `memory_location: device` - Process frames in GPU memory for maximum performance
+
+#### 3. Application Configuration
+
+Configures output options for the media player application:
+
+```yaml
+media_player_config:
+ write_to_file: false # Enable file output (saves frames to disk)
+ visualize: true # Enable real-time display via HolovizOp
+ input_format: "rgb888" # Must be compatible with advanced_network_media_rx.video_format
+```
+
+**Key Application Settings**:
+- `write_to_file` - When true, saves received frames to disk (configure path in `frames_writer` section)
+- `visualize` - When true, displays frames in real-time using HolovizOp (requires X11/display)
+- `input_format` - Must be compatible with Media RX Operator `video_format` (e.g., RGB888 → rgb888)
+
+## Output Options
+
+### Real-time Visualization
+
+When `visualize: true` is set in the configuration:
+- Received media streams are displayed in real-time using HolovizOp
+- Supports format conversion for optimal display
+- Minimal latency for live monitoring applications
+
+### File Output
+
+When `write_to_file: true` is set in the configuration:
+- Media frames are saved to disk for later analysis
+- Configure output path and number of frames in the `frames_writer` section
+- Supports both host and device memory sources
+
+## Supported Video Formats
+
+- **RGB888**: 24-bit RGB color
+- **YUV420**: 4:2:0 chroma subsampling
+- **NV12**: Semi-planar YUV 4:2:0
+
+## Troubleshooting
+
+### Common Issues
+
+1. **Permission Errors**: Run with appropriate privileges for network interface access
+2. **Network Configuration**: Verify IP addresses, ports, and interface names
+3. **Memory Issues**: Adjust buffer sizes based on available system memory
+4. **Performance**: Check system tuning and CPU isolation settings
+
+### Debug Options
+
+Enable debug logging by setting `log_level: "debug"` in the advanced_network configuration section.
+
+### Git Configuration (if needed)
+
+If you encounter Git-related issues during build:
+
+```bash
+git config --global --add safe.directory '*'
+```
+
+## Performance Optimization
+
+### Configuration Optimization
+
+For optimal performance, configure the following based on your use case:
+
+1. **HDS Configuration**: Enable `hds: true` for GPU-accelerated processing with optimal memory layout
+2. **Memory Location**: Set `memory_location: device` to process frames in GPU memory for maximum performance
+3. **Output Format**: Use `output_format: tensor` for GPU-based post-processing pipelines
+4. **Memory Regions**: Size buffers appropriately for your frame rate and resolution
+
+> **For detailed optimization strategies and implementation details**, see:
+> - [Advanced Network Media RX Operator Documentation](../../operators/advanced_network_media/README.md) - Processing strategies, HDS optimization, memory architecture
+> - [Advanced Network Operator Documentation](../../operators/advanced_network/README.md) - Memory region configuration, queue settings
+
+### System-Level Tuning
+
+Ensure your system is properly configured for high-performance networking:
+
+- **CPU Isolation**: Isolate CPU cores for network processing
+- **Memory Configuration**: Configure hugepages and memory allocation
+- **GPU Configuration**: Ensure sufficient GPU memory for frame buffering
+- **Network Tuning**: Configure interrupt mitigation and CPU affinity
+
+> **For detailed system tuning guidelines**, see the [High Performance Networking Tutorial](../../tutorials/high_performance_networking/README.md)
+
+### Performance Monitoring
+
+Monitor these key metrics for optimal performance:
+- **Frame Rate**: Consistent frame reception without drops
+- **Processing Efficiency**: Verify the operator is selecting optimal processing strategies
+- **GPU Utilization**: Monitor GPU memory usage and processing efficiency
+- **Network Statistics**: Track packet loss, timing accuracy, and throughput
+- **Memory Usage**: Monitor both CPU and GPU memory consumption
+- **Error Recovery**: Track frequency of network issues and recovery events
+
+## Related Documentation
+
+### Operator Documentation
+For detailed implementation information and advanced configuration:
+- **[Advanced Network Media RX Operator](../../operators/advanced_network_media/README.md)**: Comprehensive operator documentation and configuration options
+- **[Advanced Network Operators](../../operators/advanced_network/README.md)**: Base networking infrastructure and setup
+
+### Additional Resources
+- **[High Performance Networking Tutorial](../../tutorials/high_performance_networking/README.md)**: System tuning and optimization guide
+- **[Advanced Networking Media Sender](../adv_networking_media_sender/README.md)**: Companion application for media transmission
diff --git a/applications/adv_networking_media_player/adv_networking_media_player.yaml b/applications/adv_networking_media_player/adv_networking_media_player.yaml
new file mode 100755
index 0000000000..051886f1b0
--- /dev/null
+++ b/applications/adv_networking_media_player/adv_networking_media_player.yaml
@@ -0,0 +1,101 @@
+%YAML 1.2
+# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+---
+scheduler:
+ check_recession_period_ms: 0
+ worker_thread_number: 8
+ stop_on_deadlock: true
+ stop_on_deadlock_timeout: 500
+
+advanced_network:
+ cfg:
+ version: 1
+ manager: "rivermax"
+ master_core: 6 # Master CPU core
+ debug: 1
+ log_level: "error"
+
+ memory_regions:
+ - name: "Data_RX_CPU"
+ kind: "host"
+ affinity: 0
+ access:
+ - local
+ num_bufs: 432000
+ buf_size: 20
+ - name: "Data_RX_GPU"
+ kind: "device"
+ affinity: 0
+ access:
+ - local
+ num_bufs: 432000
+ buf_size: 1440
+ interfaces:
+ - name: data1
+ address: cc:00.1
+ rx:
+ queues:
+ - name: "Data"
+ id: 0
+ cpu_core: "12"
+ batch_size: 4320
+ output_port: "bench_rx_out_1"
+ memory_regions:
+ - "Data_RX_CPU"
+ - "Data_RX_GPU"
+ rivermax_rx_settings:
+ settings_type: "ipo_receiver"
+ memory_registration: true
+ #allocator_type: "huge_page_2mb"
+ verbose: true
+ max_path_diff_us: 10000
+ ext_seq_num: true
+ sleep_between_operations_us: 0
+ local_ip_addresses:
+ - 2.1.0.12
+ source_ip_addresses:
+ - 2.1.0.12
+ destination_ip_addresses:
+ - 224.1.1.2
+ destination_ports:
+ - 50001
+ stats_report_interval_ms: 3000
+ send_packet_ext_info: true
+
+advanced_network_media_rx:
+ interface_name: cc:00.1
+ queue_id: 0
+ video_format: RGB888
+ frame_width: 1920
+ frame_height: 1080
+ bit_depth: 8
+ hds: true
+ output_format: tensor
+ memory_location: device
+
+media_player_config:
+ write_to_file: false
+ visualize: true
+ input_format: "rgb888"
+
+frames_writer: # applied only to cpp
+ num_of_frames_to_record: 1000
+ file_path: "/tmp/output.bin"
+
+holoviz:
+ width: 1280
+ height: 720
+
\ No newline at end of file
diff --git a/applications/adv_networking_media_player/cpp/CMakeLists.txt b/applications/adv_networking_media_player/cpp/CMakeLists.txt
new file mode 100755
index 0000000000..b9dddab43b
--- /dev/null
+++ b/applications/adv_networking_media_player/cpp/CMakeLists.txt
@@ -0,0 +1,44 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+cmake_minimum_required(VERSION 3.20)
+project(adv_networking_media_player CXX)
+
+# Dependencies
+find_package(holoscan 2.6 REQUIRED CONFIG
+ PATHS "/opt/nvidia/holoscan" "/workspace/holoscan-sdk/install")
+
+# Global variables
+set(CMAKE_CUDA_ARCHITECTURES "70;80;90")
+
+# Create the executable
+add_executable(${PROJECT_NAME}
+ adv_networking_media_player.cpp
+)
+
+target_link_libraries(${PROJECT_NAME}
+ PRIVATE
+ holoscan::core
+ holoscan::advanced_network
+ holoscan::ops::advanced_network_media_rx
+ holoscan::ops::holoviz
+)
+
+# Copy config file
+add_custom_target(adv_networking_media_player_yaml
+ COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/../adv_networking_media_player.yaml" ${CMAKE_CURRENT_BINARY_DIR}
+ DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../adv_networking_media_player.yaml"
+)
+add_dependencies(${PROJECT_NAME} adv_networking_media_player_yaml)
diff --git a/applications/adv_networking_media_player/cpp/adv_networking_media_player.cpp b/applications/adv_networking_media_player/cpp/adv_networking_media_player.cpp
new file mode 100755
index 0000000000..438a6e7239
--- /dev/null
+++ b/applications/adv_networking_media_player/cpp/adv_networking_media_player.cpp
@@ -0,0 +1,338 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include
+#include
+#include
+#include
+
+#include "holoscan/holoscan.hpp"
+#include "gxf/core/gxf.h"
+#include "holoscan/operators/holoviz/holoviz.hpp"
+#include "advanced_network/common.h"
+#include "adv_network_media_rx.h"
+
+namespace ano = holoscan::advanced_network;
+using holoscan::advanced_network::NetworkConfig;
+using holoscan::advanced_network::Status;
+
+#define CUDA_TRY(stmt) \
+ { \
+ cudaError_t cuda_status = stmt; \
+ if (cudaSuccess != cuda_status) { \
+ HOLOSCAN_LOG_ERROR("Runtime call {} in line {} of file {} failed with '{}' ({})", \
+ #stmt, \
+ __LINE__, \
+ __FILE__, \
+ cudaGetErrorString(cuda_status), \
+ static_cast(cuda_status)); \
+ throw std::runtime_error("CUDA operation failed"); \
+ } \
+ }
+
+namespace holoscan::ops {
+
+/**
+ * @class FramesWriterOp
+ * @brief Operator for writing frame data to file.
+ *
+ * This operator can handle:
+ * - Input types: VideoBuffer, GXF Tensor
+ * - Memory sources: Host memory (kHost, kSystem) and Device memory (kDevice)
+ * - Automatic memory type detection and appropriate copying (device-to-host when needed)
+ *
+ * Features:
+ * - Automatic input type detection (VideoBuffer takes precedence, then GXF Tensor)
+ * - Efficient memory handling with reusable host buffer for device-to-host copies
+ * - Comprehensive error handling and logging
+ * - Binary file output with proper stream management
+ *
+ * Parameters:
+ * - num_of_frames_to_record: Number of frames to write before stopping
+ * - file_path: Output file path (default: "./output.bin")
+ */
+class FramesWriterOp : public Operator {
+ public:
+ HOLOSCAN_OPERATOR_FORWARD_ARGS(FramesWriterOp)
+
+ FramesWriterOp() = default;
+
+ ~FramesWriterOp() {
+ if (file_stream_.is_open()) { file_stream_.close(); }
+ }
+
+ void setup(OperatorSpec& spec) override {
+ spec.input("input");
+ spec.param(num_of_frames_to_record_,
+ "num_of_frames_to_record",
+ "The number of frames to write to file");
+ spec.param(
+ file_path_, "file_path", "Output File Path", "Path to the output file", "./output.bin");
+ }
+
+ void initialize() override {
+ HOLOSCAN_LOG_INFO("FramesWriterOp::initialize()");
+ holoscan::Operator::initialize();
+
+ std::string file_path = file_path_.get();
+ HOLOSCAN_LOG_INFO("Original file path from config: {}", file_path);
+ HOLOSCAN_LOG_INFO("Current working directory: {}", std::filesystem::current_path().string());
+
+ // Convert to absolute path if relative
+ std::filesystem::path path(file_path);
+ if (!path.is_absolute()) {
+ path = std::filesystem::absolute(path);
+ file_path = path.string();
+ HOLOSCAN_LOG_INFO("Converted to absolute path: {}", file_path);
+ }
+
+ HOLOSCAN_LOG_INFO("Attempting to open output file: {}", file_path);
+ file_stream_.open(file_path, std::ios::out | std::ios::binary);
+ if (!file_stream_) {
+ HOLOSCAN_LOG_ERROR("Failed to open output file: {}. Check permissions and disk space.",
+ file_path);
+
+ // Additional debugging information
+ std::error_code ec;
+ if (path.has_parent_path()) {
+ auto file_status = std::filesystem::status(path.parent_path(), ec);
+ if (!ec) {
+ auto perms = file_status.permissions();
+ HOLOSCAN_LOG_ERROR("Parent directory permissions: {}", static_cast(perms));
+ }
+ }
+
+ throw std::runtime_error("Failed to open output file: " + file_path);
+ }
+
+ HOLOSCAN_LOG_INFO("Successfully opened output file: {}", file_path);
+ }
+
+ void compute(InputContext& op_input, [[maybe_unused]] OutputContext&,
+ ExecutionContext& context) override {
+ auto maybe_entity = op_input.receive("input");
+ if (!maybe_entity) { throw std::runtime_error("Failed to receive input"); }
+
+ auto& entity = static_cast(maybe_entity.value());
+
+ if (frames_recorded_ >= num_of_frames_to_record_.get()) { return; }
+
+ auto maybe_video_buffer = entity.get();
+ if (maybe_video_buffer) {
+ process_video_buffer(maybe_video_buffer.value());
+ } else {
+ auto maybe_tensor = entity.get();
+ if (!maybe_tensor) {
+ HOLOSCAN_LOG_ERROR("Neither VideoBuffer nor Tensor found in message");
+ return;
+ }
+ process_gxf_tensor(maybe_tensor.value());
+ }
+
+ frames_recorded_++;
+ }
+
+ private:
+ /**
+ * @brief Processes a VideoBuffer input and writes its data to file.
+ *
+ * Extracts data from the VideoBuffer, determines memory storage type,
+ * and calls write_data_to_file to handle the actual file writing.
+ *
+ * @param video_buffer Handle to the GXF VideoBuffer to process
+ */
+ void process_video_buffer(nvidia::gxf::Handle video_buffer) {
+ const auto buffer_size = video_buffer->size();
+ const auto storage_type = video_buffer->storage_type();
+ const auto data_ptr = video_buffer->pointer();
+
+ HOLOSCAN_LOG_TRACE("Processing VideoBuffer: size={}, storage_type={}",
+ buffer_size,
+ static_cast(storage_type));
+
+ write_data_to_file(data_ptr, buffer_size, storage_type);
+ }
+
+ /**
+ * @brief Processes a GXF Tensor input and writes its data to file.
+ *
+ * Extracts data from the GXF Tensor, determines memory storage type,
+ * and calls write_data_to_file to handle the actual file writing.
+ *
+ * @param tensor Handle to the GXF Tensor to process
+ */
+ void process_gxf_tensor(nvidia::gxf::Handle tensor) {
+ const auto tensor_size = tensor->size();
+ const auto storage_type = tensor->storage_type();
+ const auto data_ptr = tensor->pointer();
+
+ HOLOSCAN_LOG_TRACE(
+ "Processing Tensor: size={}, storage_type={}", tensor_size, static_cast(storage_type));
+
+ write_data_to_file(data_ptr, tensor_size, storage_type);
+ }
+
+ /**
+ * @brief Writes data to file, handling both host and device memory sources.
+ *
+ * For host memory (kHost, kSystem), writes data directly to file.
+ * For device memory (kDevice), copies data to host buffer first, then writes to file.
+ * Automatically resizes the host buffer as needed and includes comprehensive error checking.
+ *
+ * @param data_ptr Pointer to the data to write
+ * @param data_size Size of the data in bytes
+ * @param storage_type Memory storage type indicating where the data resides
+ *
+ * @throws std::runtime_error If file stream is in bad state, CUDA operations fail,
+ * or unsupported memory storage type is encountered
+ */
+ void write_data_to_file(void* data_ptr, size_t data_size,
+ nvidia::gxf::MemoryStorageType storage_type) {
+ if (!data_ptr || data_size == 0) {
+ HOLOSCAN_LOG_ERROR(
+ "Invalid data pointer or size: ptr={}, size={}", static_cast(data_ptr), data_size);
+ return;
+ }
+
+ if (!file_stream_.is_open() || !file_stream_.good()) {
+ HOLOSCAN_LOG_ERROR("File stream is not open or in bad state");
+ throw std::runtime_error("File stream error");
+ }
+
+ // Ensure host buffer is large enough
+ if (host_buffer_.size() < data_size) {
+ HOLOSCAN_LOG_TRACE(
+ "Resizing host buffer from {} to {} bytes", host_buffer_.size(), data_size);
+ host_buffer_.resize(data_size);
+ }
+
+ switch (storage_type) {
+ case nvidia::gxf::MemoryStorageType::kHost:
+ case nvidia::gxf::MemoryStorageType::kSystem: {
+ // Data is already on host, write directly
+ file_stream_.write(reinterpret_cast(data_ptr), data_size);
+ break;
+ }
+ case nvidia::gxf::MemoryStorageType::kDevice: {
+ // Data is on device, copy to host first
+ CUDA_TRY(cudaMemcpy(host_buffer_.data(), data_ptr, data_size, cudaMemcpyDeviceToHost));
+ file_stream_.write(reinterpret_cast(host_buffer_.data()), data_size);
+ break;
+ }
+ default: {
+ HOLOSCAN_LOG_ERROR("Unsupported memory storage type: {}", static_cast(storage_type));
+ throw std::runtime_error("Unsupported memory storage type");
+ }
+ }
+
+ if (!file_stream_.good()) {
+ HOLOSCAN_LOG_ERROR("Failed to write data to file - stream state: fail={}, bad={}, eof={}",
+ file_stream_.fail(),
+ file_stream_.bad(),
+ file_stream_.eof());
+ throw std::runtime_error("Failed to write data to file");
+ }
+
+ // Flush to ensure data is written
+ file_stream_.flush();
+
+ HOLOSCAN_LOG_TRACE(
+ "Successfully wrote {} bytes to file (frame {})", data_size, frames_recorded_ + 1);
+ }
+
+ std::ofstream file_stream_;
+ std::vector host_buffer_; // Buffer for device-to-host copies
+ uint32_t frames_recorded_ = 0;
+ Parameter num_of_frames_to_record_;
+ Parameter file_path_;
+};
+
+} // namespace holoscan::ops
+
+class App : public holoscan::Application {
+ public:
+ void compose() override {
+ using namespace holoscan;
+
+ auto adv_net_config = from_config("advanced_network").as();
+ if (ano::adv_net_init(adv_net_config) != Status::SUCCESS) {
+ HOLOSCAN_LOG_ERROR("Failed to configure the Advanced Network manager");
+ exit(1);
+ }
+ HOLOSCAN_LOG_INFO("Configured the Advanced Network manager");
+
+ const auto [rx_en, tx_en] = ano::get_rx_tx_configs_enabled(config());
+ const auto mgr_type = ano::get_manager_type(config());
+
+ HOLOSCAN_LOG_INFO("Using Advanced Network manager {}",
+ ano::manager_type_to_string(mgr_type));
+ if (!rx_en) {
+ HOLOSCAN_LOG_ERROR("Rx is not enabled. Please enable Rx in the config file.");
+ exit(1);
+ }
+
+ auto adv_net_media_rx =
+ make_operator("advanced_network_media_rx",
+ from_config("advanced_network_media_rx"),
+ make_condition("is_alive", true));
+
+ const auto allocator = make_resource("allocator");
+
+ if (from_config("media_player_config.visualize").as()) {
+ const auto cuda_stream_pool =
+ make_resource("cuda_stream", 0, 0, 0, 1, 5);
+
+ auto visualizer = make_operator("visualizer",
+ from_config("holoviz"),
+ Arg("cuda_stream_pool", cuda_stream_pool),
+ Arg("allocator") = allocator);
+ add_flow(adv_net_media_rx, visualizer, {{"out_video_buffer", "receivers"}});
+ } else if (from_config("media_player_config.write_to_file").as()) {
+ auto frames_writer =
+ make_operator("frames_writer", from_config("frames_writer"));
+ add_flow(adv_net_media_rx, frames_writer);
+ } else {
+ HOLOSCAN_LOG_ERROR("At least one output type (write_to_file/visualize) must be defined");
+ exit(1);
+ }
+ }
+};
+
+int main(int argc, char** argv) {
+ using namespace holoscan;
+ auto app = holoscan::make_application();
+
+ // Get the configuration
+ if (argc < 2) {
+ HOLOSCAN_LOG_ERROR("Usage: {} config_file", argv[0]);
+ return -1;
+ }
+
+ std::filesystem::path config_path(argv[1]);
+ if (!config_path.is_absolute()) {
+ config_path = std::filesystem::canonical(argv[0]).parent_path() / config_path;
+ }
+
+ app->config(config_path);
+ app->scheduler(app->make_scheduler("multithread-scheduler",
+ app->from_config("scheduler")));
+ app->run();
+
+ ano::shutdown();
+
+ return 0;
+}
diff --git a/applications/adv_networking_media_player/cpp/metadata.json b/applications/adv_networking_media_player/cpp/metadata.json
new file mode 100755
index 0000000000..2e89095044
--- /dev/null
+++ b/applications/adv_networking_media_player/cpp/metadata.json
@@ -0,0 +1,39 @@
+{
+ "application": {
+ "name": "Advanced Networking Media Player",
+ "authors": [
+ {
+ "name": "Rony Rado",
+ "affiliation": "NVIDIA"
+ }
+ ],
+ "language": "C++",
+ "version": "1.0",
+ "changelog": {
+ "1.0": "Initial Release"
+ },
+ "platforms": ["x86_64", "aarch64"],
+ "tags": ["Networking and Distributed Computing", "UDP", "Ethernet", "IP", "GPUDirect", "Rivermax"],
+ "dockerfile": "operators/advanced_network/Dockerfile",
+ "holoscan_sdk": {
+ "minimum_required_version": "2.6.0",
+ "tested_versions": [
+ "2.6.0"
+ ]
+ },
+ "ranking": 3,
+ "requirements": {
+ "operators": [{
+ "name": "advanced_network_media_rx",
+ "version": "1.0"
+ }, {
+ "name": "advanced_network",
+ "version": "1.4"
+ }]
+ },
+ "run": {
+ "command": "/adv_networking_media_player adv_networking_media_player.yaml",
+ "workdir": "holohub_bin"
+ }
+ }
+}
diff --git a/applications/adv_networking_media_player/python/CMakeLists.txt b/applications/adv_networking_media_player/python/CMakeLists.txt
new file mode 100755
index 0000000000..6cc7ed9a1a
--- /dev/null
+++ b/applications/adv_networking_media_player/python/CMakeLists.txt
@@ -0,0 +1,30 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Copy adv_networking_media_player application file
+add_custom_target(python_adv_networking_media_player ALL
+ COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/adv_networking_media_player.py" ${CMAKE_CURRENT_BINARY_DIR}
+ DEPENDS "adv_networking_media_player.py"
+ BYPRODUCTS "adv_networking_media_player.py"
+)
+
+# Copy config file
+add_custom_target(python_adv_networking_media_player_yaml
+ COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/../adv_networking_media_player.yaml" ${CMAKE_CURRENT_BINARY_DIR}
+ DEPENDS "../adv_networking_media_player.yaml"
+ BYPRODUCTS "adv_networking_media_player.yaml"
+)
+
+add_dependencies(python_adv_networking_media_player python_adv_networking_media_player_yaml)
diff --git a/applications/adv_networking_media_player/python/adv_networking_media_player.py b/applications/adv_networking_media_player/python/adv_networking_media_player.py
new file mode 100755
index 0000000000..b05cb3867a
--- /dev/null
+++ b/applications/adv_networking_media_player/python/adv_networking_media_player.py
@@ -0,0 +1,227 @@
+#!/usr/bin/env python3
+
+# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import sys
+from pathlib import Path
+
+from holoscan.core import Application
+from holoscan.operators.holoviz import HolovizOp
+from holoscan.resources import CudaStreamPool, UnboundedAllocator
+from holoscan.schedulers import MultiThreadScheduler
+
+from holohub.advanced_network_common import _advanced_network_common as adv_network_common
+from holohub.advanced_network_media_rx import _advanced_network_media_rx as adv_network_media_rx
+
+logger = logging.getLogger(__name__)
+logging.basicConfig(level=logging.INFO)
+
+
+def check_rx_tx_enabled(app, require_rx=True, require_tx=False):
+ """
+ Check if RX and TX are enabled in the advanced network configuration.
+
+ Args:
+ app: The Holoscan Application instance
+ require_rx: Whether RX must be enabled (default: True)
+ require_tx: Whether TX must be enabled (default: False)
+
+ Returns:
+ tuple: (rx_enabled, tx_enabled)
+
+ Raises:
+ SystemExit: If required functionality is not enabled
+ """
+ try:
+ adv_net_config_dict = app.kwargs("advanced_network")
+
+ rx_enabled = False
+ tx_enabled = False
+
+ # Check if there are interfaces with RX/TX configurations
+ if "cfg" in adv_net_config_dict and "interfaces" in adv_net_config_dict["cfg"]:
+ for interface in adv_net_config_dict["cfg"]["interfaces"]:
+ if "rx" in interface:
+ rx_enabled = True
+ if "tx" in interface:
+ tx_enabled = True
+
+ logger.info(f"RX enabled: {rx_enabled}, TX enabled: {tx_enabled}")
+
+ if require_rx and not rx_enabled:
+ logger.error("RX is not enabled. Please enable RX in the config file.")
+ sys.exit(1)
+
+ if require_tx and not tx_enabled:
+ logger.error("TX is not enabled. Please enable TX in the config file.")
+ sys.exit(1)
+
+ return rx_enabled, tx_enabled
+
+ except Exception as e:
+ logger.warning(f"Could not check RX/TX status from advanced_network config: {e}")
+ # Fallback: check if we have the required operator configs
+ try:
+ if require_rx:
+ app.from_config("advanced_network_media_rx")
+ logger.info("RX is enabled (found advanced_network_media_rx config)")
+ if require_tx:
+ app.from_config("advanced_network_media_tx")
+ logger.info("TX is enabled (found advanced_network_media_tx config)")
+ return require_rx, require_tx
+ except Exception as e2:
+ if require_rx:
+ logger.error("RX is not enabled. Please enable RX in the config file.")
+ logger.error(f"Could not find advanced_network_media_rx configuration: {e2}")
+ sys.exit(1)
+ if require_tx:
+ logger.error("TX is not enabled. Please enable TX in the config file.")
+ logger.error(f"Could not find advanced_network_media_tx configuration: {e2}")
+ sys.exit(1)
+ return False, False
+
+
+class App(Application):
+ def compose(self):
+ # Initialize advanced network
+ try:
+ adv_net_config = self.from_config("advanced_network")
+ if adv_network_common.adv_net_init(adv_net_config) != adv_network_common.Status.SUCCESS:
+ logger.error("Failed to configure the Advanced Network manager")
+ sys.exit(1)
+ logger.info("Configured the Advanced Network manager")
+ except Exception as e:
+ logger.error(f"Failed to get advanced network config or initialize: {e}")
+ sys.exit(1)
+
+ # Get manager type
+ try:
+ mgr_type = adv_network_common.get_manager_type()
+ logger.info(
+ f"Using Advanced Network manager {adv_network_common.manager_type_to_string(mgr_type)}"
+ )
+ except Exception as e:
+ logger.warning(f"Could not get manager type: {e}")
+
+ # Check RX/TX enabled status (require RX for media player)
+ check_rx_tx_enabled(self, require_rx=True, require_tx=False)
+ logger.info("RX is enabled, proceeding with application setup")
+
+ allocator = UnboundedAllocator(self, name="allocator")
+
+ # Create shared CUDA stream pool for format converters and CUDA operations
+ # Optimized sizing for video processing workloads
+ cuda_stream_pool = CudaStreamPool(
+ self,
+ name="cuda_stream_pool",
+ dev_id=0,
+ stream_flags=0,
+ stream_priority=0,
+ reserved_size=1,
+ max_size=5,
+ )
+
+ try:
+ rx_config = self.kwargs("advanced_network_media_rx")
+
+ adv_net_media_rx = adv_network_media_rx.AdvNetworkMediaRxOp(
+ fragment=self,
+ **rx_config,
+ name="advanced_network_media_rx",
+ )
+
+ except Exception as e:
+ logger.error(f"Failed to create AdvNetworkMediaRxOp: {e}")
+ sys.exit(1)
+
+ # Set up visualization pipeline
+ try:
+ # Create visualizer
+ holoviz_config = self.kwargs("holoviz")
+ visualizer = HolovizOp(
+ fragment=self,
+ name="visualizer",
+ allocator=allocator,
+ cuda_stream_pool=cuda_stream_pool,
+ **holoviz_config,
+ )
+
+ self.add_flow(adv_net_media_rx, visualizer, {("out_video_buffer", "receivers")})
+
+ except Exception as e:
+ logger.error(f"Failed to set up visualization pipeline: {e}")
+ sys.exit(1)
+
+ # Set up scheduler
+ try:
+ scheduler_config = self.kwargs("scheduler")
+ scheduler = MultiThreadScheduler(
+ fragment=self, name="multithread-scheduler", **scheduler_config
+ )
+ self.scheduler(scheduler)
+ except Exception as e:
+ logger.error(f"Failed to set up scheduler: {e}")
+ sys.exit(1)
+
+ logger.info("Application composition completed successfully")
+
+
+def main():
+ if len(sys.argv) < 2:
+ logger.error(f"Usage: {sys.argv[0]} config_file")
+ sys.exit(1)
+
+ config_path = Path(sys.argv[1])
+
+ # Convert to absolute path if relative
+ if not config_path.is_absolute():
+ # Get the directory of the script and make path relative to it
+ script_dir = Path(sys.argv[0]).parent.resolve()
+ config_path = script_dir / config_path
+
+ if not config_path.exists():
+ logger.error(f"Config file not found: {config_path}")
+ sys.exit(1)
+
+ logger.info(f"Using config file: {config_path}")
+
+ try:
+ app = App()
+ app.config(str(config_path))
+
+ logger.info("Starting application...")
+ app.run()
+
+ logger.info("Application finished")
+
+ except Exception as e:
+ logger.error(f"Application failed: {e}")
+ import traceback
+
+ traceback.print_exc()
+ sys.exit(1)
+ finally:
+ # Shutdown advanced network
+ try:
+ adv_network_common.shutdown()
+ logger.info("Advanced Network shutdown completed")
+ except Exception as e:
+ logger.warning(f"Error during advanced network shutdown: {e}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/applications/adv_networking_media_player/python/metadata.json b/applications/adv_networking_media_player/python/metadata.json
new file mode 100755
index 0000000000..c92b625469
--- /dev/null
+++ b/applications/adv_networking_media_player/python/metadata.json
@@ -0,0 +1,39 @@
+{
+ "application": {
+ "name": "Advanced Networking Media Player",
+ "authors": [
+ {
+ "name": "Rony Rado",
+ "affiliation": "NVIDIA"
+ }
+ ],
+ "language": "Python",
+ "version": "1.0",
+ "changelog": {
+ "1.0": "Initial Release"
+ },
+ "platforms": ["x86_64", "aarch64"],
+ "tags": ["Networking and Distributed Computing", "UDP", "Ethernet", "IP", "GPUDirect", "Rivermax"],
+ "dockerfile": "operators/advanced_network/Dockerfile",
+ "holoscan_sdk": {
+ "minimum_required_version": "2.6.0",
+ "tested_versions": [
+ "2.6.0"
+ ]
+ },
+ "ranking": 3,
+ "requirements": {
+ "operators": [{
+ "name": "advanced_network_media_rx",
+ "version": "1.0"
+ }, {
+ "name": "advanced_network",
+ "version": "1.4"
+ }]
+ },
+ "run": {
+ "command": "python3 /adv_networking_media_player.py ../adv_networking_media_player.yaml",
+ "workdir": "holohub_bin"
+ }
+ }
+}
diff --git a/applications/adv_networking_media_sender/CMakeLists.txt b/applications/adv_networking_media_sender/CMakeLists.txt
new file mode 100755
index 0000000000..2e6d8900fa
--- /dev/null
+++ b/applications/adv_networking_media_sender/CMakeLists.txt
@@ -0,0 +1,20 @@
+# SPDX-FileCopyrightText: Copyright (c) 2022-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Add subdirectories
+add_subdirectory(cpp)
+if(HOLOHUB_BUILD_PYTHON)
+ add_subdirectory(python)
+endif()
diff --git a/applications/adv_networking_media_sender/README.md b/applications/adv_networking_media_sender/README.md
new file mode 100755
index 0000000000..9d818566ce
--- /dev/null
+++ b/applications/adv_networking_media_sender/README.md
@@ -0,0 +1,525 @@
+# Advanced Networking Media Sender
+
+The Advanced Networking Media Sender is a high-performance application for transmitting media streams over advanced network infrastructure using NVIDIA's Rivermax SDK. This application demonstrates professional-grade media streaming capabilities with ultra-low latency and high throughput for broadcast and media production environments.
+
+## Overview
+
+This application showcases high-performance media transmission over IP networks, utilizing NVIDIA's advanced networking technologies. It reads media files from disk and transmits them as real-time streams using the SMPTE 2110 standard, making it ideal for professional broadcast applications.
+
+### Key Features
+
+- **High-Performance Streaming**: Transmit media streams with minimal latency using Rivermax SDK
+- **SMPTE 2110 Compliance**: Industry-standard media over IP protocol support
+- **File-based Source**: Read and stream media files with precise timing control
+- **GPU Acceleration**: Leverage GPUDirect for zero-copy operations
+- **Multiple Format Support**: RGB888, YUV420, NV12, and other common video formats
+- **Real-time Playback**: Accurate frame rate control for live streaming applications
+
+### Application Architecture
+
+The Advanced Networking Media Sender implements a sophisticated frame-level processing architecture with configurable memory management strategies for optimal performance in different use cases.
+
+#### Complete Application Data Flow
+
+```mermaid
+graph TD
+ %% Application Source Layer
+ A["VideoStreamReplayer
(File Reading)"] --> B["GXF Entity Generation
(VideoBuffer/Tensor)"]
+
+ %% Media TX Operator Layer
+ B --> C["AdvNetworkMediaTxOp"]
+ C --> D["Frame Validation
& Format Check"]
+ D --> E{{"Input Entity Type"}}
+ E -->|VideoBuffer| F["VideoBufferFrameBuffer
(Wrapper)"]
+ E -->|Tensor| G["TensorFrameBuffer
(Wrapper)"]
+
+ %% MediaFrame Processing
+ F --> H["MediaFrame Creation
(Reference Only - No Copy)"]
+ G --> H
+ H --> I["BurstParams Creation"]
+ I --> J["MediaFrame Attachment
(via custom_pkt_data)"]
+
+ %% Advanced Network Manager
+ J --> K["RivermaxMgr
(Advanced Network Manager)"]
+ K --> L{{"Service Selection
(Configuration Driven)"}}
+
+ %% Service Path Selection
+ L -->|use_internal_memory_pool: false| M["MediaSenderZeroCopyService
(True Zero-Copy Path)"]
+ L -->|use_internal_memory_pool: true| N["MediaSenderService
(Single Copy + Pool Path)"]
+
+ %% Zero-Copy Path
+ M --> O["No Internal Memory Pool"]
+ O --> P["Direct Frame Reference
(custom_pkt_data → RDK)"]
+ P --> Q["RDK MediaSenderApp
(Zero-Copy Processing)"]
+ Q --> R["Frame Ownership Transfer
& Release After Processing"]
+
+ %% Memory Pool Path
+ N --> S["Pre-allocated MediaFramePool
(MEDIA_FRAME_POOL_SIZE buffers)"]
+ S --> T["Single Memory Copy
(Application Frame → Pool Buffer)"]
+ T --> U["RDK MediaSenderApp
(Pool Buffer Processing)"]
+ U --> V["Pool Buffer Reuse
(Returned to Pool)"]
+
+ %% RDK Processing (Common Path)
+ Q --> W["RDK Internal Processing
(All Packet Operations)"]
+ U --> W
+ W --> X["RTP Packetization
(SMPTE 2110 Standard)"]
+ X --> Y["Protocol Headers
& Metadata Addition"]
+ Y --> Z["Precise Timing Control
& Scheduling"]
+
+ %% Network Hardware
+ Z --> AA["Rivermax Hardware
Acceleration"]
+ AA --> BB["ConnectX NIC
Hardware Queues"]
+ BB --> CC["Network Interface
Transmission"]
+
+ %% Styling
+ classDef appLayer fill:#fff3e0
+ classDef operatorLayer fill:#e8f5e8
+ classDef managerLayer fill:#f3e5f5
+ classDef zeroCopyPath fill:#e8f5e8,stroke:#4caf50,stroke-width:3px
+ classDef poolPath fill:#fff8e1,stroke:#ff9800,stroke-width:3px
+ classDef rdkLayer fill:#e3f2fd
+ classDef networkLayer fill:#e1f5fe
+ classDef configDecision fill:#f9f9f9,stroke:#333,stroke-width:2px
+
+ class A,B appLayer
+ class C,D,E,F,G,H,I,J operatorLayer
+ class K managerLayer
+ class L configDecision
+ class M,O,P,Q,R zeroCopyPath
+ class N,S,T,U,V poolPath
+ class W,X,Y,Z rdkLayer
+ class AA,BB,CC networkLayer
+```
+
+#### Service Selection Decision Flow
+
+```mermaid
+flowchart TD
+ A["Application Configuration
(use_internal_memory_pool)"] --> B{{"Memory Pool
Configuration"}}
+
+ B -->|false| C["MediaSenderZeroCopyService
(Pipeline Mode)"]
+ B -->|true| D["MediaSenderService
(Data Generation Mode)"]
+
+ %% Zero-Copy Path Details
+ C --> E["Zero Memory Copies
Only Frame Reference Transfer"]
+ E --> F["Maximum Latency Efficiency
Minimal Memory Usage"]
+ F --> G["Best for: Pipeline Processing
Real-time Applications"]
+
+ %% Memory Pool Path Details
+ D --> H["Single Memory Copy
Application → Pool Buffer"]
+ H --> I["Sustained High Throughput
Buffer Pool Reuse"]
+ I --> J["Best for: File Reading
Batch Processing"]
+
+ %% Common Processing
+ G --> K["RDK MediaSenderApp
Processing"]
+ J --> K
+ K --> L["RTP Packetization
Network Transmission"]
+
+ %% Usage Examples
+ M["Usage Scenarios"] --> N["Pipeline Mode Applications"]
+ M --> O["Data Generation Applications"]
+
+ N --> P["• Frame-to-frame operators
• Real-time transformations
• Low-latency streaming
• GPU-accelerated processing"]
+ O --> Q["• File readers (VideoStreamReplayer)
• Camera/sensor operators
• Synthetic data generators
• Batch processing systems"]
+
+ P -.-> C
+ Q -.-> D
+
+ %% Styling
+ classDef configNode fill:#f9f9f9,stroke:#333,stroke-width:2px
+ classDef zeroCopyPath fill:#e8f5e8,stroke:#4caf50,stroke-width:3px
+ classDef poolPath fill:#fff8e1,stroke:#ff9800,stroke-width:3px
+ classDef rdkLayer fill:#e3f2fd
+ classDef usageLayer fill:#f3e5f5
+
+ class A,B configNode
+ class C,E,F,G zeroCopyPath
+ class D,H,I,J poolPath
+ class K,L rdkLayer
+ class M,N,O,P,Q usageLayer
+```
+
+#### Simplified Application Pipeline
+
+```
+Video Files → VideoStreamReplayer → AdvNetworkMediaTxOp → RDK Services → Network
+```
+
+## Requirements
+
+### Hardware Requirements
+- Linux system (x86_64 or aarch64)
+- NVIDIA NIC with ConnectX-6 or later chip
+- NVIDIA GPU (for GPU acceleration)
+- Sufficient network bandwidth for target media streams
+- Storage with adequate throughput for media file reading
+
+### Software Requirements
+- NVIDIA Rivermax SDK
+- NVIDIA GPU drivers
+- MOFED drivers (5.8-1.0.1.1 or later)
+- DOCA 2.7 or later (if using DOCA backend)
+- System tuning as described in the [High Performance Networking tutorial](../../tutorials/high_performance_networking/README.md)
+
+## Build Instructions
+
+### Build Docker Image
+
+Build the Docker image and application with Rivermax support:
+
+**C++ version:**
+```bash
+./holohub build adv_networking_media_sender --build-args="--target rivermax" --configure-args="-D ANO_MGR:STRING=rivermax" --language cpp
+```
+
+**Python version:**
+```bash
+./holohub build adv_networking_media_sender --build-args="--target rivermax" --configure-args="-D ANO_MGR:STRING=rivermax" --language python
+```
+
+### Launch Container
+
+Launch the Rivermax-enabled container:
+
+**C++ version:**
+```bash
+./holohub run-container adv_networking_media_sender --build-args="--target rivermax" --docker-opts="-u root --privileged -v /opt/mellanox/rivermax/rivermax.lic:/opt/mellanox/rivermax/rivermax.lic -v /media/video:/media/video/ -w /workspace/holohub/build/adv_networking_media_sender/applications/adv_networking_media_sender/cpp"
+```
+
+**Python version:**
+```bash
+./holohub run-container adv_networking_media_sender --build-args="--target rivermax" --docker-opts="-u root --privileged -v /opt/mellanox/rivermax/rivermax.lic:/opt/mellanox/rivermax/rivermax.lic -v /media/video:/media/video/ -w /workspace/holohub/build/adv_networking_media_sender/applications/adv_networking_media_sender/python"
+```
+
+## Running the Application
+
+### Prerequisites
+
+Before running, ensure your environment is properly configured:
+
+```bash
+# Update PYTHONPATH for Python applications
+# Note: Run this command from the container's working directory
+# (as set by -w flag in run-container command)
+export PYTHONPATH=${PYTHONPATH}:/opt/nvidia/holoscan/python/lib:$PWD/../../../python/lib:$PWD
+
+# Ensure proper system configuration (run as root if needed)
+# See High Performance Networking tutorial for system tuning
+```
+
+### C++ Application
+
+```bash
+./adv_networking_media_sender adv_networking_media_sender.yaml
+```
+
+### Python Application
+
+```bash
+python3 ./adv_networking_media_sender.py adv_networking_media_sender.yaml
+```
+
+## Configuration
+
+The application uses a YAML configuration file that defines the complete transmission pipeline. The configuration has four main sections:
+
+1. **Advanced Network Manager Configuration**: Network interfaces, memory regions, and Rivermax TX settings
+2. **Media TX Operator Configuration**: Video format, frame dimensions, and interface settings
+3. **VideoStreamReplayer Configuration**: Source file path and playback options
+4. **Memory Allocator Configuration**: GPU and host memory settings
+
+> **📁 Example Configuration Files**:
+> - `applications/adv_networking_media_sender/adv_networking_media_sender.yaml` - Standard 1080p configuration
+
+> **For detailed configuration parameter documentation**, see:
+> - [Advanced Network Operator Configuration](../../operators/advanced_network/README.md) - Network settings, memory regions, Rivermax TX settings
+> - [Advanced Network Media TX Operator Configuration](../../operators/advanced_network_media/README.md) - Service selection, memory pool modes, TX data flow, performance characteristics
+
+### Quick Reference: Key Parameters That Must Match
+
+Critical parameters must be consistent across configuration sections to ensure proper operation:
+
+| Parameter Category | Section 1 | Section 2 | Example Values | Required Match |
+|-------------------|-----------|-----------|----------------|----------------|
+| **Frame Rate** | `replayer.frame_rate` | `rivermax_tx_settings.frame_rate` | 60 | ✓ Must match exactly |
+| **Frame Dimensions** | `advanced_network_media_tx.frame_width/height` | `rivermax_tx_settings.frame_width/height` | 1920x1080 | ✓ Must match exactly |
+| **Video Format** | `advanced_network_media_tx.video_format` | `rivermax_tx_settings.video_format` | RGB888 / RGB | ✓ Must be compatible |
+| **Bit Depth** | `advanced_network_media_tx.bit_depth` | `rivermax_tx_settings.bit_depth` | 8 | ✓ Must match exactly |
+| **Interface** | `advanced_network_media_tx.interface_name` | `advanced_network.interfaces.address` | cc:00.1 | ✓ Must match exactly |
+| **Memory Location** | `rivermax_tx_settings.memory_pool_location` | Memory region types (`host`/`device`) | device | ✓ Should be consistent |
+
+> **⚠️ IMPORTANT: Configuration Parameter Consistency**
+>
+> Parameters across configuration sections must be consistent and properly matched:
+> - **Video Format Matching**: `video_format` parameters must match across Media TX operator and Rivermax TX settings
+> - **Frame Dimensions**: `frame_width` and `frame_height` must match between operator and RDK settings
+> - **Frame Rate**: VideoStreamReplayer `frame_rate` must match Rivermax TX `frame_rate` for proper timing
+> - **Memory Buffer Sizing**: `buf_size` in memory regions depends on video format, resolution, and packet size
+> - For RGB888 @ 1920x1080: Typical payload size is ~1440 bytes per packet
+> - **Memory Location**: `memory_pool_location` should match memory region types configured (`host` vs `device`)
+> - **Interface Matching**: `interface_name` (Media TX) must match the interface address/name in Advanced Network config
+> - **Service Mode Selection**: `use_internal_memory_pool` determines MediaSender service behavior (see operator documentation)
+>
+> Mismatched parameters will result in runtime errors or degraded performance.
+
+### Configuration File Structure
+
+The application configuration consists of four main sections:
+
+#### 1. Advanced Network Manager Configuration
+
+Configures network interfaces, memory regions, and Rivermax TX settings. See [Advanced Network Operator documentation](../../operators/advanced_network/README.md) for detailed parameter descriptions.
+
+```yaml
+advanced_network:
+ cfg:
+ version: 1
+ manager: "rivermax"
+ master_core: 6 # Master CPU core
+ debug: 1
+ log_level: "debug"
+
+ memory_regions:
+ - name: "Data_TX_CPU"
+ kind: "huge"
+ affinity: 0
+ num_bufs: 43200
+ buf_size: 20
+ - name: "Data_TX_GPU"
+ kind: "device"
+ affinity: 0
+ num_bufs: 43200
+ buf_size: 1440
+
+ interfaces:
+ - name: "tx_port"
+ address: cc:00.1
+ tx:
+ queues:
+ - name: "tx_q_1"
+ id: 0
+ cpu_core: "12"
+ batch_size: 4320
+ output_port: "bench_tx_out_1"
+ memory_regions:
+ - "Data_TX_CPU"
+ - "Data_TX_GPU"
+ rivermax_tx_settings:
+ settings_type: "media_sender"
+ memory_registration: true
+ memory_allocation: true
+ memory_pool_location: "device"
+ #allocator_type: "huge_page_2mb"
+ verbose: true
+ sleep_between_operations: false
+ local_ip_address: 2.1.0.12
+ destination_ip_address: 224.1.1.2
+ destination_port: 50001
+ stats_report_interval_ms: 1000
+ send_packet_ext_info: true
+ num_of_packets_in_chunk: 144
+ video_format: RGB
+ bit_depth: 8
+ frame_width: 1920
+ frame_height: 1080
+ frame_rate: 60
+ dummy_sender: false
+```
+
+**Key Rivermax TX Settings**:
+- `settings_type: "media_sender"` - Uses MediaSender RDK service for file-based streaming
+- `memory_pool_location: "device"` - Allocates memory pool in GPU memory for optimal performance
+- `memory_allocation: true` - Enables internal memory pool allocation (recommended for VideoStreamReplayer)
+- `local_ip_address` - Source IP address for transmission
+- `destination_ip_address` - Target IP address (multicast supported: 224.0.0.0 - 239.255.255.255)
+- `destination_port` - Target UDP port for stream delivery
+- `frame_rate` - Network transmission frame rate (must match `replayer.frame_rate`)
+- `video_format` - Video pixel format (RGB, YUV, etc.)
+- `bit_depth` - Color bit depth (8, 10, 12, 16)
+- `num_of_packets_in_chunk` - Number of packets per network transmission chunk
+
+#### 2. Media TX Operator Configuration
+
+Configures video format, frame dimensions, and interface settings. See [Advanced Network Media TX Operator documentation](../../operators/advanced_network_media/README.md) for detailed parameter descriptions.
+
+```yaml
+advanced_network_media_tx:
+ interface_name: cc:00.1
+ video_format: RGB888
+ frame_width: 1920
+ frame_height: 1080
+ bit_depth: 8
+```
+
+#### 3. VideoStreamReplayer Configuration
+
+Configures source file path and playback options. Files must be in GXF entity format (see [Media File Preparation](#media-file-preparation)):
+
+```yaml
+replayer:
+ directory: "/media/video" # Path to directory containing GXF entity files
+ basename: "bunny" # Base name matching converted files (bunny.gxf_entities, bunny.gxf_index)
+ frame_rate: 60 # Must match rivermax_tx_settings.frame_rate
+ repeat: true # Loop playback (true/false)
+ realtime: true # Real-time playback timing (true/false)
+ count: 0 # Number of frames to transmit (0 = unlimited)
+```
+
+**Key VideoStreamReplayer Settings**:
+- `directory` - Path to media files directory containing `.gxf_entities` and `.gxf_index` files
+- `basename` - Base name for media files (must match converted GXF entity file names)
+- `frame_rate` - Target frame rate for transmission (must match `rivermax_tx_settings.frame_rate`)
+- `repeat` - Enable looping playback for continuous streaming
+- `realtime` - Enable real-time playback timing to maintain accurate frame rates
+- `count` - Number of frames to transmit (0 = unlimited, useful for testing with specific frame counts)
+
+#### 4. Memory Allocator Configuration
+
+Configures GPU and host memory allocation:
+
+```yaml
+rmm_allocator:
+ device_memory_initial_size: "1024 MB"
+ device_memory_max_size: "1024 MB"
+ host_memory_initial_size: "1024 MB"
+ host_memory_max_size: "1024 MB"
+ dev_id: 0
+```
+
+## Media File Preparation
+
+### Supported Formats
+
+The VideoStreamReplayerOp expects video data encoded as GXF entities, not standard video files. The application requires:
+- **GXF Entity Format**: Video streams encoded as `.gxf_entities` and `.gxf_index` files
+- **Directory structure**: GXF files should be organized in a directory
+- **Naming convention**: `.gxf_entities` and `.gxf_index`
+
+### Converting Media Files
+
+To convert standard video files to the required GXF entity format, use the provided conversion script:
+
+```bash
+# Convert video file to GXF entities
+# Script is available in /opt/nvidia/holoscan/bin or on GitHub
+convert_video_to_gxf_entities.py --input input_video.mp4 --output_dir /media/video --basename bunny
+
+# This will create:
+# - /media/video/bunny.gxf_entities
+# - /media/video/bunny.gxf_index
+```
+
+### Video Conversion Parameters
+
+The conversion script supports various options:
+
+```bash
+# Basic conversion with custom resolution
+convert_video_to_gxf_entities.py \
+ --input input_video.mp4 \
+ --output_dir /media/video \
+ --basename bunny \
+ --width 1920 \
+ --height 1080 \
+ --framerate 60
+
+# For specific pixel formats
+convert_video_to_gxf_entities.py \
+ --input input_video.mp4 \
+ --output_dir /media/video \
+ --basename bunny \
+ --pixel_format rgb24
+```
+
+## Troubleshooting
+
+### Common Issues
+
+1. **File Not Found**: Verify media file paths and naming conventions
+2. **Network Errors**: Check IP addresses, ports, and network connectivity
+3. **Performance Issues**: Review system tuning and resource allocation
+4. **Memory Errors**: Adjust buffer sizes and memory allocations
+
+### Debug Options
+
+Enable debug logging by setting `log_level: "debug"` in the advanced_network configuration section.
+
+### Network Testing
+
+Test network connectivity before running the application:
+
+```bash
+# Test multicast connectivity
+ping 224.1.1.2
+
+# Verify network interface configuration
+ip addr show
+```
+
+## Performance Optimization
+
+### Configuration Optimization
+
+For optimal performance, configure the following based on your use case:
+
+#### For VideoStreamReplayer Applications (File-based Streaming)
+
+This application uses VideoStreamReplayer which generates data from files, making it a **data generation** use case. Recommended configuration:
+
+1. **Service Mode**: Set `use_internal_memory_pool: true` for MediaSenderService (memory pool mode)
+2. **Memory Location**: Use `memory_pool_location: "device"` for GPU memory optimization
+3. **Memory Allocation**: Enable `memory_allocation: true` for internal pool allocation
+4. **Timing Control**: Set `sleep_between_operations: false` for maximum throughput
+5. **Frame Rate Matching**: Ensure VideoStreamReplayer `frame_rate` matches Rivermax TX `frame_rate`
+6. **Memory Buffer Sizing**: Size buffers appropriately for your frame rate and resolution
+
+> **For detailed service selection, TX data flow, and optimization strategies**, see:
+> - [Advanced Network Media TX Operator Documentation](../../operators/advanced_network_media/README.md) - Service selection, memory pool vs zero-copy modes, TX architecture, performance characteristics
+> - [Advanced Network Operator Documentation](../../operators/advanced_network/README.md) - Memory region configuration, queue settings
+
+### System-Level Tuning
+
+Ensure your system is properly configured for high-performance networking:
+
+- **CPU Isolation**: Isolate CPU cores for network processing
+- **Memory Configuration**: Configure hugepages and memory allocation
+- **GPU Configuration**: Ensure sufficient GPU memory for frame buffering
+- **Network Tuning**: Configure interrupt mitigation and CPU affinity
+
+> **For detailed system tuning guidelines**, see the [High Performance Networking Tutorial](../../tutorials/high_performance_networking/README.md)
+
+### Performance Monitoring
+
+Monitor these key metrics for optimal performance:
+- **Frame Transmission Rate**: Consistent frame rate without drops
+- **Memory Pool Utilization**: Pool buffers are being reused effectively
+- **GPU Memory Usage**: Sufficient GPU memory for sustained operation
+- **Network Statistics**: Verify timing accuracy and packet delivery
+
+## Example Use Cases
+
+### Live Event Streaming
+- Stream pre-recorded content as live feeds using VideoStreamReplayer
+- Support for multiple concurrent streams with memory pool optimization
+- Frame-accurate timing for broadcast applications
+
+### Content Distribution
+- Distribute media content across network infrastructure from file sources
+- Support for multicast delivery to multiple receivers
+- High-throughput content delivery networks with sustained file-to-network streaming
+
+### Testing and Development
+- Generate test streams for receiver development using loop playback
+- Validate network infrastructure performance with realistic file-based sources
+- Prototype media streaming applications with known video content
+
+## Related Documentation
+
+### Operator Documentation
+For detailed implementation information and advanced configuration:
+- **[Advanced Network Media TX Operator](../../operators/advanced_network_media/README.md)**: Comprehensive operator documentation and configuration options
+- **[Advanced Network Operators](../../operators/advanced_network/README.md)**: Base networking infrastructure and setup
+
+### Additional Resources
+- **[High Performance Networking Tutorial](../../tutorials/high_performance_networking/README.md)**: System tuning and optimization guide
+- **[Advanced Networking Media Player](../adv_networking_media_player/README.md)**: Companion application for media reception
diff --git a/applications/adv_networking_media_sender/adv_networking_media_sender.yaml b/applications/adv_networking_media_sender/adv_networking_media_sender.yaml
new file mode 100755
index 0000000000..82c7638aaa
--- /dev/null
+++ b/applications/adv_networking_media_sender/adv_networking_media_sender.yaml
@@ -0,0 +1,102 @@
+%YAML 1.2
+# SPDX-FileCopyrightText: Copyright (c) 2022-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+---
+scheduler:
+ check_recession_period_ms: 0
+ worker_thread_number: 5
+ stop_on_deadlock: true
+ stop_on_deadlock_timeout: 500
+
+dual_window: false
+
+replayer:
+ directory: "/media/video"
+ basename: "bunny"
+ frame_rate: 60 # as specified in timestamps
+ repeat: true # default: false
+ realtime: true # default: true
+ count: 0 # default: 0 (no frame count restriction)
+
+# Initial size below is set to 8 MB which is sufficient for
+# a 1920 * 1080 RGBA image (uint8_t).
+rmm_allocator:
+ device_memory_initial_size: "1024 MB"
+ device_memory_max_size: "1024 MB"
+ host_memory_initial_size: "1024 MB"
+ host_memory_max_size: "1024 MB"
+ dev_id: 0
+
+advanced_network:
+ cfg:
+ version: 1
+ manager: "rivermax"
+ master_core: 6 # Master CPU core
+ debug: 1
+ log_level: "debug"
+
+ memory_regions:
+ - name: "Data_TX_CPU"
+ kind: "huge"
+ affinity: 0
+ num_bufs: 43200
+ buf_size: 20
+ - name: "Data_TX_GPU"
+ kind: "device"
+ affinity: 0
+ num_bufs: 43200
+ buf_size: 1440
+
+ interfaces:
+ - name: "tx_port"
+ address: cc:00.1
+ tx:
+ queues:
+ - name: "tx_q_1"
+ id: 0
+ cpu_core: "12"
+ batch_size: 4320
+ output_port: "bench_tx_out_1"
+ memory_regions:
+ - "Data_TX_CPU"
+ - "Data_TX_GPU"
+ rivermax_tx_settings:
+ settings_type: "media_sender"
+ memory_registration: true
+ memory_allocation: true
+ memory_pool_location: "device"
+ #allocator_type: "huge_page_2mb"
+ verbose: true
+ sleep_between_operations: false
+ local_ip_address: 2.1.0.12
+ destination_ip_address: 224.1.1.2
+ destination_port: 50001
+ stats_report_interval_ms: 1000
+ send_packet_ext_info: true
+ num_of_packets_in_chunk: 144
+ video_format: RGB
+ bit_depth: 8
+ frame_width: 1920
+ frame_height: 1080
+ frame_rate: 60
+ dummy_sender: false
+
+advanced_network_media_tx:
+ interface_name: cc:00.1
+ video_format: RGB888
+ frame_width: 1920
+ frame_height: 1080
+ bit_depth: 8
+
diff --git a/applications/adv_networking_media_sender/cpp/CMakeLists.txt b/applications/adv_networking_media_sender/cpp/CMakeLists.txt
new file mode 100755
index 0000000000..2c2d6a48cc
--- /dev/null
+++ b/applications/adv_networking_media_sender/cpp/CMakeLists.txt
@@ -0,0 +1,80 @@
+# SPDX-FileCopyrightText: Copyright (c) 2022-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+cmake_minimum_required(VERSION 3.20)
+project(adv_networking_media_sender CXX)
+
+# Dependencies
+find_package(holoscan 2.6 REQUIRED CONFIG
+ PATHS "/opt/nvidia/holoscan" "/workspace/holoscan-sdk/install")
+
+# Global variables
+set(CMAKE_CUDA_ARCHITECTURES "70;80;90")
+
+# Create the executable
+add_executable(${PROJECT_NAME}
+ adv_networking_media_sender.cpp
+)
+
+target_link_libraries(${PROJECT_NAME}
+ PRIVATE
+ holoscan::core
+ holoscan::advanced_network
+ holoscan::ops::video_stream_replayer
+ holoscan::ops::advanced_network_media_tx
+)
+
+# Copy config file
+add_custom_target(adv_networking_media_sender_yaml
+ COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/../adv_networking_media_sender.yaml" ${CMAKE_CURRENT_BINARY_DIR}
+ DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../adv_networking_media_sender.yaml"
+)
+add_dependencies(${PROJECT_NAME} adv_networking_media_sender_yaml)
+
+
+# Installation
+install(TARGETS ${PROJECT_NAME}
+ DESTINATION examples/${PROJECT_NAME}
+ COMPONENT ${PROJECT_NAME}-cpp)
+
+install(
+ FILES
+ ../adv_networking_media_sender.yaml
+ DESTINATION examples/${PROJECT_NAME}
+ COMPONENT ${PROJECT_NAME}-configs
+ PERMISSIONS OWNER_READ OWNER_WRITE
+ GROUP_READ
+ WORLD_READ
+)
+
+install(
+ FILES CMakeLists.txt.install
+ RENAME CMakeLists.txt
+ DESTINATION examples/${PROJECT_NAME}
+ COMPONENT ${PROJECT_NAME}-cppsrc
+ PERMISSIONS OWNER_READ OWNER_WRITE
+ GROUP_READ
+ WORLD_READ
+)
+
+install(
+ FILES
+ adv_networking_media_sender.cpp
+ DESTINATION examples/${PROJECT_NAME}
+ COMPONENT ${PROJECT_NAME}-cppsrc
+ PERMISSIONS OWNER_READ OWNER_WRITE
+ GROUP_READ
+ WORLD_READ
+)
diff --git a/applications/adv_networking_media_sender/cpp/adv_networking_media_sender.cpp b/applications/adv_networking_media_sender/cpp/adv_networking_media_sender.cpp
new file mode 100755
index 0000000000..f3f0508152
--- /dev/null
+++ b/applications/adv_networking_media_sender/cpp/adv_networking_media_sender.cpp
@@ -0,0 +1,87 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include
+#include
+#include
+
+#include "holoscan/holoscan.hpp"
+#include
+#include "advanced_network/common.h"
+#include "adv_network_media_tx.h"
+
+namespace ano = holoscan::advanced_network;
+using holoscan::advanced_network::NetworkConfig;
+using holoscan::advanced_network::Status;
+
+class App : public holoscan::Application {
+ public:
+ void compose() override {
+ using namespace holoscan;
+
+ auto adv_net_config = from_config("advanced_network").as();
+ if (ano::adv_net_init(adv_net_config) != Status::SUCCESS) {
+ HOLOSCAN_LOG_ERROR("Failed to configure the Advanced Network manager");
+ exit(1);
+ }
+ HOLOSCAN_LOG_INFO("Configured the Advanced Network manager");
+
+ const auto [rx_en, tx_en] = ano::get_rx_tx_configs_enabled(config());
+ const auto mgr_type = ano::get_manager_type(config());
+
+ HOLOSCAN_LOG_INFO("Using Advanced Network manager {}",
+ ano::manager_type_to_string(mgr_type));
+
+ if (!tx_en) {
+ HOLOSCAN_LOG_ERROR("Tx is not enabled. Please enable Tx in the config file.");
+ exit(1);
+ }
+
+ auto adv_net_media_tx = make_operator(
+ "advanced_network_media_tx", from_config("advanced_network_media_tx"));
+
+ ArgList args;
+ args.add(Arg("allocator",
+ make_resource("rmm_allocator", from_config("rmm_allocator"))));
+
+ auto replayer =
+ make_operator("replayer", from_config("replayer"), args);
+ add_flow(replayer, adv_net_media_tx, {{"output", "input"}});
+ }
+};
+
+int main(int argc, char** argv) {
+ using namespace holoscan;
+ auto app = make_application();
+
+ // Get the configuration
+ if (argc < 2) {
+ HOLOSCAN_LOG_ERROR("Usage: {} config_file", argv[0]);
+ return -1;
+ }
+
+ std::filesystem::path config_path(argv[1]);
+ if (!config_path.is_absolute()) {
+ config_path = std::filesystem::canonical(argv[0]).parent_path() / config_path;
+ }
+ app->config(config_path);
+ app->scheduler(app->make_scheduler("multithread-scheduler",
+ app->from_config("scheduler")));
+ app->run();
+
+ ano::shutdown();
+ return 0;
+}
diff --git a/applications/adv_networking_media_sender/cpp/metadata.json b/applications/adv_networking_media_sender/cpp/metadata.json
new file mode 100755
index 0000000000..20334fd412
--- /dev/null
+++ b/applications/adv_networking_media_sender/cpp/metadata.json
@@ -0,0 +1,39 @@
+{
+ "application": {
+ "name": "Advanced Networking Media Sender",
+ "authors": [
+ {
+ "name": "Rony Rado",
+ "affiliation": "NVIDIA"
+ }
+ ],
+ "language": "C++",
+ "version": "1.0",
+ "changelog": {
+ "1.0": "Initial Release"
+ },
+ "platforms": ["x86_64", "aarch64"],
+ "tags": ["Networking and Distributed Computing", "UDP", "Ethernet", "IP", "GPUDirect", "Rivermax", "Media", "Media Sender"],
+ "dockerfile": "operators/advanced_network/Dockerfile",
+ "holoscan_sdk": {
+ "minimum_required_version": "2.6.0",
+ "tested_versions": [
+ "2.6.0"
+ ]
+ },
+ "ranking": 3,
+ "requirements": {
+ "operators": [{
+ "name": "advanced_network_media_tx",
+ "version": "1.0"
+ }, {
+ "name": "advanced_network",
+ "version": "1.4"
+ }]
+ },
+ "run": {
+ "command": "/adv_networking_media_sender adv_networking_media_sender.yaml",
+ "workdir": "holohub_bin"
+ }
+ }
+}
diff --git a/applications/adv_networking_media_sender/python/CMakeLists.txt b/applications/adv_networking_media_sender/python/CMakeLists.txt
new file mode 100755
index 0000000000..c2edcd14b9
--- /dev/null
+++ b/applications/adv_networking_media_sender/python/CMakeLists.txt
@@ -0,0 +1,30 @@
+# SPDX-FileCopyrightText: Copyright (c) 2022-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Copy adv_networking_media_sender application file
+add_custom_target(python_adv_networking_media_sender ALL
+ COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/adv_networking_media_sender.py" ${CMAKE_CURRENT_BINARY_DIR}
+ DEPENDS "adv_networking_media_sender.py"
+ BYPRODUCTS "adv_networking_media_sender.py"
+)
+
+# Copy config file
+add_custom_target(python_adv_networking_media_sender_yaml
+ COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/../adv_networking_media_sender.yaml" ${CMAKE_CURRENT_BINARY_DIR}
+ DEPENDS "../adv_networking_media_sender.yaml"
+ BYPRODUCTS "adv_networking_media_sender.yaml"
+)
+
+add_dependencies(python_adv_networking_media_sender python_adv_networking_media_sender_yaml)
diff --git a/applications/adv_networking_media_sender/python/adv_networking_media_sender.py b/applications/adv_networking_media_sender/python/adv_networking_media_sender.py
new file mode 100755
index 0000000000..6e1ed238c6
--- /dev/null
+++ b/applications/adv_networking_media_sender/python/adv_networking_media_sender.py
@@ -0,0 +1,222 @@
+#!/usr/bin/env python3
+
+# SPDX-FileCopyrightText: Copyright (c) 2022-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import sys
+from pathlib import Path
+
+from holoscan.core import Application
+from holoscan.operators import VideoStreamReplayerOp
+from holoscan.schedulers import MultiThreadScheduler
+
+from holohub.advanced_network_common import _advanced_network_common as adv_network_common
+from holohub.advanced_network_media_tx import _advanced_network_media_tx as adv_network_media_tx
+
+logger = logging.getLogger(__name__)
+logging.basicConfig(level=logging.INFO)
+
+
+def check_rx_tx_enabled(app, require_rx=True, require_tx=False):
+ """
+ Check if RX and TX are enabled in the advanced network configuration.
+
+ Args:
+ app: The Holoscan Application instance
+ require_rx: Whether RX must be enabled (default: True)
+ require_tx: Whether TX must be enabled (default: False)
+
+ Returns:
+ tuple: (rx_enabled, tx_enabled)
+
+ Raises:
+ SystemExit: If required functionality is not enabled
+ """
+ try:
+ # Manual parsing of the advanced_network config
+ adv_net_config_dict = app.kwargs("advanced_network")
+
+ rx_enabled = False
+ tx_enabled = False
+
+ # Check if there are interfaces with RX/TX configurations
+ if "cfg" in adv_net_config_dict and "interfaces" in adv_net_config_dict["cfg"]:
+ for interface in adv_net_config_dict["cfg"]["interfaces"]:
+ if "rx" in interface:
+ rx_enabled = True
+ if "tx" in interface:
+ tx_enabled = True
+
+ logger.info(f"RX enabled: {rx_enabled}, TX enabled: {tx_enabled}")
+
+ if require_rx and not rx_enabled:
+ logger.error("RX is not enabled. Please enable RX in the config file.")
+ sys.exit(1)
+
+ if require_tx and not tx_enabled:
+ logger.error("TX is not enabled. Please enable TX in the config file.")
+ sys.exit(1)
+
+ return rx_enabled, tx_enabled
+
+ except Exception as e:
+ logger.warning(f"Could not check RX/TX status from advanced_network config: {e}")
+ # Fallback: check if we have the required operator configs
+ try:
+ if require_rx:
+ app.from_config("advanced_network_media_rx")
+ logger.info("RX is enabled (found advanced_network_media_rx config)")
+ if require_tx:
+ app.from_config("advanced_network_media_tx")
+ logger.info("TX is enabled (found advanced_network_media_tx config)")
+ return require_rx, require_tx
+ except Exception as e2:
+ if require_rx:
+ logger.error("RX is not enabled. Please enable RX in the config file.")
+ logger.error(f"Could not find advanced_network_media_rx configuration: {e2}")
+ sys.exit(1)
+ if require_tx:
+ logger.error("TX is not enabled. Please enable TX in the config file.")
+ logger.error(f"Could not find advanced_network_media_tx configuration: {e2}")
+ sys.exit(1)
+ return False, False
+
+
+class App(Application):
+ def compose(self):
+ # Initialize advanced network
+ try:
+ adv_net_config = self.from_config("advanced_network")
+ if adv_network_common.adv_net_init(adv_net_config) != adv_network_common.Status.SUCCESS:
+ logger.error("Failed to configure the Advanced Network manager")
+ sys.exit(1)
+ logger.info("Configured the Advanced Network manager")
+ except Exception as e:
+ logger.error(f"Failed to get advanced network config or initialize: {e}")
+ sys.exit(1)
+
+ # Get manager type
+ try:
+ mgr_type = adv_network_common.get_manager_type()
+ logger.info(
+ f"Using Advanced Network manager {adv_network_common.manager_type_to_string(mgr_type)}"
+ )
+ except Exception as e:
+ logger.warning(f"Could not get manager type: {e}")
+
+ # Check TX enabled status (require TX for media sender)
+ check_rx_tx_enabled(self, require_rx=False, require_tx=True)
+ logger.info("TX is enabled, proceeding with application setup")
+
+ # Create allocator - use RMMAllocator for better performance (matches C++ version)
+ # the RMMAllocator supported since v2.6 is much faster than the default UnboundedAllocator
+ try:
+ from holoscan.resources import RMMAllocator
+
+ allocator = RMMAllocator(self, name="rmm_allocator", **self.kwargs("rmm_allocator"))
+ logger.info("Using RMMAllocator for better performance")
+ except Exception as e:
+ logger.warning(f"Could not create RMMAllocator: {e}")
+ from holoscan.resources import UnboundedAllocator
+
+ allocator = UnboundedAllocator(self, name="allocator")
+ logger.info("Using UnboundedAllocator (RMMAllocator not available)")
+
+ # Create video stream replayer
+ try:
+ replayer = VideoStreamReplayerOp(
+ fragment=self, name="replayer", allocator=allocator, **self.kwargs("replayer")
+ )
+ except Exception as e:
+ logger.error(f"Failed to create VideoStreamReplayerOp: {e}")
+ sys.exit(1)
+
+ # Create advanced network media TX operator
+ try:
+ adv_net_media_tx = adv_network_media_tx.AdvNetworkMediaTxOp(
+ fragment=self,
+ name="advanced_network_media_tx",
+ **self.kwargs("advanced_network_media_tx"),
+ )
+ except Exception as e:
+ logger.error(f"Failed to create AdvNetworkMediaTxOp: {e}")
+ sys.exit(1)
+
+ # Set up the pipeline: replayer -> TX operator
+ try:
+ self.add_flow(replayer, adv_net_media_tx, {("output", "input")})
+ except Exception as e:
+ logger.error(f"Failed to set up pipeline: {e}")
+ sys.exit(1)
+
+ # Set up scheduler
+ try:
+ scheduler = MultiThreadScheduler(
+ fragment=self, name="multithread-scheduler", **self.kwargs("scheduler")
+ )
+ self.scheduler(scheduler)
+ except Exception as e:
+ logger.error(f"Failed to set up scheduler: {e}")
+ sys.exit(1)
+
+ logger.info("Application composition completed successfully")
+
+
+def main():
+ if len(sys.argv) < 2:
+ logger.error(f"Usage: {sys.argv[0]} config_file")
+ sys.exit(1)
+
+ config_path = Path(sys.argv[1])
+
+ # Convert to absolute path if relative (matching C++ behavior)
+ if not config_path.is_absolute():
+ # Get the directory of the script and make path relative to it
+ script_dir = Path(sys.argv[0]).parent.resolve()
+ config_path = script_dir / config_path
+
+ if not config_path.exists():
+ logger.error(f"Config file not found: {config_path}")
+ sys.exit(1)
+
+ logger.info(f"Using config file: {config_path}")
+
+ try:
+ app = App()
+ app.config(str(config_path))
+
+ logger.info("Starting application...")
+ app.run()
+
+ logger.info("Application finished")
+
+ except Exception as e:
+ logger.error(f"Application failed: {e}")
+ import traceback
+
+ traceback.print_exc()
+ sys.exit(1)
+ finally:
+ # Shutdown advanced network (matching C++ behavior)
+ try:
+ adv_network_common.shutdown()
+ logger.info("Advanced Network shutdown completed")
+ except Exception as e:
+ logger.warning(f"Error during advanced network shutdown: {e}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/applications/adv_networking_media_sender/python/metadata.json b/applications/adv_networking_media_sender/python/metadata.json
new file mode 100755
index 0000000000..83657d9a71
--- /dev/null
+++ b/applications/adv_networking_media_sender/python/metadata.json
@@ -0,0 +1,39 @@
+{
+ "application": {
+ "name": "Advanced Networking Media Sender",
+ "authors": [
+ {
+ "name": "Rony Rado",
+ "affiliation": "NVIDIA"
+ }
+ ],
+ "language": "Python",
+ "version": "1.0",
+ "changelog": {
+ "1.0": "Initial Release"
+ },
+ "platforms": ["x86_64", "aarch64"],
+ "tags": ["Networking and Distributed Computing", "UDP", "Ethernet", "IP", "TCP", "Rivermax"],
+ "dockerfile": "operators/advanced_network/Dockerfile",
+ "holoscan_sdk": {
+ "minimum_required_version": "2.6.0",
+ "tested_versions": [
+ "2.6.0"
+ ]
+ },
+ "ranking": 3,
+ "requirements": {
+ "operators": [{
+ "name": "advanced_network_media_tx",
+ "version": "1.0"
+ }, {
+ "name": "advanced_network",
+ "version": "1.4"
+ }]
+ },
+ "run": {
+ "command": "python3 /adv_networking_media_sender.py ../adv_networking_media_sender.yaml",
+ "workdir": "holohub_bin"
+ }
+ }
+}
diff --git a/operators/CMakeLists.txt b/operators/CMakeLists.txt
index 730ffe5138..49243ce01a 100644
--- a/operators/CMakeLists.txt
+++ b/operators/CMakeLists.txt
@@ -15,6 +15,7 @@
# Add operators (in alphabetical order)
add_holohub_operator(advanced_network)
+add_subdirectory(advanced_network_media)
add_holohub_operator(aja_source)
add_holohub_operator(apriltag_detector)
add_holohub_operator(basic_network)
diff --git a/operators/advanced_network/Dockerfile b/operators/advanced_network/Dockerfile
index 8229525c1d..994c26c5d5 100644
--- a/operators/advanced_network/Dockerfile
+++ b/operators/advanced_network/Dockerfile
@@ -140,8 +140,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
RUN python3 -m pip install --no-cache-dir \
pytest \
pyyaml \
- scapy
-
+ scapy \
+ holoscan==${HOLOSCAN_DEB_REMOTE_VERSION} \
+ && SITE_PACKAGES=$(python3 -c "import site; print(site.getsitepackages()[0])") \
+ && chmod -R a+w ${SITE_PACKAGES}/holoscan-*.dist-info/ \
+ && chmod -R a+rw ${SITE_PACKAGES}/holoscan
# ==============================
# DOCA Target
# This stage is only built when --target doca is specified. It contains any DOCA-specific configurations.
@@ -192,6 +195,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
libglew-dev \
cuda-nvml-dev-12-6 \
cuda-nvtx-12-6 \
+ libvulkan1 \
+ xvfb \
+ x11vnc \
&& rm -rf /var/lib/apt/lists/*
# Copy and extract the Rivermax SDK
diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/adv_network_rivermax_mgr.cpp b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/adv_network_rivermax_mgr.cpp
index f885e163ee..14b542d6ac 100644
--- a/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/adv_network_rivermax_mgr.cpp
+++ b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/adv_network_rivermax_mgr.cpp
@@ -131,6 +131,7 @@ class RivermaxMgr::RivermaxMgrImpl {
Status get_mac_addr(int port, char* mac);
private:
+ void apply_burst_pool_configuration_to_service(uint32_t service_id);
static void flush_packets(int port);
void setup_accurate_send_scheduling_mask();
int setup_pools_and_rings(int max_rx_batch, int max_tx_batch);
@@ -225,6 +226,7 @@ void RivermaxMgr::RivermaxMgrImpl::initialize() {
burst_tx_pool[i].hdr.hdr.port_id = 0;
burst_tx_pool[i].hdr.hdr.q_id = 0;
burst_tx_pool[i].hdr.hdr.num_pkts = MAX_NUM_OF_FRAMES_IN_BURST;
+ burst_tx_pool[i].hdr.hdr.burst_flags = FLAGS_NONE;
burst_tx_pool[i].pkts[0] = new void*[MAX_NUM_OF_FRAMES_IN_BURST];
burst_tx_pool[i].pkt_lens[0] = new uint32_t[MAX_NUM_OF_FRAMES_IN_BURST];
burst_tx_pool[i].pkt_extra_info = new void*[MAX_NUM_OF_FRAMES_IN_BURST];
@@ -299,6 +301,10 @@ bool RivermaxMgr::RivermaxMgrImpl::initialize_rx_service(
}
rx_services_[service_id] = std::move(rx_service);
+
+ // Apply burst pool adaptive dropping configuration
+ apply_burst_pool_configuration_to_service(service_id);
+
return true;
}
@@ -362,7 +368,9 @@ RivermaxMgr::RivermaxMgrImpl::~RivermaxMgrImpl() {}
void RivermaxMgr::RivermaxMgrImpl::run() {
std::size_t num_services = rx_services_.size();
- if (num_services > 0) { HOLOSCAN_LOG_INFO("Starting {} RX Services", num_services); }
+ if (num_services > 0) {
+ HOLOSCAN_LOG_INFO("Starting {} RX Services", num_services);
+ }
for (const auto& entry : rx_services_) {
uint32_t key = entry.first;
@@ -372,7 +380,9 @@ void RivermaxMgr::RivermaxMgrImpl::run() {
}
num_services = tx_services_.size();
- if (num_services > 0) { HOLOSCAN_LOG_INFO("Starting {} TX Services", num_services); }
+ if (num_services > 0) {
+ HOLOSCAN_LOG_INFO("Starting {} TX Services", num_services);
+ }
for (const auto& entry : tx_services_) {
uint32_t key = entry.first;
@@ -407,7 +417,8 @@ uint16_t RivermaxMgr::RivermaxMgrImpl::get_packet_length(BurstParams* burst, int
void* RivermaxMgr::RivermaxMgrImpl::get_packet_extra_info(BurstParams* burst, int idx) {
RivermaxBurst* rivermax_burst = static_cast(burst);
- if (rivermax_burst->is_packet_info_per_packet()) return burst->pkt_extra_info[idx];
+ if (rivermax_burst->is_packet_info_per_packet())
+ return burst->pkt_extra_info[idx];
return nullptr;
}
@@ -518,7 +529,9 @@ Status RivermaxMgr::RivermaxMgrImpl::get_rx_burst(BurstParams** burst, int port,
}
auto out_burst_shared = queue_it->second->dequeue_burst();
- if (out_burst_shared == nullptr) { return Status::NULL_PTR; }
+ if (out_burst_shared == nullptr) {
+ return Status::NULL_PTR;
+ }
*burst = out_burst_shared.get();
return Status::SUCCESS;
}
@@ -546,7 +559,9 @@ Status RivermaxMgr::RivermaxMgrImpl::send_tx_burst(BurstParams* burst) {
}
void RivermaxMgr::RivermaxMgrImpl::shutdown() {
- if (force_quit.load()) { return; }
+ if (force_quit.load()) {
+ return;
+ }
HOLOSCAN_LOG_INFO("Advanced Network Rivermax manager shutting down");
force_quit.store(true);
print_stats();
@@ -563,10 +578,14 @@ void RivermaxMgr::RivermaxMgrImpl::shutdown() {
}
for (auto& rx_service_thread : rx_service_threads_) {
- if (rx_service_thread.joinable()) { rx_service_thread.join(); }
+ if (rx_service_thread.joinable()) {
+ rx_service_thread.join();
+ }
}
for (auto& tx_service_thread : tx_service_threads_) {
- if (tx_service_thread.joinable()) { tx_service_thread.join(); }
+ if (tx_service_thread.joinable()) {
+ tx_service_thread.join();
+ }
}
HOLOSCAN_LOG_INFO("All service threads finished");
rx_services_.clear();
@@ -601,6 +620,33 @@ Status RivermaxMgr::RivermaxMgrImpl::get_mac_addr(int port, char* mac) {
return Status::NOT_SUPPORTED;
}
+void RivermaxMgr::RivermaxMgrImpl::apply_burst_pool_configuration_to_service(uint32_t service_id) {
+ // Extract port_id and queue_id from service_id
+ int port_id = RivermaxBurst::burst_port_id_from_burst_tag(service_id);
+ int queue_id = RivermaxBurst::burst_queue_id_from_burst_tag(service_id);
+
+ // Find the service and apply configuration from parsed settings
+ auto it = rx_services_.find(service_id);
+ if (it != rx_services_.end()) {
+ auto service = it->second;
+ auto rx_service = std::dynamic_pointer_cast(service);
+ if (rx_service) {
+ // Apply the burst pool configuration using the service's method
+ rx_service->apply_burst_pool_configuration();
+
+ HOLOSCAN_LOG_INFO("Applied burst pool configuration to service {} (port={}, queue={})",
+ service_id,
+ port_id,
+ queue_id);
+ } else {
+ HOLOSCAN_LOG_ERROR("Failed to cast service to RivermaxManagerRxService for service {}",
+ service_id);
+ }
+ } else {
+ HOLOSCAN_LOG_ERROR("Failed to find service {}", service_id);
+ }
+}
+
RivermaxMgr::RivermaxMgr() : pImpl(std::make_unique()) {}
RivermaxMgr::~RivermaxMgr() = default;
diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/burst_manager.cpp b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/burst_manager.cpp
index d42495660b..9e349e429b 100644
--- a/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/burst_manager.cpp
+++ b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/burst_manager.cpp
@@ -19,6 +19,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -47,14 +48,18 @@ class NonBlockingQueue : public QueueInterface {
public:
void enqueue(const T& value) override {
- if (stop_) { return; }
+ if (stop_) {
+ return;
+ }
std::lock_guard lock(mutex_);
queue_.push(value);
}
bool try_dequeue(T& value) override {
std::lock_guard lock(mutex_);
- if (queue_.empty() || stop_) { return false; }
+ if (queue_.empty() || stop_) {
+ return false;
+ }
value = queue_.front();
queue_.pop();
return true;
@@ -74,9 +79,7 @@ class NonBlockingQueue : public QueueInterface {
while (!queue_.empty()) { queue_.pop(); }
}
- void stop() override {
- stop_ = true;
- }
+ void stop() override { stop_ = true; }
};
/**
@@ -93,7 +96,9 @@ class BlockingQueue : public QueueInterface {
public:
void enqueue(const T& value) override {
- if (stop_) { return; }
+ if (stop_) {
+ return;
+ }
std::lock_guard lock(mutex_);
queue_.push(value);
cond_.notify_one();
@@ -102,7 +107,9 @@ class BlockingQueue : public QueueInterface {
bool try_dequeue(T& value) override {
std::unique_lock lock(mutex_);
cond_.wait(lock, [this] { return !queue_.empty() || stop_; });
- if (stop_) { return false; }
+ if (stop_) {
+ return false;
+ }
value = queue_.front();
queue_.pop();
return true;
@@ -111,9 +118,11 @@ class BlockingQueue : public QueueInterface {
bool try_dequeue(T& value, std::chrono::milliseconds timeout) override {
std::unique_lock lock(mutex_);
if (!cond_.wait_for(lock, timeout, [this] { return !queue_.empty() || stop_; })) {
- return false;
+ return false;
+ }
+ if (stop_) {
+ return false;
}
- if (stop_) { return false; }
value = queue_.front();
queue_.pop();
return true;
@@ -130,8 +139,8 @@ class BlockingQueue : public QueueInterface {
}
void stop() override {
- stop_ = true;
- cond_.notify_all();
+ stop_ = true;
+ cond_.notify_all();
}
};
@@ -236,7 +245,7 @@ std::shared_ptr AnoBurstsMemoryPool::dequeue_burst() {
std::shared_ptr burst;
if (queue_->try_dequeue(burst,
- std::chrono::milliseconds(RxBurstsManager::GET_BURST_TIMEOUT_MS))) {
+ std::chrono::milliseconds(RxBurstsManager::GET_BURST_TIMEOUT_MS))) {
return burst;
}
return nullptr;
@@ -277,7 +286,7 @@ std::shared_ptr AnoBurstsQueue::dequeue_burst() {
std::shared_ptr burst;
if (queue_->try_dequeue(burst,
- std::chrono::milliseconds(RxBurstsManager::GET_BURST_TIMEOUT_MS))) {
+ std::chrono::milliseconds(RxBurstsManager::GET_BURST_TIMEOUT_MS))) {
return burst;
}
return nullptr;
@@ -394,8 +403,9 @@ RxBurstsManager::RxBurstsManager(bool send_packet_ext_info, int port_id, int que
const uint32_t burst_tag = RivermaxBurst::burst_tag_from_port_and_queue_id(port_id, queue_id);
gpu_direct_ = (gpu_id_ != INVALID_GPU_ID);
+ initial_pool_size_ = DEFAULT_NUM_RX_BURSTS;
rx_bursts_mempool_ =
- std::make_unique(DEFAULT_NUM_RX_BURSTS, *burst_handler_, burst_tag);
+ std::make_unique(initial_pool_size_, *burst_handler_, burst_tag);
if (!rx_bursts_out_queue_) {
rx_bursts_out_queue_ = std::make_shared();
@@ -403,19 +413,185 @@ RxBurstsManager::RxBurstsManager(bool send_packet_ext_info, int port_id, int que
}
if (burst_out_size_ > RivermaxBurst::MAX_PKT_IN_BURST || burst_out_size_ == 0)
- burst_out_size_ = RivermaxBurst::MAX_PKT_IN_BURST;
+ burst_out_size_ = RivermaxBurst::MAX_PKT_IN_BURST;
+
+ // Initialize timing for capacity monitoring
+ last_capacity_warning_time_ = std::chrono::steady_clock::now();
+ last_capacity_critical_time_ = std::chrono::steady_clock::now();
+
+ HOLOSCAN_LOG_INFO(
+ "RxBurstsManager initialized: port={}, queue={}, pool_size={}, adaptive_dropping={}",
+ port_id_,
+ queue_id_,
+ initial_pool_size_,
+ adaptive_dropping_enabled_);
}
RxBurstsManager::~RxBurstsManager() {
- if (using_shared_out_queue_) { return; }
+ if (using_shared_out_queue_) {
+ return;
+ }
std::shared_ptr burst;
// Get all bursts from the queue and return them to the memory pool
while (rx_bursts_out_queue_->available_bursts() > 0) {
burst = rx_bursts_out_queue_->dequeue_burst();
- if (burst == nullptr) break;
+ if (burst == nullptr)
+ break;
rx_bursts_mempool_->enqueue_burst(burst);
}
}
+std::string RxBurstsManager::get_pool_status_string() const {
+ uint32_t utilization = get_pool_utilization_percent();
+ size_t available = rx_bursts_mempool_->available_bursts();
+
+ std::ostringstream oss;
+ oss << "Pool Status: " << utilization << "% available (" << available << "/" << initial_pool_size_
+ << "), ";
+
+ if (utilization < pool_critical_threshold_percent_) {
+ oss << "CRITICAL";
+ } else if (utilization < pool_low_threshold_percent_) {
+ oss << "LOW";
+ } else if (utilization < pool_recovery_threshold_percent_) {
+ oss << "RECOVERING";
+ } else {
+ oss << "HEALTHY";
+ }
+
+ return oss.str();
+}
+
+std::string RxBurstsManager::get_burst_drop_statistics() const {
+ std::ostringstream oss;
+ oss << "Burst Drop Stats: total=" << total_bursts_dropped_.load()
+ << ", low_capacity=" << bursts_dropped_low_capacity_.load()
+ << ", critical_capacity=" << bursts_dropped_critical_capacity_.load()
+ << ", capacity_warnings=" << pool_capacity_warnings_.load()
+ << ", critical_events=" << pool_capacity_critical_events_.load();
+ return oss.str();
+}
+
+bool RxBurstsManager::should_drop_burst_due_to_capacity() {
+ if (!adaptive_dropping_enabled_) {
+ return false;
+ }
+
+ // CORE MONITORING: Check memory pool availability
+ // utilization = percentage of bursts still available in memory pool
+ // Low utilization = pool running out of free bursts = memory pressure
+ uint32_t utilization = get_pool_utilization_percent();
+
+ // Log capacity status periodically
+ log_pool_capacity_status(utilization);
+
+ // Critical capacity - definitely drop with video-aware logic
+ if (utilization < pool_critical_threshold_percent_) {
+ auto now = std::chrono::steady_clock::now();
+ auto time_since_last =
+ std::chrono::duration_cast(now - last_capacity_critical_time_)
+ .count();
+
+ if (time_since_last > 1000) { // Log every second
+ HOLOSCAN_LOG_ERROR(
+ "CRITICAL: Pool capacity at {}% - dropping new bursts only (port={}, queue={})",
+ utilization,
+ port_id_,
+ queue_id_);
+ last_capacity_critical_time_ = now;
+ }
+
+ // In critical mode, use adaptive dropping but be more aggressive
+ bool should_drop = should_drop_burst_adaptive(utilization);
+ if (should_drop) {
+ bursts_dropped_critical_capacity_++;
+ total_bursts_dropped_++;
+ pool_capacity_critical_events_++;
+ }
+ return should_drop;
+ }
+
+ // Low capacity - use adaptive dropping policies
+ if (utilization < pool_low_threshold_percent_) {
+ auto now = std::chrono::steady_clock::now();
+ auto time_since_last =
+ std::chrono::duration_cast(now - last_capacity_warning_time_)
+ .count();
+
+ if (time_since_last > 5000) { // Log every 5 seconds
+ HOLOSCAN_LOG_WARN("LOW: Pool capacity at {}% - adaptive burst dropping (port={}, queue={})",
+ utilization,
+ port_id_,
+ queue_id_);
+ last_capacity_warning_time_ = now;
+ }
+
+ bool should_drop = should_drop_burst_adaptive(utilization);
+ if (should_drop) {
+ bursts_dropped_low_capacity_++;
+ total_bursts_dropped_++;
+ pool_capacity_warnings_++;
+ }
+ return should_drop;
+ }
+
+ return false;
+}
+
+void RxBurstsManager::log_pool_capacity_status(uint32_t current_utilization) const {
+ static thread_local auto last_status_log = std::chrono::steady_clock::now();
+ auto now = std::chrono::steady_clock::now();
+ auto time_since_last =
+ std::chrono::duration_cast(now - last_status_log).count();
+
+ // Log detailed status every 30 seconds when adaptive dropping is enabled
+ if (adaptive_dropping_enabled_ && time_since_last > 30) {
+ HOLOSCAN_LOG_INFO("Pool Monitor: {} | {} | Policy: CRITICAL_THRESHOLD | Dropping mode: {}",
+ get_pool_status_string(),
+ get_burst_drop_statistics(),
+ in_critical_dropping_mode_ ? "ACTIVE" : "INACTIVE");
+ last_status_log = now;
+ }
+}
+
+bool RxBurstsManager::should_drop_burst_adaptive(uint32_t current_utilization) const {
+ switch (burst_drop_policy_) {
+ case BurstDropPolicy::NONE:
+ return false;
+
+ case BurstDropPolicy::CRITICAL_THRESHOLD:
+ default: {
+ // CORE LOGIC: Drop new bursts when critical, stop when recovered
+
+ // Enter critical dropping mode when pool capacity falls below critical threshold
+ if (current_utilization < pool_critical_threshold_percent_) {
+ if (!in_critical_dropping_mode_) {
+ in_critical_dropping_mode_ = true;
+ HOLOSCAN_LOG_WARN(
+ "CRITICAL: Pool capacity {}% - entering burst dropping mode (port={}, queue={})",
+ current_utilization,
+ port_id_,
+ queue_id_);
+ }
+ return true; // Drop ALL new bursts in critical mode
+ }
+
+ // Exit critical dropping mode when pool capacity recovers to target threshold
+ if (in_critical_dropping_mode_ && current_utilization >= pool_recovery_threshold_percent_) {
+ in_critical_dropping_mode_ = false;
+ HOLOSCAN_LOG_INFO(
+ "RECOVERY: Pool capacity {}% - exiting burst dropping mode (port={}, queue={})",
+ current_utilization,
+ port_id_,
+ queue_id_);
+ return false;
+ }
+
+ // Stay in current mode (dropping or not dropping)
+ return in_critical_dropping_mode_;
+ }
+ }
+}
+
}; // namespace holoscan::advanced_network
diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/burst_manager.h b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/burst_manager.h
index 894fb9d40f..3647dffffc 100644
--- a/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/burst_manager.h
+++ b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/burst_manager.h
@@ -20,6 +20,9 @@
#include
#include
+#include
+#include
+#include
#include
@@ -84,7 +87,9 @@ class RivermaxBurst : public BurstParams {
*/
inline uint16_t get_burst_id() const {
auto burst_info = get_burst_info();
- if (burst_info == nullptr) { return 0; }
+ if (burst_info == nullptr) {
+ return 0;
+ }
return burst_info->burst_id;
}
@@ -137,7 +142,9 @@ class RivermaxBurst : public BurstParams {
auto burst_info = get_burst_info();
- if (burst_info == nullptr) { return; }
+ if (burst_info == nullptr) {
+ return;
+ }
burst_info->hds_on = hds_on;
burst_info->header_stride_size = header_stride_size;
@@ -161,9 +168,7 @@ class RivermaxBurst : public BurstParams {
*
* @return The flags of the burst.
*/
- inline BurstFlags get_burst_flags() const {
- return static_cast(hdr.hdr.burst_flags);
- }
+ inline BurstFlags get_burst_flags() const { return static_cast(hdr.hdr.burst_flags); }
/**
* @brief Gets the extended info of a burst.
@@ -311,9 +316,22 @@ class RivermaxBurst::BurstHandler {
*/
class RxBurstsManager {
public:
- static constexpr uint32_t DEFAULT_NUM_RX_BURSTS = 64;
+ static constexpr uint32_t DEFAULT_NUM_RX_BURSTS = 256;
static constexpr uint32_t GET_BURST_TIMEOUT_MS = 1000;
+ // Pool capacity monitoring thresholds (as percentage of total pool size)
+ // Default pool capacity thresholds (percentages) - can be overridden via configuration
+ static constexpr uint32_t DEFAULT_POOL_LOW_CAPACITY_THRESHOLD_PERCENT = 25; // Warning level
+ static constexpr uint32_t DEFAULT_POOL_CRITICAL_CAPACITY_THRESHOLD_PERCENT =
+ 10; // Start dropping
+ static constexpr uint32_t DEFAULT_POOL_RECOVERY_THRESHOLD_PERCENT = 50; // Stop dropping
+
+ // Burst dropping policies (simplified)
+ enum class BurstDropPolicy {
+ NONE = 0, // No dropping
+ CRITICAL_THRESHOLD = 1 // Drop new bursts when critical, stop when recovered (default)
+ };
+
/**
* @brief Constructor for the RxBurstsManager class.
*
@@ -389,7 +407,9 @@ class RxBurstsManager {
auto out_burst = rx_bursts_out_queue_->dequeue_burst().get();
*burst = static_cast(out_burst);
- if (*burst == nullptr) { return Status::NULL_PTR; }
+ if (*burst == nullptr) {
+ return Status::NULL_PTR;
+ }
return Status::SUCCESS;
}
@@ -400,6 +420,73 @@ class RxBurstsManager {
*/
void rx_burst_done(RivermaxBurst* burst);
+ /**
+ * @brief Gets the current pool capacity utilization as a percentage.
+ *
+ * This monitors the MEMORY POOL where we allocate new bursts from.
+ * Lower percentage = fewer available bursts = higher memory pressure.
+ *
+ * @return Pool utilization percentage (0-100).
+ * 100% = all bursts available, 0% = no bursts available (pool exhausted)
+ */
+ inline uint32_t get_pool_utilization_percent() const {
+ if (initial_pool_size_ == 0)
+ return 0;
+ size_t available = rx_bursts_mempool_->available_bursts();
+ return static_cast((available * 100) / initial_pool_size_);
+ }
+
+ /**
+ * @brief Checks if pool capacity is below the specified threshold.
+ *
+ * @param threshold_percent Threshold percentage (0-100).
+ * @return True if pool capacity is below threshold.
+ */
+ inline bool is_pool_capacity_below_threshold(uint32_t threshold_percent) const {
+ return get_pool_utilization_percent() < threshold_percent;
+ }
+
+ /**
+ * @brief Gets pool capacity status for monitoring.
+ *
+ * @return String description of current pool status.
+ */
+ std::string get_pool_status_string() const;
+
+ /**
+ * @brief Enables or disables adaptive burst dropping.
+ *
+ * @param enabled True to enable adaptive dropping.
+ * @param policy Burst dropping policy to use.
+ */
+ inline void set_adaptive_burst_dropping(
+ bool enabled, BurstDropPolicy policy = BurstDropPolicy::CRITICAL_THRESHOLD) {
+ adaptive_dropping_enabled_ = enabled;
+ burst_drop_policy_ = policy;
+ }
+
+ /**
+ * @brief Configure pool capacity thresholds for adaptive dropping.
+ *
+ * @param low_threshold_percent Pool capacity % that triggers low capacity warnings (0-100)
+ * @param critical_threshold_percent Pool capacity % that triggers burst dropping (0-100)
+ * @param recovery_threshold_percent Pool capacity % that stops burst dropping (0-100)
+ */
+ inline void configure_pool_thresholds(uint32_t low_threshold_percent,
+ uint32_t critical_threshold_percent,
+ uint32_t recovery_threshold_percent) {
+ pool_low_threshold_percent_ = low_threshold_percent;
+ pool_critical_threshold_percent_ = critical_threshold_percent;
+ pool_recovery_threshold_percent_ = recovery_threshold_percent;
+ }
+
+ /**
+ * @brief Gets burst dropping statistics.
+ *
+ * @return String with burst dropping statistics.
+ */
+ std::string get_burst_drop_statistics() const;
+
protected:
/**
* @brief Allocates a new burst.
@@ -407,7 +494,30 @@ class RxBurstsManager {
* @return Shared pointer to the allocated burst parameters.
*/
inline std::shared_ptr allocate_burst() {
+ auto start_time = std::chrono::high_resolution_clock::now();
+
auto burst = rx_bursts_mempool_->dequeue_burst();
+
+ auto end_time = std::chrono::high_resolution_clock::now();
+ auto duration = std::chrono::duration_cast(end_time - start_time);
+
+ if (burst != nullptr) {
+ HOLOSCAN_LOG_DEBUG(
+ "allocate_burst: dequeue_burst succeeded in {} μs (port_id: {}, queue_id: {}, burst_id: "
+ "{})",
+ duration.count(),
+ port_id_,
+ queue_id_,
+ burst->get_burst_id());
+ } else {
+ HOLOSCAN_LOG_WARN(
+ "allocate_burst: dequeue_burst FAILED (timeout/no bursts available) in {} μs (port_id: "
+ "{}, queue_id: {})",
+ duration.count(),
+ port_id_,
+ queue_id_);
+ }
+
return burst;
}
@@ -441,6 +551,15 @@ class RxBurstsManager {
return Status::NULL_PTR;
}
+ // Check if we should drop this COMPLETED burst due to critical pool capacity
+ if (should_drop_burst_due_to_capacity()) {
+ // Drop the completed burst by returning it to memory pool instead of enqueuing to output
+ // queue (counter is already incremented inside should_drop_burst_due_to_capacity)
+ rx_bursts_mempool_->enqueue_burst(cur_out_burst_);
+ reset_current_burst();
+ return Status::SUCCESS;
+ }
+
bool res = rx_bursts_out_queue_->enqueue_burst(cur_out_burst_);
reset_current_burst();
if (!res) {
@@ -456,6 +575,28 @@ class RxBurstsManager {
*/
inline void reset_current_burst() { cur_out_burst_ = nullptr; }
+ /**
+ * @brief Checks pool capacity and decides whether to drop bursts.
+ *
+ * @return True if burst should be dropped due to low capacity.
+ */
+ bool should_drop_burst_due_to_capacity();
+
+ /**
+ * @brief Logs pool capacity warnings and statistics.
+ *
+ * @param current_utilization Current pool utilization percentage.
+ */
+ void log_pool_capacity_status(uint32_t current_utilization) const;
+
+ /**
+ * @brief Implements generic adaptive burst dropping logic.
+ *
+ * @param current_utilization Current pool utilization percentage.
+ * @return True if burst should be dropped based on network-level policies.
+ */
+ bool should_drop_burst_adaptive(uint32_t current_utilization) const;
+
protected:
bool send_packet_ext_info_ = false;
int port_id_ = 0;
@@ -472,6 +613,30 @@ class RxBurstsManager {
std::shared_ptr cur_out_burst_ = nullptr;
AnoBurstExtendedInfo burst_info_;
std::unique_ptr burst_handler_;
+
+ // Pool monitoring and adaptive dropping
+ size_t initial_pool_size_ = DEFAULT_NUM_RX_BURSTS;
+ bool adaptive_dropping_enabled_ = false;
+ BurstDropPolicy burst_drop_policy_ = BurstDropPolicy::CRITICAL_THRESHOLD;
+
+ // Configurable thresholds (defaults from constants)
+ uint32_t pool_low_threshold_percent_ = DEFAULT_POOL_LOW_CAPACITY_THRESHOLD_PERCENT;
+ uint32_t pool_critical_threshold_percent_ = DEFAULT_POOL_CRITICAL_CAPACITY_THRESHOLD_PERCENT;
+ uint32_t pool_recovery_threshold_percent_ = DEFAULT_POOL_RECOVERY_THRESHOLD_PERCENT;
+
+ // Critical threshold dropping state
+ mutable bool in_critical_dropping_mode_ = false; // Track if we're actively dropping
+
+ // Statistics for burst dropping
+ mutable std::atomic total_bursts_dropped_{0};
+ mutable std::atomic bursts_dropped_low_capacity_{0};
+ mutable std::atomic bursts_dropped_critical_capacity_{0};
+ mutable std::atomic pool_capacity_warnings_{0};
+ mutable std::atomic pool_capacity_critical_events_{0};
+
+ // Performance monitoring
+ mutable std::chrono::steady_clock::time_point last_capacity_warning_time_;
+ mutable std::chrono::steady_clock::time_point last_capacity_critical_time_;
};
}; // namespace holoscan::advanced_network
diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_config_manager.cpp b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_config_manager.cpp
index 2a7964ff64..4a62ecddae 100644
--- a/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_config_manager.cpp
+++ b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_config_manager.cpp
@@ -89,8 +89,9 @@ bool RivermaxConfigContainer::parse_configuration(const NetworkConfig& cfg) {
set_rivermax_log_level(cfg.log_level_);
if (rivermax_rx_config_found == 0 && rivermax_tx_config_found == 0) {
- HOLOSCAN_LOG_ERROR("Failed to parse Rivermax advanced_network settings. "
- "No valid settings found");
+ HOLOSCAN_LOG_ERROR(
+ "Failed to parse Rivermax advanced_network settings. "
+ "No valid settings found");
return false;
}
@@ -112,12 +113,16 @@ int RivermaxConfigContainer::parse_rx_queues(uint16_t port_id,
auto rx_config_manager = std::dynamic_pointer_cast(
get_config_manager(RivermaxConfigContainer::ConfigType::RX));
- if (!rx_config_manager) { return 0; }
+ if (!rx_config_manager) {
+ return 0;
+ }
rx_config_manager->set_configuration(cfg_);
for (const auto& q : queues) {
- if (!rx_config_manager->append_candidate_for_rx_queue(port_id, q)) { continue; }
+ if (!rx_config_manager->append_candidate_for_rx_queue(port_id, q)) {
+ continue;
+ }
rivermax_rx_config_found++;
}
@@ -208,7 +213,9 @@ bool RxConfigManager::append_ipo_receiver_candidate_for_rx_queue(
return false;
}
- if (config_memory_allocator(rivermax_rx_config, q) == false) { return false; }
+ if (config_memory_allocator(rivermax_rx_config, q) == false) {
+ return false;
+ }
rivermax_rx_config.cpu_cores = q.common_.cpu_core_;
rivermax_rx_config.master_core = cfg_.common_.master_core_;
@@ -239,7 +246,9 @@ bool RxConfigManager::append_rtp_receiver_candidate_for_rx_queue(
return false;
}
- if (config_memory_allocator(rivermax_rx_config, q) == false) { return false; }
+ if (config_memory_allocator(rivermax_rx_config, q) == false) {
+ return false;
+ }
rivermax_rx_config.cpu_cores = q.common_.cpu_core_;
rivermax_rx_config.master_core = cfg_.common_.master_core_;
@@ -270,12 +279,16 @@ int RivermaxConfigContainer::parse_tx_queues(uint16_t port_id,
auto tx_config_manager = std::dynamic_pointer_cast(
get_config_manager(RivermaxConfigContainer::ConfigType::TX));
- if (!tx_config_manager) { return 0; }
+ if (!tx_config_manager) {
+ return 0;
+ }
tx_config_manager->set_configuration(cfg_);
for (const auto& q : queues) {
- if (!tx_config_manager->append_candidate_for_tx_queue(port_id, q)) { continue; }
+ if (!tx_config_manager->append_candidate_for_tx_queue(port_id, q)) {
+ continue;
+ }
rivermax_tx_config_found++;
}
@@ -355,7 +368,9 @@ bool TxConfigManager::append_media_sender_candidate_for_tx_queue(
return false;
}
- if (config_memory_allocator(rivermax_tx_config, q) == false) { return false; }
+ if (config_memory_allocator(rivermax_tx_config, q) == false) {
+ return false;
+ }
rivermax_tx_config.cpu_cores = q.common_.cpu_core_;
rivermax_tx_config.master_core = cfg_.common_.master_core_;
@@ -546,6 +561,72 @@ bool RivermaxConfigParser::parse_common_rx_settings(
rivermax_rx_config.stats_report_interval_ms =
rx_settings["stats_report_interval_ms"].as(0);
+ // Parse burst pool adaptive dropping configuration (optional)
+ const auto& burst_pool_config = rx_settings["burst_pool_adaptive_dropping"];
+ if (burst_pool_config) {
+ rivermax_rx_config.burst_pool_adaptive_dropping_enabled =
+ burst_pool_config["enabled"].as(false);
+ rivermax_rx_config.burst_pool_low_threshold_percent =
+ burst_pool_config["low_threshold_percent"].as(25);
+ rivermax_rx_config.burst_pool_critical_threshold_percent =
+ burst_pool_config["critical_threshold_percent"].as(10);
+ rivermax_rx_config.burst_pool_recovery_threshold_percent =
+ burst_pool_config["recovery_threshold_percent"].as(50);
+
+ // Validate threshold percentages
+ uint32_t critical = rivermax_rx_config.burst_pool_critical_threshold_percent;
+ uint32_t low = rivermax_rx_config.burst_pool_low_threshold_percent;
+ uint32_t recovery = rivermax_rx_config.burst_pool_recovery_threshold_percent;
+
+ // Check valid range (0..100)
+ if (critical > 100 || low > 100 || recovery > 100) {
+ HOLOSCAN_LOG_ERROR(
+ "Invalid burst pool threshold percentages: all values must be in range 0..100 "
+ "(critical={}, low={}, recovery={})",
+ critical, low, recovery);
+ return false;
+ }
+
+ // Check for nonsensical zero values
+ if (critical == 0 || recovery == 0) {
+ HOLOSCAN_LOG_ERROR(
+ "Invalid burst pool threshold percentages: critical and recovery cannot be 0 "
+ "(critical={}, low={}, recovery={})",
+ critical, low, recovery);
+ return false;
+ }
+
+ // Check proper ordering: critical < low < recovery
+ if (critical >= low) {
+ HOLOSCAN_LOG_ERROR(
+ "Invalid burst pool threshold ordering: critical must be < low "
+ "(critical={}, low={})",
+ critical, low);
+ return false;
+ }
+
+ if (low >= recovery) {
+ HOLOSCAN_LOG_ERROR(
+ "Invalid burst pool threshold ordering: low must be < recovery "
+ "(low={}, recovery={})",
+ low, recovery);
+ return false;
+ }
+
+ HOLOSCAN_LOG_INFO(
+ "Parsed burst pool adaptive dropping config: enabled={}, thresholds={}%/{}%/{}%",
+ rivermax_rx_config.burst_pool_adaptive_dropping_enabled,
+ rivermax_rx_config.burst_pool_low_threshold_percent,
+ rivermax_rx_config.burst_pool_critical_threshold_percent,
+ rivermax_rx_config.burst_pool_recovery_threshold_percent);
+ } else {
+ // Use default values if not specified
+ rivermax_rx_config.burst_pool_adaptive_dropping_enabled = false;
+ rivermax_rx_config.burst_pool_low_threshold_percent = 25;
+ rivermax_rx_config.burst_pool_critical_threshold_percent = 10;
+ rivermax_rx_config.burst_pool_recovery_threshold_percent = 50;
+ }
+
return true;
}
@@ -631,8 +712,7 @@ bool RivermaxConfigParser::parse_common_tx_settings(
rivermax_tx_config.print_parameters = tx_settings["verbose"].as(false);
rivermax_tx_config.num_of_threads = tx_settings["num_of_threads"].as(1);
rivermax_tx_config.send_packet_ext_info = tx_settings["send_packet_ext_info"].as(true);
- rivermax_tx_config.num_of_packets_in_chunk =
- tx_settings["num_of_packets_in_chunk"].as(
+ rivermax_tx_config.num_of_packets_in_chunk = tx_settings["num_of_packets_in_chunk"].as(
MediaSenderSettings::DEFAULT_NUM_OF_PACKETS_IN_CHUNK_FHD);
rivermax_tx_config.sleep_between_operations =
tx_settings["sleep_between_operations"].as(true);
@@ -653,9 +733,8 @@ bool RivermaxConfigParser::parse_media_sender_settings(
rivermax_tx_config.use_internal_memory_pool =
tx_settings["use_internal_memory_pool"].as(false);
if (rivermax_tx_config.use_internal_memory_pool) {
- rivermax_tx_config.memory_pool_location =
- GetMemoryKindFromString(tx_settings["memory_pool_location"].template
- as("device"));
+ rivermax_tx_config.memory_pool_location = GetMemoryKindFromString(
+ tx_settings["memory_pool_location"].template as("device"));
if (rivermax_tx_config.memory_pool_location == MemoryKind::INVALID) {
rivermax_tx_config.memory_pool_location = MemoryKind::DEVICE;
HOLOSCAN_LOG_ERROR("Invalid memory pool location, setting to DEVICE");
@@ -760,7 +839,9 @@ bool ConfigManagerUtilities::validate_cores(const std::string& cores) {
void ConfigManagerUtilities::set_allocator_type(AppSettings& app_settings_config,
const std::string& allocator_type) {
auto setAllocatorType = [&](const std::string& allocatorTypeStr, AllocatorTypeUI allocatorType) {
- if (allocator_type == allocatorTypeStr) { app_settings_config.allocator_type = allocatorType; }
+ if (allocator_type == allocatorTypeStr) {
+ app_settings_config.allocator_type = allocatorType;
+ }
};
app_settings_config.allocator_type = AllocatorTypeUI::Auto;
diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_mgr_service.cpp b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_mgr_service.cpp
index ed25652c4d..2678b24dcd 100644
--- a/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_mgr_service.cpp
+++ b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_mgr_service.cpp
@@ -58,8 +58,8 @@ bool RivermaxManagerRxService::initialize() {
rx_packet_processor_ = std::make_shared(rx_burst_manager_);
- auto rivermax_chunk_consumer = std::make_unique(rx_packet_processor_,
- max_chunk_size_);
+ auto rivermax_chunk_consumer =
+ std::make_unique(rx_packet_processor_, max_chunk_size_);
auto status = rx_service_->set_receive_data_consumer(0, std::move(rivermax_chunk_consumer));
if (status != ReturnStatus::success) {
@@ -71,6 +71,24 @@ bool RivermaxManagerRxService::initialize() {
return true;
}
+void RivermaxManagerRxService::apply_burst_pool_configuration() {
+ if (rx_burst_manager_) {
+ // Apply the configuration to the burst manager
+ rx_burst_manager_->set_adaptive_burst_dropping(burst_pool_adaptive_dropping_enabled_);
+ rx_burst_manager_->configure_pool_thresholds(burst_pool_low_threshold_percent_,
+ burst_pool_critical_threshold_percent_,
+ burst_pool_recovery_threshold_percent_);
+
+ HOLOSCAN_LOG_INFO("Applied burst pool configuration: enabled={}, thresholds={}%/{}%/{}%",
+ burst_pool_adaptive_dropping_enabled_,
+ burst_pool_low_threshold_percent_,
+ burst_pool_critical_threshold_percent_,
+ burst_pool_recovery_threshold_percent_);
+ } else {
+ HOLOSCAN_LOG_ERROR("Cannot apply burst pool configuration: burst manager not initialized");
+ }
+}
+
void RivermaxManagerRxService::free_rx_burst(BurstParams* burst) {
if (!rx_burst_manager_) {
HOLOSCAN_LOG_ERROR("RX burst manager not initialized");
@@ -95,7 +113,9 @@ void RivermaxManagerRxService::run() {
}
void RivermaxManagerRxService::shutdown() {
- if (rx_service_) { HOLOSCAN_LOG_INFO("Shutting down Receiver:{}", service_id_); }
+ if (rx_service_) {
+ HOLOSCAN_LOG_INFO("Shutting down Receiver:{}", service_id_);
+ }
initialized_ = false;
}
@@ -106,7 +126,9 @@ Status RivermaxManagerRxService::get_rx_burst(BurstParams** burst) {
}
auto out_burst = rx_bursts_out_queue_->dequeue_burst().get();
*burst = static_cast(out_burst);
- if (*burst == nullptr) { return Status::NOT_READY; }
+ if (*burst == nullptr) {
+ return Status::NOT_READY;
+ }
return Status::SUCCESS;
}
@@ -125,6 +147,15 @@ bool IPOReceiverService::configure_service() {
send_packet_ext_info_ = ipo_receiver_builder_->send_packet_ext_info_;
gpu_id_ = ipo_receiver_builder_->built_settings_.gpu_id;
max_chunk_size_ = ipo_receiver_builder_->built_settings_.max_packets_in_rx_chunk;
+
+ // Copy burst pool configuration
+ burst_pool_adaptive_dropping_enabled_ =
+ ipo_receiver_builder_->burst_pool_adaptive_dropping_enabled_;
+ burst_pool_low_threshold_percent_ = ipo_receiver_builder_->burst_pool_low_threshold_percent_;
+ burst_pool_critical_threshold_percent_ =
+ ipo_receiver_builder_->burst_pool_critical_threshold_percent_;
+ burst_pool_recovery_threshold_percent_ =
+ ipo_receiver_builder_->burst_pool_recovery_threshold_percent_;
return true;
}
@@ -155,7 +186,9 @@ void IPOReceiverService::print_stats(std::stringstream& ss) const {
ss << " dropped: ";
for (uint32_t s_index = 0; s_index < stream_stats[i].path_stats.size(); ++s_index) {
- if (s_index > 0) { ss << ", "; }
+ if (s_index > 0) {
+ ss << ", ";
+ }
ss << stream_stats[i].path_stats[s_index].rx_dropped + stream_stats[i].rx_dropped;
}
ss << " |"
@@ -194,6 +227,15 @@ bool RTPReceiverService::configure_service() {
send_packet_ext_info_ = rtp_receiver_builder_->send_packet_ext_info_;
gpu_id_ = rtp_receiver_builder_->built_settings_.gpu_id;
max_chunk_size_ = rtp_receiver_builder_->max_chunk_size_;
+
+ // Copy burst pool configuration
+ burst_pool_adaptive_dropping_enabled_ =
+ rtp_receiver_builder_->burst_pool_adaptive_dropping_enabled_;
+ burst_pool_low_threshold_percent_ = rtp_receiver_builder_->burst_pool_low_threshold_percent_;
+ burst_pool_critical_threshold_percent_ =
+ rtp_receiver_builder_->burst_pool_critical_threshold_percent_;
+ burst_pool_recovery_threshold_percent_ =
+ rtp_receiver_builder_->burst_pool_recovery_threshold_percent_;
return true;
}
@@ -282,7 +324,9 @@ void RivermaxManagerTxService::run() {
}
void RivermaxManagerTxService::shutdown() {
- if (tx_service_) { HOLOSCAN_LOG_INFO("Shutting down TX Service:{}", service_id_); }
+ if (tx_service_) {
+ HOLOSCAN_LOG_INFO("Shutting down TX Service:{}", service_id_);
+ }
initialized_ = false;
}
@@ -525,7 +569,9 @@ Status MediaSenderService::send_tx_burst(BurstParams* burst) {
}
bool MediaSenderService::is_tx_burst_available(BurstParams* burst) {
- if (!initialized_ || !tx_media_frame_pool_) { return false; }
+ if (!initialized_ || !tx_media_frame_pool_) {
+ return false;
+ }
// Check if we have available frames in the pool and no current processing frame
return (tx_media_frame_pool_->get_available_frames_count() > 0 && !processing_frame_);
}
@@ -536,13 +582,25 @@ void MediaSenderService::free_tx_burst(BurstParams* burst) {
std::lock_guard lock(mutex_);
HOLOSCAN_LOG_TRACE(
"MediaSenderService{}:{}::free_tx_burst(): Processing frame was reset", port_id_, queue_id_);
- if (processing_frame_) { processing_frame_.reset(); }
+ if (processing_frame_) {
+ processing_frame_.reset();
+ }
}
void MediaSenderService::shutdown() {
- if (processing_frame_) { processing_frame_.reset(); }
- if (tx_media_frame_provider_) { tx_media_frame_provider_->stop(); }
- if (tx_media_frame_pool_) { tx_media_frame_pool_->stop(); }
+ {
+ std::lock_guard lock(mutex_);
+ if (processing_frame_) {
+ processing_frame_.reset();
+ }
+ }
+
+ if (tx_media_frame_provider_) {
+ tx_media_frame_provider_->stop();
+ }
+ if (tx_media_frame_pool_) {
+ tx_media_frame_pool_->stop();
+ }
}
MediaSenderZeroCopyService::MediaSenderZeroCopyService(
@@ -576,10 +634,11 @@ Status MediaSenderZeroCopyService::get_tx_packet_burst(BurstParams* burst) {
return Status::INVALID_PARAMETER;
}
if (burst->hdr.hdr.q_id != queue_id_ || burst->hdr.hdr.port_id != port_id_) {
- HOLOSCAN_LOG_ERROR("MediaSenderZeroCopyService{}:{}::get_tx_packet_burst(): Burst queue ID "
- "mismatch",
- port_id_,
- queue_id_);
+ HOLOSCAN_LOG_ERROR(
+ "MediaSenderZeroCopyService{}:{}::get_tx_packet_burst(): Burst queue ID "
+ "mismatch",
+ port_id_,
+ queue_id_);
return Status::INVALID_PARAMETER;
}
@@ -611,7 +670,7 @@ Status MediaSenderZeroCopyService::send_tx_burst(BurstParams* burst) {
return Status::INVALID_PARAMETER;
}
std::shared_ptr out_frame =
- std::static_pointer_cast(burst->custom_pkt_data);
+ std::static_pointer_cast(burst->custom_pkt_data);
burst->custom_pkt_data.reset();
if (!out_frame) {
HOLOSCAN_LOG_ERROR(
@@ -642,24 +701,27 @@ Status MediaSenderZeroCopyService::send_tx_burst(BurstParams* burst) {
}
bool MediaSenderZeroCopyService::is_tx_burst_available(BurstParams* burst) {
- if (!initialized_) { return false; }
+ if (!initialized_) {
+ return false;
+ }
return (!is_frame_in_process_ &&
- tx_media_frame_provider_->get_queue_size() < MEDIA_FRAME_PROVIDER_SIZE);
+ tx_media_frame_provider_->get_queue_size() < MEDIA_FRAME_PROVIDER_SIZE);
}
void MediaSenderZeroCopyService::free_tx_burst(BurstParams* burst) {
// If we have a processing frame but we're told to free the burst,
// we should clear the processing frame flag
std::lock_guard lock(mutex_);
- HOLOSCAN_LOG_TRACE(
- "MediaSenderZeroCopyService{}:{}::free_tx_burst(): Processing frame was reset",
- port_id_,
- queue_id_);
+ HOLOSCAN_LOG_TRACE("MediaSenderZeroCopyService{}:{}::free_tx_burst(): Processing frame was reset",
+ port_id_,
+ queue_id_);
is_frame_in_process_ = false;
}
void MediaSenderZeroCopyService::shutdown() {
- if (tx_media_frame_provider_) { tx_media_frame_provider_->stop(); }
+ if (tx_media_frame_provider_) {
+ tx_media_frame_provider_->stop();
+ }
}
} // namespace holoscan::advanced_network
diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_mgr_service.h b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_mgr_service.h
index 063fef8027..78a52c599f 100644
--- a/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_mgr_service.h
+++ b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_mgr_service.h
@@ -20,6 +20,7 @@
#include
#include
+#include
#include
@@ -141,6 +142,21 @@ class RivermaxManagerRxService : public RivermaxManagerService {
*/
virtual void print_stats(std::stringstream& ss) const {}
+ /**
+ * @brief Gets the burst manager for this service.
+ *
+ * @return Shared pointer to the burst manager.
+ */
+ std::shared_ptr get_burst_manager() const { return rx_burst_manager_; }
+
+ /**
+ * @brief Applies the parsed burst pool configuration to the burst manager.
+ *
+ * This method configures the burst manager with the burst pool adaptive dropping
+ * settings that were parsed from the YAML configuration.
+ */
+ void apply_burst_pool_configuration();
+
bool initialize() override;
void run() override;
void shutdown() override;
@@ -174,6 +190,12 @@ class RivermaxManagerRxService : public RivermaxManagerService {
bool send_packet_ext_info_ = false; ///< Flag for extended packet info
int gpu_id_ = INVALID_GPU_ID; ///< GPU device ID
size_t max_chunk_size_ = 0; ///< Maximum chunk size for received data
+
+ // Burst pool adaptive dropping configuration (private)
+ bool burst_pool_adaptive_dropping_enabled_ = false;
+ uint32_t burst_pool_low_threshold_percent_ = 25;
+ uint32_t burst_pool_critical_threshold_percent_ = 10;
+ uint32_t burst_pool_recovery_threshold_percent_ = 50;
};
/**
@@ -507,7 +529,7 @@ class MediaSenderZeroCopyService : public MediaSenderBaseService {
private:
std::shared_ptr
- tx_media_frame_provider_; ///< Provider for buffered media frames
+ tx_media_frame_provider_; ///< Provider for buffered media frames
bool is_frame_in_process_ = false;
mutable std::mutex mutex_;
diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_queue_configs.cpp b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_queue_configs.cpp
index 700e1f9e9e..92a9e8b053 100644
--- a/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_queue_configs.cpp
+++ b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_queue_configs.cpp
@@ -45,11 +45,17 @@ RivermaxCommonRxQueueConfig::RivermaxCommonRxQueueConfig(const RivermaxCommonRxQ
send_packet_ext_info(other.send_packet_ext_info),
stats_report_interval_ms(other.stats_report_interval_ms),
cpu_cores(other.cpu_cores),
- master_core(other.master_core) {}
+ master_core(other.master_core),
+ burst_pool_adaptive_dropping_enabled(other.burst_pool_adaptive_dropping_enabled),
+ burst_pool_low_threshold_percent(other.burst_pool_low_threshold_percent),
+ burst_pool_critical_threshold_percent(other.burst_pool_critical_threshold_percent),
+ burst_pool_recovery_threshold_percent(other.burst_pool_recovery_threshold_percent) {}
RivermaxCommonRxQueueConfig& RivermaxCommonRxQueueConfig::operator=(
const RivermaxCommonRxQueueConfig& other) {
- if (this == &other) { return *this; }
+ if (this == &other) {
+ return *this;
+ }
BaseQueueConfig::operator=(other);
max_packet_size = other.max_packet_size;
max_chunk_size = other.max_chunk_size;
@@ -68,6 +74,10 @@ RivermaxCommonRxQueueConfig& RivermaxCommonRxQueueConfig::operator=(
stats_report_interval_ms = other.stats_report_interval_ms;
cpu_cores = other.cpu_cores;
master_core = other.master_core;
+ burst_pool_adaptive_dropping_enabled = other.burst_pool_adaptive_dropping_enabled;
+ burst_pool_low_threshold_percent = other.burst_pool_low_threshold_percent;
+ burst_pool_critical_threshold_percent = other.burst_pool_critical_threshold_percent;
+ burst_pool_recovery_threshold_percent = other.burst_pool_recovery_threshold_percent;
return *this;
}
@@ -82,7 +92,9 @@ RivermaxIPOReceiverQueueConfig::RivermaxIPOReceiverQueueConfig(
RivermaxIPOReceiverQueueConfig& RivermaxIPOReceiverQueueConfig::operator=(
const RivermaxIPOReceiverQueueConfig& other) {
- if (this == &other) { return *this; }
+ if (this == &other) {
+ return *this;
+ }
RivermaxCommonRxQueueConfig::operator=(other);
local_ips = other.local_ips;
source_ips = other.source_ips;
@@ -102,7 +114,9 @@ RivermaxRTPReceiverQueueConfig::RivermaxRTPReceiverQueueConfig(
RivermaxRTPReceiverQueueConfig& RivermaxRTPReceiverQueueConfig::operator=(
const RivermaxRTPReceiverQueueConfig& other) {
- if (this == &other) { return *this; }
+ if (this == &other) {
+ return *this;
+ }
RivermaxCommonRxQueueConfig::operator=(other);
local_ip = other.local_ip;
source_ip = other.source_ip;
@@ -134,7 +148,9 @@ RivermaxCommonTxQueueConfig::RivermaxCommonTxQueueConfig(const RivermaxCommonTxQ
RivermaxCommonTxQueueConfig& RivermaxCommonTxQueueConfig::operator=(
const RivermaxCommonTxQueueConfig& other) {
- if (this == &other) { return *this; }
+ if (this == &other) {
+ return *this;
+ }
gpu_direct = other.gpu_direct;
gpu_device_id = other.gpu_device_id;
lock_gpu_clocks = other.lock_gpu_clocks;
@@ -170,7 +186,9 @@ RivermaxMediaSenderQueueConfig::RivermaxMediaSenderQueueConfig(
RivermaxMediaSenderQueueConfig& RivermaxMediaSenderQueueConfig::operator=(
const RivermaxMediaSenderQueueConfig& other) {
- if (this == &other) { return *this; }
+ if (this == &other) {
+ return *this;
+ }
RivermaxCommonTxQueueConfig::operator=(other);
video_format = other.video_format;
bit_depth = other.bit_depth;
@@ -281,9 +299,13 @@ void RivermaxMediaSenderQueueConfig::dump_parameters() const {
ReturnStatus RivermaxCommonRxQueueValidator::validate(
const std::shared_ptr& settings) const {
ReturnStatus rc = ValidatorUtils::validate_core(settings->master_core);
- if (rc != ReturnStatus::success) { return rc; }
+ if (rc != ReturnStatus::success) {
+ return rc;
+ }
bool res = ConfigManagerUtilities::validate_cores(settings->cpu_cores);
- if (!res) { return ReturnStatus::failure; }
+ if (!res) {
+ return ReturnStatus::failure;
+ }
return ReturnStatus::success;
}
@@ -292,7 +314,9 @@ ReturnStatus RivermaxIPOReceiverQueueValidator::validate(
const std::shared_ptr& settings) const {
auto validator = std::make_shared();
ReturnStatus rc = validator->validate(settings);
- if (rc != ReturnStatus::success) { return rc; }
+ if (rc != ReturnStatus::success) {
+ return rc;
+ }
if (settings->source_ips.empty()) {
HOLOSCAN_LOG_ERROR("Source IP addresses are not set for RTP stream");
@@ -317,13 +341,21 @@ ReturnStatus RivermaxIPOReceiverQueueValidator::validate(
}
rc = ValidatorUtils::validate_ip4_address(settings->source_ips);
- if (rc != ReturnStatus::success) { return rc; }
+ if (rc != ReturnStatus::success) {
+ return rc;
+ }
rc = ValidatorUtils::validate_ip4_address(settings->local_ips);
- if (rc != ReturnStatus::success) { return rc; }
+ if (rc != ReturnStatus::success) {
+ return rc;
+ }
rc = ValidatorUtils::validate_ip4_address(settings->destination_ips);
- if (rc != ReturnStatus::success) { return rc; }
+ if (rc != ReturnStatus::success) {
+ return rc;
+ }
rc = ValidatorUtils::validate_ip4_port(settings->destination_ports);
- if (rc != ReturnStatus::success) { return rc; }
+ if (rc != ReturnStatus::success) {
+ return rc;
+ }
return ReturnStatus::success;
}
@@ -331,35 +363,55 @@ ReturnStatus RivermaxRTPReceiverQueueValidator::validate(
const std::shared_ptr& settings) const {
auto validator = std::make_shared();
ReturnStatus rc = validator->validate(settings);
- if (rc != ReturnStatus::success) { return rc; }
+ if (rc != ReturnStatus::success) {
+ return rc;
+ }
if (settings->split_boundary == 0 && settings->gpu_direct) {
HOLOSCAN_LOG_ERROR("GPU Direct is supported only in header-data split mode");
return ReturnStatus::failure;
}
rc = ValidatorUtils::validate_ip4_address(settings->source_ip);
- if (rc != ReturnStatus::success) { return rc; }
+ if (rc != ReturnStatus::success) {
+ return rc;
+ }
rc = ValidatorUtils::validate_ip4_address(settings->local_ip);
- if (rc != ReturnStatus::success) { return rc; }
+ if (rc != ReturnStatus::success) {
+ return rc;
+ }
rc = ValidatorUtils::validate_ip4_address(settings->destination_ip);
- if (rc != ReturnStatus::success) { return rc; }
+ if (rc != ReturnStatus::success) {
+ return rc;
+ }
rc = ValidatorUtils::validate_ip4_port(settings->destination_port);
- if (rc != ReturnStatus::success) { return rc; }
+ if (rc != ReturnStatus::success) {
+ return rc;
+ }
return ReturnStatus::success;
}
ReturnStatus RivermaxCommonTxQueueValidator::validate(
const std::shared_ptr& settings) const {
ReturnStatus rc = ValidatorUtils::validate_core(settings->master_core);
- if (rc != ReturnStatus::success) { return rc; }
+ if (rc != ReturnStatus::success) {
+ return rc;
+ }
bool res = ConfigManagerUtilities::validate_cores(settings->cpu_cores);
- if (!res) { return ReturnStatus::failure; }
+ if (!res) {
+ return ReturnStatus::failure;
+ }
rc = ValidatorUtils::validate_ip4_address(settings->local_ip);
- if (rc != ReturnStatus::success) { return rc; }
+ if (rc != ReturnStatus::success) {
+ return rc;
+ }
rc = ValidatorUtils::validate_ip4_address(settings->destination_ip);
- if (rc != ReturnStatus::success) { return rc; }
+ if (rc != ReturnStatus::success) {
+ return rc;
+ }
rc = ValidatorUtils::validate_ip4_port(settings->destination_port);
- if (rc != ReturnStatus::success) { return rc; }
+ if (rc != ReturnStatus::success) {
+ return rc;
+ }
if (!settings->memory_allocation && settings->memory_registration) {
HOLOSCAN_LOG_ERROR(
"Register memory option is supported only with application memory allocation");
@@ -377,7 +429,9 @@ ReturnStatus RivermaxMediaSenderQueueValidator::validate(
const std::shared_ptr& settings) const {
auto validator = std::make_shared();
ReturnStatus rc = validator->validate(settings);
- if (rc != ReturnStatus::success) { return rc; }
+ if (rc != ReturnStatus::success) {
+ return rc;
+ }
return ReturnStatus::success;
}
@@ -415,8 +469,8 @@ ReturnStatus RivermaxQueueToIPOReceiverSettingsBuilder::convert_settings(
target_settings->sleep_between_operations_us = source_settings->sleep_between_operations_us;
target_settings->packet_payload_size = source_settings->max_packet_size;
target_settings->packet_app_header_size = source_settings->split_boundary;
- (target_settings->packet_app_header_size == 0) ? target_settings->header_data_split = false :
- target_settings->header_data_split = true;
+ (target_settings->packet_app_header_size == 0) ? target_settings->header_data_split = false
+ : target_settings->header_data_split = true;
target_settings->num_of_packets_in_chunk =
std::pow(2, std::ceil(std::log2(source_settings->packets_buffers_size)));
@@ -432,6 +486,13 @@ ReturnStatus RivermaxQueueToIPOReceiverSettingsBuilder::convert_settings(
target_settings->max_packets_in_rx_chunk = source_settings->max_chunk_size;
send_packet_ext_info_ = source_settings->send_packet_ext_info;
+
+ // Copy burst pool configuration
+ burst_pool_adaptive_dropping_enabled_ = source_settings->burst_pool_adaptive_dropping_enabled;
+ burst_pool_low_threshold_percent_ = source_settings->burst_pool_low_threshold_percent;
+ burst_pool_critical_threshold_percent_ = source_settings->burst_pool_critical_threshold_percent;
+ burst_pool_recovery_threshold_percent_ = source_settings->burst_pool_recovery_threshold_percent;
+
settings_built_ = true;
built_settings_ = *target_settings;
@@ -471,8 +532,8 @@ ReturnStatus RivermaxQueueToRTPReceiverSettingsBuilder::convert_settings(
target_settings->sleep_between_operations_us = source_settings->sleep_between_operations_us;
target_settings->packet_payload_size = source_settings->max_packet_size;
target_settings->packet_app_header_size = source_settings->split_boundary;
- (target_settings->packet_app_header_size == 0) ? target_settings->header_data_split = false :
- target_settings->header_data_split = true;
+ (target_settings->packet_app_header_size == 0) ? target_settings->header_data_split = false
+ : target_settings->header_data_split = true;
target_settings->num_of_packets_in_chunk =
std::pow(2, std::ceil(std::log2(source_settings->packets_buffers_size)));
@@ -483,6 +544,12 @@ ReturnStatus RivermaxQueueToRTPReceiverSettingsBuilder::convert_settings(
max_chunk_size_ = source_settings->max_chunk_size;
send_packet_ext_info_ = source_settings->send_packet_ext_info;
+ // Copy burst pool configuration
+ burst_pool_adaptive_dropping_enabled_ = source_settings->burst_pool_adaptive_dropping_enabled;
+ burst_pool_low_threshold_percent_ = source_settings->burst_pool_low_threshold_percent;
+ burst_pool_critical_threshold_percent_ = source_settings->burst_pool_critical_threshold_percent;
+ burst_pool_recovery_threshold_percent_ = source_settings->burst_pool_recovery_threshold_percent;
+
settings_built_ = true;
built_settings_ = *target_settings;
@@ -520,8 +587,8 @@ ReturnStatus RivermaxQueueToMediaSenderSettingsBuilder::convert_settings(
target_settings->print_parameters = source_settings->print_parameters;
target_settings->sleep_between_operations = source_settings->sleep_between_operations;
target_settings->packet_app_header_size = source_settings->split_boundary;
- (target_settings->packet_app_header_size == 0) ? target_settings->header_data_split = false :
- target_settings->header_data_split = true;
+ (target_settings->packet_app_header_size == 0) ? target_settings->header_data_split = false
+ : target_settings->header_data_split = true;
target_settings->stats_report_interval_ms = source_settings->stats_report_interval_ms;
target_settings->register_memory = source_settings->memory_registration;
diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_queue_configs.h b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_queue_configs.h
index 3164cad2be..1b1eecef8b 100644
--- a/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_queue_configs.h
+++ b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_queue_configs.h
@@ -106,6 +106,12 @@ struct RivermaxCommonRxQueueConfig : public BaseQueueConfig {
uint32_t stats_report_interval_ms;
std::string cpu_cores;
int master_core;
+
+ // Burst pool adaptive dropping configuration
+ bool burst_pool_adaptive_dropping_enabled = false;
+ uint32_t burst_pool_low_threshold_percent = 25;
+ uint32_t burst_pool_critical_threshold_percent = 10;
+ uint32_t burst_pool_recovery_threshold_percent = 50;
};
/**
@@ -264,6 +270,13 @@ class RivermaxQueueToIPOReceiverSettingsBuilder
public:
static constexpr int USECS_IN_SECOND = 1000000;
bool send_packet_ext_info_ = false;
+
+ // Burst pool adaptive dropping configuration
+ bool burst_pool_adaptive_dropping_enabled_ = false;
+ uint32_t burst_pool_low_threshold_percent_ = 25;
+ uint32_t burst_pool_critical_threshold_percent_ = 10;
+ uint32_t burst_pool_recovery_threshold_percent_ = 50;
+
IPOReceiverSettings built_settings_;
bool settings_built_ = false;
};
@@ -286,6 +299,13 @@ class RivermaxQueueToRTPReceiverSettingsBuilder
static constexpr int USECS_IN_SECOND = 1000000;
bool send_packet_ext_info_ = false;
size_t max_chunk_size_ = 0;
+
+ // Burst pool adaptive dropping configuration
+ bool burst_pool_adaptive_dropping_enabled_ = false;
+ uint32_t burst_pool_low_threshold_percent_ = 25;
+ uint32_t burst_pool_critical_threshold_percent_ = 10;
+ uint32_t burst_pool_recovery_threshold_percent_ = 50;
+
RTPReceiverSettings built_settings_;
bool settings_built_ = false;
};
diff --git a/operators/advanced_network_media/CMakeLists.txt b/operators/advanced_network_media/CMakeLists.txt
new file mode 100644
index 0000000000..ecca87af59
--- /dev/null
+++ b/operators/advanced_network_media/CMakeLists.txt
@@ -0,0 +1,33 @@
+# SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+cmake_minimum_required(VERSION 3.20)
+
+# Find holoscan package (required for common library and operators)
+find_package(holoscan 2.6 QUIET CONFIG
+ PATHS "/opt/nvidia/holoscan" "/workspace/holoscan-sdk/install")
+
+# Only build if holoscan is found
+if(holoscan_FOUND)
+ # Build common code first
+ add_subdirectory(common)
+
+ # Build operator-specific code
+ add_holohub_operator(advanced_network_media_rx)
+ add_holohub_operator(advanced_network_media_tx)
+else()
+ message(STATUS "Holoscan SDK not found - skipping advanced_network_media operators")
+endif()
+
diff --git a/operators/advanced_network_media/README.md b/operators/advanced_network_media/README.md
new file mode 100644
index 0000000000..8090e42d23
--- /dev/null
+++ b/operators/advanced_network_media/README.md
@@ -0,0 +1,851 @@
+# Advanced Network Media Operators
+
+This directory contains operators for high-performance media streaming over advanced network infrastructure. The operators provide efficient transmission and reception of media frames (such as video) using NVIDIA's Rivermax SDK and other high-performance networking technologies.
+
+> [!NOTE]
+> These operators build upon the [Advanced Network library](../advanced_network/README.md) to provide specialized functionality for media streaming applications. They are designed for professional broadcast and media streaming use cases that require strict timing and high throughput.
+
+## Operators
+
+The Advanced Network Media library provides two main operators:
+
+### `holoscan::ops::AdvNetworkMediaRxOp`
+
+Operator for receiving media frames over advanced network infrastructure. This operator receives video frames over Rivermax-enabled network infrastructure and outputs them as GXF VideoBuffer or Tensor entities.
+
+**Inputs**
+- None (receives data directly from network interface via Advanced Network Manager library)
+
+**Outputs**
+- **`output`**: Video frames as GXF entities (VideoBuffer or Tensor)
+ - type: `gxf::Entity`
+
+**Parameters**
+- **`interface_name`**: Name of the network interface to use for receiving
+ - type: `std::string`
+- **`queue_id`**: Queue ID for the network interface (default: 0)
+ - type: `uint16_t`
+- **`frame_width`**: Width of incoming video frames in pixels
+ - type: `uint32_t`
+- **`frame_height`**: Height of incoming video frames in pixels
+ - type: `uint32_t`
+- **`bit_depth`**: Bit depth of the video format
+ - type: `uint32_t`
+- **`video_format`**: Video format specification (e.g., "RGB888", "YUV422")
+ - type: `std::string`
+- **`hds`**: Indicates if header-data split mode is enabled in the input data
+ - type: `bool`
+- **`output_format`**: Output format for the received frames ("video_buffer" for VideoBuffer, "tensor" for Tensor)
+ - type: `std::string`
+- **`memory_location`**: Memory location for frame buffers ("device", "host", etc.)
+ - type: `std::string`
+
+### `holoscan::ops::AdvNetworkMediaTxOp`
+
+Operator for transmitting media frames over advanced network infrastructure. This operator processes video frames from GXF entities (either VideoBuffer or Tensor) and transmits them over Rivermax-enabled network infrastructure.
+
+**Inputs**
+- **`input`**: Video frames as GXF entities (VideoBuffer or Tensor)
+ - type: `gxf::Entity`
+
+**Outputs**
+- None (transmits data directly to network interface)
+
+**Parameters**
+- **`interface_name`**: Name of the network interface to use for transmission
+ - type: `std::string`
+- **`queue_id`**: Queue ID for the network interface (default: 0)
+ - type: `uint16_t`
+- **`video_format`**: Video format specification (e.g., "RGB888", "YUV422")
+ - type: `std::string`
+- **`bit_depth`**: Bit depth of the video format
+ - type: `uint32_t`
+- **`frame_width`**: Width of video frames to transmit in pixels
+ - type: `uint32_t`
+- **`frame_height`**: Height of video frames to transmit in pixels
+ - type: `uint32_t`
+
+## Requirements
+
+- All requirements from the [Advanced Network library](../advanced_network/README.md)
+- NVIDIA Rivermax SDK
+- Compatible video formats and frame rates
+- Proper network configuration for media streaming
+
+## Features
+
+- **High-performance media streaming**: Optimized for professional broadcast applications
+- **SMPTE 2110 compliance**: Supports industry-standard media over IP protocols
+- **Low latency**: Direct hardware access minimizes processing delays
+- **GPU acceleration**: Supports GPUDirect for zero-copy operations
+- **Flexible formats**: Support for various video formats and bit depths
+- **Header-data split**: Optimized memory handling for improved performance
+
+## RX Data Flow Architecture
+
+When using Rivermax for media reception, the data flows through multiple optimized layers from network interface to the application. This section traces the complete RX path:
+
+### Complete RX Data Flow
+
+```mermaid
+graph TD
+ %% Network Layer
+ A["Network Interface
(ConnectX NIC)"] --> B["Rivermax Hardware
Acceleration"]
+ B --> C{{"RDK Service Selection"}}
+ C -->|ipo_receiver| D["IPO Receiver Service
(rmax_ipo_receiver)"]
+ C -->|rtp_receiver| E["RTP Receiver Service
(rmax_rtp_receiver)"]
+
+ %% Hardware to Memory
+ D --> F["Direct Memory Access
(DMA)"]
+ E --> F
+ F --> G["Pre-allocated Memory
Regions"]
+
+ %% Advanced Network Manager Layer
+ G --> H["RivermaxMgr
(Advanced Network Manager)"]
+ H --> I["Burst Assembly
(burst_manager)"]
+ I --> J["RivermaxBurst Container
+ AnoBurstExtendedInfo"]
+
+ %% Memory Layout Decision
+ J --> K{{"Header-Data Split
(HDS) Config"}}
+ K -->|HDS Enabled| L["Headers: CPU Memory
burst->pkts[0]
Payloads: GPU Memory
burst->pkts[1]"]
+ K -->|HDS Disabled| M["Headers + Payloads
CPU Memory
burst->pkts[0] + offset"]
+
+ %% Queue Management
+ L --> N["AnoBurstsQueue
(Pointer Distribution)"]
+ M --> N
+
+ %% Media RX Operator
+ N --> O["AdvNetworkMediaRxOp"]
+ O --> P["Burst Processor
(network_burst_processor)"]
+ P --> Q["Frame Assembly
(MediaFrameAssembler)"]
+
+ %% Strategy Selection
+ Q --> R{{"Memory Layout
Analysis"}}
+ R -->|Contiguous| S["ContiguousStrategy
cudaMemcpy()"]
+ R -->|Strided| T["StridedStrategy
cudaMemcpy2D()"]
+
+ %% Frame Assembly
+ S --> U["Frame Assembly
(Single Copy)"]
+ T --> U
+ U --> V["VideoBuffer/Tensor
Entity Creation"]
+
+ %% Output
+ V --> W["Media Player Application"]
+ W --> X["HolovizOp
(Real-time Display)"]
+ W --> Y["FramesWriter
(File Output)"]
+
+ %% Styling
+ classDef networkLayer fill:#e1f5fe
+ classDef managerLayer fill:#f3e5f5
+ classDef operatorLayer fill:#e8f5e8
+ classDef applicationLayer fill:#fff3e0
+ classDef dataStructure fill:#ffebee
+
+ class A,B,C,D,E,F,G networkLayer
+ class H,I,J,K,L,M,N managerLayer
+ class O,P,Q,R,S,T,U,V operatorLayer
+ class W,X,Y applicationLayer
+ class J,L,M dataStructure
+```
+
+### 1. Network Layer → Hardware Acceleration
+```
+Network Interface (ConnectX NIC) → Rivermax Hardware Acceleration
+├── IPO (Inline Packet Ordering) Receiver Service [OR]
+├── RTP Receiver Service [OR]
+└── Direct Memory Access (DMA) to pre-allocated buffers
+```
+
+**Key Components:**
+- **ConnectX NIC**: NVIDIA ConnectX-6 or later with hardware streaming acceleration
+- **Rivermax SDK (RDK) Services** (configured as one of the following):
+ - `rmax_ipo_receiver`: RX service for receiving RTP data using Rivermax Inline Packet Ordering (IPO) feature
+ - `rmax_rtp_receiver`: RX service for receiving RTP data using Rivermax Striding protocol
+- **Hardware Features**: RDMA, GPUDirect, hardware timestamping for minimal latency
+
+> **Note**: The choice between IPO and RTP receiver services is determined by the `settings_type` configuration parameter (`"ipo_receiver"` or `"rtp_receiver"`). These services are provided by the Rivermax Development Kit (RDK).
+
+### 2. Advanced Network Manager Layer
+```
+Rivermax Services → Advanced Network Manager (RivermaxMgr)
+├── Configuration Management (rivermax_config_manager)
+├── Service Management (rivermax_mgr_service)
+├── Burst Management (burst_manager)
+└── Memory Management (CPU/GPU memory regions)
+```
+
+**Key Responsibilities:**
+- **Packet Reception**: Rivermax services receive packets from NIC hardware directly into pre-allocated memory regions
+- **Burst Assembly**: Packet **pointers** are aggregated into `RivermaxBurst` containers for efficient processing (no data copying)
+- **Memory Coordination**: Manages memory regions (host/device) and buffer allocation
+- **Metadata Extraction**: Extract timing, flow, and RTP sequence information from packet headers
+- **Burst Metadata**: Includes `AnoBurstExtendedInfo` with configuration details (HDS status, stride sizes, memory locations)
+- **Queue Management**: Thread-safe queues (`AnoBurstsQueue`) for burst pointer distribution
+
+> **Zero-Copy Architecture**: Bursts contain only pointers to packets in memory, not the packet data itself. No memcpy operations are performed at the burst level.
+
+### 3. Advanced Network Media RX Operator
+```
+Advanced Network Manager → AdvNetworkMediaRxOp
+├── Burst Processing (network_burst_processor)
+├── Frame Assembly (media_frame_assembler)
+│ ├── Frame Assembly Controller (state management)
+│ ├── Memory Copy Strategy Detection (ContiguousStrategy/StridedStrategy)
+│ ├── Memory Copy Optimization (cudaMemcpy/cudaMemcpy2D)
+│ └── RTP Sequence Validation
+└── Frame Buffer Management (frame_buffer)
+```
+
+**Conversion Process:**
+- **Burst Processing**: Receives burst containers with **packet pointers** from Advanced Network Manager (no data copying)
+- **HDS-Aware Packet Access**: Extracts packet data based on Header-Data Split configuration:
+ - **HDS Enabled**: RTP headers from `CPU_PKTS` array (CPU memory), payloads from `GPU_PKTS` array (GPU memory)
+ - **HDS Disabled**: Both headers and payloads from `CPU_PKTS` array with RTP header offset
+- **Dynamic Strategy Detection**: Analyzes packet memory layout via pointers to determine optimal copy strategy:
+ - `ContiguousStrategy`: For exactly contiguous packets in memory (single `cudaMemcpy` operation)
+ - `StridedStrategy`: For packets with memory gaps/strides (optimized `cudaMemcpy2D` with stride parameters)
+ - **Adaptive Behavior**: Strategy can switch mid-stream if memory layout changes (buffer wraparound, stride breaks)
+- **Frame Assembly**: Converts RTP packet data (accessed via pointers) to complete video frames
+- **Format Handling**: Supports RGB888, YUV420, NV12 with proper stride calculation
+- **Memory Optimization**: Only one copy operation from packet memory to frame buffer (when needed)
+
+> **Key Point**: The burst processing layer works entirely with packet pointers. The only data copying occurs during frame assembly, directly from packet memory locations to final frame buffers.
+
+### 4. Media Player Application
+```
+AdvNetworkMediaRxOp → Media Player Application
+├── HolovizOp (Real-time Display)
+├── FramesWriter (File Output)
+└── Format Conversion (if needed)
+```
+
+**Output Options:**
+- **Real-time Visualization**: Direct display using HolovizOp with minimal latency
+- **File Output**: Save frames to disk for analysis or post-processing
+- **Format Flexibility**: Output as GXF VideoBuffer or Tensor entities
+
+### Memory Flow Optimization
+
+The RX path is optimized for minimal memory copies through pointer-based architecture:
+
+```
+Network → NIC Hardware → Pre-allocated Memory Regions → Packet Pointers → Frame Assembly → Application
+ ↓ ↓ ↓ ↓ ↓ ↓
+ConnectX DMA Transfer CPU/GPU Buffers Burst Containers Single Copy Display/File
+ (pointers only) (if needed)
+```
+
+### Memory Architecture and HDS Configuration
+
+```mermaid
+graph TB
+ subgraph "Network Hardware"
+ A["ConnectX NIC
Hardware"]
+ B["DMA Engine"]
+ A --> B
+ end
+
+ subgraph "Memory Regions"
+ C["Pre-allocated
Memory Regions"]
+ D["CPU Memory
(Host)"]
+ E["GPU Memory
(Device)"]
+ C --> D
+ C --> E
+ end
+
+ B --> C
+
+ subgraph "HDS Enabled Configuration"
+ F["RTP Headers
CPU Memory"]
+ G["Payload Data
GPU Memory"]
+ H["burst->pkts[0]
(Header Pointers)"]
+ I["burst->pkts[1]
(Payload Pointers)"]
+
+ D --> F
+ E --> G
+ F --> H
+ G --> I
+ end
+
+ subgraph "HDS Disabled Configuration"
+ J["Headers + Payloads
Together"]
+ K["CPU Memory Only"]
+ L["burst->pkts[0]
(Combined Pointers)"]
+ M["RTP Header Offset
Calculation"]
+
+ D --> J
+ J --> K
+ K --> L
+ L --> M
+ end
+
+ subgraph "Burst Processing"
+ N["AnoBurstExtendedInfo
Metadata"]
+ O["header_stride_size
payload_stride_size
hds_on
payload_on_cpu"]
+
+ N --> O
+ end
+
+ H --> N
+ I --> N
+ L --> N
+
+ subgraph "Copy Strategy Selection"
+ P{{"Memory Layout
Analysis"}}
+ Q["Contiguous
Strategy"]
+ R["Strided
Strategy"]
+ S["cudaMemcpy
(Single Block)"]
+ T["cudaMemcpy2D
(Strided Copy)"]
+
+ N --> P
+ P -->|Contiguous| Q
+ P -->|Gaps/Strides| R
+ Q --> S
+ R --> T
+ end
+
+ subgraph "Frame Assembly"
+ U["Frame Buffer
(Target)"]
+ V["Single Copy Operation
(Packet → Frame)"]
+ W["VideoBuffer/Tensor
Entity"]
+
+ S --> V
+ T --> V
+ V --> U
+ U --> W
+ end
+
+ %% Styling
+ classDef hardwareLayer fill:#e1f5fe
+ classDef memoryLayer fill:#f3e5f5
+ classDef hdsEnabled fill:#e8f5e8
+ classDef hdsDisabled fill:#fff3e0
+ classDef metadataLayer fill:#ffebee
+ classDef processingLayer fill:#e3f2fd
+ classDef outputLayer fill:#f1f8e9
+
+ class A,B hardwareLayer
+ class C,D,E memoryLayer
+ class F,G,H,I hdsEnabled
+ class J,K,L,M hdsDisabled
+ class N,O metadataLayer
+ class P,Q,R,S,T processingLayer
+ class U,V,W outputLayer
+```
+
+**Memory Architecture:**
+- **Direct DMA**: Packets arrive directly into pre-allocated memory regions via hardware DMA
+- **Pointer Management**: Bursts contain only pointers to packet locations, no intermediate data copying
+- **Single Copy Strategy**: Only one memory copy operation (from packet memory to frame buffer) when format conversion or memory location change is needed
+- **Header-Data Split (HDS) Configuration**:
+ - **HDS Enabled**: Headers stored in CPU memory (`burst->pkts[0]`), payloads in GPU memory (`burst->pkts[1]`)
+ - **HDS Disabled**: Headers and payloads together in CPU memory (`burst->pkts[0]`) with RTP header offset
+- **Memory Regions**: Configured via `AnoBurstExtendedInfo` metadata (header/payload locations, stride sizes)
+- **True Zero-Copy Paths**: When packet memory and frame buffer are in same location and format, no copying required
+
+### Performance Characteristics
+
+- **Latency**: Sub-millisecond packet processing through hardware acceleration and pointer-based architecture
+- **Throughput**: Multi-gigabit streaming with batched packet processing (no burst-level copying)
+- **Efficiency**: Minimal CPU usage through hardware offload, GPU acceleration, and zero-copy pointer management
+- **Memory Efficiency**: Maximum one copy operation from packet memory to frame buffer (often zero copies)
+- **Scalability**: Multiple queues and CPU core isolation for parallel processing
+
+### Configuration Integration
+
+The RX path is configured through YAML files that specify:
+- **Network Settings**: Interface addresses, IP addresses, ports, multicast groups
+- **Memory Regions**: Buffer sizes, memory types (host/device), allocation strategies
+- **Video Parameters**: Format, resolution, bit depth, frame rate
+- **Rivermax Settings**: IPO/RTP receiver configuration, timing parameters, statistics
+
+
+This architecture provides professional-grade media streaming with hardware-accelerated packet processing, pointer-based zero-copy optimization, and flexible output options suitable for broadcast and media production workflows. The key advantage is that packet data is never unnecessarily copied - bursts manage only pointers, and actual data movement occurs only once during frame assembly when needed.
+
+## Data Structures and Relationships
+
+Understanding the key data structures and their relationships is crucial for working with the Advanced Network Media operators:
+
+### Core Data Structures
+
+```mermaid
+classDiagram
+ %% RX Data Structures
+ class BurstParams {
+ +BurstHeader hdr
+ +std::array~void**~ pkts
+ +std::array~uint32_t*~ pkt_lens
+ +void** pkt_extra_info
+ +std::shared_ptr~void~ custom_pkt_data
+ +cudaEvent_t event
+ }
+
+ class RivermaxBurst {
+ +get_burst_info() AnoBurstExtendedInfo*
+ +get_port_id() uint16_t
+ +get_queue_id() uint16_t
+ +get_burst_id() uint16_t
+ +reset_burst_packets() void
+ }
+
+ class AnoBurstExtendedInfo {
+ +uint32_t tag
+ +uint16_t burst_id
+ +bool hds_on
+ +uint16_t header_stride_size
+ +uint16_t payload_stride_size
+ +bool header_on_cpu
+ +bool payload_on_cpu
+ +uint16_t header_seg_idx
+ +uint16_t payload_seg_idx
+ }
+
+ class MediaFrameAssembler {
+ -std::shared_ptr~IFrameProvider~ frame_provider_
+ -std::shared_ptr~FrameBufferBase~ current_frame_
+ -std::unique_ptr~IMemoryCopyStrategy~ copy_strategy_
+ +process_incoming_packet(RtpParams, payload) void
+ +configure_burst_parameters() void
+ +set_completion_handler() void
+ +get_statistics() Statistics
+ }
+
+ class IFrameProvider {
+ <>
+ +get_new_frame() FrameBufferBase*
+ +get_frame_size() size_t
+ +has_available_frames() bool
+ +return_frame_to_pool() void
+ }
+
+ class FrameBufferBase {
+ <>
+ #MemoryLocation memory_location_
+ #MemoryStorageType src_storage_type_
+ #size_t frame_size_
+ #Entity entity_
+ +get() byte_t*
+ +get_size() size_t
+ +get_memory_location() MemoryLocation
+ }
+
+ class VideoFrameBufferBase {
+ <>
+ #uint32_t width_
+ #uint32_t height_
+ #VideoFormat format_
+ +validate_frame_parameters() Status
+ #validate_format_compliance() Status
+ }
+
+ %% TX Data Structures
+ class VideoBufferFrameBuffer {
+ -Handle~VideoBuffer~ buffer_
+ -std::vector~ColorPlane~ planes_
+ +get() byte_t*
+ +wrap_in_entity() Entity
+ }
+
+ class TensorFrameBuffer {
+ -Handle~Tensor~ tensor_
+ +get() byte_t*
+ +wrap_in_entity() Entity
+ }
+
+ class AllocatedVideoBufferFrameBuffer {
+ -void* data_
+ +get() byte_t*
+ +wrap_in_entity() Entity
+ }
+
+ class AllocatedTensorFrameBuffer {
+ -void* data_
+ -uint32_t channels_
+ +get() byte_t*
+ +wrap_in_entity() Entity
+ }
+
+ %% RDK Service Classes (External SDK)
+ class MediaFrame {
+ <>
+ Note: Rivermax SDK class
+ }
+
+ class MediaFramePool {
+ <>
+ Note: Rivermax SDK class
+ }
+
+ class MediaSenderZeroCopyService {
+ -std::shared_ptr~BufferedMediaFrameProvider~ tx_media_frame_provider_
+ -bool is_frame_in_process_
+ +get_tx_packet_burst() Status
+ +send_tx_burst() Status
+ +is_tx_burst_available() bool
+ }
+
+ class MediaSenderService {
+ -std::unique_ptr~MediaFramePool~ tx_media_frame_pool_
+ -std::shared_ptr~BufferedMediaFrameProvider~ tx_media_frame_provider_
+ -std::shared_ptr~MediaFrame~ processing_frame_
+ +get_tx_packet_burst() Status
+ +send_tx_burst() Status
+ +is_tx_burst_available() bool
+ }
+
+ %% Inheritance Relationships
+ RivermaxBurst --|> BurstParams : inherits
+ VideoFrameBufferBase --|> FrameBufferBase : inherits
+ VideoBufferFrameBuffer --|> VideoFrameBufferBase : inherits
+ TensorFrameBuffer --|> VideoFrameBufferBase : inherits
+ AllocatedVideoBufferFrameBuffer --|> VideoFrameBufferBase : inherits
+ AllocatedTensorFrameBuffer --|> VideoFrameBufferBase : inherits
+
+ %% Composition and Association Relationships
+ RivermaxBurst *-- AnoBurstExtendedInfo : stores in hdr.custom_burst_data
+ RivermaxBurst ..> MediaFrameAssembler : processed by
+ MediaFrameAssembler ..> FrameBufferBase : creates
+ MediaFrameAssembler o-- IFrameProvider : uses
+
+ BurstParams ..> MediaFrame : references via custom_pkt_data
+ BurstParams ..> FrameBufferBase : references via custom_pkt_data
+
+ MediaSenderService *-- MediaFramePool : manages
+ MediaFramePool o-- MediaFrame : provides
+
+ MediaSenderZeroCopyService ..> MediaFrame : uses directly
+ MediaSenderService ..> MediaFrame : copies to pool
+```
+
+## TX Data Flow Architecture
+
+When using Rivermax for media transmission, the data flows from application through frame-level processing layers to the RDK MediaSender service, which handles all packet processing internally. This section traces the complete TX path:
+
+### Complete TX Data Flow
+
+```mermaid
+graph TD
+ %% Application Layer
+ A["Media Sender Application"] --> B["VideoBuffer/Tensor
GXF Entity"]
+
+ %% Media TX Operator
+ B --> C["AdvNetworkMediaTxOp"]
+ C --> D["Frame Validation
& Format Check"]
+ D --> E["Frame Wrapping"]
+ E --> F{{"Input Type"}}
+ F -->|VideoBuffer| G["VideoBufferFrameBuffer
(Wrapper)"]
+ F -->|Tensor| H["TensorFrameBuffer
(Wrapper)"]
+
+ %% MediaFrame Creation
+ G --> I["MediaFrame Creation
(Reference Only)"]
+ H --> I
+ I --> J["BurstParams Creation"]
+ J --> K["MediaFrame Attachment
(via custom_pkt_data)"]
+
+ %% Advanced Network Manager
+ K --> L["RivermaxMgr
(Advanced Network Manager)"]
+ L --> M{{"Service Selection
(use_internal_memory_pool)"}}
+
+ %% Service Paths
+ M -->|false| N["MediaSenderZeroCopyService
(True Zero-Copy)"]
+ M -->|true| O["MediaSenderService
(Single Copy + Pool)"]
+
+ %% Zero-Copy Path
+ N --> P["No Internal Memory Pool"]
+ P --> Q["Direct Frame Reference
(custom_pkt_data)"]
+ Q --> R["RDK MediaSenderApp
(Zero Copy Processing)"]
+
+ %% Memory Pool Path
+ O --> S["Pre-allocated MediaFramePool"]
+ S --> T["Single Memory Copy
(burst->pkts[0][0])"]
+ T --> U["RDK MediaSenderApp
(Pool Buffer Processing)"]
+
+ %% RDK Processing (Common)
+ R --> V["RDK Internal Processing"]
+ U --> V
+ V --> W["RTP Packetization
(SMPTE 2110)"]
+ W --> X["Protocol Headers
& Metadata"]
+ X --> Y["Timing Control
& Scheduling"]
+
+ %% Hardware Transmission
+ Y --> Z["Rivermax Hardware
Acceleration"]
+ Z --> AA["ConnectX NIC
Hardware Queues"]
+ AA --> BB["Network Interface
Transmission"]
+
+ %% Cleanup Paths
+ R --> CC["Frame Ownership Transfer
& Release"]
+ T --> DD["Pool Buffer Reuse"]
+
+ %% Styling
+ classDef applicationLayer fill:#fff3e0
+ classDef operatorLayer fill:#e8f5e8
+ classDef managerLayer fill:#f3e5f5
+ classDef rdkLayer fill:#e3f2fd
+ classDef networkLayer fill:#e1f5fe
+ classDef dataStructure fill:#ffebee
+ classDef zeroCopyPath fill:#e8f5e8,stroke:#4caf50,stroke-width:3px
+ classDef poolPath fill:#fff8e1,stroke:#ff9800,stroke-width:3px
+
+ class A,B applicationLayer
+ class C,D,E,F,G,H,I,J,K operatorLayer
+ class L,M managerLayer
+ class N,P,Q,R,O,S,T,U rdkLayer
+ class V,W,X,Y,Z,AA,BB networkLayer
+ class I,J,K,S dataStructure
+ class N,P,Q,R,CC zeroCopyPath
+ class O,S,T,U,DD poolPath
+```
+
+### 1. Media Sender Application → Advanced Network Media TX Operator
+```
+Media Sender Application → AdvNetworkMediaTxOp
+├── Video Frame Input (GXF VideoBuffer or Tensor entities)
+├── Frame Validation and Wrapping
+│ ├── VideoBufferFrameBuffer (for VideoBuffer input)
+│ ├── TensorFrameBuffer (for Tensor input)
+│ └── MediaFrame Creation (wrapper around frame buffer)
+└── Frame Buffer Management (no packet processing)
+```
+
+**Frame Processing:**
+- **Frame Reception**: Receives video frames as GXF VideoBuffer or Tensor entities from application
+- **Format Validation**: Validates input format against configured video parameters (resolution, bit depth, format)
+- **Frame Wrapping**: Creates MediaFrame wrapper around the original frame buffer data (no data copying)
+- **Buffer Management**: Manages frame buffer lifecycle and memory references
+
+> **Key Point**: No packet processing occurs at this level. All operations work with complete video frames.
+
+### 2. Advanced Network Media TX Operator → Advanced Network Manager
+```
+AdvNetworkMediaTxOp → Advanced Network Manager (RivermaxMgr)
+├── Burst Parameter Creation
+├── MediaFrame Attachment (via custom_pkt_data)
+├── Service Coordination
+└── Frame Handoff to RDK Services
+```
+
+**Frame Handoff Process:**
+- **Burst Creation**: Creates burst parameters container (`BurstParams`) for transmission
+- **Frame Attachment**: Attaches MediaFrame to burst via `custom_pkt_data` field (no data copying)
+- **Service Routing**: Routes burst to appropriate MediaSender service based on configuration
+- **Memory Management**: Coordinates frame buffer ownership transfer
+
+> **Zero-Copy Architecture**: MediaFrame objects contain only references to original frame buffer memory. No frame data copying occurs.
+
+### 3. Advanced Network Manager → Rivermax SDK (RDK) Services
+```
+Advanced Network Manager → Rivermax SDK (RDK) Services
+├── MediaSenderZeroCopyService (true zero-copy) [OR]
+├── MediaSenderService (single copy + mempool) [OR]
+├── MediaSenderMockService (testing mode) [OR]
+└── Internal Frame Processing via MediaSenderApp
+```
+
+**RDK Service Paths:**
+
+#### MediaSenderZeroCopyService (True Zero-Copy Path)
+- **No Internal Memory Pool**: Does not create internal frame buffers
+- **Direct Frame Transfer**: Application's MediaFrame is passed directly to RDK via `custom_pkt_data`
+- **Zero Memory Copies**: No memcpy operations - only ownership transfer of application's frame buffer
+- **Frame Lifecycle**: After RDK processes the frame, it is released/destroyed (not reused)
+- **Memory Efficiency**: Maximum efficiency - application frame buffer used directly by RDK
+
+#### MediaSenderService (Single Copy + Memory Pool Path)
+- **Internal Memory Pool**: Creates pre-allocated `MediaFramePool` with `MEDIA_FRAME_POOL_SIZE` buffers
+- **One Memory Copy**: Application's frame data is copied from `custom_pkt_data` to pool buffer via `burst->pkts[0][0]`
+- **Pool Management**: Pool frames are reused after RDK finishes processing
+- **Frame Lifecycle**: Pool frames are returned to memory pool for subsequent transmissions
+- **Buffering**: Provides buffering capability for sustained high-throughput transmission
+
+#### MediaSenderMockService (Testing Mode)
+- **Mock Implementation**: Testing service with minimal functionality
+- **Single Pre-allocated Frame**: Uses one static frame buffer for testing
+
+> **Note**: The choice of MediaSender service depends on configuration (`use_internal_memory_pool`, `dummy_sender` flags). All services are provided by the Rivermax Development Kit (RDK).
+
+### 4. RDK MediaSender Service → Network Hardware
+```
+MediaSenderApp (RDK) → Internal Processing → Hardware Transmission
+├── Frame-to-Packet Conversion (RTP/SMPTE 2110)
+├── Packet Timing and Scheduling
+├── Hardware Queue Management
+└── DMA to Network Interface
+```
+
+**RDK Internal Processing:**
+- **Packetization**: RDK internally converts video frames to RTP packets following SMPTE 2110 standards
+- **Timing Control**: Applies precise packet timing for synchronized transmission
+- **Hardware Integration**: Direct submission to ConnectX NIC hardware queues via DMA
+- **Network Transmission**: Packets transmitted with hardware-controlled timing precision
+
+### Memory Flow Optimization
+
+The TX path maintains frame-level processing until RDK services, with two distinct memory management strategies:
+
+#### MediaSenderZeroCopyService Path (True Zero-Copy)
+```
+Application → Frame Buffer → MediaFrame Wrapper → Direct RDK Transfer → Internal Packetization → Network
+ ↓ ↓ ↓ ↓ ↓ ↓
+Media Sender GXF Entity Reference Only Zero-Copy Transfer RTP Packets Wire Transmission
+ (ownership transfer) (RDK Internal)
+```
+
+#### MediaSenderService Path (Single Copy + Pool)
+```
+Application → Frame Buffer → MediaFrame Wrapper → Pool Buffer Copy → Internal Packetization → Network
+ ↓ ↓ ↓ ↓ ↓ ↓
+Media Sender GXF Entity Reference Only Single memcpy RTP Packets Wire Transmission
+ (to pool buffer) (RDK Internal)
+ ↓
+ Pool Reuse
+```
+
+**Memory Architecture Comparison:**
+
+**Zero-Copy Path (`MediaSenderZeroCopyService`):**
+- **Frame Input**: Video frames received from application as GXF VideoBuffer or Tensor
+- **Reference Management**: MediaFrame wrapper maintains references to original frame buffer
+- **Direct Transfer**: Application's frame buffer ownership transferred directly to RDK (no copying)
+- **Frame Lifecycle**: Frames are released/destroyed after RDK processing
+- **Maximum Efficiency**: True zero-copy from application to RDK
+
+**Memory Pool Path (`MediaSenderService`):**
+- **Frame Input**: Video frames received from application as GXF VideoBuffer or Tensor
+- **Reference Management**: MediaFrame wrapper maintains references to original frame buffer
+- **Single Copy**: One memcpy operation from application frame to pre-allocated pool buffer
+- **Pool Management**: Pool buffers are reused for subsequent frames
+- **Sustained Throughput**: Buffering capability for high-throughput sustained transmission
+- **Hardware DMA**: RDK performs direct memory access from pool buffers to NIC hardware
+
+### Performance Characteristics
+
+#### MediaSenderZeroCopyService (True Zero-Copy Path)
+- **Latency**: Absolute minimal latency - no memory copying overhead
+- **Throughput**: Maximum single-stream throughput with zero-copy efficiency
+- **Memory Efficiency**: Perfect efficiency - application frame buffer used directly by RDK
+- **CPU Usage**: Minimal CPU usage - no memory copying operations
+- **Frame Rate**: Optimal for variable frame rates and low-latency applications
+
+#### MediaSenderService (Memory Pool Path)
+- **Latency**: Single memory copy latency (application frame → pool buffer)
+- **Throughput**: High sustained throughput with buffering capabilities
+- **Memory Efficiency**: One copy operation but optimized through buffer pool reuse
+- **CPU Usage**: Minimal additional CPU usage for single memory copy
+- **Frame Rate**: Optimal for sustained high frame rate streaming with consistent throughput
+
+#### Common Characteristics
+- **Timing Precision**: Hardware-controlled packet scheduling managed by RDK services
+- **Packet Processing**: Zero CPU usage for RTP packetization (handled by RDK + hardware)
+- **Scalability**: Multiple transmission flows supported through service instances
+- **Hardware Integration**: Direct NIC hardware acceleration for transmission
+
+### Configuration Integration
+
+The TX path is configured through YAML files that specify:
+- **Network Settings**: Interface addresses, destination IP addresses, ports
+- **Video Parameters**: Format, resolution, bit depth, frame rate (for RDK packetization)
+- **Memory Regions**: Buffer allocation strategies for RDK internal processing
+- **Service Mode Selection**: Choose between zero-copy and memory pool modes
+
+#### Service Mode Configuration
+
+**Zero-Copy Mode** (`MediaSenderZeroCopyService`):
+```yaml
+advanced_network:
+ interfaces:
+ - tx:
+ queues:
+ - rivermax_tx_settings:
+ use_internal_memory_pool: false # Enables zero-copy mode
+```
+
+**Memory Pool Mode** (`MediaSenderService`):
+```yaml
+advanced_network:
+ interfaces:
+ - tx:
+ queues:
+ - rivermax_tx_settings:
+ use_internal_memory_pool: true # Enables memory pool mode
+```
+
+#### Service Selection Decision Flow
+
+Understanding when each MediaSender service is selected is crucial for optimizing your application:
+
+```mermaid
+flowchart TD
+ A["Application Frame
Input"] --> B{{"Configuration
use_internal_memory_pool"}}
+
+ B -->|false| C["MediaSenderZeroCopyService
(True Zero-Copy Path)"]
+ B -->|true| D["MediaSenderService
(Single Copy + Pool Path)"]
+
+ C --> E["No Internal Memory Pool"]
+ E --> F["Direct Frame Reference
(custom_pkt_data)"]
+ F --> G["Zero Memory Copies
Only Ownership Transfer"]
+ G --> H["Frame Released/Destroyed
After RDK Processing"]
+
+ D --> I["Pre-allocated MediaFramePool"]
+ I --> J["Single Memory Copy
(Frame → Pool Buffer)"]
+ J --> K["Pool Buffer Reused
After RDK Processing"]
+
+ H --> L["RDK MediaSenderApp
Processing"]
+ K --> L
+ L --> M["RTP Packetization
& Network Transmission"]
+
+ %% Usage Scenarios
+ N["Usage Scenarios"] --> O["Pipeline Mode
(Zero-Copy)"]
+ N --> P["Data Generation Mode
(Memory Pool)"]
+
+ O --> Q["• Operators receiving MediaFrame/VideoBuffer/Tensor
• Pipeline processing
• Frame-to-frame transformations
• Real-time streaming applications"]
+
+ P --> R["• File readers
• Camera/sensor operators
• Synthetic data generators
• Batch processing applications"]
+
+ %% Connect scenarios to services
+ Q -.-> C
+ R -.-> D
+
+ %% Styling
+ classDef configNode fill:#f9f9f9,stroke:#333,stroke-width:2px
+ classDef zeroCopyPath fill:#e8f5e8,stroke:#4caf50,stroke-width:3px
+ classDef poolPath fill:#fff8e1,stroke:#ff9800,stroke-width:3px
+ classDef rdkLayer fill:#e3f2fd
+ classDef usageLayer fill:#f3e5f5
+
+ class A,B configNode
+ class C,E,F,G,H zeroCopyPath
+ class D,I,J,K poolPath
+ class L,M rdkLayer
+ class N,O,P,Q,R usageLayer
+```
+
+#### When to Use Each Mode
+
+**Use Zero-Copy Mode** (`use_internal_memory_pool: false`):
+- **Low-latency applications**: When minimal latency is critical
+- **Variable frame rates**: When frame timing is irregular
+- **Memory-constrained environments**: When minimizing memory usage is important
+- **Single/few streams**: When not requiring high sustained throughput buffering
+
+**Use Memory Pool Mode** (`use_internal_memory_pool: true`):
+- **High sustained throughput**: When streaming at consistent high frame rates
+- **Buffering requirements**: When you need frame buffering capabilities
+- **Multiple concurrent streams**: When handling multiple transmission flows
+- **Production broadcast**: When requiring consistent performance under sustained load
+
+This TX architecture provides professional-grade media transmission by maintaining frame-level processing in Holohub components while delegating all packet-level operations to optimized RDK services. The key advantages are:
+- **MediaSenderZeroCopyService**: True zero-copy frame handoff for maximum efficiency and minimal latency
+- **MediaSenderService**: Single copy with memory pool management for sustained high-throughput transmission
+Both modes benefit from hardware-accelerated packet processing in the RDK layer.
+
+## System Requirements
+
+> [!IMPORTANT]
+> Review the [High Performance Networking tutorial](../../tutorials/high_performance_networking/README.md) for guided instructions to configure your system and test the Advanced Network Media operators.
+
+- Linux
+- NVIDIA NIC with ConnectX-6 or later chip
+- NVIDIA Rivermax SDK
+- System tuning as described in the High Performance Networking tutorial
+- Sufficient memory and bandwidth for media streaming workloads
+
diff --git a/operators/advanced_network_media/advanced_network_media_rx/CMakeLists.txt b/operators/advanced_network_media/advanced_network_media_rx/CMakeLists.txt
new file mode 100644
index 0000000000..26632b3892
--- /dev/null
+++ b/operators/advanced_network_media/advanced_network_media_rx/CMakeLists.txt
@@ -0,0 +1,78 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+cmake_minimum_required(VERSION 3.20)
+project(advanced_network_media_rx)
+
+find_package(holoscan 2.6 REQUIRED CONFIG
+ PATHS "/opt/nvidia/holoscan" "/workspace/holoscan-sdk/install")
+
+add_library(${PROJECT_NAME} SHARED
+ adv_network_media_rx.cpp
+ frame_assembly_controller.cpp
+ memory_copy_strategies.cpp
+ media_frame_assembler.cpp
+ network_burst_processor.cpp
+)
+
+add_library(holoscan::ops::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
+
+target_include_directories(${PROJECT_NAME} PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../advanced_network
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../advanced_network/advanced_network
+)
+
+target_link_libraries(${PROJECT_NAME}
+ PRIVATE
+ holoscan::core
+ GXF::multimedia
+ rivermax-dev-kit
+ advanced_network_common
+ advanced_network_media_common
+)
+
+# Add CUDA support for memory copy strategies
+find_package(CUDAToolkit REQUIRED)
+target_link_libraries(${PROJECT_NAME}
+ PRIVATE
+ CUDA::cudart
+)
+
+set_target_properties(${PROJECT_NAME} PROPERTIES
+ OUTPUT_NAME "holoscan_op_advanced_network_media_rx"
+ EXPORT_NAME ops::advanced_network_media_rx
+)
+
+# Installation
+install(
+ TARGETS
+ ${PROJECT_NAME}
+ EXPORT holoscan-networking-targets
+ COMPONENT advanced_network-cpp
+)
+
+# Install only public interface headers (detail namespace classes are internal)
+install(
+ FILES
+ frame_provider.h
+ media_frame_assembler.h
+ network_burst_processor.h
+ DESTINATION include/holoscan/operators/advanced_network_media_rx
+ COMPONENT advanced_network-dev
+)
+
+if(HOLOHUB_BUILD_PYTHON)
+ add_subdirectory(python)
+endif()
diff --git a/operators/advanced_network_media/advanced_network_media_rx/adv_network_media_rx.cpp b/operators/advanced_network_media/advanced_network_media_rx/adv_network_media_rx.cpp
new file mode 100644
index 0000000000..f375daee36
--- /dev/null
+++ b/operators/advanced_network_media/advanced_network_media_rx/adv_network_media_rx.cpp
@@ -0,0 +1,797 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "adv_network_media_rx.h"
+#include "advanced_network/common.h"
+#include "../common/adv_network_media_common.h"
+#include "media_frame_assembler.h"
+#include "network_burst_processor.h"
+#include
+#include
+#include "../common/frame_buffer.h"
+#include "../common/video_parameters.h"
+#include "advanced_network/managers/rivermax/rivermax_ano_data_types.h"
+
+namespace holoscan::ops {
+
+namespace ano = holoscan::advanced_network;
+using holoscan::advanced_network::AnoBurstExtendedInfo;
+using holoscan::advanced_network::BurstParams;
+using holoscan::advanced_network::Status;
+
+constexpr size_t FRAMES_IN_POOL = 250;
+constexpr size_t PACKETS_DISPLAY_INTERVAL = 1000000; // 1e6 packets
+
+#if ENABLE_STATISTICS_LOGGING
+// Statistics timing constants
+constexpr size_t STATS_REPORT_INTERVAL_MS = 30000; // Report every 30 seconds
+constexpr size_t STATS_WINDOW_SIZE_MS = 30000; // Calculate rates over 30 seconds
+
+// Constraint: Window must be at least as large as report interval to have enough samples
+static_assert(STATS_WINDOW_SIZE_MS >= STATS_REPORT_INTERVAL_MS,
+ "STATS_WINDOW_SIZE_MS must be >= STATS_REPORT_INTERVAL_MS to ensure "
+ "sufficient samples for rate calculation");
+
+// Structure to hold timestamped statistics samples
+struct StatsSample {
+ std::chrono::steady_clock::time_point timestamp;
+ size_t packets_received;
+ size_t bursts_processed;
+ size_t frames_emitted;
+};
+#endif
+
+// Enumeration for output format types
+enum class OutputFormatType { VIDEO_BUFFER, TENSOR };
+
+/**
+ * @brief Frame completion handler for the RX operator
+ */
+class RxOperatorFrameCompletionHandler : public IFrameCompletionHandler {
+ public:
+ explicit RxOperatorFrameCompletionHandler(class AdvNetworkMediaRxOpImpl* impl) : impl_(impl) {}
+
+ void on_frame_completed(std::shared_ptr frame) override;
+ void on_frame_error(const std::string& error_message) override;
+
+ private:
+ class AdvNetworkMediaRxOpImpl* impl_;
+};
+
+/**
+ * @class AdvNetworkMediaRxOpImpl
+ * @brief Implementation class for the AdvNetworkMediaRxOp operator.
+ *
+ * Handles high-level network management, frame pool management, and
+ * coordinates with MediaFrameAssembler for packet-level operations.
+ */
+class AdvNetworkMediaRxOpImpl : public IFrameProvider {
+ public:
+ /**
+ * @brief Constructs an implementation for the given operator.
+ *
+ * @param parent Reference to the parent operator.
+ */
+ explicit AdvNetworkMediaRxOpImpl(AdvNetworkMediaRxOp& parent) : parent_(parent) {}
+
+ /**
+ * @brief Initializes the implementation.
+ *
+ * Sets up the network port, allocates frame buffers, and prepares
+ * for media reception.
+ */
+ void initialize() {
+ ANM_LOG_INFO("AdvNetworkMediaRxOp::initialize()");
+
+ // Initialize timing for statistics
+#if ENABLE_STATISTICS_LOGGING
+ start_time_ = std::chrono::steady_clock::now();
+ last_stats_report_ = start_time_;
+
+ // Initialize rolling window with first sample
+ StatsSample initial_sample{start_time_, 0, 0, 0};
+ stats_samples_.push_back(initial_sample);
+#endif
+
+ port_id_ = ano::get_port_id(parent_.interface_name_.get());
+ if (port_id_ == -1) {
+ std::string error_message = "Invalid RX port interface name '" +
+ std::string(parent_.interface_name_.get()) +
+ "' specified in the config";
+ ANM_LOG_ERROR("Invalid RX port {} specified in the config", parent_.interface_name_.get());
+ throw std::runtime_error(error_message);
+ } else {
+ ANM_CONFIG_LOG("RX port {} found", port_id_);
+ }
+
+ // Convert params to video parameters
+ auto video_sampling = get_video_sampling_format(parent_.video_format_.get());
+ auto color_bit_depth = get_color_bit_depth(parent_.bit_depth_.get());
+
+ // Get video format and calculate frame size
+ video_format_ = get_expected_gxf_video_format(video_sampling, color_bit_depth);
+ frame_size_ = calculate_frame_size(
+ parent_.frame_width_.get(), parent_.frame_height_.get(), video_sampling, color_bit_depth);
+
+ // Determine output format type
+ const auto& output_format_str = parent_.output_format_.get();
+ if (output_format_str == "tensor") {
+ output_format_ = OutputFormatType::TENSOR;
+ ANM_CONFIG_LOG("Using Tensor output format");
+ } else {
+ output_format_ = OutputFormatType::VIDEO_BUFFER;
+ ANM_CONFIG_LOG("Using VideoBuffer output format");
+ }
+
+ // Determine memory location type for output frames
+ const auto& memory_location_str = parent_.memory_location_.get();
+ if (memory_location_str == "host") {
+ storage_type_ = nvidia::gxf::MemoryStorageType::kHost;
+ ANM_CONFIG_LOG("Using Host memory location for output frames");
+ } else {
+ storage_type_ = nvidia::gxf::MemoryStorageType::kDevice;
+ ANM_CONFIG_LOG("Using Device memory location for output frames");
+ }
+
+ // Create pool of allocated frame buffers
+ create_frame_pool();
+
+ // Create media frame assembler and network burst processor
+ create_media_frame_assembler();
+
+ // Create network burst processor
+ burst_processor_ = std::make_unique(assembler_);
+ }
+
+ /**
+ * @brief Creates a pool of frame buffers for receiving frames.
+ */
+ void create_frame_pool() {
+ frames_pool_.clear();
+
+ // Get the appropriate channel count based on video format
+ uint32_t channels = get_channel_count_for_format(video_format_);
+
+ for (size_t i = 0; i < FRAMES_IN_POOL; ++i) {
+ void* data = nullptr;
+
+ // Allocate memory based on storage type
+ if (storage_type_ == nvidia::gxf::MemoryStorageType::kHost) {
+ CUDA_TRY(cudaMallocHost(&data, frame_size_));
+ } else {
+ CUDA_TRY(cudaMalloc(&data, frame_size_));
+ }
+
+ // Create appropriate frame buffer type
+ if (output_format_ == OutputFormatType::TENSOR) {
+ frames_pool_.push_back(std::make_shared(
+ data,
+ frame_size_,
+ parent_.frame_width_.get(),
+ parent_.frame_height_.get(),
+ channels, // Use format-specific channel count
+ video_format_,
+ storage_type_));
+ } else {
+ frames_pool_.push_back(
+ std::make_shared(data,
+ frame_size_,
+ parent_.frame_width_.get(),
+ parent_.frame_height_.get(),
+ video_format_,
+ storage_type_));
+ }
+ }
+ }
+
+ /**
+ * @brief Creates the media frame assembler with minimal configuration
+ * @note Full configuration will be done when first burst arrives
+ */
+ void create_media_frame_assembler() {
+ // Create minimal assembler configuration (will be completed from burst data)
+ auto config = AssemblerConfiguration{};
+
+ // Set operator-known parameters only
+ config.source_memory_type =
+ nvidia::gxf::MemoryStorageType::kHost; // Will be updated from burst
+ config.destination_memory_type = storage_type_;
+ config.enable_memory_copy_strategy_detection = true;
+ config.force_contiguous_memory_copy_strategy = false;
+
+ // Create frame provider (this class implements IFrameProvider)
+ auto frame_provider = std::shared_ptr(this, [](IFrameProvider*) {});
+
+ // Create frame assembler with minimal config
+ assembler_ = std::make_shared(frame_provider, config);
+
+ // Create completion handler
+ completion_handler_ = std::make_shared(this);
+ assembler_->set_completion_handler(completion_handler_);
+
+ ANM_CONFIG_LOG("Media frame assembler created with minimal configuration");
+ }
+
+ /**
+ * @brief Clean up resources properly when the operator is destroyed.
+ */
+ void cleanup() {
+ // Free all allocated frames
+ for (auto& frame : frames_pool_) {
+ if (storage_type_ == nvidia::gxf::MemoryStorageType::kHost) {
+ CUDA_TRY(cudaFreeHost(frame->get()));
+ } else {
+ CUDA_TRY(cudaFree(frame->get()));
+ }
+ }
+ frames_pool_.clear();
+
+ // Free any frames in the ready queue
+ for (auto& frame : ready_frames_) {
+ if (storage_type_ == nvidia::gxf::MemoryStorageType::kHost) {
+ CUDA_TRY(cudaFreeHost(frame->get()));
+ } else {
+ CUDA_TRY(cudaFree(frame->get()));
+ }
+ }
+ ready_frames_.clear();
+
+ // Free all in-flight network bursts awaiting cleanup
+ while (!bursts_awaiting_cleanup_.empty()) {
+ auto burst_to_free = bursts_awaiting_cleanup_.front();
+ ano::free_all_packets_and_burst_rx(burst_to_free);
+ bursts_awaiting_cleanup_.pop_front();
+ }
+ bursts_awaiting_cleanup_.clear();
+ }
+
+ /**
+ * @brief Processes a received burst of packets and generates video frames.
+ *
+ * @param op_input The operator input context.
+ * @param op_output The operator output context.
+ * @param context The execution context.
+ */
+ void compute(InputContext& op_input, OutputContext& op_output, ExecutionContext& context) {
+ compute_calls_++;
+
+ BurstParams* burst;
+ auto status = ano::get_rx_burst(&burst, port_id_, parent_.queue_id_.get());
+ if (status != Status::SUCCESS)
+ return;
+
+ const auto& packets_received = burst->hdr.hdr.num_pkts;
+ total_packets_received_ += packets_received;
+ total_bursts_processed_++;
+
+ if (packets_received == 0) {
+ ano::free_all_packets_and_burst_rx(burst);
+ return;
+ }
+
+ // Report periodic comprehensive statistics
+ report_periodic_statistics();
+
+ ANM_FRAME_TRACE("Processing burst: port={}, queue={}, packets={}, burst_ptr={}",
+ port_id_,
+ parent_.queue_id_.get(),
+ packets_received,
+ static_cast(burst));
+
+ append_to_frame(burst);
+
+ if (ready_frames_.empty())
+ return;
+
+ size_t total_frames = ready_frames_.size();
+
+ if (total_frames > 1) {
+ size_t frames_to_drop = total_frames - 1;
+ total_frames_dropped_ += frames_to_drop;
+ ANM_LOG_WARN(
+ "Multiple frames ready ({}), dropping {} earlier frames to prevent pipeline issues",
+ total_frames,
+ frames_to_drop);
+ }
+
+ ANM_FRAME_TRACE("Ready frames count: {}, processing frame emission", total_frames);
+
+ // Pop all frames but keep only the last one
+ std::shared_ptr last_frame = nullptr;
+ while (auto frame = pop_ready_frame()) {
+ if (last_frame) {
+ // Return the previous frame back to pool (dropping it)
+ frames_pool_.push_back(last_frame);
+ ANM_FRAME_TRACE("Dropped frame returned to pool: size={}, ptr={}",
+ last_frame->get_size(),
+ static_cast(last_frame->get()));
+ }
+ last_frame = frame;
+ }
+
+ // Emit only the last (most recent) frame
+ if (last_frame) {
+ total_frames_emitted_++;
+ ANM_FRAME_TRACE("Emitting latest frame: {} bytes", last_frame->get_size());
+ ANM_FRAME_TRACE("Frame emission details: size={}, ptr={}, memory_location={}",
+ last_frame->get_size(),
+ static_cast(last_frame->get()),
+ static_cast(last_frame->get_memory_location()));
+ auto result = create_frame_entity(last_frame, context);
+ op_output.emit(result);
+ }
+ }
+
+ /**
+ * @brief Appends packet data from a burst to the current frame being constructed.
+ *
+ * @param burst The burst containing packets to process.
+ */
+ void append_to_frame(BurstParams* burst) {
+ // Configure assembler on first burst
+ if (!assembler_configured_) {
+ configure_assembler_from_burst(burst);
+ assembler_configured_ = true;
+ }
+
+ size_t ready_frames_before = ready_frames_.size();
+
+ ANM_FRAME_TRACE("Processing burst: ready_frames_before={}, queue_size={}, burst_packets={}",
+ ready_frames_before,
+ bursts_awaiting_cleanup_.size(),
+ burst->hdr.hdr.num_pkts);
+
+ burst_processor_->process_burst(burst);
+
+ size_t ready_frames_after = ready_frames_.size();
+
+ ANM_FRAME_TRACE("Burst processed: ready_frames_after={}, frames_completed={}",
+ ready_frames_after,
+ ready_frames_after - ready_frames_before);
+
+ // If new frames were completed, free all accumulated bursts
+ if (ready_frames_after > ready_frames_before) {
+ size_t frames_completed = ready_frames_after - ready_frames_before;
+ ANM_FRAME_TRACE("{} frame(s) completed, freeing {} accumulated bursts",
+ frames_completed,
+ bursts_awaiting_cleanup_.size());
+ ANM_FRAME_TRACE("Freeing accumulated bursts: count={}", bursts_awaiting_cleanup_.size());
+ while (!bursts_awaiting_cleanup_.empty()) {
+ auto burst_to_free = bursts_awaiting_cleanup_.front();
+ ANM_FRAME_TRACE("Freeing burst: ptr={}, packets={}",
+ static_cast(burst_to_free),
+ burst_to_free->hdr.hdr.num_pkts);
+ ano::free_all_packets_and_burst_rx(burst_to_free);
+ bursts_awaiting_cleanup_.pop_front();
+ }
+ }
+
+ // Add current burst to the queue after freeing previous bursts
+ bursts_awaiting_cleanup_.push_back(burst);
+
+ ANM_FRAME_TRACE("Final queue_size={}, burst_ptr={}",
+ bursts_awaiting_cleanup_.size(),
+ static_cast(burst));
+ }
+
+ /**
+ * @brief Retrieves a ready frame from the queue if available.
+ *
+ * @return Shared pointer to a ready frame, or nullptr if none is available.
+ */
+ std::shared_ptr pop_ready_frame() {
+ if (ready_frames_.empty()) {
+ return nullptr;
+ }
+
+ auto frame = ready_frames_.front();
+ ready_frames_.pop_front();
+ return frame;
+ }
+
+ /**
+ * @brief Creates a GXF entity containing the frame for output.
+ *
+ * @param frame The frame to wrap.
+ * @param context The execution context.
+ * @return The GXF entity ready for emission.
+ */
+ holoscan::gxf::Entity create_frame_entity(std::shared_ptr frame,
+ ExecutionContext& context) {
+ // Create lambda to return frame to pool when done
+ auto release_func = [this, frame](void*) -> nvidia::gxf::Expected {
+ frames_pool_.push_back(frame);
+ return {};
+ };
+
+ if (output_format_ == OutputFormatType::TENSOR) {
+ // Cast to AllocatedTensorFrameBuffer and wrap
+ auto tensor_frame = std::static_pointer_cast(frame);
+ auto entity = tensor_frame->wrap_in_entity(context.context(), release_func);
+ return holoscan::gxf::Entity(std::move(entity));
+ } else {
+ // Cast to AllocatedVideoBufferFrameBuffer and wrap
+ auto video_frame = std::static_pointer_cast(frame);
+ auto entity = video_frame->wrap_in_entity(context.context(), release_func);
+ return holoscan::gxf::Entity(std::move(entity));
+ }
+ }
+
+ // Frame management methods (used internally)
+ std::shared_ptr get_allocated_frame() {
+ if (frames_pool_.empty()) {
+ throw std::runtime_error("Running out of resources, frames pool is empty");
+ }
+ auto frame = frames_pool_.front();
+ frames_pool_.pop_front();
+ return frame;
+ }
+
+ void on_new_frame(std::shared_ptr frame) {
+ ready_frames_.push_back(frame);
+ ANM_FRAME_TRACE("New frame ready: {}", frame->get_size());
+ }
+
+ std::shared_ptr get_new_frame() override {
+ return get_allocated_frame();
+ }
+
+ size_t get_frame_size() const override {
+ return frame_size_;
+ }
+
+ bool has_available_frames() const override {
+ return !frames_pool_.empty();
+ }
+
+ void return_frame_to_pool(std::shared_ptr frame) override {
+ if (frame) {
+ frames_pool_.push_back(frame);
+ ANM_FRAME_TRACE("Frame returned to pool: pool_size={}, frame_ptr={}",
+ frames_pool_.size(),
+ static_cast(frame->get()));
+ }
+ }
+
+ private:
+ /**
+ * @brief Configure assembler with burst parameters and validate against operator parameters
+ * @param burst The burst containing configuration info
+ */
+ void configure_assembler_from_burst(BurstParams* burst) {
+ // Access burst extended info from custom_burst_data
+ const auto* burst_info =
+ reinterpret_cast(&(burst->hdr.custom_burst_data));
+
+ // Validate operator parameters against burst data
+ validate_configuration_consistency(burst_info);
+
+ // Configure assembler with burst parameters
+ assembler_->configure_burst_parameters(
+ burst_info->header_stride_size, burst_info->payload_stride_size, burst_info->hds_on);
+
+ // Configure memory types based on burst info
+ nvidia::gxf::MemoryStorageType src_type = burst_info->payload_on_cpu
+ ? nvidia::gxf::MemoryStorageType::kHost
+ : nvidia::gxf::MemoryStorageType::kDevice;
+
+ assembler_->configure_memory_types(src_type, storage_type_);
+
+ ANM_CONFIG_LOG(
+ "Assembler configured from burst: header_stride={}, payload_stride={}, "
+ "hds_on={}, payload_on_cpu={}, src_memory={}, dst_memory={}",
+ burst_info->header_stride_size,
+ burst_info->payload_stride_size,
+ burst_info->hds_on,
+ burst_info->payload_on_cpu,
+ static_cast(src_type),
+ static_cast(storage_type_));
+ }
+
+ /**
+ * @brief Validate consistency between operator parameters and burst configuration
+ * @param burst_info The burst configuration data
+ */
+ void validate_configuration_consistency(const AnoBurstExtendedInfo* burst_info) {
+ // Validate HDS configuration
+ bool operator_hds = parent_.hds_.get();
+ bool burst_hds = burst_info->hds_on;
+
+ if (operator_hds != burst_hds) {
+ ANM_LOG_WARN(
+ "HDS configuration mismatch: operator parameter={}, burst data={} - using burst data as "
+ "authoritative",
+ operator_hds,
+ burst_hds);
+ }
+
+ ANM_CONFIG_LOG("Configuration validation completed: operator_hds={}, burst_hds={}",
+ operator_hds,
+ burst_hds);
+ }
+
+ /**
+ * @brief Report periodic statistics for monitoring
+ */
+ void report_periodic_statistics() {
+#if ENABLE_STATISTICS_LOGGING
+ auto now = std::chrono::steady_clock::now();
+ auto time_since_last_report =
+ std::chrono::duration_cast(now - last_stats_report_).count();
+
+ if (time_since_last_report >= STATS_REPORT_INTERVAL_MS) {
+ // Add current sample to rolling window
+ StatsSample current_sample{now,
+ total_packets_received_,
+ total_bursts_processed_,
+ total_frames_emitted_};
+ stats_samples_.push_back(current_sample);
+
+ // Remove samples outside the window, but always keep at least 2 samples for rate calculation
+ auto window_start_time = now - std::chrono::milliseconds(STATS_WINDOW_SIZE_MS);
+ while (stats_samples_.size() > 2 &&
+ stats_samples_.front().timestamp < window_start_time) {
+ stats_samples_.pop_front();
+ }
+
+ // Calculate rates based on rolling window
+ double window_packets_per_sec = 0.0;
+ double window_frames_per_sec = 0.0;
+ double window_bursts_per_sec = 0.0;
+ double actual_window_duration_sec = 0.0;
+
+ if (stats_samples_.size() >= 2) {
+ const auto& oldest = stats_samples_.front();
+ const auto& newest = stats_samples_.back();
+
+ auto window_duration_ms = std::chrono::duration_cast(
+ newest.timestamp - oldest.timestamp).count();
+ actual_window_duration_sec = window_duration_ms / 1000.0;
+
+ if (actual_window_duration_sec > 0.0) {
+ size_t window_packets = newest.packets_received - oldest.packets_received;
+ size_t window_bursts = newest.bursts_processed - oldest.bursts_processed;
+ size_t window_frames = newest.frames_emitted - oldest.frames_emitted;
+
+ window_packets_per_sec = window_packets / actual_window_duration_sec;
+ window_frames_per_sec = window_frames / actual_window_duration_sec;
+ window_bursts_per_sec = window_bursts / actual_window_duration_sec;
+ }
+ }
+
+ // Calculate lifetime averages
+ auto total_runtime =
+ std::chrono::duration_cast(now - start_time_).count();
+ double lifetime_packets_per_sec =
+ total_runtime > 0 ? static_cast(total_packets_received_) / total_runtime : 0.0;
+ double lifetime_frames_per_sec =
+ total_runtime > 0 ? static_cast(total_frames_emitted_) / total_runtime : 0.0;
+ double lifetime_bursts_per_sec =
+ total_runtime > 0 ? static_cast(total_bursts_processed_) / total_runtime : 0.0;
+
+ ANM_STATS_LOG("AdvNetworkMediaRx Statistics Report:");
+ ANM_STATS_LOG(" Runtime: {} seconds", total_runtime);
+ ANM_STATS_LOG(" Total packets received: {}", total_packets_received_);
+ ANM_STATS_LOG(" Total bursts processed: {}", total_bursts_processed_);
+ ANM_STATS_LOG(" Total frames emitted: {}", total_frames_emitted_);
+ ANM_STATS_LOG(" Total frames dropped: {}", total_frames_dropped_);
+ ANM_STATS_LOG(" Compute calls: {}", compute_calls_);
+
+ // Report current rates with actual measurement window
+ if (actual_window_duration_sec > 0.0) {
+ ANM_STATS_LOG(
+ " Current rates (over {:.1f}s): {:.2f} packets/sec, {:.2f} frames/sec, "
+ "{:.2f} bursts/sec",
+ actual_window_duration_sec,
+ window_packets_per_sec,
+ window_frames_per_sec,
+ window_bursts_per_sec);
+ } else {
+ ANM_STATS_LOG(" Current rates: N/A (insufficient samples, need {} more seconds)",
+ STATS_REPORT_INTERVAL_MS / 1000);
+ }
+
+ ANM_STATS_LOG(
+ " Lifetime avg rates: {:.2f} packets/sec, {:.2f} frames/sec, "
+ "{:.2f} bursts/sec",
+ lifetime_packets_per_sec,
+ lifetime_frames_per_sec,
+ lifetime_bursts_per_sec);
+ ANM_STATS_LOG(" Ready frames queue: {}, Burst cleanup queue: {}",
+ ready_frames_.size(),
+ bursts_awaiting_cleanup_.size());
+ ANM_STATS_LOG(" Frame pool available: {}", frames_pool_.size());
+
+ // Report assembler statistics if available
+ if (assembler_) {
+ auto assembler_stats = assembler_->get_statistics();
+ ANM_STATS_LOG(" Frame Assembler - Packets: {}, Frames completed: {}, Errors recovered: {}",
+ assembler_stats.packets_processed,
+ assembler_stats.frames_completed,
+ assembler_stats.errors_recovered);
+ ANM_STATS_LOG(" Frame Assembler - Current state: {}, Strategy: {}",
+ assembler_stats.current_frame_state,
+ assembler_stats.current_strategy);
+ }
+
+ last_stats_report_ = now;
+ }
+#endif
+ }
+
+ private:
+ AdvNetworkMediaRxOp& parent_;
+ int port_id_;
+
+ // Frame assembly components
+ std::shared_ptr assembler_;
+ std::shared_ptr completion_handler_;
+ std::unique_ptr burst_processor_;
+
+ // Frame management
+ std::deque> frames_pool_;
+ std::deque> ready_frames_;
+ std::deque bursts_awaiting_cleanup_;
+
+ // Enhanced statistics and configuration
+ size_t total_packets_received_ = 0;
+ size_t total_bursts_processed_ = 0;
+ size_t total_frames_emitted_ = 0;
+ size_t total_frames_dropped_ = 0;
+ size_t compute_calls_ = 0;
+#if ENABLE_STATISTICS_LOGGING
+ std::chrono::steady_clock::time_point start_time_;
+ std::chrono::steady_clock::time_point last_stats_report_;
+ std::deque stats_samples_; // Rolling window of statistics samples
+#endif
+
+ nvidia::gxf::VideoFormat video_format_;
+ size_t frame_size_;
+ OutputFormatType output_format_{OutputFormatType::VIDEO_BUFFER};
+ nvidia::gxf::MemoryStorageType storage_type_{nvidia::gxf::MemoryStorageType::kDevice};
+ bool assembler_configured_ = false; ///< Whether assembler has been configured from burst data
+};
+
+// ========================================================================================
+// RxOperatorFrameCompletionHandler Implementation
+// ========================================================================================
+
+void RxOperatorFrameCompletionHandler::on_frame_completed(std::shared_ptr frame) {
+ if (!impl_ || !frame)
+ return;
+
+ // Add completed frame to ready queue (same as old on_new_frame)
+ impl_->on_new_frame(frame);
+
+ ANM_FRAME_TRACE("Frame assembly completed: {} bytes", frame->get_size());
+}
+
+void RxOperatorFrameCompletionHandler::on_frame_error(const std::string& error_message) {
+ if (!impl_)
+ return;
+
+ ANM_LOG_ERROR("Frame assembly error: {}", error_message);
+ // Could add error statistics or recovery logic here
+}
+
+// ========================================================================================
+// AdvNetworkMediaRxOp Implementation
+// ========================================================================================
+
+AdvNetworkMediaRxOp::AdvNetworkMediaRxOp() : pimpl_(nullptr) {}
+
+AdvNetworkMediaRxOp::~AdvNetworkMediaRxOp() {
+ if (pimpl_) {
+ pimpl_->cleanup(); // Clean up allocated resources
+ delete pimpl_;
+ pimpl_ = nullptr;
+ }
+}
+
+void AdvNetworkMediaRxOp::setup(OperatorSpec& spec) {
+ ANM_LOG_INFO("AdvNetworkMediaRxOp::setup() - Configuring operator parameters");
+
+ spec.output("out_video_buffer");
+ spec.param(interface_name_,
+ "interface_name",
+ "Name of NIC from advanced_network config",
+ "Name of NIC from advanced_network config");
+ spec.param(queue_id_, "queue_id", "Queue ID", "Queue ID", default_queue_id);
+ spec.param(frame_width_, "frame_width", "Frame width", "Width of the frame", 1920);
+ spec.param(frame_height_, "frame_height", "Frame height", "Height of the frame", 1080);
+ spec.param(bit_depth_, "bit_depth", "Bit depth", "Number of bits per pixel", 8);
+ spec.param(
+ video_format_, "video_format", "Video Format", "Video sample format", std::string("RGB888"));
+ spec.param(hds_,
+ "hds",
+ "Header Data Split",
+ "The packets received split Data in the GPU and Headers in the CPU");
+ spec.param(output_format_,
+ "output_format",
+ "Output Format",
+ "Output format type ('video_buffer' or 'tensor')",
+ std::string("video_buffer"));
+ spec.param(memory_location_,
+ "memory_location",
+ "Memory Location for Output Frames",
+ "Memory location for output frames ('host' or 'devices')",
+ std::string("device"));
+
+ ANM_CONFIG_LOG("AdvNetworkMediaRxOp setup completed - parameters registered");
+}
+
+void AdvNetworkMediaRxOp::initialize() {
+ ANM_LOG_INFO("AdvNetworkMediaRxOp::initialize() - Starting operator initialization");
+ holoscan::Operator::initialize();
+
+ ANM_CONFIG_LOG("Creating implementation instance for AdvNetworkMediaRxOp");
+ if (!pimpl_) {
+ pimpl_ = new AdvNetworkMediaRxOpImpl(*this);
+ }
+
+ ANM_CONFIG_LOG(
+ "Initializing implementation with parameters: interface={}, queue_id={}, frame_size={}x{}, "
+ "format={}, hds={}, output_format={}, memory={}",
+ interface_name_.get(),
+ queue_id_.get(),
+ frame_width_.get(),
+ frame_height_.get(),
+ video_format_.get(),
+ hds_.get(),
+ output_format_.get(),
+ memory_location_.get());
+
+ pimpl_->initialize();
+
+ ANM_LOG_INFO("AdvNetworkMediaRxOp initialization completed successfully");
+}
+
+void AdvNetworkMediaRxOp::compute(InputContext& op_input, OutputContext& op_output,
+ ExecutionContext& context) {
+ ANM_FRAME_TRACE("AdvNetworkMediaRxOp::compute() - Processing frame");
+
+#if ENABLE_PERFORMANCE_LOGGING
+ auto start_time = std::chrono::high_resolution_clock::now();
+#endif
+
+ try {
+ pimpl_->compute(op_input, op_output, context);
+
+#if ENABLE_PERFORMANCE_LOGGING
+ auto end_time = std::chrono::high_resolution_clock::now();
+ auto duration = std::chrono::duration_cast(end_time - start_time);
+
+ ANM_PERF_LOG("AdvNetworkMediaRxOp::compute() completed in {} microseconds", duration.count());
+#endif
+ } catch (const std::exception& e) {
+ ANM_LOG_ERROR("AdvNetworkMediaRxOp::compute() failed with exception: {}", e.what());
+ throw;
+ } catch (...) {
+ ANM_LOG_ERROR("AdvNetworkMediaRxOp::compute() failed with unknown exception");
+ throw;
+ }
+}
+
+}; // namespace holoscan::ops
diff --git a/operators/advanced_network_media/advanced_network_media_rx/adv_network_media_rx.h b/operators/advanced_network_media/advanced_network_media_rx/adv_network_media_rx.h
new file mode 100644
index 0000000000..1a8faa1ebc
--- /dev/null
+++ b/operators/advanced_network_media/advanced_network_media_rx/adv_network_media_rx.h
@@ -0,0 +1,76 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OPERATORS_ADVANCED_NETWORK_MEDIA_RX_ADV_NETWORK_MEDIA_RX_H_
+#define OPERATORS_ADVANCED_NETWORK_MEDIA_RX_ADV_NETWORK_MEDIA_RX_H_
+
+#include
+
+namespace holoscan::ops {
+
+// Forward declare the implementation class
+class AdvNetworkMediaRxOpImpl;
+
+/**
+ * @class AdvNetworkMediaRxOp
+ * @brief Operator for receiving media frames over advanced network infrastructure.
+ *
+ * This operator receives video frames over Rivermax-enabled network infrastructure
+ * and outputs them as GXF VideoBuffer entities.
+ */
+class AdvNetworkMediaRxOp : public Operator {
+ public:
+ static constexpr uint16_t default_queue_id = 0;
+
+ HOLOSCAN_OPERATOR_FORWARD_ARGS(AdvNetworkMediaRxOp)
+
+ /**
+ * @brief Constructs an AdvNetworkMediaRxOp operator.
+ */
+ AdvNetworkMediaRxOp();
+
+ /**
+ * @brief Destroys the AdvNetworkMediaRxOp operator and its implementation.
+ */
+ ~AdvNetworkMediaRxOp();
+
+ void initialize() override;
+ void setup(OperatorSpec& spec) override;
+ void compute(InputContext& op_input, OutputContext& op_output,
+ ExecutionContext& context) override;
+
+ protected:
+ // Operator parameters
+ Parameter interface_name_;
+ Parameter queue_id_;
+ Parameter frame_width_;
+ Parameter frame_height_;
+ Parameter bit_depth_;
+ Parameter video_format_;
+ Parameter hds_;
+ Parameter output_format_;
+ Parameter memory_location_;
+
+ private:
+ friend class AdvNetworkMediaRxOpImpl;
+
+ AdvNetworkMediaRxOpImpl* pimpl_;
+};
+
+} // namespace holoscan::ops
+
+#endif // OPERATORS_ADVANCED_NETWORK_MEDIA_RX_ADV_NETWORK_MEDIA_RX_H_
diff --git a/operators/advanced_network_media/advanced_network_media_rx/frame_assembly_controller.cpp b/operators/advanced_network_media/advanced_network_media_rx/frame_assembly_controller.cpp
new file mode 100644
index 0000000000..f88c693a86
--- /dev/null
+++ b/operators/advanced_network_media/advanced_network_media_rx/frame_assembly_controller.cpp
@@ -0,0 +1,480 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "frame_assembly_controller.h"
+#include "memory_copy_strategies.h"
+#include "../common/adv_network_media_common.h"
+
+#include
+#include
+#include
+#include
+
+namespace holoscan::ops {
+namespace detail {
+
+// Import public interfaces for cleaner usage
+using holoscan::ops::IFrameProvider;
+
+// ========================================================================================
+// FrameAssemblyController Implementation
+// ========================================================================================
+
+FrameAssemblyController::FrameAssemblyController(std::shared_ptr frame_provider)
+ : frame_provider_(frame_provider) {
+ if (!frame_provider_) {
+ throw std::invalid_argument("FrameProvider cannot be null");
+ }
+
+ // Don't allocate frame in constructor - wait for first packet
+ // This prevents reducing the pool size unnecessarily
+ context_.current_frame = nullptr;
+ context_.frame_position = 0;
+ context_.frame_state = FrameState::IDLE;
+
+ ANM_CONFIG_LOG("FrameAssemblyController initialized");
+}
+
+StateTransitionResult FrameAssemblyController::process_event(StateEvent event,
+ const RtpParams* rtp_params,
+ uint8_t* payload) {
+ // NOTE: rtp_params and payload are currently unused in state transition logic.
+ // This assembly controller focuses purely on event-driven state transitions.
+ // Parameters are kept for API consistency and future extensibility.
+ (void)rtp_params; // Suppress unused parameter warning
+ (void)payload; // Suppress unused parameter warning
+
+ packets_processed_++;
+
+ ANM_STATE_TRACE(
+ "State machine context: frame_position={}, current_frame={}, packets_processed={}",
+ context_.frame_position,
+ context_.current_frame ? "allocated" : "null",
+ packets_processed_);
+
+ StateTransitionResult result;
+
+ // Route to appropriate state handler
+ ANM_STATE_TRACE("Routing event {} to state handler for state {}",
+ FrameAssemblyHelper::event_to_string(event),
+ FrameAssemblyHelper::state_to_string(context_.frame_state));
+
+ switch (context_.frame_state) {
+ case FrameState::IDLE:
+ result = handle_idle_state(event, rtp_params, payload);
+ break;
+ case FrameState::RECEIVING_PACKETS:
+ result = handle_receiving_state(event, rtp_params, payload);
+ break;
+ case FrameState::ERROR_RECOVERY:
+ result = handle_error_recovery_state(event, rtp_params, payload);
+ break;
+ default:
+ ANM_STATE_TRACE("Unhandled state encountered: {}",
+ FrameAssemblyHelper::state_to_string(context_.frame_state));
+ result = create_error_result("Unknown state");
+ break;
+ }
+
+ // Attempt state transition whenever new_frame_state differs from current state
+ if (result.new_frame_state != context_.frame_state) {
+ FrameState old_state = context_.frame_state;
+ ANM_STATE_TRACE(
+ "Attempting state transition: {} -> {} with result actions: allocate={}, complete={}, "
+ "emit={}",
+ FrameAssemblyHelper::state_to_string(old_state),
+ FrameAssemblyHelper::state_to_string(result.new_frame_state),
+ result.should_allocate_new_frame,
+ result.should_complete_frame,
+ result.should_emit_frame);
+
+ if (transition_to_state(result.new_frame_state)) {
+ ANM_STATE_TRACE("State transition: {} -> {}",
+ FrameAssemblyHelper::state_to_string(old_state),
+ FrameAssemblyHelper::state_to_string(result.new_frame_state));
+ } else {
+ result = create_error_result("Invalid state transition");
+ }
+ } else if (result.success) {
+ ANM_STATE_TRACE(
+ "Event processed, staying in state {} with result actions: allocate={}, complete={}, "
+ "emit={}",
+ FrameAssemblyHelper::state_to_string(context_.frame_state),
+ result.should_allocate_new_frame,
+ result.should_complete_frame,
+ result.should_emit_frame);
+ }
+
+ return result;
+}
+
+void FrameAssemblyController::reset() {
+ context_.frame_state = FrameState::IDLE;
+ context_.frame_position = 0;
+
+ // Reset memory copy strategy if set
+ if (strategy_) {
+ strategy_->reset();
+ }
+
+ // Don't allocate frame in reset - wait for first packet
+ // This prevents reducing the pool size unnecessarily
+ context_.current_frame = nullptr;
+
+ ANM_CONFIG_LOG("Assembly controller reset to initial state");
+}
+
+bool FrameAssemblyController::advance_frame_position(size_t bytes) {
+ if (!validate_frame_bounds(bytes)) {
+ ANM_LOG_ERROR(
+ "Frame position advancement would exceed bounds: current={}, bytes={}, frame_size={}",
+ context_.frame_position,
+ bytes,
+ context_.current_frame ? context_.current_frame->get_size() : 0);
+ return false;
+ }
+
+ context_.frame_position += bytes;
+
+ ANM_FRAME_TRACE(
+ "Frame position advanced by {} bytes to position {}", bytes, context_.frame_position);
+ return true;
+}
+
+void FrameAssemblyController::set_strategy(std::shared_ptr strategy) {
+ strategy_ = strategy;
+
+ if (strategy_) {
+ ANM_CONFIG_LOG("Strategy set: {}",
+ strategy_->get_type() == CopyStrategy::CONTIGUOUS ? "CONTIGUOUS" : "STRIDED");
+ } else {
+ ANM_CONFIG_LOG("Strategy cleared");
+ }
+}
+
+bool FrameAssemblyController::allocate_new_frame() {
+ context_.current_frame = frame_provider_->get_new_frame();
+ context_.frame_position = 0;
+
+ if (!context_.current_frame) {
+ ANM_LOG_ERROR("Frame allocation failed");
+ return false;
+ }
+
+ ANM_FRAME_TRACE("New frame allocated: size={}", context_.current_frame->get_size());
+ return true;
+}
+
+void FrameAssemblyController::release_current_frame() {
+ if (context_.current_frame) {
+ ANM_FRAME_TRACE("Releasing current frame back to pool: size={}",
+ context_.current_frame->get_size());
+ // Return frame to pool through frame provider
+ frame_provider_->return_frame_to_pool(context_.current_frame);
+ context_.current_frame.reset();
+ context_.frame_position = 0;
+ }
+}
+
+bool FrameAssemblyController::validate_frame_bounds(size_t required_bytes) const {
+ if (!context_.current_frame) {
+ return false;
+ }
+
+ return (context_.frame_position + required_bytes <= context_.current_frame->get_size());
+}
+
+bool FrameAssemblyController::transition_to_state(FrameState new_state) {
+ if (!FrameAssemblyHelper::is_valid_transition(context_.frame_state, new_state)) {
+ ANM_LOG_ERROR("Invalid state transition: {} -> {}",
+ FrameAssemblyHelper::state_to_string(context_.frame_state),
+ FrameAssemblyHelper::state_to_string(new_state));
+ return false;
+ }
+
+ context_.frame_state = new_state;
+ return true;
+}
+
+StateTransitionResult FrameAssemblyController::handle_idle_state(StateEvent event,
+ const RtpParams* rtp_params,
+ uint8_t* payload) {
+ ANM_STATE_TRACE("IDLE state handler: processing event {}, current_frame={}",
+ FrameAssemblyHelper::event_to_string(event),
+ context_.current_frame ? "allocated" : "null");
+
+ switch (event) {
+ case StateEvent::PACKET_ARRIVED: {
+ // Start receiving packets - allocate frame if we don't have one
+ auto result = create_success_result(FrameState::RECEIVING_PACKETS);
+ if (!context_.current_frame) {
+ result.should_allocate_new_frame = true;
+ }
+ return result;
+ }
+
+ case StateEvent::MARKER_DETECTED: {
+ // Single packet frame (edge case) - complete atomically
+ auto result = create_success_result(FrameState::IDLE);
+
+ if (!context_.current_frame) {
+ // No frame allocated yet - allocate one for this single packet
+ ANM_STATE_TRACE(
+ "IDLE single-packet frame: no frame allocated, requesting new frame for single packet");
+ result.should_allocate_new_frame = true;
+ // Don't complete/emit since we just allocated
+ return result;
+ } else {
+ // Frame exists - complete it and allocate new one
+ ANM_STATE_TRACE(
+ "IDLE single-packet frame: completing existing frame and requesting new one");
+ result.should_complete_frame = true;
+ result.should_emit_frame = true;
+ if (frame_provider_->has_available_frames()) {
+ result.should_allocate_new_frame = true;
+ } else {
+ ANM_LOG_WARN("Frame completed but pool is empty - staying in IDLE without new frame");
+ }
+ frames_completed_++;
+ return result;
+ }
+ }
+
+ case StateEvent::CORRUPTION_DETECTED:
+ return create_success_result(FrameState::ERROR_RECOVERY);
+
+ case StateEvent::STRATEGY_DETECTED:
+ // This should not happen in IDLE state - memory copy strategy detection requires packets
+ ANM_LOG_WARN("STRATEGY_DETECTED event received in IDLE state - treating as PACKET_ARRIVED");
+ return create_success_result(FrameState::RECEIVING_PACKETS);
+
+ default:
+ return create_error_result("Unexpected event in IDLE state");
+ }
+}
+
+StateTransitionResult FrameAssemblyController::handle_receiving_state(StateEvent event,
+ const RtpParams* rtp_params,
+ uint8_t* payload) {
+ ANM_STATE_TRACE("RECEIVING_PACKETS state handler: processing event {}, frame_position={}",
+ FrameAssemblyHelper::event_to_string(event),
+ context_.frame_position);
+
+ switch (event) {
+ case StateEvent::PACKET_ARRIVED:
+ // Continue receiving packets
+ return create_success_result(FrameState::RECEIVING_PACKETS);
+
+ case StateEvent::MARKER_DETECTED: {
+ // Frame completion triggered - complete atomically
+ // Only allocate new frame if pool has available frames
+ ANM_STATE_TRACE("RECEIVING_PACKETS->IDLE: marker detected, completing frame at position {}",
+ context_.frame_position);
+ auto result = create_success_result(FrameState::IDLE);
+ result.should_complete_frame = true;
+ result.should_emit_frame = true;
+ if (frame_provider_->has_available_frames()) {
+ result.should_allocate_new_frame = true;
+ } else {
+ ANM_LOG_WARN("Frame completed but pool is empty - staying in IDLE without new frame");
+ }
+ frames_completed_++;
+ return result;
+ }
+
+ case StateEvent::COPY_EXECUTED:
+ // This should not happen - COPY_EXECUTED events are not sent to state machine
+ ANM_LOG_WARN("COPY_EXECUTED received in RECEIVING_PACKETS state - this indicates dead code");
+ return create_success_result(FrameState::RECEIVING_PACKETS);
+
+ case StateEvent::CORRUPTION_DETECTED:
+ return create_success_result(FrameState::ERROR_RECOVERY);
+
+ case StateEvent::STRATEGY_DETECTED:
+ // Memory copy strategy detection completed while receiving packets
+ return create_success_result(FrameState::RECEIVING_PACKETS);
+
+ default:
+ return create_error_result("Unexpected event in RECEIVING_PACKETS state");
+ }
+}
+
+StateTransitionResult FrameAssemblyController::handle_error_recovery_state(
+ StateEvent event, const RtpParams* rtp_params, uint8_t* payload) {
+ ANM_STATE_TRACE(
+ "ERROR_RECOVERY state handler: processing event {}, current_frame={}, error_recoveries={}",
+ FrameAssemblyHelper::event_to_string(event),
+ context_.current_frame ? "allocated" : "null",
+ error_recoveries_);
+
+ switch (event) {
+ case StateEvent::RECOVERY_MARKER: {
+ // Recovery marker received - release any corrupted frame and try to start new one
+ // Release corrupted frame first to free the pool slot, then check availability
+ if (context_.current_frame) {
+ ANM_FRAME_TRACE("Releasing corrupted frame during recovery: size={}, ptr={}",
+ context_.current_frame->get_size(),
+ static_cast(context_.current_frame->get()));
+ ANM_STATE_TRACE("ERROR_RECOVERY: releasing corrupted frame before recovery completion");
+ release_current_frame();
+ }
+
+ // Only exit recovery if we can allocate a new frame
+ if (frame_provider_->has_available_frames()) {
+ error_recoveries_++;
+ ANM_LOG_INFO("Recovery marker (M-bit) received - exiting error recovery");
+ ANM_STATE_TRACE(
+ "ERROR_RECOVERY->IDLE: recovery successful, requesting new frame, total recoveries={}",
+ error_recoveries_);
+ auto result = create_success_result(FrameState::IDLE);
+ result.should_allocate_new_frame = true;
+ return result;
+ } else {
+ // Stay in recovery if no frames available
+ ANM_LOG_WARN("Recovery marker detected but frame pool is empty - staying in recovery");
+ auto result = create_success_result(FrameState::ERROR_RECOVERY);
+ return result;
+ }
+ }
+
+ case StateEvent::PACKET_ARRIVED: {
+ // Stay in recovery state, waiting for marker - packet discarded
+ ANM_STATE_TRACE("ERROR_RECOVERY: packet discarded, packets_processed={}", packets_processed_);
+ auto result = create_success_result(FrameState::ERROR_RECOVERY);
+ result.should_skip_memory_copy_processing = true;
+ return result;
+ }
+
+ case StateEvent::CORRUPTION_DETECTED:
+ // Additional corruption detected, stay in recovery
+ return create_success_result(FrameState::ERROR_RECOVERY);
+
+ case StateEvent::MARKER_DETECTED: {
+ // This should not happen in ERROR_RECOVERY - should be RECOVERY_MARKER instead
+ ANM_LOG_WARN(
+ "MARKER_DETECTED received in ERROR_RECOVERY state - treating as RECOVERY_MARKER");
+ // Release corrupted frame first to free the pool slot, then check availability
+ if (context_.current_frame) {
+ ANM_FRAME_TRACE("Releasing corrupted frame during recovery: size={}, ptr={}",
+ context_.current_frame->get_size(),
+ static_cast(context_.current_frame->get()));
+ ANM_STATE_TRACE("ERROR_RECOVERY: releasing corrupted frame before recovery completion");
+ release_current_frame();
+ }
+
+ // Only exit recovery if we can allocate a new frame
+ if (frame_provider_->has_available_frames()) {
+ error_recoveries_++;
+ auto result = create_success_result(FrameState::IDLE);
+ result.should_allocate_new_frame = true;
+ return result;
+ } else {
+ // Stay in recovery if no frames available
+ ANM_LOG_WARN("Marker detected but frame pool is empty - staying in recovery");
+ auto result = create_success_result(FrameState::ERROR_RECOVERY);
+ return result;
+ }
+ }
+
+ default:
+ return create_error_result("Unexpected event in ERROR_RECOVERY state");
+ }
+}
+
+StateTransitionResult FrameAssemblyController::create_success_result(FrameState new_state) {
+ StateTransitionResult result;
+ result.success = true;
+ result.new_frame_state = new_state;
+ return result;
+}
+
+StateTransitionResult FrameAssemblyController::create_error_result(
+ const std::string& error_message) {
+ StateTransitionResult result;
+ result.success = false;
+ result.error_message = error_message;
+ result.new_frame_state = FrameState::ERROR_RECOVERY;
+ return result;
+}
+
+// ========================================================================================
+// FrameAssemblyHelper Implementation
+// ========================================================================================
+
+std::string FrameAssemblyHelper::state_to_string(FrameState state) {
+ switch (state) {
+ case FrameState::IDLE:
+ return "IDLE";
+ case FrameState::RECEIVING_PACKETS:
+ return "RECEIVING_PACKETS";
+ case FrameState::ERROR_RECOVERY:
+ return "ERROR_RECOVERY";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+std::string FrameAssemblyHelper::event_to_string(StateEvent event) {
+ switch (event) {
+ case StateEvent::PACKET_ARRIVED:
+ return "PACKET_ARRIVED";
+ case StateEvent::MARKER_DETECTED:
+ return "MARKER_DETECTED";
+ case StateEvent::COPY_EXECUTED:
+ return "COPY_EXECUTED";
+ case StateEvent::CORRUPTION_DETECTED:
+ return "CORRUPTION_DETECTED";
+ case StateEvent::RECOVERY_MARKER:
+ return "RECOVERY_MARKER";
+ case StateEvent::STRATEGY_DETECTED:
+ return "STRATEGY_DETECTED";
+ case StateEvent::FRAME_COMPLETED:
+ return "FRAME_COMPLETED";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+bool FrameAssemblyHelper::is_valid_transition(FrameState from_state, FrameState to_state) {
+ // Define valid state transitions
+ switch (from_state) {
+ case FrameState::IDLE:
+ return (to_state == FrameState::RECEIVING_PACKETS || to_state == FrameState::ERROR_RECOVERY);
+
+ case FrameState::RECEIVING_PACKETS:
+ return (to_state == FrameState::RECEIVING_PACKETS || to_state == FrameState::IDLE ||
+ to_state == FrameState::ERROR_RECOVERY);
+
+ case FrameState::ERROR_RECOVERY:
+ return (to_state == FrameState::IDLE || to_state == FrameState::ERROR_RECOVERY);
+
+ default:
+ return false;
+ }
+}
+
+std::vector FrameAssemblyHelper::get_valid_events(FrameState state) {
+ switch (state) {
+ case FrameState::IDLE:
+ return {
+ StateEvent::PACKET_ARRIVED, StateEvent::MARKER_DETECTED, StateEvent::CORRUPTION_DETECTED};
+
+ case FrameState::RECEIVING_PACKETS:
+ return {StateEvent::PACKET_ARRIVED,
+ StateEvent::MARKER_DETECTED,
+ StateEvent::CORRUPTION_DETECTED,
+ StateEvent::STRATEGY_DETECTED};
+
+ case FrameState::ERROR_RECOVERY:
+ return {
+ StateEvent::RECOVERY_MARKER, StateEvent::PACKET_ARRIVED, StateEvent::CORRUPTION_DETECTED};
+
+ default:
+ return {};
+ }
+}
+
+} // namespace detail
+} // namespace holoscan::ops
diff --git a/operators/advanced_network_media/advanced_network_media_rx/frame_assembly_controller.h b/operators/advanced_network_media/advanced_network_media_rx/frame_assembly_controller.h
new file mode 100644
index 0000000000..fd838aa580
--- /dev/null
+++ b/operators/advanced_network_media/advanced_network_media_rx/frame_assembly_controller.h
@@ -0,0 +1,288 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef OPERATORS_ADVANCED_NETWORK_MEDIA_RX_FRAME_ASSEMBLY_CONTROLLER_H_
+#define OPERATORS_ADVANCED_NETWORK_MEDIA_RX_FRAME_ASSEMBLY_CONTROLLER_H_
+
+#include
+#include
+#include
+#include "frame_provider.h"
+#include "advanced_network/common.h"
+#include "../common/adv_network_media_common.h"
+#include "../common/frame_buffer.h"
+
+namespace holoscan::ops {
+
+namespace detail {
+
+// Import public interfaces for cleaner usage
+using holoscan::ops::IFrameProvider;
+
+// Forward declarations for internal implementation
+class IMemoryCopyStrategy;
+
+/**
+ * @brief Main frame processing states
+ */
+enum class FrameState {
+ IDLE, // No active frame, awaiting first packet
+ RECEIVING_PACKETS, // Actively receiving and processing packets
+ ERROR_RECOVERY // Frame corrupted, waiting for recovery marker
+};
+
+/**
+ * @brief State machine events that drive transitions
+ */
+enum class StateEvent {
+ PACKET_ARRIVED, // Regular packet received
+ MARKER_DETECTED, // M-bit packet received
+ COPY_EXECUTED, // Copy operation completed
+ CORRUPTION_DETECTED, // Frame corruption detected
+ RECOVERY_MARKER, // M-bit received during error recovery
+ STRATEGY_DETECTED, // Memory copy strategy detection completed
+ FRAME_COMPLETED // Frame processing finished
+};
+
+// Forward declaration - enum defined in memory_copy_strategies.h
+enum class CopyStrategy;
+
+/**
+ * @brief Stride information for strided copy operations
+ */
+struct StrideInfo {
+ size_t stride_size = 0; // Stride between packet payloads
+ size_t payload_size = 0; // Size of each payload
+};
+
+/**
+ * @brief Result of state machine transition
+ */
+struct StateTransitionResult {
+ bool success = false; // Whether transition succeeded
+ FrameState new_frame_state = FrameState::IDLE; // New state after transition
+ bool should_execute_copy = false; // Whether copy operation should be executed
+ bool should_complete_frame = false; // Whether frame completion should be triggered
+ bool should_emit_frame = false; // Whether frame should be emitted
+ bool should_allocate_new_frame = false; // Whether new frame should be allocated
+ bool should_skip_memory_copy_processing =
+ false; // Whether to skip memory copy processing (e.g., during recovery)
+ std::string error_message; // Error description if success=false
+};
+
+/**
+ * @brief Assembly controller context (internal state)
+ */
+struct FrameAssemblyContext {
+ FrameState frame_state = FrameState::IDLE;
+ size_t frame_position = 0; // Current byte position in frame
+ std::shared_ptr current_frame; // Active frame buffer
+};
+
+// Forward declaration - interface defined in memory_copy_strategies.h
+class IMemoryCopyStrategy;
+
+/**
+ * @brief Main frame processing state machine
+ *
+ * This class provides centralized state management for the entire packet-to-frame
+ * conversion process, coordinating between strategies, frame allocation, and
+ * error handling.
+ *
+ * @note Design Principle: This state machine focuses purely on state transitions
+ * and does not directly process packet data. All methods accept rtp_params
+ * and payload parameters for API consistency and future extensibility, but
+ * these parameters are currently unused. Actual packet processing is handled
+ * by the strategy layer (IMemoryCopyStrategy implementations).
+ */
+class FrameAssemblyController {
+ public:
+ /**
+ * @brief Constructor
+ * @param frame_provider Provider for frame allocation
+ */
+ explicit FrameAssemblyController(std::shared_ptr frame_provider);
+
+ /**
+ * @brief Process state machine event
+ * @param event Event to process
+ * @param rtp_params RTP packet parameters (currently unused, reserved for future use)
+ * @param payload Packet payload (currently unused, reserved for future use)
+ * @return Transition result with actions to execute
+ *
+ * @note This state machine focuses purely on state transitions based on events.
+ * Packet data processing is handled by the strategy layer. The rtp_params
+ * and payload parameters are provided for API consistency and future
+ * extensibility but are not currently used in state transition logic.
+ */
+ StateTransitionResult process_event(StateEvent event, const RtpParams* rtp_params = nullptr,
+ uint8_t* payload = nullptr);
+
+ /**
+ * @brief Reset assembly controller to initial state
+ */
+ void reset();
+
+ /**
+ * @brief Get current frame state
+ * @return Current state of the assembly controller
+ */
+ FrameState get_frame_state() const { return context_.frame_state; }
+
+ /**
+ * @brief Get current frame buffer
+ * @return Current frame or nullptr
+ */
+ std::shared_ptr get_current_frame() const { return context_.current_frame; }
+
+ /**
+ * @brief Get current frame position
+ * @return Current byte position in frame
+ */
+ size_t get_frame_position() const { return context_.frame_position; }
+
+ /**
+ * @brief Advance frame position (with bounds checking)
+ * @param bytes Number of bytes to advance
+ * @return True if advancement succeeded
+ */
+ bool advance_frame_position(size_t bytes);
+
+ /**
+ * @brief Set strategy (for compatibility with old interface)
+ * @param strategy Strategy to use (can be nullptr)
+ */
+ void set_strategy(std::shared_ptr strategy);
+
+ /**
+ * @brief Get currently set strategy
+ * @return Current strategy or nullptr
+ */
+ std::shared_ptr get_strategy() const { return strategy_; }
+
+ /**
+ * @brief Allocate new frame for processing
+ * @return True if allocation succeeded
+ */
+ bool allocate_new_frame();
+
+ /**
+ * @brief Release current frame back to the pool
+ */
+ void release_current_frame();
+
+ private:
+ /**
+ * @brief Validate frame bounds for operations
+ * @param required_bytes Number of bytes that will be written
+ * @return True if operation is safe
+ */
+ bool validate_frame_bounds(size_t required_bytes) const;
+
+ /**
+ * @brief Transition to new state with validation
+ * @param new_state Target state
+ * @return True if transition is valid
+ */
+ bool transition_to_state(FrameState new_state);
+
+ /**
+ * @brief Handle IDLE state events
+ * @param event Event to process
+ * @param rtp_params RTP parameters
+ * @param payload Packet payload
+ * @return Transition result
+ */
+ StateTransitionResult handle_idle_state(StateEvent event, const RtpParams* rtp_params,
+ uint8_t* payload);
+
+ /**
+ * @brief Handle RECEIVING_PACKETS state events
+ * @param event Event to process
+ * @param rtp_params RTP parameters
+ * @param payload Packet payload
+ * @return Transition result
+ */
+ StateTransitionResult handle_receiving_state(StateEvent event, const RtpParams* rtp_params,
+ uint8_t* payload);
+
+ /**
+ * @brief Handle ERROR_RECOVERY state events
+ * @param event Event to process
+ * @param rtp_params RTP parameters
+ * @param payload Packet payload
+ * @return Transition result
+ */
+ StateTransitionResult handle_error_recovery_state(StateEvent event, const RtpParams* rtp_params,
+ uint8_t* payload);
+
+ /**
+ * @brief Create successful transition result
+ * @param new_state New state after transition
+ * @return Success result
+ */
+ StateTransitionResult create_success_result(FrameState new_state);
+
+ /**
+ * @brief Create error transition result
+ * @param error_message Error description
+ * @return Error result
+ */
+ StateTransitionResult create_error_result(const std::string& error_message);
+
+ private:
+ // Core components
+ std::shared_ptr frame_provider_;
+ std::shared_ptr strategy_;
+
+ // Assembly controller context
+ FrameAssemblyContext context_;
+
+ // Statistics and debugging
+ size_t packets_processed_ = 0;
+ size_t frames_completed_ = 0;
+ size_t error_recoveries_ = 0;
+};
+
+/**
+ * @brief Utility functions for frame assembly operations
+ */
+class FrameAssemblyHelper {
+ public:
+ /**
+ * @brief Convert state to string for logging
+ * @param state Frame state
+ * @return String representation
+ */
+ static std::string state_to_string(FrameState state);
+
+ /**
+ * @brief Convert event to string for logging
+ * @param event State event
+ * @return String representation
+ */
+ static std::string event_to_string(StateEvent event);
+
+ /**
+ * @brief Check if state transition is valid
+ * @param from_state Source state
+ * @param to_state Target state
+ * @return True if transition is allowed
+ */
+ static bool is_valid_transition(FrameState from_state, FrameState to_state);
+
+ /**
+ * @brief Get expected events for a given state
+ * @param state Current state
+ * @return List of valid events for the state
+ */
+ static std::vector get_valid_events(FrameState state);
+};
+
+} // namespace detail
+
+} // namespace holoscan::ops
+
+#endif // OPERATORS_ADVANCED_NETWORK_MEDIA_RX_FRAME_ASSEMBLY_CONTROLLER_H_
diff --git a/operators/advanced_network_media/advanced_network_media_rx/frame_provider.h b/operators/advanced_network_media/advanced_network_media_rx/frame_provider.h
new file mode 100644
index 0000000000..304c727d72
--- /dev/null
+++ b/operators/advanced_network_media/advanced_network_media_rx/frame_provider.h
@@ -0,0 +1,53 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef OPERATORS_ADVANCED_NETWORK_MEDIA_RX_FRAME_PROVIDER_H_
+#define OPERATORS_ADVANCED_NETWORK_MEDIA_RX_FRAME_PROVIDER_H_
+
+#include
+#include "../common/frame_buffer.h"
+
+namespace holoscan::ops {
+
+/**
+ * @brief Interface for frame buffer allocation and management
+ *
+ * This interface abstracts frame buffer allocation, allowing different
+ * components to provide frame buffers without coupling to specific
+ * allocation strategies. Implementations typically handle memory pool
+ * management and frame lifecycle.
+ */
+class IFrameProvider {
+ public:
+ virtual ~IFrameProvider() = default;
+
+ /**
+ * @brief Get a new frame buffer for processing
+ * @return Frame buffer or nullptr if allocation failed
+ */
+ virtual std::shared_ptr get_new_frame() = 0;
+
+ /**
+ * @brief Get expected frame size
+ * @return Frame size in bytes
+ */
+ virtual size_t get_frame_size() const = 0;
+
+ /**
+ * @brief Check if frames are available for allocation
+ * @return True if frames are available, false if pool is empty
+ */
+ virtual bool has_available_frames() const = 0;
+
+ /**
+ * @brief Return a frame back to the pool
+ * @param frame Frame to return to pool
+ */
+ virtual void return_frame_to_pool(std::shared_ptr frame) = 0;
+};
+
+} // namespace holoscan::ops
+
+#endif // OPERATORS_ADVANCED_NETWORK_MEDIA_RX_FRAME_PROVIDER_H_
diff --git a/operators/advanced_network_media/advanced_network_media_rx/media_frame_assembler.cpp b/operators/advanced_network_media/advanced_network_media_rx/media_frame_assembler.cpp
new file mode 100644
index 0000000000..5ec754d026
--- /dev/null
+++ b/operators/advanced_network_media/advanced_network_media_rx/media_frame_assembler.cpp
@@ -0,0 +1,693 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "media_frame_assembler.h"
+#include "../common/adv_network_media_common.h"
+#include
+#include
+#include
+#include
+
+namespace holoscan::ops {
+
+// Import detail namespace classes for convenience
+using detail::CopyStrategy;
+using detail::FrameAssemblyController;
+using detail::FrameState;
+using detail::IMemoryCopyStrategy;
+using detail::MemoryCopyStrategyDetector;
+using detail::StateEvent;
+using detail::StrategyFactory;
+
+// RTP sequence number gap threshold for detecting potential buffer wraparound
+constexpr int32_t kRtpSequenceGapWraparoundThreshold = 16384; // 2^14
+
+// Helper functions to convert internal types to strings for statistics
+std::string convert_strategy_to_string(CopyStrategy internal_strategy) {
+ switch (internal_strategy) {
+ case CopyStrategy::CONTIGUOUS:
+ return "CONTIGUOUS";
+ case CopyStrategy::STRIDED:
+ return "STRIDED";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+std::string convert_state_to_string(FrameState internal_state) {
+ switch (internal_state) {
+ case FrameState::IDLE:
+ return "IDLE";
+ case FrameState::RECEIVING_PACKETS:
+ return "RECEIVING_PACKETS";
+
+ case FrameState::ERROR_RECOVERY:
+ return "ERROR_RECOVERY";
+ default:
+ return "IDLE";
+ }
+}
+
+// ========================================================================================
+// MediaFrameAssembler Implementation
+// ========================================================================================
+
+MediaFrameAssembler::MediaFrameAssembler(std::shared_ptr frame_provider,
+ const AssemblerConfiguration& config)
+ : config_(config) {
+ // Validate configuration
+ if (!AssemblerConfigurationHelper::validate_configuration(config_)) {
+ throw std::invalid_argument("Invalid assembler configuration");
+ }
+
+ // Create frame assembly controller
+ assembly_controller_ = std::make_unique(frame_provider);
+
+ // Create memory copy strategy detector if needed
+ if (config_.enable_memory_copy_strategy_detection &&
+ !config_.force_contiguous_memory_copy_strategy) {
+ memory_copy_strategy_detector_ = StrategyFactory::create_detector();
+ memory_copy_strategy_detection_active_ = true;
+ } else if (config_.force_contiguous_memory_copy_strategy) {
+ // Create contiguous memory copy strategy immediately
+ current_copy_strategy_ = StrategyFactory::create_contiguous_strategy(
+ config_.source_memory_type, config_.destination_memory_type);
+ setup_memory_copy_strategy(std::move(current_copy_strategy_));
+ memory_copy_strategy_detection_active_ = false;
+ }
+
+ ANM_CONFIG_LOG("MediaFrameAssembler initialized: strategy_detection={}, force_contiguous={}",
+ config_.enable_memory_copy_strategy_detection,
+ config_.force_contiguous_memory_copy_strategy);
+}
+
+void MediaFrameAssembler::set_completion_handler(std::shared_ptr handler) {
+ completion_handler_ = handler;
+}
+
+void MediaFrameAssembler::configure_burst_parameters(size_t header_stride_size,
+ size_t payload_stride_size, bool hds_enabled) {
+ config_.header_stride_size = header_stride_size;
+ config_.payload_stride_size = payload_stride_size;
+ config_.hds_enabled = hds_enabled;
+
+ // Configure memory copy strategy detector if active
+ if (memory_copy_strategy_detector_) {
+ memory_copy_strategy_detector_->configure_burst_parameters(
+ header_stride_size, payload_stride_size, hds_enabled);
+ }
+
+ ANM_CONFIG_LOG("Burst parameters configured: header_stride={}, payload_stride={}, hds={}",
+ header_stride_size,
+ payload_stride_size,
+ hds_enabled);
+}
+
+void MediaFrameAssembler::configure_memory_types(nvidia::gxf::MemoryStorageType source_type,
+ nvidia::gxf::MemoryStorageType destination_type) {
+ config_.source_memory_type = source_type;
+ config_.destination_memory_type = destination_type;
+
+ ANM_CONFIG_LOG("Memory types configured: source={}, destination={}",
+ static_cast(source_type),
+ static_cast(destination_type));
+
+ // If memory copy strategy is already set up, we may need to recreate it with new memory types
+ if (current_copy_strategy_ && !memory_copy_strategy_detection_active_) {
+ CopyStrategy strategy_type = current_copy_strategy_->get_type();
+
+ if (strategy_type == CopyStrategy::CONTIGUOUS) {
+ current_copy_strategy_ =
+ StrategyFactory::create_contiguous_strategy(source_type, destination_type);
+ }
+ // Note: For strided memory copy strategy, we would need the stride info, so we'd trigger
+ // redetection
+
+ setup_memory_copy_strategy(std::move(current_copy_strategy_));
+ }
+}
+
+void MediaFrameAssembler::process_incoming_packet(const RtpParams& rtp_params, uint8_t* payload) {
+ try {
+ // Update statistics
+ update_statistics(StateEvent::PACKET_ARRIVED);
+ update_packet_statistics(rtp_params);
+
+ // Determine appropriate event for this packet
+ StateEvent event = determine_event(rtp_params, payload);
+
+ // Check current state before processing for recovery completion detection
+ FrameState previous_state = assembly_controller_->get_frame_state();
+
+ // Process event through assembly controller
+ auto result = assembly_controller_->process_event(event, &rtp_params, payload);
+
+ if (!result.success) {
+ ANM_FRAME_ERROR(statistics_.current_frame_number,
+ "Assembly controller processing failed: {}",
+ result.error_message);
+ handle_error_recovery(result.error_message);
+ return;
+ }
+
+ // Log error recovery state changes
+ if (result.new_frame_state == FrameState::ERROR_RECOVERY) {
+ ANM_STATE_LOG("Error recovery active - discarding packets until M-bit marker received");
+ } else if (previous_state == FrameState::ERROR_RECOVERY &&
+ result.new_frame_state == FrameState::IDLE) {
+ ANM_STATE_LOG("Error recovery completed successfully - resuming normal frame processing");
+ }
+
+ // Execute actions based on assembly controller result
+ execute_actions(result, rtp_params, payload);
+
+ ANM_PACKET_TRACE("Packet processed successfully: seq={}, event={}, new_state={}",
+ rtp_params.sequence_number,
+ static_cast(event),
+ static_cast(result.new_frame_state));
+
+ // Special logging for recovery marker processing
+ if (event == StateEvent::RECOVERY_MARKER) {
+ ANM_STATE_LOG("RECOVERY_MARKER event processed - should have exited error recovery");
+ }
+ } catch (const std::exception& e) {
+ std::string error_msg = std::string("Exception in packet processing: ") + e.what();
+ ANM_FRAME_ERROR(statistics_.current_frame_number, "{}", error_msg);
+ handle_error_recovery(error_msg);
+ }
+}
+
+void MediaFrameAssembler::force_memory_copy_strategy_redetection() {
+ if (memory_copy_strategy_detector_) {
+ memory_copy_strategy_detector_->reset();
+ memory_copy_strategy_detection_active_ = true;
+ current_copy_strategy_.reset();
+ assembly_controller_->set_strategy(nullptr);
+ statistics_.memory_copy_strategy_redetections++;
+
+ ANM_CONFIG_LOG("Memory copy strategy redetection forced");
+ }
+}
+
+void MediaFrameAssembler::reset() {
+ assembly_controller_->reset();
+
+ if (memory_copy_strategy_detector_) {
+ memory_copy_strategy_detector_->reset();
+ memory_copy_strategy_detection_active_ = config_.enable_memory_copy_strategy_detection &&
+ !config_.force_contiguous_memory_copy_strategy;
+ }
+
+ if (config_.force_contiguous_memory_copy_strategy) {
+ current_copy_strategy_ = StrategyFactory::create_contiguous_strategy(
+ config_.source_memory_type, config_.destination_memory_type);
+ setup_memory_copy_strategy(std::move(current_copy_strategy_));
+ } else {
+ current_copy_strategy_.reset();
+ }
+
+ // Reset statistics (keep cumulative counters)
+ statistics_.current_strategy = "UNKNOWN";
+ statistics_.current_frame_state = "IDLE";
+ statistics_.last_error.clear();
+
+ ANM_CONFIG_LOG("Media Frame assembler has been reset to initial state");
+}
+
+MediaFrameAssembler::Statistics MediaFrameAssembler::get_statistics() const {
+ // Update current state information
+ statistics_.current_frame_state =
+ convert_state_to_string(assembly_controller_->get_frame_state());
+
+ if (current_copy_strategy_) {
+ statistics_.current_strategy = convert_strategy_to_string(current_copy_strategy_->get_type());
+ }
+
+ return statistics_;
+}
+
+bool MediaFrameAssembler::has_accumulated_data() const {
+ return current_copy_strategy_ && current_copy_strategy_->has_accumulated_data();
+}
+
+std::shared_ptr MediaFrameAssembler::get_current_frame() const {
+ return assembly_controller_->get_current_frame();
+}
+
+size_t MediaFrameAssembler::get_frame_position() const {
+ return assembly_controller_->get_frame_position();
+}
+
+StateEvent MediaFrameAssembler::determine_event(const RtpParams& rtp_params, uint8_t* payload) {
+ // Check for M-bit marker first
+ if (rtp_params.m_bit) {
+ if (assembly_controller_->get_frame_state() == FrameState::ERROR_RECOVERY) {
+ ANM_STATE_LOG("M-bit detected during error recovery - generating RECOVERY_MARKER event");
+ return StateEvent::RECOVERY_MARKER;
+ } else {
+ return StateEvent::MARKER_DETECTED;
+ }
+ }
+
+ // Validate packet integrity
+ if (!validate_packet_integrity(rtp_params)) {
+ return StateEvent::CORRUPTION_DETECTED;
+ }
+
+ // Check if we're in memory copy strategy detection phase
+ if (memory_copy_strategy_detection_active_ && memory_copy_strategy_detector_) {
+ if (memory_copy_strategy_detector_->collect_packet(
+ rtp_params, payload, rtp_params.payload_size)) {
+ // Enough packets collected, attempt memory copy strategy detection
+ auto detected_strategy = memory_copy_strategy_detector_->detect_strategy(
+ config_.source_memory_type, config_.destination_memory_type);
+
+ if (detected_strategy) {
+ setup_memory_copy_strategy(std::move(detected_strategy));
+ memory_copy_strategy_detection_active_ = false;
+ return StateEvent::STRATEGY_DETECTED;
+ } else {
+ // Detection failed, will retry with more packets
+ return StateEvent::PACKET_ARRIVED;
+ }
+ } else {
+ // Still collecting packets for detection
+ return StateEvent::PACKET_ARRIVED;
+ }
+ }
+
+ return StateEvent::PACKET_ARRIVED;
+}
+
+void MediaFrameAssembler::execute_actions(const StateTransitionResult& result,
+ const RtpParams& rtp_params, uint8_t* payload) {
+ ANM_FRAME_TRACE("execute_actions: should_emit_frame={}, should_complete_frame={}, new_state={}",
+ result.should_emit_frame,
+ result.should_complete_frame,
+ static_cast(result.new_frame_state));
+ // Memory copy strategy processing (skip during error recovery as indicated by state machine)
+ if (result.new_frame_state == FrameState::RECEIVING_PACKETS &&
+ !result.should_skip_memory_copy_processing && current_copy_strategy_ && payload) {
+ StateEvent copy_strategy_result = current_copy_strategy_->process_packet(
+ *assembly_controller_, payload, rtp_params.payload_size);
+
+ if (copy_strategy_result == StateEvent::CORRUPTION_DETECTED) {
+ handle_error_recovery("Memory copy strategy detected corruption");
+ return;
+ } else if (copy_strategy_result == StateEvent::COPY_EXECUTED) {
+ ANM_MEMCOPY_TRACE("Memory copy strategy executed operation successfully");
+ }
+ }
+
+ // Execute pending copies if requested
+ if (result.should_execute_copy && current_copy_strategy_) {
+ if (current_copy_strategy_->has_accumulated_data()) {
+ StateEvent copy_result =
+ current_copy_strategy_->execute_accumulated_copy(*assembly_controller_);
+ if (copy_result == StateEvent::CORRUPTION_DETECTED) {
+ handle_error_recovery("Copy execution failed");
+ return;
+ }
+ }
+ }
+
+ // Handle frame completion
+ if (result.should_complete_frame) {
+ handle_frame_completion();
+ }
+
+ // Handle frame emission
+ if (result.should_emit_frame) {
+ auto frame = assembly_controller_->get_current_frame();
+ if (frame && completion_handler_) {
+ ANM_FRAME_TRACE("Emitting frame to completion handler");
+ completion_handler_->on_frame_completed(frame);
+ // Note: frames_completed is incremented in state controller atomic operation
+ }
+ }
+
+ // Handle new frame allocation (atomic with frame completion)
+ if (result.should_allocate_new_frame) {
+ ANM_FRAME_TRACE("Allocating new frame for next packet sequence");
+ if (!assembly_controller_->allocate_new_frame()) {
+ ANM_FRAME_ERROR(statistics_.current_frame_number,
+ "Failed to allocate new frame after completion");
+ } else {
+ // Starting a new frame - update statistics
+ ANM_STATS_UPDATE(statistics_.current_frame_number++; statistics_.frames_started++;
+ statistics_.packets_in_current_frame = 0;
+ statistics_.bytes_in_current_frame = 0;
+ statistics_.first_sequence_in_frame = 0;);
+
+ ANM_STATS_TRACE("Starting new frame {}", statistics_.current_frame_number);
+ }
+ }
+}
+
+bool MediaFrameAssembler::handle_memory_copy_strategy_detection(const RtpParams& rtp_params,
+ uint8_t* payload) {
+ if (!memory_copy_strategy_detection_active_ || !memory_copy_strategy_detector_) {
+ return true; // No detection needed or memory copy strategy already available
+ }
+
+ // Collect packet for analysis
+ if (memory_copy_strategy_detector_->collect_packet(
+ rtp_params, payload, rtp_params.payload_size)) {
+ // Attempt memory copy strategy detection
+ auto detected_strategy = memory_copy_strategy_detector_->detect_strategy(
+ config_.source_memory_type, config_.destination_memory_type);
+
+ if (detected_strategy) {
+ setup_memory_copy_strategy(std::move(detected_strategy));
+ memory_copy_strategy_detection_active_ = false;
+ return true;
+ } else {
+ ANM_STRATEGY_LOG("Strategy detection failed, will retry");
+ return false;
+ }
+ }
+
+ ANM_STRATEGY_LOG("Still collecting packets for strategy detection ({}/{})",
+ memory_copy_strategy_detector_->get_packets_analyzed(),
+ MemoryCopyStrategyDetector::DETECTION_PACKET_COUNT);
+ return false;
+}
+
+void MediaFrameAssembler::setup_memory_copy_strategy(
+ std::unique_ptr strategy) {
+ current_copy_strategy_ = std::move(strategy);
+
+ // Note: For the old interface compatibility, we would set the memory copy strategy in the
+ // assembly controller but since IMemoryCopyStrategy is different from IPacketCopyStrategy, we
+ // manage it here
+
+ if (current_copy_strategy_) {
+ ANM_CONFIG_LOG(
+ "Memory copy strategy setup completed: {}",
+ current_copy_strategy_->get_type() == CopyStrategy::CONTIGUOUS ? "CONTIGUOUS" : "STRIDED");
+ }
+}
+
+bool MediaFrameAssembler::validate_packet_integrity(const RtpParams& rtp_params) {
+ auto frame = assembly_controller_->get_current_frame();
+ if (!frame) {
+ return false;
+ }
+
+ int64_t bytes_left = frame->get_size() - assembly_controller_->get_frame_position();
+
+ if (bytes_left < 0) {
+ return false; // Frame overflow
+ }
+
+ bool frame_full = (bytes_left == 0);
+ if (frame_full && !rtp_params.m_bit) {
+ return false; // Frame full but no marker
+ }
+
+ return true;
+}
+
+void MediaFrameAssembler::handle_frame_completion() {
+ // Execute any accumulated copy operations
+ if (current_copy_strategy_ && current_copy_strategy_->has_accumulated_data()) {
+ StateEvent copy_result =
+ current_copy_strategy_->execute_accumulated_copy(*assembly_controller_);
+ if (copy_result == StateEvent::CORRUPTION_DETECTED) {
+ handle_error_recovery("Final copy operation failed");
+ return;
+ }
+ }
+
+ // Update frame completion statistics
+ ANM_STATS_UPDATE(statistics_.frames_completed++; statistics_.frames_completed_successfully++;
+ statistics_.last_frame_completion_time = std::chrono::steady_clock::now(););
+
+ ANM_STATS_TRACE("Frame {} completed successfully - {} packets, {} bytes",
+ statistics_.current_frame_number,
+ statistics_.packets_in_current_frame,
+ statistics_.bytes_in_current_frame);
+
+ // Reset current frame statistics for next frame
+ ANM_STATS_UPDATE(statistics_.packets_in_current_frame = 0; statistics_.bytes_in_current_frame = 0;
+ statistics_.first_sequence_in_frame = 0;);
+}
+
+void MediaFrameAssembler::handle_error_recovery(const std::string& error_message) {
+ ANM_STATS_UPDATE(statistics_.last_error = error_message; statistics_.errors_recovered++;
+ statistics_.error_recovery_cycles++;
+ statistics_.frames_dropped++;
+ statistics_.last_error_time = std::chrono::steady_clock::now(););
+
+ // Log dropped frame information
+ ANM_FRAME_WARN(
+ statistics_.current_frame_number,
+ "Error recovery initiated: {} - discarding {} packets, {} bytes - waiting for M-bit marker",
+ error_message,
+ statistics_.packets_in_current_frame,
+ statistics_.bytes_in_current_frame);
+
+ if (completion_handler_) {
+ completion_handler_->on_frame_error(error_message);
+ }
+
+ // Reset memory copy strategy if needed
+ if (current_copy_strategy_) {
+ current_copy_strategy_->reset();
+ }
+
+ // Reset current frame statistics since frame is being dropped
+ ANM_STATS_UPDATE(statistics_.packets_in_current_frame = 0; statistics_.bytes_in_current_frame = 0;
+ statistics_.first_sequence_in_frame = 0;);
+}
+
+void MediaFrameAssembler::update_statistics(StateEvent event) {
+ switch (event) {
+ case StateEvent::PACKET_ARRIVED:
+ statistics_.packets_processed++;
+ ANM_STATS_UPDATE(statistics_.packets_in_current_frame++);
+ break;
+ case StateEvent::STRATEGY_DETECTED:
+ ANM_STATS_UPDATE(statistics_.memory_copy_strategy_redetections++);
+ break;
+ case StateEvent::MARKER_DETECTED:
+ // Frame completion handled elsewhere
+ break;
+ case StateEvent::RECOVERY_MARKER:
+ // Recovery completion handled elsewhere
+ break;
+ case StateEvent::CORRUPTION_DETECTED:
+ ANM_STATS_UPDATE(statistics_.memory_corruption_errors++);
+ break;
+ default:
+ break;
+ }
+}
+
+void MediaFrameAssembler::update_packet_statistics(const RtpParams& rtp_params) {
+ ANM_STATS_UPDATE(
+ // Check for sequence discontinuity (only if we have a previous sequence number)
+ if (statistics_.last_sequence_number != 0 && statistics_.packets_processed > 1) {
+ uint32_t expected_seq = statistics_.last_sequence_number + 1;
+ if (rtp_params.sequence_number != expected_seq) {
+ statistics_.sequence_discontinuities++;
+
+ // Check for potential buffer overflow (large gaps)
+ int32_t gap =
+ static_cast(rtp_params.sequence_number) - static_cast