diff --git a/applications/CMakeLists.txt b/applications/CMakeLists.txt index a2db3e2473..f59ac45500 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_bench/README.md b/applications/adv_networking_bench/README.md index c3e13b21da..7c6bc26262 100644 --- a/applications/adv_networking_bench/README.md +++ b/applications/adv_networking_bench/README.md @@ -101,7 +101,7 @@ To run with a different configuration file than the default `adv_networking_benc ./adv_networking_bench adv_networking_bench_gpunetio_tx_rx.yaml # Run with Rivermax configuration -./adv_networking_bench adv_networking_bench_rmax_rx.yaml +./adv_networking_bench adv_networking_bench_rivermax_rx.yaml ``` For Rivermax, you will need extra flags: @@ -117,7 +117,7 @@ For Rivermax, you will need extra flags: " # Run with Rivermax configuration -./adv_networking_bench adv_networking_bench_rmax_rx.yaml +./adv_networking_bench adv_networking_bench_rivermax_rx.yaml ``` ### Test Instructions diff --git a/applications/adv_networking_bench/adv_networking_bench_rmax_rx.yaml b/applications/adv_networking_bench/adv_networking_bench_rivermax_rx.yaml similarity index 69% rename from applications/adv_networking_bench/adv_networking_bench_rmax_rx.yaml rename to applications/adv_networking_bench/adv_networking_bench_rivermax_rx.yaml index 79adaf26f1..0646087e43 100644 --- a/applications/adv_networking_bench/adv_networking_bench_rmax_rx.yaml +++ b/applications/adv_networking_bench/adv_networking_bench_rivermax_rx.yaml @@ -24,7 +24,7 @@ advanced_network: cfg: version: 1 manager: "rivermax" - master_core: 5 # Master CPU core + master_core: 6 # Master CPU core debug: 1 log_level: "warn" loopback: "" @@ -46,34 +46,28 @@ advanced_network: address: cc:00.1 rx: queues: - - name: "rx_q_1" - id: 1 - cpu_core: "11,12,13" + - name: "rx_q_0" + id: 0 + cpu_core: "10" batch_size: 4320 memory_regions: - "Data_RX_CPU" - "Data_RX_GPU" - rmax_rx_settings: - memory_registration: true - #allocator_type: "huge_page_2mb" + rivermax_rx_settings: + settings_type: "ipo_receiver" verbose: true - max_path_diff_us: 100 - ext_seq_num: true - sleep_between_operations_us: 100 - local_ip_addresses: - - 2.1.0.12 - - 2.1.0.12 - source_ip_addresses: - - 2.1.0.2 - - 2.1.0.2 - destination_ip_addresses: - - 224.1.1.1 - - 224.1.1.2 - destination_ports: - - 50001 - - 50001 - rx_stats_period_report_ms: 3000 - send_packet_ext_info: true + rx_threads: + - thread_id: 0 + network_settings: + - stream_id: 0 + local_ip_addresses: + - 2.1.0.11 + source_ip_addresses: + - 2.1.0.12 + destination_ip_addresses: + - 224.1.1.1 + destination_ports: + - 50000 bench_rx: interface_name: "rx_port" # Name of the RX port from the advanced_network config diff --git a/applications/adv_networking_bench/adv_networking_bench_rivermax_tx.yaml b/applications/adv_networking_bench/adv_networking_bench_rivermax_tx.yaml new file mode 100644 index 0000000000..68a65ab5dd --- /dev/null +++ b/applications/adv_networking_bench/adv_networking_bench_rivermax_tx.yaml @@ -0,0 +1,95 @@ +%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 + +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: 1200 + + interfaces: + - name: "tx_port" + address: cc:00.1 + tx: + queues: + - name: "tx_q_1" + id: 0 + cpu_core: "14" + 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" + use_internal_memory_pool: true + #allocator_type: "huge_page_2mb" + verbose: true + sleep_between_operations: false + stats_report_interval_ms: 1000 + send_packet_ext_info: true + video_format: YCbCr-4:2:2 + bit_depth: 10 + frame_width: 1920 + frame_height: 1080 + frame_rate: 60 + dummy_sender: false + tx_threads: + - thread_id: 0 + network_settings: + - stream_id: 0 + local_ip_address: 1.1.164.19 + destination_ip_address: 224.1.50.1 + destination_port: 50505 + - thread_id: 1 + network_settings: + - stream_id: 1 + local_ip_address: 1.1.164.19 + destination_ip_address: 224.1.50.2 + destination_port: 50505 + +bench_tx: + interface_name: cc:00.1 + sample_format: YUV422 + frame_width: 1920 + frame_height: 1080 + bit_depth: 10 + file_path: "./build/adv_networking_bench/applications/adv_networking_bench/cpp/input_file.ycbcr" + payload_memory: "device" + diff --git a/applications/adv_networking_bench/adv_networking_bench_rivermax_tx_rx.yaml b/applications/adv_networking_bench/adv_networking_bench_rivermax_tx_rx.yaml new file mode 100644 index 0000000000..f652b2ecda --- /dev/null +++ b/applications/adv_networking_bench/adv_networking_bench_rivermax_tx_rx.yaml @@ -0,0 +1,133 @@ +%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 + +advanced_network: + cfg: + version: 1 + manager: "rivermax" + master_core: 6 # Master CPU core + debug: 1 + log_level: "debug" + + memory_regions: + - name: "Data_RX_CPU" + kind: "huge" + affinity: 0 + num_bufs: 43200 + buf_size: 20 + - name: "Data_RX_GPU" + kind: "device" + affinity: 0 + num_bufs: 43200 + buf_size: 1200 + - 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: 1200 + + interfaces: + - name: "rx_port" + address: cc:00.1 + rx: + queues: + - name: "rx_q_0" + id: 0 + cpu_core: "10" + batch_size: 4320 + 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 + - 2.1.0.12 + source_ip_addresses: + - 2.1.0.2 + - 2.1.0.2 + destination_ip_addresses: + - 224.1.1.1 + - 224.1.1.2 + destination_ports: + - 50001 + - 50001 + stats_report_interval_ms: 1000 + send_packet_ext_info: true + tx: + queues: + - name: "tx_q_1" + id: 0 + cpu_core: "14" + 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.3 + destination_port: 50001 + stats_report_interval_ms: 1000 + send_packet_ext_info: true + video_format: YCbCr-4:2:2 + bit_depth: 10 + frame_width: 1920 + frame_height: 1080 + frame_rate: 60 + dummy_sender: false + +bench_rx: + interface_name: "rx_port" # Name of the RX port from the advanced_network config + gpu_direct: true # Set to true if using a GPU region for the Rx queues. + split_boundary: true # Whether header and data is split (Header to CPU) + batch_size: 8640 + max_packet_size: 1220 + header_size: 20 + +bench_tx: + interface_name: cc:00.1 + sample_format: YUV422 + frame_width: 1920 + frame_height: 1080 + bit_depth: 10 + file_path: "./build/adv_networking_bench/applications/adv_networking_bench/cpp/input_file.ycbcr" + payload_memory: "device" + diff --git a/applications/adv_networking_bench/cpp/CMakeLists.txt b/applications/adv_networking_bench/cpp/CMakeLists.txt index 91b0001151..e2a14e45df 100644 --- a/applications/adv_networking_bench/cpp/CMakeLists.txt +++ b/applications/adv_networking_bench/cpp/CMakeLists.txt @@ -91,11 +91,23 @@ add_custom_target(adv_networking_bench_gpunetio_tx_rx_yaml ) add_dependencies(adv_networking_bench adv_networking_bench_gpunetio_tx_rx_yaml) -add_custom_target(adv_networking_bench_rmax_rx_yaml - COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/../adv_networking_bench_rmax_rx.yaml" ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../adv_networking_bench_rmax_rx.yaml" +add_custom_target(adv_networking_bench_rivermax_rx_yaml + COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/../adv_networking_bench_rivermax_rx.yaml" ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../adv_networking_bench_rivermax_rx.yaml" ) -add_dependencies(adv_networking_bench adv_networking_bench_rmax_rx_yaml) +add_dependencies(adv_networking_bench adv_networking_bench_rivermax_rx_yaml) + +add_custom_target(adv_networking_bench_rivermax_tx_yaml + COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/../adv_networking_bench_rivermax_tx.yaml" ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../adv_networking_bench_rivermax_tx.yaml" +) +add_dependencies(adv_networking_bench adv_networking_bench_rivermax_tx_yaml) + +add_custom_target(adv_networking_bench_rivermax_tx_rx_yaml + COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/../adv_networking_bench_rivermax_tx_rx.yaml" ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../adv_networking_bench_rivermax_rx.yaml" +) +add_dependencies(adv_networking_bench adv_networking_bench_rivermax_tx_rx_yaml) # Installation install(TARGETS adv_networking_bench @@ -109,7 +121,9 @@ install( ../adv_networking_bench_default_rx_multi_q.yaml ../adv_networking_bench_default_sw_loopback.yaml ../adv_networking_bench_gpunetio_tx_rx.yaml - ../adv_networking_bench_rmax_rx.yaml + ../adv_networking_bench_rivermax_rx.yaml + ../adv_networking_bench_rivermax_tx.yaml + ../adv_networking_bench_rivermax_tx_rx.yaml DESTINATION examples/adv_networking_bench COMPONENT adv_networking_bench-configs PERMISSIONS OWNER_READ OWNER_WRITE diff --git a/applications/adv_networking_bench/cpp/CMakeLists.txt.install b/applications/adv_networking_bench/cpp/CMakeLists.txt.install index e11175ade7..270ca6130b 100644 --- a/applications/adv_networking_bench/cpp/CMakeLists.txt.install +++ b/applications/adv_networking_bench/cpp/CMakeLists.txt.install @@ -82,8 +82,20 @@ add_custom_target(adv_networking_bench_gpunetio_tx_rx_yaml ) add_dependencies(adv_networking_bench adv_networking_bench_gpunetio_tx_rx_yaml) -add_custom_target(adv_networking_bench_rmax_rx_yaml - COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/adv_networking_bench_rmax_rx.yaml" ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/adv_networking_bench_rmax_rx.yaml" +add_custom_target(adv_networking_bench_rivermax_rx_yaml + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/adv_networking_bench_rivermax_rx.yaml" ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/adv_networking_bench_rivermax_rx.yaml" ) add_dependencies(adv_networking_bench adv_networking_bench_rmax_rx_yaml) + +add_custom_target(adv_networking_bench_rivermax_tx_yaml + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/adv_networking_bench_rivermax_tx.yaml" ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/adv_networking_bench_rivermax_tx.yaml" +) +add_dependencies(adv_networking_bench adv_networking_bench_rmax_tx_yaml) + +add_custom_target(adv_networking_bench_rivermax_tx_rx_yaml + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/adv_networking_bench_rivermax_tx_rx.yaml" ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/adv_networking_bench_rivermax_tx_rx.yaml" +) +add_dependencies(adv_networking_bench adv_networking_bench_rmax_tx_rx_yaml) diff --git a/applications/adv_networking_bench/cpp/main.cpp b/applications/adv_networking_bench/cpp/main.cpp index 7dd034b371..dc2482c94b 100644 --- a/applications/adv_networking_bench/cpp/main.cpp +++ b/applications/adv_networking_bench/cpp/main.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,10 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#if ANO_MGR_DPDK || ANO_MGR_RIVERMAX +#if ANO_MGR_DPDK #include "default_bench_op_rx.h" #include "default_bench_op_tx.h" #endif +#if ANO_MGR_RIVERMAX +#include "default_bench_op_rx.h" +#include "rivermax_bench_op_tx.h" +#endif #if ANO_MGR_GPUNETIO #include "doca_bench_op_rx.h" #include "doca_bench_op_tx.h" @@ -96,8 +100,11 @@ class App : public holoscan::Application { add_operator(bench_rx); } if (tx_en) { - HOLOSCAN_LOG_ERROR("RIVERMAX manager/backend doesn't support TX"); - exit(1); + auto bench_tx = make_operator( + "bench_tx", + from_config("bench_tx"), + make_condition("is_alive", true)); + add_operator(bench_tx); } #else HOLOSCAN_LOG_ERROR("RIVERMAX manager/backend is not supported"); diff --git a/applications/adv_networking_bench/cpp/rivermax_bench_op_tx.h b/applications/adv_networking_bench/cpp/rivermax_bench_op_tx.h new file mode 100644 index 0000000000..3696ae7a88 --- /dev/null +++ b/applications/adv_networking_bench/cpp/rivermax_bench_op_tx.h @@ -0,0 +1,650 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023-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 "advanced_network/common.h" +#include "advanced_network/kernels.h" +#include "kernels.cuh" +#include "holoscan/holoscan.hpp" +#include +#include +#include +#include + +using namespace holoscan::advanced_network; + +namespace holoscan::ops { +/** + * @brief: Holds the essential frame data. + */ +class FrameBuffer { + public: + /** + * @brief: Constructor that allocates internal memory. + * + * @param [in] buffer_size: Size of the buffer to allocate. + */ + explicit FrameBuffer(size_t buffer_size) + : buffer_(new uint8_t[buffer_size]), buffer_size_(buffer_size) {} + + /** + * @brief: Move constructor. + */ + FrameBuffer(FrameBuffer&& other) noexcept + : buffer_(std::move(other.buffer_)), buffer_size_(other.buffer_size_) {} + + /** + * @brief: Move assignment operator. + */ + FrameBuffer& operator=(FrameBuffer&& other) noexcept { + if (this != &other) { + buffer_ = std::move(other.buffer_); + buffer_size_ = other.buffer_size_; + } + return *this; + } + + // Delete copy constructor and assignment operator + FrameBuffer(const FrameBuffer&) = delete; + FrameBuffer& operator=(const FrameBuffer&) = delete; + ~FrameBuffer() = default; + + /** + * @brief: Return pointer to the buffer. + * + * @return: Pointer to the buffer. + */ + uint8_t* get() const { + return buffer_.get(); + } + + /** + * @brief: Return size of the buffer. + * + * @return: Size of the buffer. + */ + size_t get_size() const { + return buffer_size_; + } + + private: + /* Smart pointer for the owned memory */ + std::unique_ptr buffer_; + /* Size of the buffer */ + size_t buffer_size_; +}; + +/** + * @brief Thread-safe buffer for concurrent item production and consumption. + * + * This template class provides a thread-safe queue with blocking and non-blocking + * operations for adding and retrieving items. It's designed for producer-consumer + * scenarios where multiple threads may be accessing the buffer concurrently. + * + * @tparam T The type of items stored in the buffer + */ +template +class ConcurrentItemBuffer { + public: + /** + * @brief Constructor. + * + * @param [in] max_queue_size Maximum size of the internal queue (0 for unlimited). + */ + explicit ConcurrentItemBuffer(size_t max_queue_size = 0) + : max_queue_size_(max_queue_size), stop_(false) {} + + /** + * @brief Destructor. + */ + virtual ~ConcurrentItemBuffer() { stop(); } + + /** + * @brief Get an item from the buffer, blocking if none is available. + * + * This method will block until an item is available or the buffer is stopped. + * + * @return Shared pointer to an item, or nullptr if the buffer is stopped. + */ + std::shared_ptr get_item_blocking() { + std::unique_lock lock(mutex_); + // Wait until an item is available or stop is requested + cv_.wait(lock, [this] { return !item_queue_.empty() || stop_; }); + + if (stop_ && item_queue_.empty()) { return nullptr; } + + auto item = item_queue_.front(); + item_queue_.pop(); + + // Notify any threads waiting to add items that space is now available + if (max_queue_size_ > 0) { cv_.notify_one(); } + + return item; + } + + /** + * @brief Get an item from the buffer without blocking. + * + * @return Shared pointer to an item, or nullptr if no items are available. + */ + std::shared_ptr get_item_not_blocking() { + std::lock_guard lock(mutex_); + + if (item_queue_.empty()) { return nullptr; } + + auto item = item_queue_.front(); + item_queue_.pop(); + + // Notify any threads waiting to add items that space is now available + if (max_queue_size_ > 0) { cv_.notify_one(); } + + return item; + } + + /** + * @brief Add an item to the buffer without blocking. + * + * @param [in] item Shared pointer to an item. + * @return Status of the operation. + */ + virtual Status add_item(std::shared_ptr item) { + if (!item) { return Status::NULL_PTR; } + + std::lock_guard lock(mutex_); + + if (max_queue_size_ > 0 && item_queue_.size() >= max_queue_size_) { + return Status::NO_SPACE_AVAILABLE; + } + + item_queue_.push(std::move(item)); + cv_.notify_one(); + return Status::SUCCESS; + } + + /** + * @brief Add an item to the buffer, blocking if buffer is full. + * + * This method will block until there's space in the buffer or the buffer is stopped. + * + * @param [in] item Shared pointer to an item. + * @return Status of the operation. + */ + virtual Status add_item_blocking(std::shared_ptr item) { + if (!item) { return Status::NULL_PTR; } + + std::unique_lock lock(mutex_); + + // Wait until there's space in the queue or stop is requested + if (max_queue_size_ > 0) { + cv_.wait(lock, [this] { return item_queue_.size() < max_queue_size_ || stop_; }); + } + + // If we're stopping and don't want to add more items + if (stop_) { return Status::NOT_READY; } + + item_queue_.push(std::move(item)); + cv_.notify_one(); // Notify any waiting consumers + return Status::SUCCESS; + } + + /** + * @brief Stop the buffer. + * + * This will unblock any threads waiting in get_item_blocking() or add_item_blocking(). + */ + void stop() { + std::lock_guard lock(mutex_); + stop_ = true; + cv_.notify_all(); + } + + /** + * @brief Return the number of items in the buffer. + * + * @return Number of items in the buffer. + */ + size_t get_queue_size() const { + std::lock_guard lock(mutex_); + return item_queue_.size(); + } + + /** + * @brief Check if more items can be added without blocking. + * + * @return True if items can be added, false if the buffer is full. + */ + bool has_space() const { + std::lock_guard lock(mutex_); + return max_queue_size_ == 0 || item_queue_.size() < max_queue_size_; + } + + /** + * @brief Check if the buffer is empty. + * + * @return True if the buffer is empty, false otherwise. + */ + bool is_empty() const { + std::lock_guard lock(mutex_); + return item_queue_.empty(); + } + + /** + * @brief Get the maximum size of the buffer. + * + * @return Maximum size of the buffer (0 for unlimited). + */ + size_t get_max_size() const { return max_queue_size_; } + + private: + /* Queue of items */ + std::queue> item_queue_; + /* Mutex for thread safety */ + mutable std::mutex mutex_; + /* Condition variable for blocking operations */ + std::condition_variable cv_; + /* Maximum size of the queue (0 for unlimited) */ + size_t max_queue_size_; + /* Flag to indicate if the buffer is stopping */ + bool stop_; +}; +/** + * @brief: Reads frames from a file and streams them to a @ref BufferedMediaFrameProvider. + * + * This class reads media frames from a file, uses a @ref MediaFramePool for memory management, + * and pushes the frames to a @ref BufferedMediaFrameProvider for consumption by other components. + * It supports looping through the file when reaching the end and provides thread-safe + * operations for starting and stopping the streaming process. + */ +class MediaFileFrameProvider { + public: + /** + * @brief: Constructor. + * + * @param [in] file_path: Path to the media file. + * @param [in] frame_size: Size of each frame in bytes. + * @param [in] loop: Whether to loop through the file when reaching the end. + * @param [in] sleep_duration_microseconds: Sleep duration in microseconds between reading frames. + */ + MediaFileFrameProvider(const std::string& file_path, size_t frame_size, bool loop, + size_t sleep_duration_microseconds = SLEEP_DURATION_MICROSECONDS) + : file_path_(file_path), + frame_size_(frame_size), + frame_buffer_queue_(FRAME_BUFFER_QUEUE_SIZE), + loop_frames_(loop), + sleep_duration_microseconds_(sleep_duration_microseconds) {} + + /** + * @brief: Destructor. + */ + virtual ~MediaFileFrameProvider() { stop(); } + + /** + * @brief: Initialize the streaming process. + * + * @return: Status of the operation. + */ + Status initialize() { + if (initialized_) { return Status::SUCCESS; } + + input_file_.open(file_path_, std::ios::binary); + if (!input_file_.is_open()) { + HOLOSCAN_LOG_ERROR("Failed to open file: {}", file_path_); + return Status::INVALID_PARAMETER; + } + + initialized_ = true; + return Status::SUCCESS; + } + + /** + * @brief: Stop the streaming process. + */ + void stop() { + stop_ = true; + frame_buffer_queue_.stop(); + } + /** + * @brief: Returns number of available frames in the queue. + * + * This method returns the number of frames currently available in the queue. + * + * @return: Number of available frames. + */ + size_t get_number_of_available_frames() { return frame_buffer_queue_.get_queue_size(); } + + /** + * @brief: Get the next available frame. + * + * This method attempts to get a frame from the queue without blocking. + * If no frames are available, it returns nullptr. + * + * @return: Shared pointer to a frame buffer, or nullptr if none available. + */ + std::shared_ptr get_next_frame() { + return frame_buffer_queue_.get_item_not_blocking(); + } + + /** + * @brief: Get the next frame, blocking if none available. + * + * This method blocks until a frame is available or the provider is stopped. + * + * @return: Shared pointer to a frame buffer, or nullptr if provider stopped. + */ + std::shared_ptr get_next_frame_blocking() { + return frame_buffer_queue_.get_item_blocking(); + } + + /** + * @brief: Call operator for running in a separate thread. + */ + void operator()() { + if (!initialized_) { + HOLOSCAN_LOG_ERROR("MediaFileFrameProvider is not initialized"); + return; + } + if (!input_file_.is_open()) { + HOLOSCAN_LOG_ERROR("File is not open: {}", file_path_); + return; + } + + while (!stop_) { + auto frame_buffer = std::make_shared(frame_size_); + input_file_.read(reinterpret_cast(frame_buffer->get()), frame_size_); + std::streamsize bytes_read = input_file_.gcount(); + + if (bytes_read == 0) { + if (input_file_.eof()) { + if (!loop_frames_) { break; } + // Loop the file, start reading from the beginning + input_file_.clear(); + input_file_.seekg(0, std::ios::beg); + continue; + } else if (input_file_.fail()) { + HOLOSCAN_LOG_ERROR("Error reading from file: {}", file_path_); + break; + } + } + + // Handle partial frame read if needed + if (bytes_read < static_cast(frame_size_)) { + std::memset(frame_buffer->get() + bytes_read, 0, frame_size_ - bytes_read); + } + + while (!stop_) { + auto status = frame_buffer_queue_.add_item_blocking(frame_buffer); + + if (status == Status::SUCCESS) { break; } + std::this_thread::sleep_for(std::chrono::microseconds(sleep_duration_microseconds_)); + } + } + + input_file_.close(); + frame_buffer_queue_.stop(); + initialized_ = false; + } + + private: + static constexpr auto SLEEP_DURATION_MICROSECONDS = 10000; + static constexpr auto FRAME_BUFFER_QUEUE_SIZE = 15; + + std::string file_path_; + size_t frame_size_; + ConcurrentItemBuffer frame_buffer_queue_; + bool loop_frames_; + std::atomic stop_{false}; + std::ifstream input_file_; + std::atomic initialized_{false}; + size_t sleep_duration_microseconds_; +}; + +void shutdown(std::string message) { + advanced_network::shutdown(); + HOLOSCAN_LOG_ERROR("{}", message); + exit(1); +} + +class AdvNetworkingBenchRivermaxTxOp : public Operator { + public: + HOLOSCAN_OPERATOR_FORWARD_ARGS(AdvNetworkingBenchRivermaxTxOp) + + AdvNetworkingBenchRivermaxTxOp() = default; + + ~AdvNetworkingBenchRivermaxTxOp() { + // Stop the provider and join the provider thread + if (frame_provider_) { frame_provider_->stop(); } + if (provider_thread_.joinable()) { provider_thread_.join(); } + HOLOSCAN_LOG_INFO("ANO benchmark Rivermax TX op shutting down"); + } + + void initialize() override { + HOLOSCAN_LOG_INFO("AdvNetworkingBenchRivermaxTxOp::initialize()"); + holoscan::Operator::initialize(); + + port_id_ = get_port_id(interface_name_.get()); + if (port_id_ == -1) { + HOLOSCAN_LOG_ERROR("Invalid TX port {} specified in the config", interface_name_.get()); + exit(1); + } + + frame_size_ = calculate_frame_size( + frame_width_.get(), frame_height_.get(), sample_format_.get(), bit_depth_.get()); + std::cout << "Frame size: " << frame_size_ << " bytes" << std::endl; + + frame_provider_ = + std::make_unique(file_path_.get(), frame_size_, loop_frames_); + + auto status = frame_provider_->initialize(); + if (status != Status::SUCCESS) { + shutdown("Failed to initialize media file provider"); + } + + // Start the provider thread + provider_thread_ = std::thread([this]() { + (*frame_provider_)(); // Run the provider + }); + + HOLOSCAN_LOG_INFO("AdvNetworkingBenchRivermaxTxOp::initialize() complete"); + } + + void setup(OperatorSpec& spec) override { + 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( + sample_format_, "sample_format", "Sample Format", "Format of the frame", "RGB888"); + spec.param( + payload_memory_, "payload_memory", "Payload Memory", "Memory for the payload", "host"); + spec.param( + file_path_, "file_path", "File Path", "Path to the file", "data/frames.dat"); + } + + void compute(InputContext&, OutputContext& op_output, ExecutionContext&) override { + static int not_available_count = 0; + static int sent = 0; + static int err = 0; + if (!cur_msg_) { + cur_msg_ = create_tx_burst_params(); + set_header(cur_msg_, port_id_, queue_id_.get(), 1, 1); + } + + if (!is_tx_burst_available(cur_msg_)) { + std::this_thread::sleep_for(std::chrono::microseconds(SLEEP_WHEN_BURST_NOT_AVAILABLE_US)); + if (++not_available_count == DISPLAY_WARNING_AFTER_BURST_NOT_AVAILABLE) { + HOLOSCAN_LOG_ERROR( + "TX port {}, queue {}, burst not available too many times consecutively. " + "Make sure memory region has enough buffers. Sent {} and error {}", + port_id_, + queue_id_.get(), + sent, + err); + not_available_count = 0; + err++; + } + return; + } + + not_available_count = 0; + Status ret; + if ((ret = get_tx_packet_burst(cur_msg_)) != Status::SUCCESS) { + HOLOSCAN_LOG_ERROR("Error returned from get_tx_packet_burst: {}", static_cast(ret)); + return; + } + + auto frame = frame_provider_->get_next_frame(); + if (!frame) { + HOLOSCAN_LOG_DEBUG("No frames available from provider"); + free_tx_burst(cur_msg_); + return; + } + + // Copy frame data into the burst + if (payload_memory_.get() == "device") { + cudaMemcpy(cur_msg_->pkts[0][0], frame->get(), frame->get_size(), cudaMemcpyDefault); + } else { + std::memcpy(cur_msg_->pkts[0][0], frame->get(), frame->get_size()); + } + + ret = send_tx_burst(cur_msg_); + if (ret != Status::SUCCESS) { + HOLOSCAN_LOG_ERROR("Error returned from send_tx_burst: {}", static_cast(ret)); + free_tx_burst(cur_msg_); + err++; + } else { + sent++; + } + cur_msg_ = nullptr; + HOLOSCAN_LOG_TRACE("AdvNetworkingBenchRivermaxTxOp::compute() {}:{} done. Emitted{}/Error{}", + port_id_, + queue_id_.get(), + sent, + err); + } + + enum class VideoSampling { RGB, YCbCr_4_2_2, YCbCr_4_2_0, YCbCr_4_4_4, Unknown}; + enum class ColorBitDepth { _8, _10, _12, Unknown}; + using BytesPerPixelRatio = std::pair; + using ColorDepthPixelRatioMap = + std::unordered_map>; + + const ColorDepthPixelRatioMap COLOR_DEPTH_TO_PIXEL_RATIO = { + {VideoSampling::RGB, + {{ColorBitDepth::_8, {3, 1}}, {ColorBitDepth::_10, {15, 4}}, {ColorBitDepth::_12, {9, 2}}}}, + {VideoSampling::YCbCr_4_4_4, + {{ColorBitDepth::_8, {3, 1}}, {ColorBitDepth::_10, {15, 4}}, {ColorBitDepth::_12, {9, 2}}}}, + {VideoSampling::YCbCr_4_2_2, + {{ColorBitDepth::_8, {4, 2}}, {ColorBitDepth::_10, {5, 2}}, {ColorBitDepth::_12, {6, 2}}}}, + {VideoSampling::YCbCr_4_2_0, + {{ColorBitDepth::_8, {6, 4}}, {ColorBitDepth::_10, {15, 8}}, {ColorBitDepth::_12, {9, 4}}}}}; + + /** + * @brief: Returns the corresponding VideoSampling enum value for the given video sampling format. + * + * @param format a string representing the video sampling format + * + * @return the corresponding VideoSampling enum value + * + * @note exits the application if the video sampling format is not supported + */ + VideoSampling get_video_sampling_format(const std::string& format) { + if (format == "RGB888") return VideoSampling::RGB; + if (format == "YUV422") return VideoSampling::YCbCr_4_2_2; + if (format == "YUV420") return VideoSampling::YCbCr_4_2_0; + if (format == "YUV442") return VideoSampling::YCbCr_4_4_4; + return VideoSampling::Unknown; + } + + /** + * @brief Returns the corresponding ColorBitDepth enum value for the given bit depth. + * + * @param bit_depth the bit depth to map + * + * @return the corresponding ColorBitDepth enum value + * + * @note exits the application if the bit depth is not supported + */ + ColorBitDepth get_color_bit_depth(int bit_depth) { + switch (bit_depth) { + case 8: + return ColorBitDepth::_8; + case 10: + return ColorBitDepth::_10; + case 12: + return ColorBitDepth::_12; + default: + return ColorBitDepth::Unknown; + } + } + + /** + * @brief Calculates the size of a frame based on its width, height, format, and bit depth. + * + * @param width The width of the frame. + * @param height The height of the frame. + * @param format_str The format of the frame. + * @param bit_depth_int The bit depth of the frame. + * + * @return The size of the frame in bytes. + * + * @note exits the application if the format or bit depth is unsupported. + */ + size_t calculate_frame_size(uint32_t width, uint32_t height, const std::string& format_str, + uint32_t bit_depth_int) { + VideoSampling sampling_format = get_video_sampling_format(format_str); + ColorBitDepth bit_depth = get_color_bit_depth(bit_depth_int); + + auto format_it = COLOR_DEPTH_TO_PIXEL_RATIO.find(sampling_format); + if (format_it == COLOR_DEPTH_TO_PIXEL_RATIO.end()) { + shutdown("Unsupported sampling format"); + } + + auto depth_it = format_it->second.find(bit_depth); + if (depth_it == format_it->second.end()) { + shutdown("Unsupported bit depth"); + } + + float bytes_per_pixel = static_cast(depth_it->second.first) / depth_it->second.second; + + return static_cast(width * height * bytes_per_pixel); + } + + private: + static constexpr uint16_t default_queue_id = 0; + static constexpr auto SLEEP_WHEN_BURST_NOT_AVAILABLE_US = 1000; + static constexpr auto DISPLAY_WARNING_AFTER_BURST_NOT_AVAILABLE = 10000; + int port_id_ = -1; + Parameter interface_name_; + Parameter queue_id_; + size_t frame_size_; + Parameter frame_width_; + Parameter frame_height_; + Parameter sample_format_; + Parameter bit_depth_; + Parameter payload_memory_; + Parameter file_path_; + bool loop_frames_ = true; + std::unique_ptr frame_provider_; + std::thread provider_thread_; + BurstParams* cur_msg_ = nullptr; +}; + +} // namespace holoscan::ops diff --git a/applications/adv_networking_bench/cpp/testing/benchmark_utils.py b/applications/adv_networking_bench/cpp/testing/benchmark_utils.py index 45387ae4c2..8afe78707f 100644 --- a/applications/adv_networking_bench/cpp/testing/benchmark_utils.py +++ b/applications/adv_networking_bench/cpp/testing/benchmark_utils.py @@ -17,6 +17,7 @@ import logging import re import sys +import os from typing import Dict # Configure the logger @@ -310,6 +311,8 @@ def parse_benchmark_results(log: str, manager_type: str) -> BenchmarkResults: return parse_dpdk_benchmark_results(log) elif manager_type == "gpunetio": return parse_gpunetio_benchmark_results(log) + elif manager_type == "rivermax": + return parse_rivermax_benchmark_results(log) else: raise ValueError(f"Unsupported manager type: {manager_type}") @@ -486,6 +489,130 @@ def parse_gpunetio_benchmark_results(log: str) -> BenchmarkResults: ) +def parse_rivermax_benchmark_results(log: str) -> BenchmarkResults: + """ + Parse benchmark results from Rivermax log output. + + Args: + log: The log output as a string + + Returns: + BenchmarkResults: A structured representation of the benchmark results + """ + # Initialize result dictionaries + tx_packets = {} # Not provided in logs + tx_bytes = {} # Not provided in logs + rx_packets = {} + rx_bytes = {} # Not provided in logs + missed_packets = {} + errored_packets = {} + rx_queue_packets = {} # Not provided in logs + tx_queue_packets = {} # Not provided in logs + + # Extract execution time + exec_time_pattern = r"TOTAL EXECUTION TIME OF SCHEDULER : (\d+\.\d+) ms" + exec_time = 0.0 + exec_time_match = re.search(exec_time_pattern, log) + if exec_time_match: + exec_time = float(exec_time_match.group(1)) + + # Regex patterns for Rivermax benchmark summary + benchmark_summery_ipo_receiver = ( + r"\[stream_index\s+(\d+)\] Got\s+(\d+) packets \|\s+(\d+\.\d+) GB \| " + r"dropped:\s+(\d+) \| consumed:\s+(\d+) \| unconsumed:\s+(\d+) \| " + r"lost:\s+(\d+) \| exceed MD:\s+(\d+) \| bad RTP hdr:\s+(\d+) \|\s+(\d+)%" + ) + benchmark_summery_rtp_receiver = ( + r"\[stream_index\s+(\d+)\] Got\s+(\d+) packets \|\s+(\d+\.\d+) GB \|\s+\|\s+" + r"consumed:\s+(\d+) \| unconsumed:\s+(\d+) \|\s+" + r"lost:\s+(\d+) \| bad RTP hdr:\s+(\d+) \|" + ) + + matches = re.findall(benchmark_summery_ipo_receiver, log) + if matches: + (stream_index, total_rx_packets, throughput, dropped, consumed, + unconsumed, lost, exceed_md, bad_rtp_hdr, extra_info) = matches[0] + missed_packets["0"] = int(dropped)+int(lost)+int(exceed_md) + else: + matches = re.findall(benchmark_summery_rtp_receiver, log) + if matches: + (stream_index, total_rx_packets, throughput, consumed, + unconsumed, lost, bad_rtp_hdr) = matches[0] + missed_packets["0"] = int(lost) # Only lost packets, no dropped or exceed_md + else: + logger.error("No matches found for Rivermax benchmark summary") + raise ValueError("No matches found for Rivermax benchmark summary") + + rx_packets["0"] = int(total_rx_packets) + errored_packets["0"] = int(bad_rtp_hdr) + + return BenchmarkResults( + tx_pkts=tx_packets, + tx_bytes=tx_bytes, + rx_pkts=rx_packets, + rx_bytes=rx_bytes, + missed_pkts=missed_packets, + errored_pkts=errored_packets, + q_rx_pkts=rx_queue_packets, + q_tx_pkts=tx_queue_packets, + exec_time=exec_time, + ) + + +def generate_media_file(file_path: str, width: int = 1920, height: int = 1080, bit_depth: int = 10, num_frames: int = 10): + """ + Generate a zero-filled YCbCr 4:2:2 media file with specified parameters. + + Args: + file_path: Output file path + width: Video width in pixels (default: 1920 for 1080p) + height: Video height in pixels (default: 1080 for 1080p) + bit_depth: Bit depth in bits - must be 8, 10, or 12 (default: 10) + num_frames: Number of frames to generate (default: 10) + """ + # Validate input parameters + if bit_depth not in [8, 10, 12]: + raise ValueError(f"Unsupported bit depth: {bit_depth}. Supported values: 8, 10, 12") + + if width <= 0 or height <= 0 or num_frames <= 0: + raise ValueError("Width, height, and num_frames must be positive integers") + + logger.info(f"Generating YCbCr 4:2:2 media file: {file_path} ({width}x{height}, {bit_depth}-bit, {num_frames} frames)") + + # SMPTE 2110-20 specification: YCbCr 4:2:2 pgroup sizes + # Each pgroup covers 2 pixels and contains Y0, Cb, Y1, Cr samples + SMPTE_2110_PGROUP_SIZES = { + 8: 4, # 4 bytes per 2 pixels = 2.0 bytes/pixel + 10: 5, # 5 bytes per 2 pixels = 2.5 bytes/pixel + 12: 6, # 6 bytes per 2 pixels = 3.0 bytes/pixel + } + + # Calculate total file size + pixels_per_frame = width * height + pgroups_per_frame = pixels_per_frame // 2 # Each pgroup covers 2 pixels + bytes_per_frame = pgroups_per_frame * SMPTE_2110_PGROUP_SIZES[bit_depth] + total_size = int(bytes_per_frame * num_frames) + + logger.info(f"Frame size: {bytes_per_frame} bytes, Total file size: {total_size} bytes ({total_size / (1024*1024):.1f} MB)") + + # Create the directory if it doesn't exist + try: + os.makedirs(os.path.dirname(file_path), exist_ok=True) + except OSError as e: + logger.error(f"Failed to create directory: {e}") + raise + + # Generate the zero-filled file + try: + with open(file_path, 'wb') as f: + f.write(b'\x00' * total_size) + except OSError as e: + logger.error(f"Failed to write file {file_path}: {e}") + raise + + logger.info(f"Successfully generated YCbCr 4:2:2 media file: {file_path}") + + if __name__ == "__main__": # Set up console logging if run directly logging.basicConfig( diff --git a/applications/adv_networking_bench/cpp/testing/nvidia_nic_utils.py b/applications/adv_networking_bench/cpp/testing/nvidia_nic_utils.py index 1ca8b503ad..edb6a7e823 100644 --- a/applications/adv_networking_bench/cpp/testing/nvidia_nic_utils.py +++ b/applications/adv_networking_bench/cpp/testing/nvidia_nic_utils.py @@ -33,10 +33,11 @@ class NetworkInterface: bus_id: str # PCI bus ID (e.g., "0000:3b:00.0") mac_address: str # MAC address is_up: bool # Whether the interface is up + ip_address: str # IP address def __str__(self) -> str: status = "Up" if self.is_up else "Down" - return f"NetworkInterface(name={self.interface_name}, bus_id={self.bus_id}, mac={self.mac_address}, status={status})" + return f"NetworkInterface(name={self.interface_name}, bus_id={self.bus_id}, mac={self.mac_address}, status={status}, ip={self.ip_address})" def get_nvidia_nics() -> List[NetworkInterface]: @@ -84,9 +85,17 @@ def get_nvidia_nics() -> List[NetworkInterface]: mac_address = result.stdout.strip() + # Get IP address + result = run_command(f"ip -4 addr show {interface_name} | grep -oP '(?<=inet\s)\d+(\.\d+){{3}}'") + if result.returncode != 0: + ip_address = None + else: + ip_address = result.stdout.strip() + # Create and add the interface interface = NetworkInterface( - interface_name=interface_name, bus_id=bus_id, mac_address=mac_address, is_up=is_up + interface_name=interface_name, bus_id=bus_id, mac_address=mac_address, is_up=is_up, + ip_address=ip_address ) interfaces.append(interface) diff --git a/applications/adv_networking_bench/cpp/testing/test_ano_bench.py b/applications/adv_networking_bench/cpp/testing/test_ano_bench.py index 34387bf6b8..bbf6034e85 100644 --- a/applications/adv_networking_bench/cpp/testing/test_ano_bench.py +++ b/applications/adv_networking_bench/cpp/testing/test_ano_bench.py @@ -20,7 +20,7 @@ from time import sleep import pytest -from benchmark_utils import parse_benchmark_results +from benchmark_utils import parse_benchmark_results, generate_media_file from nvidia_nic_utils import get_nvidia_nics, print_nvidia_nics from process_utils import monitor_process, run_command, start_process from yaml_config_utils import update_yaml_file @@ -275,3 +275,82 @@ def test_multi_q_hds_tx_rx(executable, work_dir, nvidia_nics): expected_q_pkts = {i: 1 for i in range(9)} rx_queue_pkts_check = results.validate_rx_queue_packets(1, expected_q_pkts, allow_greater=True) assert rx_queue_pkts_check, "RX queue packet distribution validation failed" + + +@pytest.mark.parametrize("rivermax_receiver_type", ["ipo_receiver", "rtp_receiver"]) +def test_rivermax_tx_rx(executable, work_dir, nvidia_nics, rivermax_receiver_type): + """ + Test 6: Rivermax TX/RX. + """ + # Get the first two NICs for this test + # Rivermax TX/RX test requires two NICs with IP addresses + tx_interface = None + rx_interface = None + for i in range(len(nvidia_nics)): + if nvidia_nics[i].ip_address: + print(f"NVIDIA NIC {i} has IP address: {nvidia_nics[i].ip_address}") + if (tx_interface is None): + tx_interface = nvidia_nics[i] + else: + rx_interface = nvidia_nics[i] + break + else: + print(f"NVIDIA NIC {i} does not have IP address") + if (tx_interface is None or rx_interface is None): + pytest.skip("NVIDIA NICs do not have IP addresses") + + # Generate media file + media_file_path = os.path.join(work_dir, "test_media_file.ycbcr") + generate_media_file(media_file_path) + + # Prepare config + config_file = os.path.join(work_dir, "adv_networking_bench_rivermax_tx_rx.yaml") + config_file_test = os.path.join(work_dir, "adv_networking_bench_rivermax_tx_rx_test.yaml") + + rx_settings_path = "advanced_network.cfg.interfaces[0].rx.queues[0].rivermax_rx_settings" + tx_settings_path = "advanced_network.cfg.interfaces[0].tx.queues[0].rivermax_tx_settings" + update_yaml_file( + config_file, + config_file_test, + { + "scheduler.max_duration_ms": 10000, + f"{rx_settings_path}.settings_type": rivermax_receiver_type, + f"{tx_settings_path}.local_ip_address": tx_interface.ip_address, + f"{tx_settings_path}.destination_ip_address": "224.1.1.1", + f"{tx_settings_path}.destination_port": 50001, + "bench_tx.file_path": media_file_path, + }, + ) + + if (rivermax_receiver_type == "ipo_receiver"): + update_yaml_file( + config_file_test, + config_file_test, + { + f"{rx_settings_path}.local_ip_addresses": [rx_interface.ip_address], + f"{rx_settings_path}.source_ip_addresses": [tx_interface.ip_address], + f"{rx_settings_path}.destination_ip_addresses": ["224.1.1.1"], + f"{rx_settings_path}.destination_ports": [50001], + }, + ) + else: + update_yaml_file( + config_file_test, + config_file_test, + { + f"{rx_settings_path}.local_ip_address": rx_interface.ip_address, + f"{rx_settings_path}.source_ip_address": tx_interface.ip_address, + f"{rx_settings_path}.destination_ip_address": "224.1.1.1", + f"{rx_settings_path}.destination_port": 50001, + }, + ) + + command = f"{executable} {config_file_test}" + result = run_command(command, stream_output=True) + results = parse_benchmark_results(result.stdout + result.stderr, "rivermax") + + received_packets = results.get_rx_packets(0) + missed_pkts_check = results.get_missed_packets(0) + errored_pkts_check = results.get_errored_packets(0) + assert received_packets > 0 and missed_pkts_check == 0 and errored_pkts_check == 0, \ + "Validation failed" diff --git a/applications/adv_networking_media_player/CMakeLists.txt b/applications/adv_networking_media_player/CMakeLists.txt new file mode 100755 index 0000000000..ef14b32dd5 --- /dev/null +++ b/applications/adv_networking_media_player/CMakeLists.txt @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: Copyright (c) 2022-2023 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..8b9a78ce40 --- /dev/null +++ b/applications/adv_networking_media_player/README.md @@ -0,0 +1,395 @@ +# 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. + +This application serves as a reference implementation demonstrating how to integrate multiple media streams into a unified HoloHub processing pipeline. It showcases the advanced networking framework's ability to handle complex multi-stream scenarios that are common in professional media production, broadcasting, and real-time analytics applications. + + +## 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 +- **Multi-Stream Support**: Handle multiple concurrent streams with independent processing + +### 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 + A["Network Interface
(ConnectX NIC)"] --> B["Rivermax Hardware
Acceleration"] + + %% RDK Service Layer + B --> 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)"] + + %% Memory Management + D --> F["Direct Memory Access
(DMA to Pre-allocated Regions)"] + E --> F + F --> G["Memory Regions
(Data_RX_CPU + Data_RX_GPU)"] + + %% Advanced Network Manager + G --> H["RivermaxMgr
(Advanced Network Manager)"] + H --> I["Burst Assembly
(Packet Pointers Only)"] + I --> J["AnoBurstsQueue
(Pointer Distribution)"] + + %% Media RX Operator + J --> K["AdvNetworkMediaRxOp"] + K --> L["Burst Processor
(burst_processor)"] + L --> M{{"HDS Configuration
(Header-Data Split)"}} + + %% HDS Memory Layout + M -->|hds: true| N["Headers: CPU Memory
Payloads: GPU Memory"] + M -->|hds: false| O["Headers+Payloads: CPU Memory
with RTP offset"] + + %% Strategy Detection & Processing + N --> P["PacketsToFramesConverter
(Strategy Detection)"] + O --> P + P --> Q{{"Memory Layout
Analysis"}} + Q -->|Contiguous| R["ContiguousStrategy
cudaMemcpy()"] + Q -->|Strided| S["StridedStrategy
cudaMemcpy2D()"] + + %% Frame Assembly + R --> T["Frame Assembly
(Single Copy Operation)"] + S --> T + T --> U{{"Output Format
Configuration"}} + U -->|output_format: video_buffer| V["VideoBuffer Entity"] + U -->|output_format: tensor| W["Tensor Entity"] + + %% Application Layer + V --> X["Media Player Application
(Python/C++)"] + W --> X + X --> Y{{"Output Mode
Configuration"}} + + %% Output Options + Y -->|visualize: true| Z["HolovizOp
(Real-time Display)"] + Y -->|write_to_file: true| AA["FramesWriter
(File Output)"] + + %% Styling + classDef networkLayer fill:#e1f5fe + classDef rdkLayer fill:#e3f2fd + classDef managerLayer fill:#f3e5f5 + classDef operatorLayer fill:#e8f5e8 + classDef appLayer fill:#fff3e0 + classDef configDecision fill:#f9f9f9,stroke:#333,stroke-width:2px + classDef hdsPath fill:#e8f5e8 + classDef memoryOpt fill:#ffebee + + class A,B networkLayer + class C,D,E,F,G rdkLayer + class H,I,J managerLayer + class K,L,P,R,S,T operatorLayer + class X,Z,AA appLayer + class M,Q,U,Y configDecision + class N,O hdsPath + class R,S,T memoryOpt +``` + +#### Simplified Application Pipeline + +``` +Network → RDK Services → AdvNetworkMediaRxOp → Application → Display/File +``` + +#### Multi-Stream Processing Flow +``` + ---- stream ID 0 --- AdvNetworkMediaRxOp --- FramesWriterOp + | +Receiving 2 streams -| + | + ---- stream ID 1 --- AdvNetworkMediaRxOp --- FramesWriterOp +``` + +## 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 with Rivermax support: + +```bash +./holohub build adv_networking_media_player --build-args="--target rivermax" --configure-args="-D ANO_MGR:STRING=rivermax" +``` + +### Launch Container + +Launch the Rivermax-enabled container: + +```bash +./holohub run-container adv_networking_media_player --build-args="--target rivermax" --docker-opts="-u root --privileged -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" +``` + +### Run Application + +Inside the container, run the application: + +```bash +# For C++ version +./adv_networking_media_player adv_networking_media_player.yaml +``` + +## Configuration + +The application uses a YAML configuration file that defines the complete data flow pipeline. Understanding these configuration options is crucial for optimizing performance: + +- **Network settings**: Interface configuration, IP addresses, ports, RDK service selection +- **Memory regions**: CPU and GPU buffer configurations affecting HDS and copy strategies +- **Video parameters**: Format, resolution, bit depth, frame rate for packet-to-frame conversion +- **Output options**: Visualization or file output settings with memory location control + +### Key Configuration Parameters + +#### RDK Service Selection +- **`settings_type: "ipo_receiver"`**: Uses IPO (Inline Packet Ordering) for high-throughput streams +- **`settings_type: "rtp_receiver"`**: Uses RTP receiver for standard compliance + +#### Header-Data Split (HDS) Configuration +- **`hds: true`**: Enables optimal memory layout - headers in CPU, payloads in GPU memory +- **`hds: false`**: Traditional layout - headers and payloads together in CPU memory + +#### Memory and Copy Strategy Optimization +- **`memory_location: "device"`**: Processes frames in GPU memory for maximum performance +- **`memory_location: "host"`**: Processes frames in CPU memory when GPU memory is limited +- **`output_format: "tensor"`**: Optimized for GPU-based post-processing +- **`output_format: "video_buffer"`**: Compatible with standard video processing operators + +### Example Configuration Sections + +#### Network Interface Configuration +```yaml +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: 43200 + buf_size: 20 + - name: "Data_RX_GPU" + kind: "device" + affinity: 0 + access: + - local + num_bufs: 43200 + 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" + verbose: true + rx_threads: + - thread_id: 0 + network_settings: + - stream_id: 0 # Stream 1 network config + local_ip_addresses: 2.1.0.12 + source_ip_addresses: 2.1.0.11 + destination_ip_addresses: 224.1.1.1 + destination_ports: 50000 + - stream_id: 1 # [OPTIONAL] Stream 2 network config + local_ip_addresses: 2.1.0.12 + source_ip_addresses: 2.1.0.11 + destination_ip_addresses: 224.1.1.2 + destination_ports: 50000 +``` + +#### Per-Stream Media RX Configuration + +Each stream requires its own media RX operator configuration: + +```yaml +advanced_network_media_rx: + - name: "stream_1" # First stream + interface_name: cc:00.1 + queue_id: 0 + stream_id: 0 # Maps to network_settings stream_id + video_format: RGB888 + frame_width: 1920 + frame_height: 1080 + bit_depth: 8 + hds: true + output_format: tensor + memory_location: device + + - name: "stream_2" # [OPTIONAL] Second stream + interface_name: cc:00.1 + queue_id: 0 + stream_id: 1 + video_format: RGB888 # Can be different per stream + frame_width: 1920 # Can be different per stream + frame_height: 1080 + bit_depth: 8 + hds: true + output_format: tensor + mem +``` + +#### Output Configuration +```yaml +media_player_config: + write_to_file: false # Set to true for file output + visualize: true # Set to true for real-time display +``` + +#### Per-Stream File Output Configuration + +Configure independent frame writers for each stream: + +```yaml +frames_writer: + - name: "stream_1" # Must match media RX name + num_of_frames_to_record: 1000 + file_path: "/tmp/output_stream_1.bin" + + - name: "stream_2" # [OPTIONAL] + num_of_frames_to_record: 1000 + file_path: "/tmp/output_stream_2.bin" +``` + +#### Single Stream Display + +```yaml +holoviz: + width: 1920 + height: 1080 +``` + +## 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 + +### Architecture-Aware Tuning + +Understanding the data flow architecture enables targeted performance optimization: + +#### Copy Strategy Optimization +The application automatically detects optimal copy strategies based on memory layout: +- **Contiguous Strategy**: Achieved with proper buffer alignment and HDS configuration +- **Strided Strategy**: Handles non-contiguous packet layouts with optimized `cudaMemcpy2D` +- **Adaptive Behavior**: Strategy can switch dynamically based on buffer wraparound conditions + +#### HDS Configuration for Maximum Performance +```yaml +# Optimal HDS configuration for GPU-accelerated processing +advanced_network_media_rx: + hds: true # Enable header-data split + memory_location: device # Process frames in GPU memory + output_format: tensor # Optimize for GPU post-processing + +# Memory regions optimized for HDS +memory_regions: +- name: "Data_RX_CPU" # Headers in CPU memory + kind: "host" + buf_size: 20 # RTP header size +- name: "Data_RX_GPU" # Payloads in GPU memory + kind: "device" + buf_size: 1440 # Payload size per packet +``` + +### System-Level Tuning + +- **CPU Isolation**: Isolate CPU cores for network processing (`cpu_core: "12"`) +- **Memory Configuration**: Tune buffer sizes based on frame rate and resolution +- **GPU Memory**: Ensure sufficient GPU memory for frame buffers +- **Network Settings**: Configure appropriate batch sizes and queue parameters (`batch_size: 4320`) +- **Interrupt Mitigation**: Use proper CPU affinity to reduce interrupt overhead + +### Performance Monitoring + +Monitor these key metrics for optimal performance: +- **Frame Rate**: Consistent frame reception without drops +- **Memory Copy Strategy**: Verify contiguous strategy is being used when possible +- **GPU Utilization**: Monitor GPU memory usage and copy operations +- **Network Statistics**: Track packet loss and timing accuracy + +## Related Documentation + +- [Advanced Network Operators](../../operators/advanced_network/README.md) +- [Advanced Network Media Operators](../../operators/advanced_network_media/README.md) +- [High Performance Networking Tutorial](../../tutorials/high_performance_networking/README.md) +- [Advanced Networking Media Sender](../adv_networking_media_sender/README.md) 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..9b5715e337 --- /dev/null +++ b/applications/adv_networking_media_player/adv_networking_media_player.yaml @@ -0,0 +1,99 @@ +%YAML 1.2 +# SPDX-FileCopyrightText: Copyright (c) 2024 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: 43200 + buf_size: 20 + - name: "Data_RX_GPU" + kind: "device" + affinity: 0 + access: + - local + num_bufs: 43200 + 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" + verbose: true + rx_threads: + - thread_id: 0 + network_settings: + - stream_id: 0 + local_ip_addresses: + - 2.1.0.11 + source_ip_addresses: + - 2.1.0.12 + destination_ip_addresses: + - 224.1.1.1 + destination_ports: + - 50000 + +advanced_network_media_rx: + - name: "stream_1" + interface_name: cc:00.1 + queue_id: 0 # from advanced_network + stream_id: 0 # from advanced_network + 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: true + visualize: false + +frames_writer: # applied only to cpp + - name: "stream_1" # from advanced_network_media_rx + num_of_frames_to_record: 1000 + file_path: "/tmp/output.bin" + +holoviz: + width: 1920 + height: 1080 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..2dac16a81a --- /dev/null +++ b/applications/adv_networking_media_player/cpp/CMakeLists.txt @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: Copyright (c) 2022-2023 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..dd04f39700 --- /dev/null +++ b/applications/adv_networking_media_player/cpp/adv_networking_media_player.cpp @@ -0,0 +1,441 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023 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" + +using namespace holoscan::advanced_network; + +#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_; +}; + +/** + * @class MockReceiverOp + * @brief A mock operator to simulate data reception when no output operator is defined. + */ +class MockReceiverOp : public holoscan::Operator { + public: + HOLOSCAN_OPERATOR_FORWARD_ARGS(MockReceiverOp) + + MockReceiverOp() = default; + + void initialize() override { + cudaError_t cuda_error; + HOLOSCAN_LOG_INFO("AdvNetworkingBenchDefaultRxOp::initialize()"); + holoscan::Operator::initialize(); + + port_id_ = get_port_id(interface_name_.get()); + if (port_id_ == -1) { + HOLOSCAN_LOG_ERROR("Invalid RX port {} specified in the config", interface_name_.get()); + exit(1); + } + } + + void setup(OperatorSpec& spec) override { + spec.param(interface_name_, + "interface_name", + "Port name", + "Name of the port to poll on from the advanced_network config", + "rx_port"); + } + + void compute(InputContext& op_input, OutputContext&, ExecutionContext& context) override { + + BurstParams *burst; + + // In this example, we'll loop through all the rx queues of the interface + // assuming we want to process the packets the same way for all queues + const auto num_rx_queues = get_num_rx_queues(port_id_); + for (int q = 0; q < num_rx_queues; q++) { + auto status = get_rx_burst(&burst, port_id_, q); + + if (status != Status::SUCCESS) { + HOLOSCAN_LOG_DEBUG("No RX burst available"); + continue; + } + + free_all_packets_and_burst_rx(burst); + } + } + + private: + int port_id_ = 0; + Parameter interface_name_; // Port name from advanced_network config +}; + +} // 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 (advanced_network::adv_net_init(adv_net_config) != advanced_network::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] = advanced_network::get_rx_tx_configs_enabled(config()); + const auto mgr_type = advanced_network::get_manager_type(config()); + + HOLOSCAN_LOG_INFO("Using Advanced Network manager {}", + advanced_network::manager_type_to_string(mgr_type)); + + HOLOSCAN_LOG_INFO("Using ANO manager {}", advanced_network::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); + } + + std::string interface_name = ""; + + auto multi_streams_adv_net_media_rx_yaml = config().yaml_nodes()[0]["advanced_network_media_rx"]; + std::unordered_map> adv_net_media_rx_map; + for (const auto& stream : multi_streams_adv_net_media_rx_yaml) { + std::string stream_name = stream["name"].as(); + interface_name = stream["interface_name"].as(""); + auto adv_net_media_rx = make_operator( + "adv_net_media_rx_" + stream_name, + Arg("interface_name", stream["interface_name"].as("")), + Arg("queue_id", stream["queue_id"].as(0)), + Arg("stream_id", stream["stream_id"].as(0)), + Arg("frame_width", stream["frame_width"].as(1920)), + Arg("frame_height", stream["frame_height"].as(1080)), + Arg("bit_depth", stream["bit_depth"].as(8)), + Arg("video_format", stream["video_format"].as("RGB888")), + Arg("hds", stream["hds"].as(true)), + Arg("output_format", stream["output_format"].as("video_buffer")), + Arg("memory_location", stream["memory_location"].as("device")), + make_condition("is_alive", true) + ); + adv_net_media_rx_map[stream_name] = adv_net_media_rx; + } + if (adv_net_media_rx_map.empty()) { + HOLOSCAN_LOG_ERROR("No advanced_network_media_rx entries found in the config"); + exit(1); + } + + const auto allocator = make_resource("allocator"); + + if (from_config("media_player_config.visualize").as()) { + if (multi_streams_adv_net_media_rx_yaml.size() > 1) { + HOLOSCAN_LOG_ERROR("Visualization with multiple streams is not supported yet."); + exit(1); + } + 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_map[0], visualizer, {{"out_video_buffer", "receivers"}}); + } else if (from_config("media_player_config.write_to_file").as()) { + auto frames_writer_yaml = config().yaml_nodes()[0]["frames_writer"]; + if (frames_writer_yaml.size() == adv_net_media_rx_map.size()) { + for (const auto& stream : frames_writer_yaml) { + std::string stream_name = stream["name"].as(); + auto frames_writer = make_operator( + "frames_writer_" + stream_name, + Arg("file_path", stream["file_path"].as("")), + Arg("num_of_frames_to_record", stream["num_of_frames_to_record"].as(1000)) + ); + // Connect the corresponding network rx operator to this frames writer + if (adv_net_media_rx_map.find(stream_name) != adv_net_media_rx_map.end()) { + add_flow(adv_net_media_rx_map[stream_name], frames_writer); + } else { + HOLOSCAN_LOG_ERROR("Stream {} not found in adv_net_media_rx_map", stream_name); + exit(1); + } + } + } else { + HOLOSCAN_LOG_ERROR("Number of frames_writer entries must match number of advanced_network_media_rx entries"); + exit(1); + } + } else { + HOLOSCAN_LOG_WARN("No output type (write_to_file/visualize) defined. Data will be received but not processed."); + auto mock_receiver = make_operator( + "mock_receiver", + Arg("interface_name", interface_name), + make_condition("is_alive", true) + ); + add_operator(mock_receiver); + } + } +}; + +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(); + + advanced_network::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..e899e947c5 --- /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": ["Network", "Networking", "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, + "dependencies": { + "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..44cd928e94 --- /dev/null +++ b/applications/adv_networking_media_player/python/CMakeLists.txt @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: Copyright (c) 2022-2023 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. + +# target does nothing but be created if this folder is reached +# to set dependencies on adequate operators +# add_custom_target(advanced_networking_bench_python ALL +# DEPENDS advanced_network_python +# ) + +# Install application and dependencies into the install/ directory for packaging +install( + FILES adv_networking_media_player.py + DESTINATION bin/adv_networking_media_player/python + COMPONENT adv_networking_media_player-py +) + +install( + FILES ../adv_networking_media_player.yaml + DESTINATION bin/adv_networking_media_player/python + COMPONENT adv_networking_media_player-py +) 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..e4666e2f51 --- /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) 2022-2023 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..4ebb64f891 --- /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": ["Network", "Networking", "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, + "dependencies": { + "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_sender/CMakeLists.txt b/applications/adv_networking_media_sender/CMakeLists.txt new file mode 100755 index 0000000000..ef14b32dd5 --- /dev/null +++ b/applications/adv_networking_media_sender/CMakeLists.txt @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: Copyright (c) 2022-2023 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..4325a5574f --- /dev/null +++ b/applications/adv_networking_media_sender/README.md @@ -0,0 +1,549 @@ +# 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 with Rivermax support: + +```bash +./dev_container build --docker_file operators/advanced_network/Dockerfile --img holohub:rivermax --build-args "--target rivermax" +``` + +### Launch Container + +Launch the Rivermax-enabled container: + +```bash +./operators/advanced_network/run_rivermax.sh +``` + +### Build Application + +Inside the container, build the application: + +```bash +# For C++ version +./run build adv_networking_media_sender --configure-args "-DANO_MGR=rivermax" + +``` + +## Configuration + +The application uses a YAML configuration file that defines the complete transmission pipeline. Understanding these configuration options is crucial for optimizing performance and selecting the appropriate memory management strategy: + +- **Network settings**: Interface configuration, IP addresses, ports, MediaSender service selection +- **Memory regions**: CPU and GPU buffer configurations affecting service selection and performance +- **Video parameters**: Format, resolution, bit depth, frame rate for RDK packetization +- **Source settings**: Media file path and playback options for VideoStreamReplayer +- **Service mode**: Memory pool configuration determining zero-copy vs single-copy behavior + +### Critical Configuration Parameters + +#### MediaSender Service Selection +```yaml +# Zero-Copy Mode (Pipeline Applications) +rivermax_tx_settings: + use_internal_memory_pool: false # Enables MediaSenderZeroCopyService + +# Memory Pool Mode (Data Generation Applications) +rivermax_tx_settings: + use_internal_memory_pool: true # Enables MediaSenderService +``` + +#### Memory Pool Configuration (when enabled) +```yaml +# Memory pool settings affect sustained throughput +rivermax_tx_settings: + memory_allocation: true # Enable pool allocation + memory_pool_location: "device" # GPU memory for zero-copy to NIC + num_of_packets_in_chunk: 144 # Chunk size for packetization +``` + +#### Performance-Critical Settings +```yaml +# Network timing and batching +rivermax_tx_settings: + frame_rate: 60 # Target transmission frame rate + sleep_between_operations: false # Disable for maximum performance + stats_report_interval_ms: 1000 # Performance monitoring interval + +# Memory regions for optimal DMA +memory_regions: +- name: "Data_TX_GPU" # GPU memory for zero-copy transmission + kind: "device" + num_bufs: 43200 # Buffer count for sustained throughput + buf_size: 1440 # Per-packet payload size +``` + +### Example Configuration Sections + +#### Network Interface Configuration +```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 +``` + +#### Video Transmission Configuration +```yaml +advanced_network_media_tx: + interface_name: cc:00.1 + video_format: RGB888 + frame_width: 1920 + frame_height: 1080 + bit_depth: 8 +``` + +#### Source File Configuration +```yaml +replayer: + directory: "/media/video" + basename: "bunny" + frame_rate: 60 + repeat: true # Loop playback + realtime: true # Real-time playback + count: 0 # 0 = no frame limit +``` + +#### Memory Configuration +```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 +``` + +## Running the Application + +### Prerequisites + +Before running, ensure your environment is properly configured: + +```bash +# Update PYTHONPATH for Python applications +export PYTHONPATH=${PYTHONPATH}:/opt/nvidia/holoscan/python/lib:$PWD/build/adv_networking_media_sender/python/lib:$PWD + +# Ensure proper system configuration (run as root if needed) +# See High Performance Networking tutorial for system tuning +``` + +### C++ Application + +```bash +./build/adv_networking_media_sender/applications/adv_networking_media_sender/cpp/adv_networking_media_sender adv_networking_media_sender.yaml +``` + +### Python Application + +```bash +python applications/adv_networking_media_sender/python/adv_networking_media_sender.py ../adv_networking_media_sender.yaml +``` + +## Configuration Parameters + +### Source Configuration Options + +- **`directory`**: Path to media files directory +- **`basename`**: Base name for media files (without frame numbers) +- **`frame_rate`**: Target frame rate for transmission +- **`repeat`**: Enable looping playback (true/false) +- **`realtime`**: Enable real-time playback timing (true/false) +- **`count`**: Number of frames to transmit (0 = unlimited) + +### Network Configuration Options + +- **`local_ip_address`**: Source IP address for transmission +- **`destination_ip_address`**: Target IP address (multicast supported) +- **`destination_port`**: Target UDP port +- **`frame_rate`**: Network transmission frame rate +- **`video_format`**: Video pixel format (RGB, YUV, etc.) +- **`bit_depth`**: Color bit depth (8, 10, 12, 16) + +## 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 + +### Architecture-Aware Configuration + +Understanding the service selection and data flow enables optimal configuration for your specific use case: + +#### For VideoStreamReplayer Applications (Current Use Case) + +The VideoStreamReplayer operator generates video data from files, making it a **data generation** application. For optimal performance: + +```yaml +# Recommended configuration for VideoStreamReplayer +rivermax_tx_settings: + use_internal_memory_pool: true # Use MediaSenderService (memory pool) + memory_allocation: true # Enable internal pool allocation + memory_pool_location: "device" # GPU memory for optimal performance + sleep_between_operations: false # Maximum throughput mode +``` + +**Why Memory Pool Mode for VideoStreamReplayer:** +- **Sustained Throughput**: Pre-allocated buffers enable consistent high frame rates +- **Buffer Reuse**: Pool buffers are reused, reducing allocation overhead +- **File Reading Optimization**: Single copy from file data to pool buffer, then reuse +- **Production Stability**: Buffering provides resilience against variable file I/O timing + +#### Service Selection Guidelines + +```yaml +# Zero-Copy Mode (use_internal_memory_pool: false) +# Best for: Pipeline operators receiving MediaFrame/VideoBuffer/Tensor +# Performance: Absolute minimum latency, zero memory copies +# Use when: Processing data from previous operators in pipeline + +# Memory Pool Mode (use_internal_memory_pool: true) +# Best for: Data generation from files, sensors, cameras, synthetic sources +# Performance: Single copy with sustained high throughput via buffer reuse +# Use when: Reading/generating original data (like VideoStreamReplayer) +``` + +#### Memory Configuration for Maximum Performance + +```yaml +# GPU memory optimization for zero-copy transmission +memory_regions: +- name: "Data_TX_GPU" + kind: "device" # GPU memory + affinity: 0 # GPU device ID + num_bufs: 43200 # High buffer count for sustained throughput + buf_size: 1440 # Optimized for packet payload size + +# CPU memory for control data +- name: "Data_TX_CPU" + kind: "huge" # Huge pages for optimal DMA + num_bufs: 43200 # Match GPU buffer count + buf_size: 20 # RTP header size +``` + +#### Frame Rate and Timing Optimization + +```yaml +# VideoStreamReplayer timing configuration +replayer: + frame_rate: 60 # Match network transmission rate + realtime: true # Enable precise timing control + repeat: true # Loop for sustained streaming + +# Network transmission timing +rivermax_tx_settings: + frame_rate: 60 # Must match replayer frame rate + num_of_packets_in_chunk: 144 # Optimize for frame size + stats_report_interval_ms: 1000 # Monitor performance +``` + +### Performance Monitoring + +Monitor these key metrics for optimal VideoStreamReplayer performance: +- **Frame Transmission Rate**: Consistent 60fps without frame 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 + +- [Advanced Network Operators](../../operators/advanced_network/README.md) +- [Advanced Network Media Operators](../../operators/advanced_network_media/README.md) +- [High Performance Networking Tutorial](../../tutorials/high_performance_networking/README.md) +- [Advanced Networking Media Player](../adv_networking_media_player/README.md) 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..f47840e53e --- /dev/null +++ b/applications/adv_networking_media_sender/adv_networking_media_sender.yaml @@ -0,0 +1,106 @@ +%YAML 1.2 +# SPDX-FileCopyrightText: Copyright (c) 2022-2023 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 + 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 + tx_threads: + - thread_id: 0 + network_settings: + - stream_id: 0 + local_ip_address: 2.1.0.12 + destination_ip_address: 224.1.1.2 + destination_port: 50001 + +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..2c91de5783 --- /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 GROUP_WRITE + WORLD_READ WORLD_WRITE +) + +install( + FILES CMakeLists.txt.install + RENAME CMakeLists.txt + DESTINATION examples/${PROJECT_NAME} + COMPONENT ${PROJECT_NAME}-cppsrc + PERMISSIONS OWNER_READ OWNER_WRITE + GROUP_READ GROUP_WRITE + WORLD_READ WORLD_WRITE +) + +install( + FILES + adv_networking_media_sender.cpp + DESTINATION examples/${PROJECT_NAME} + COMPONENT ${PROJECT_NAME}-cppsrc + PERMISSIONS OWNER_READ OWNER_WRITE + GROUP_READ GROUP_WRITE + WORLD_READ WORLD_WRITE +) 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..25c32249ed --- /dev/null +++ b/applications/adv_networking_media_sender/cpp/adv_networking_media_sender.cpp @@ -0,0 +1,86 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023 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" + +using namespace holoscan::advanced_network; + +class App : public holoscan::Application { + public: + void compose() override { + using namespace holoscan; + + auto adv_net_config = from_config("advanced_network").as(); + if (advanced_network::adv_net_init(adv_net_config) != advanced_network::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] = advanced_network::get_rx_tx_configs_enabled(config()); + const auto mgr_type = advanced_network::get_manager_type(config()); + + HOLOSCAN_LOG_INFO("Using Advanced Network manager {}", + advanced_network::manager_type_to_string(mgr_type)); + + HOLOSCAN_LOG_INFO("Using ANO manager {}", advanced_network::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(); + + advanced_network::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..446334040d --- /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": ["Network", "Networking", "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, + "dependencies": { + "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..80140f7d38 --- /dev/null +++ b/applications/adv_networking_media_sender/python/CMakeLists.txt @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: Copyright (c) 2022-2023 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. + +# target does nothing but be created if this folder is reached +# to set dependencies on adequate operators +# add_custom_target(advanced_networking_bench_python ALL +# DEPENDS advanced_network_python +# ) + +# Install application and dependencies into the install/ directory for packaging +install( + FILES adv_networking_media_sender.py + DESTINATION bin/adv_networking_media_sender/python + COMPONENT adv_networking_media_sender-py +) + +install( + FILES ../adv_networking_media_sender.yaml + DESTINATION bin/adv_networking_media_sender/python + COMPONENT adv_networking_media_sender-py +) 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..ba9a46c590 --- /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-2023 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..e5ec380efa --- /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": ["Network", "Networking", "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, + "dependencies": { + "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_app_bin" + } + } +} diff --git a/operators/CMakeLists.txt b/operators/CMakeLists.txt index ed8a9cd65e..46107955bb 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/CHANGELOG.md b/operators/advanced_network/CHANGELOG.md index 040b0505ed..aa7e893a4d 100644 --- a/operators/advanced_network/CHANGELOG.md +++ b/operators/advanced_network/CHANGELOG.md @@ -14,6 +14,18 @@ ### Non-Breaking +Rivermax Manager: +- **Framework Migration** + - Migrated from legacy rmax-ral-lib to modern rivermax-dev-kit framework (1.70.32). +- **SMPTE 2110-20 Support** + - Implemented hardware-accelerated video streaming with support for multiple memory modes (GPU, host pinned, huge pages) and video formats (YCbCr 4:2:2/4:4:4/4:2:0, RGB 8/10/12-bit). +- **Zero-Copy Performance** + - Added MediaSenderZeroCopyService that forwards received frames without owning memory pools for improved performance. +- **Enhanced Configuration** + - Added configurable packet chunking (`num_of_packets_in_chunk`) and burst flags for better flow control. +- **Code Quality** + - Standardized naming convention from 'rmax' to 'rivermax' project-wide and removed redundant documentation. + Common: - Added `get_port_id` to get the port ID for a given PCIe address or config name. - Added `get_num_rx_queues` to get the number of RX queues for a given port. diff --git a/operators/advanced_network/Dockerfile b/operators/advanced_network/Dockerfile index c568da537b..79ede97900 100644 --- a/operators/advanced_network/Dockerfile +++ b/operators/advanced_network/Dockerfile @@ -131,6 +131,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ibverbs-utils \ python3-pyelftools \ python3-dev \ + libvulkan1 \ && rm -rf /var/lib/apt/lists/* # PIP installs @@ -163,7 +164,7 @@ FROM common-deps AS dpdk FROM common-deps AS rivermax # Define Rivermax-specific build arguments and environment variables -ARG RIVERMAX_VERSION=1.60.6 +ARG RIVERMAX_VERSION=1.71.4 ARG RIVERMAX_SDK_ZIP_PATH=./rivermax_ubuntu2204_${RIVERMAX_VERSION}.tar.gz ARG MAXPROC=8 @@ -175,8 +176,24 @@ ARG RMAX_TEGRA=OFF ENV RMAX_TEGRA=${RMAX_TEGRA} # Install additional dependencies required for Rivermax -RUN apt install -y iproute2 libcap-dev gdb ethtool iputils-ping net-tools \ - libfmt-dev libnl-3-dev libnl-genl-3-dev libcap-dev libglu1-mesa-dev freeglut3-dev mesa-common-dev libglew-dev +# Update package cache and install additional dependencies required for Rivermax +RUN apt-get update && apt-get install -y --no-install-recommends \ + iproute2 \ + libcap-dev \ + gdb \ + ethtool \ + iputils-ping \ + net-tools \ + libfmt-dev \ + libnl-3-dev \ + libnl-genl-3-dev \ + libglu1-mesa-dev \ + freeglut3-dev \ + mesa-common-dev \ + libglew-dev \ + cuda-nvml-dev-12-6 \ + cuda-nvtx-12-6 \ + && rm -rf /var/lib/apt/lists/* # Copy and extract the Rivermax SDK COPY ${RIVERMAX_SDK_ZIP_PATH} /tmp/rivermax_sdk.tar.gz @@ -205,21 +222,11 @@ RUN cd apps && \ cmake -B build -DRMAX_CUDA=ON -DRMAX_TEGRA=${RMAX_TEGRA} -DRMAX_BUILD_VIEWER=ON && \ cmake --build build -j $(nproc) -# Temporarily add missing definitions for rmax_apps_lib build until added to the SDK -RUN echo '\ - target_compile_definitions(rmax-apps-util-reduced PUBLIC \ - \$<\$:CUDA_ENABLED> \ - \$<\$:TEGRA_ENABLED> \ - )' >> rmax_apps_lib/util/CMakeLists.txt - # Build rmax_apps_lib sample applications -RUN cd rmax_apps_lib && \ +RUN cd rivermax-dev-kit && \ cmake -B build -DRMAX_CUDA=ON -DRMAX_TEGRA=${RMAX_TEGRA} -DRMAX_BUILD_VIEWER=ON && \ cmake --build build -j $(nproc) -# Temporarily exclude apps from rmax_apps_lib build until added to the SDK -RUN sed -i 's/\"apps\"//' rmax_apps_lib/CMakeLists.txt - # ============================== # Default stage: common-deps # If no target is specified, the common-deps stage will be built by default. diff --git a/operators/advanced_network/README.md b/operators/advanced_network/README.md index 157fd03b19..a604ad455b 100644 --- a/operators/advanced_network/README.md +++ b/operators/advanced_network/README.md @@ -111,7 +111,7 @@ To build and run the Dockerfile with `Rivermax` support, follow these steps: - Visit the [Rivermax SDK Page](https://developer.nvidia.com/networking/rivermax-getting-started) to download the Rivermax Release SDK. - Obtain a Rivermax developer license from the same page. This is necessary for using the SDK. -- Copy the downloaded SDK tar file (e.g., `rivermax_ubuntu2204_1.60.1.tar.gz`) into your current working directory. +- Copy the downloaded SDK tar file (e.g., `rivermax_ubuntu2204_1.70.31.tar.gz`) into your current working directory. - You can adjust the path using the `RIVERMAX_SDK_ZIP_PATH` build argument if needed. - Modify the version using the `RIVERMAX_VERSION` build argument if you're using a different SDK version. - Place the obtained Rivermax developer license file (`rivermax.lic`) into the `/opt/mellanox/rivermax/` directory. @@ -231,10 +231,10 @@ Too low means risk of dropped packets from NIC having nowhere to write (Rx) or h ##### Extended Receive Configuration for Rivermax manager -- **`rmax_rx_settings`**: Extended RX settings for Rivermax Manager. Rivermax Manager supports receiving the same stream from multiple redundant paths (IPO - Inline Packet Ordering). +- **`rivermax_rx_settings`**: Extended RX settings for Rivermax Manager. Rivermax Manager supports receiving the same stream from multiple redundant paths (IPO - Inline Packet Ordering). Each path is a combination of a source IP address, a destination IP address, a destination port, and a local IP address of the receiver device. type: `list` - full path: `cfg\interfaces\rx\queues\rmax_rx_settings` + full path: `cfg\interfaces\rx\queues\rivermax_rx_settings` - **`memory_registration`**: Flag, when enabled, reduces the number of memory keys in use by registering all the memory in a single pass on the application side. Can be used only together with HDS enabled - type: `boolean` @@ -258,7 +258,7 @@ Too low means risk of dropped packets from NIC having nowhere to write (Rx) or h - type: `sequence` - **`destination_ports`**: List of Destination IP ports (one port per receiving path) - type: `sequence` - - **`rx_stats_period_report_ms`**: Specifies the duration, in milliseconds, that the receiver will display statistics in the log. Set `0` to disable statistics logging feature + - **`stats_report_interval_ms`**: Specifies the duration, in milliseconds, that the receiver will display statistics in the log. Set `0` to disable statistics logging feature - type: `integer` - default:`0` - **`send_packet_ext_info`**: Enables the transmission of extended metadata for each received packet @@ -293,14 +293,15 @@ Too low means risk of dropped packets from NIC having nowhere to write (Rx) or h rx: queues: - name: Data1 - id: 1 + id: 0 cpu_core: '11' batch_size: 4320 - rmax_rx_settings: + rivermax_rx_settings: + settings_type: "ipo_receiver" memory_registration: true - max_path_diff_us: 100 + max_path_diff_us: 10000 ext_seq_num: true - sleep_between_operations_us: 100 + sleep_between_operations_us: 0 memory_regions: - "Data_RX_CPU" - "Data_RX_GPU" @@ -316,15 +317,13 @@ Too low means risk of dropped packets from NIC having nowhere to write (Rx) or h destination_ports: - 50001 - 50001 - rx_stats_period_report_ms: 3000 + stats_report_interval_ms: 3000 send_packet_ext_info: true ``` ##### Transmit Configuration (tx) - - (Current version of Rivermax manager doesn't support TX) - + - **`queues`**: List of queues on NIC type: `list` full path: `cfg\interfaces\tx\queues` @@ -341,7 +340,157 @@ Too low means risk of dropped packets from NIC having nowhere to write (Rx) or h - type: `integer` - **`memory_regions`**: List of memory regions where buffers are stored. memory regions names are configured in the [Memory Regions](#memory-regions) section type: `list` +##### Transmit Configuration (tx) +- **`queues`**: List of queues on NIC + **Type**: `list` + **Full Path**: `cfg\interfaces\tx\queues` + + - **`name`**: Name of the queue + - **Type**: `string` + + - **`id`**: Integer ID used for flow connection or lookup in operator compute method + - **Type**: `integer` + + - **`cpu_core`**: CPU core ID. Should be isolated when CPU polls the NIC for best performance. Not in use for DOCA GPUNetIO. Rivermax Manager can accept comma-separated list of CPU IDs. + - **Type**: `string` + + - **`batch_size`**: Number of packets in batch passed between operators. Larger values increase throughput at cost of latency. + - **Type**: `integer` + + - **`memory_regions`**: List of memory regions where buffers are stored (configured in Memory Regions section) + - **Type**: `list` + +##### Extended Transmit Configuration for Rivermax manager +The Rivermax TX configuration enables hardware-assisted SMPTE 2110-20 compliant video streaming with: +- Precision timestamping via PCIe PTP clock synchronization +- Jitter-free packetization of rasterized video frames +- Automatic UDP checksum offload +- Traffic shaping for constant bitrate delivery + + - **`rivermax_tx_settings`**: Extended TX settings for SMPTE 2110-20 media streaming + **Type**: `sequence` + **Full Path**: `cfg\interfaces\tx\queues\rivermax_tx_settings` + + - **`settings_type`**: Transmission mode. Must be `media_sender` for video + - **Type**: `string` + - **Required**: Yes + - **Valid Values**: `media_sender` + + - **`memory_registration`**: Enables bulk registration of GPU/CPU memory regions with NIC for zero-copy transfers. Recommended for high-throughput scenarios. + - **Type**: `boolean` + - **Default**: `true` + + - **`memory_allocation`**: I\O Memory allocated by application + - **Type**: `boolean` + - **Default**: `true` + + - **`memory_pool_location`**: Buffer memory type (`device/huge_pages/host_pinned/host`) + - **Type**: `string` + - **Required**: Yes + + - **`local_ip_address`**: Source IP address bound to transmitting network interface. + - **Type**: `string` + - **Required**: Yes + + - **`destination_ip_address`**: Unicast/Multicast group address for media stream distribution. + - **Type**: `string` + - **Required**: Yes + + - **`destination_port`**: UDP port number for media stream transmission + - **Type**: `integer` + - **Range**: 1024-65535 + - **Required**: Yes + + - **`video_format`**: Defines pixel sampling structure per SMPTE ST 2110-20 + - **Type**: `string` + - **Required**: Yes + - **Valid Values**: `YCbCr-4:2:2`, `YCbCr-4:4:4`,`YCbCr-4:2:0`, `RGB` + + - **`bit_depth`**: Color component quantization precision + - **Type**: `integer` + - **Required**: Yes + - **Valid Values**: `8`, `10`, `12` + + - **`frame_width`**: Horizontal resolution in pixels + - **Type**: `integer` + - **Required**: Yes + - **Valid Values**: `1920` for HD, `3840` for 4K UHD + + - **`frame_height`**: The vertical resolution of the video in pixels + - **Type**: `integer` + - **Required**: Yes + - **Valid Values**: `1080` for HD, `2160` for 4K UHD + + - **`frame_rate`**: Frame rate in fps + - **Type**: `integer` + - **Required**: Yes + - **Valid Values**: `24`, `25`, `30`, `50`, and `60` + + - **`dummy_sender`**: Test mode without NIC transmission + - **Type**: `boolean` + - **Default**: `false` + + - **`stats_report_interval_ms`**: Transmission stats logging interval (0=disable) + - **Type**: `integer` + - **Default**: `0` + + - **`verbose`**: Enable detailed transmission logging + - **Type**: `boolean` + - **Default**: `false` + + - **`sleep_between_operations`**: Add inter-burst delays for timing sync + - **Type**: `boolean` + - **Default**: `false` + +#### Example Configuration +```YAML + 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: 1200 + + interfaces: + - name: "tx_port" + address: cc:00.1 + tx: + queues: + - name: "tx_q_1" + id: 0 + cpu_core: "13" + 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: "host_pinned" + #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 + video_format: YCbCr-4:2:2 + bit_depth: 10 + frame_width: 1920 + frame_height: 1080 + frame_rate: 60 + dummy_sender: false + +``` #### API Structures diff --git a/operators/advanced_network/advanced_network/common.cpp b/operators/advanced_network/advanced_network/common.cpp index 4892ee9861..8432bc0686 100644 --- a/operators/advanced_network/advanced_network/common.cpp +++ b/operators/advanced_network/advanced_network/common.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +28,7 @@ #include #endif #if ANO_MGR_RIVERMAX -#include "advanced_network/managers/rivermax/adv_network_rmax_mgr.h" +#include "advanced_network/managers/rivermax/adv_network_rivermax_mgr.h" #endif #define ASSERT_ANO_MGR_INITIALIZED() \ @@ -277,6 +277,11 @@ Status send_tx_burst(BurstParams* burst) { return g_ano_mgr->send_tx_burst(burst); } +Status get_rx_burst(BurstParams** burst, int port, int q, int stream) { + ASSERT_ANO_MGR_INITIALIZED(); + return g_ano_mgr->get_rx_burst(burst, port, q, stream); +} + Status get_rx_burst(BurstParams** burst, int port, int q) { ASSERT_ANO_MGR_INITIALIZED(); return g_ano_mgr->get_rx_burst(burst, port, q); @@ -456,7 +461,7 @@ bool YAML::convert::parse_rx_queue_co #if ANO_MGR_RIVERMAX if (_manager_type == holoscan::advanced_network::ManagerType::RIVERMAX) { holoscan::advanced_network::Status status = - holoscan::advanced_network::RmaxMgr::parse_rx_queue_rivermax_config(q_item, q); + holoscan::advanced_network::RivermaxMgr::parse_rx_queue_rivermax_config(q_item, q); if (status != holoscan::advanced_network::Status::SUCCESS) { HOLOSCAN_LOG_ERROR("Failed to parse RX Queue config for Rivermax"); return false; @@ -480,6 +485,7 @@ bool YAML::convert::parse_rx_queue_co bool YAML::convert::parse_tx_queue_common_config( const YAML::Node& q_item, holoscan::advanced_network::TxQueueConfig& q) { if (!parse_common_queue_config(q_item, q.common_)) { return false; } +#if !ANO_MGR_RIVERMAX try { const auto& offload = q_item["offloads"]; q.common_.offloads_.reserve(offload.size()); @@ -488,6 +494,7 @@ bool YAML::convert::parse_tx_queue_co HOLOSCAN_LOG_ERROR("Error parsing TxQueueConfig: {}", e.what()); return false; } +#endif return true; } @@ -514,7 +521,7 @@ bool YAML::convert::parse_tx_queue_co #if ANO_MGR_RIVERMAX if (_manager_type == holoscan::advanced_network::ManagerType::RIVERMAX) { holoscan::advanced_network::Status status = - holoscan::advanced_network::RmaxMgr::parse_tx_queue_rivermax_config(q_item, q); + holoscan::advanced_network::RivermaxMgr::parse_tx_queue_rivermax_config(q_item, q); if (status != holoscan::advanced_network::Status::SUCCESS) { HOLOSCAN_LOG_ERROR("Failed to parse TX Queue config for Rivermax"); return false; diff --git a/operators/advanced_network/advanced_network/common.h b/operators/advanced_network/advanced_network/common.h index 3f05f8f919..1652166d85 100644 --- a/operators/advanced_network/advanced_network/common.h +++ b/operators/advanced_network/advanced_network/common.h @@ -408,6 +408,16 @@ void set_num_packets(BurstParams* burst, int64_t num); */ Status send_tx_burst(BurstParams* burst); +/** + * @brief Get a RX burst from a specific stream + * + * @param burst Burst structure + * @param port Port ID of interface + * @param q Queue ID of interface + * @param stream Stream ID + */ +Status get_rx_burst(BurstParams** burst, int port, int q, int stream); + /** * @brief Get a RX burst * diff --git a/operators/advanced_network/advanced_network/manager.cpp b/operators/advanced_network/advanced_network/manager.cpp index 28f92646a2..2a77c1a26c 100644 --- a/operators/advanced_network/advanced_network/manager.cpp +++ b/operators/advanced_network/advanced_network/manager.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,7 @@ #include "advanced_network/managers/gpunetio/adv_network_doca_mgr.h" #endif #if ANO_MGR_RIVERMAX -#include "advanced_network/managers/rivermax/adv_network_rmax_mgr.h" +#include "advanced_network/managers/rivermax/adv_network_rivermax_mgr.h" #endif #if ANO_MGR_DPDK || ANO_MGR_GPUNETIO @@ -71,7 +71,7 @@ std::unique_ptr ManagerFactory::create_instance(ManagerType type) { #endif #if ANO_MGR_RIVERMAX case ManagerType::RIVERMAX: - _manager = std::make_unique(); + _manager = std::make_unique(); break; #endif case ManagerType::DEFAULT: @@ -316,4 +316,8 @@ Status Manager::get_rx_burst(BurstParams** burst) { return Status::NULL_PTR; } +Status Manager::get_rx_burst(BurstParams** burst, int port, int q, int stream) { + return Status::NOT_SUPPORTED; +} + }; // namespace holoscan::advanced_network diff --git a/operators/advanced_network/advanced_network/manager.h b/operators/advanced_network/advanced_network/manager.h index 1b14dd11ea..e68f992fb6 100644 --- a/operators/advanced_network/advanced_network/manager.h +++ b/operators/advanced_network/advanced_network/manager.h @@ -69,6 +69,7 @@ class Manager { virtual void print_stats() = 0; virtual uint64_t get_burst_tot_byte(BurstParams* burst) = 0; virtual BurstParams* create_tx_burst_params() = 0; + virtual Status get_rx_burst(BurstParams** burst, int port, int q, int stream); virtual Status get_rx_burst(BurstParams** burst, int port, int q) = 0; virtual Status get_rx_burst(BurstParams** burst, int port_id); virtual Status get_rx_burst(BurstParams** burst); diff --git a/operators/advanced_network/advanced_network/managers/rivermax/CMakeLists.txt b/operators/advanced_network/advanced_network/managers/rivermax/CMakeLists.txt index 96224a985b..3f8fdc291d 100644 --- a/operators/advanced_network/advanced_network/managers/rivermax/CMakeLists.txt +++ b/operators/advanced_network/advanced_network/managers/rivermax/CMakeLists.txt @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,17 +20,20 @@ enable_language(CUDA) target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) -add_subdirectory(rmax_service) +add_subdirectory(rivermax_service) +add_subdirectory(applications) -target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/rmax_mgr_impl) +target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/rivermax_mgr_impl) target_sources(${PROJECT_NAME} PRIVATE - rmax_mgr_impl/adv_network_rmax_mgr.cpp - rmax_mgr_impl/burst_manager.cpp - rmax_mgr_impl/rmax_config_manager.cpp + rivermax_mgr_impl/adv_network_rivermax_mgr.cpp + rivermax_mgr_impl/rivermax_mgr_service.cpp + rivermax_mgr_impl/burst_manager.cpp + rivermax_mgr_impl/rivermax_config_manager.cpp + rivermax_mgr_impl/rivermax_queue_configs.cpp PUBLIC - adv_network_rmax_mgr.h + adv_network_rivermax_mgr.h ) target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) @@ -39,8 +42,6 @@ target_compile_options(${PROJECT_NAME} PUBLIC -std=c++17) target_link_libraries(${PROJECT_NAME} PRIVATE - rmax_service - rmax-ral-lib - rmax-ral-build + rivermax_service holoscan::core ) \ No newline at end of file diff --git a/operators/advanced_network/advanced_network/managers/rivermax/README.md b/operators/advanced_network/advanced_network/managers/rivermax/README.md new file mode 100644 index 0000000000..790155c387 --- /dev/null +++ b/operators/advanced_network/advanced_network/managers/rivermax/README.md @@ -0,0 +1,517 @@ +# Rivermax Manager Implementation + +This directory contains the complete implementation of the Rivermax Manager for the Advanced Network library. The Rivermax Manager provides high-performance network streaming capabilities using NVIDIA's Rivermax SDK, specifically designed for professional media and broadcast applications requiring ultra-low latency and high throughput. + +## Architecture Overview + +The Rivermax Manager implements a service-oriented architecture that abstracts the complexity of NVIDIA's Rivermax SDK while providing a unified interface through the Advanced Network library's Manager interface. + +### High-Level Architecture + +```mermaid +graph TB + subgraph "Application Layer" + A["Advanced Network Manager Interface
(Common API)"] + end + + subgraph "Rivermax Manager Implementation" + B["RivermaxMgr
(Main Manager Class)"] + + subgraph "Core Management Components" + C["Configuration Management
(rivermax_config_manager)"] + D["Service Management
(rivermax_mgr_service)"] + E["Burst & Packet Processing
(burst_manager)"] + end + + subgraph "Implementation Details" + F["Queue Configurations
(rivermax_queue_configs)"] + G["Data Types & Structures
(rivermax_ano_data_types)"] + H["Packet Processing
(packet_processor)"] + end + end + + subgraph "Rivermax SDK (RDK) Services" + subgraph "RX Services" + I["IPO Receiver Service
(rmax_ipo_receiver)"] + J["RTP Receiver Service
(rmax_rtp_receiver)"] + end + + subgraph "TX Services" + K["MediaSenderZeroCopyService
(True Zero-Copy)"] + L["MediaSenderService
(Memory Pool)"] + M["MediaSenderMockService
(Testing)"] + end + end + + subgraph "Hardware Abstraction Layer" + N["NVIDIA ConnectX NICs"] + O["Rivermax Hardware Drivers"] + P["RDMA & GPUDirect"] + end + + %% Connections + A --> B + B --> C + B --> D + B --> E + C --> F + D --> I + D --> J + D --> K + D --> L + D --> M + E --> G + E --> H + F --> I + F --> J + F --> K + F --> L + + I --> N + J --> N + K --> N + L --> N + M --> N + N --> O + N --> P + + %% Styling + classDef appLayer fill:#fff3e0 + classDef managerLayer fill:#e8f5e8 + classDef implLayer fill:#f3e5f5 + classDef rdkLayer fill:#e3f2fd + classDef hardwareLayer fill:#e1f5fe + + class A appLayer + class B,C,D,E managerLayer + class F,G,H implLayer + class I,J,K,L,M rdkLayer + class N,O,P hardwareLayer +``` + +## Directory Structure and File Roles + +### Root Level Files + +#### `adv_network_rivermax_mgr.h/cpp` +- **Purpose**: Main manager class implementing the Advanced Network Manager interface +- **Role**: Entry point for all Advanced Network operations, coordinates service lifecycle +- **Key Responsibilities**: + - Manager initialization and configuration + - Service creation and management + - Burst and packet API implementation + - Memory management coordination + +#### `rivermax_ano_data_types.h` +- **Purpose**: Core data structures and type definitions for Rivermax operations +- **Role**: Defines the fundamental data types used throughout the manager +- **Key Components**: + - `RivermaxBurst` - Packet burst container with metadata + - `AnoBurstsQueue` - Thread-safe queue for burst management + - `RivermaxPacketData` - Individual packet data structure + - Extended info structures for packet metadata + +#### `CMakeLists.txt` +- **Purpose**: Build configuration for the Rivermax manager +- **Role**: Defines compilation dependencies and linking requirements +- **Dependencies**: Rivermax SDK, Advanced Network common libraries + +### Implementation Directory (`rivermax_mgr_impl/`) + +This directory contains the core implementation components that handle the detailed functionality of the Rivermax manager. + +#### Configuration Management + +##### `rivermax_config_manager.h/cpp` +- **Purpose**: Comprehensive configuration parsing and validation +- **Role**: Translates Advanced Network configurations to Rivermax-specific settings +- **Key Components**: + - `ConfigBuilderContainer` - Manages multiple configuration builders + - `RxConfigManager` - Handles receive queue configurations + - `TxConfigManager` - Handles transmit queue configurations + - Configuration validation and memory region setup + +##### `rivermax_queue_configs.h/cpp` +- **Purpose**: Queue-specific configuration builders and settings +- **Role**: Creates and manages Rivermax service configurations +- **Key Components**: + - IPO Receiver configuration builders + - RTP Receiver configuration builders + - Media Sender configuration builders + - Memory allocator configuration + +#### Service Management + +##### `rivermax_mgr_service.h/cpp` +- **Purpose**: Service abstraction layer for Rivermax applications +- **Role**: Manages the lifecycle of Rivermax SDK services +- **Key Components**: + - `RivermaxManagerService` - Base service interface + - `RivermaxManagerRxService` - Base receiver service + - `RivermaxManagerTxService` - Base transmitter service + - `IPOReceiverService` - IPO protocol receiver implementation + - `RTPReceiverService` - RTP protocol receiver implementation + - `MediaSenderService` - Media transmission service with memory pool management + - `MediaSenderZeroCopyService` - Zero-copy media transmission service + +#### Packet and Burst Processing + +##### `burst_manager.h/cpp` +- **Purpose**: Manages packet bursts and memory allocation +- **Role**: Handles burst lifecycle, memory management, and packet pointer organization +- **Key Components**: + - `RivermaxBurst` - Enhanced burst implementation with `AnoBurstExtendedInfo` metadata + - `RxBurstsManager` - Manages received packet bursts with pointer-based architecture + - Burst allocation and deallocation strategies + - Packet pointer management (no data copying at burst level) + +##### `packet_processor.h` +- **Purpose**: Packet-level processing and transformation +- **Role**: Handles individual packet operations and metadata processing +- **Key Components**: + - Packet validation and filtering + - Metadata extraction and processing + - Protocol-specific packet handling + +#### Core Implementation + +##### `adv_network_rivermax_mgr.cpp` +- **Purpose**: Main manager implementation +- **Role**: Implements all Manager interface methods using Rivermax services +- **Key Functions**: + - Service initialization and coordination + - Burst and packet API implementations + - Memory management and buffer allocation + - Statistics collection and reporting + +##### `rivermax_chunk_consumer_ano.h` +- **Purpose**: Chunk-based data consumption interface +- **Role**: Provides an interface for consuming data chunks from Rivermax streams +- **Integration**: Used by the burst manager for efficient data handling + +### Service Directory (`rivermax_service/`) + +#### `CMakeLists.txt` +- **Purpose**: Build configuration for Rivermax service components +- **Role**: Manages additional service-specific build requirements +- **Integration**: Extends the main build configuration with service-specific dependencies + +## External Services and Dependencies + +### NVIDIA Rivermax SDK (RDK) + +The manager relies heavily on the Rivermax Development Kit, which provides: + +> **Note**: For more information about the Rivermax Development Kit, see the official repository: [NVIDIA Rivermax Dev Kit](https://github.com/NVIDIA/rivermax-dev-kit). The Rivermax Dev Kit is a high-level C++ software kit designed to accelerate and simplify Rivermax application development, offering intuitive abstractions and developer-friendly services for IP-based media and data streaming use cases. + +#### Core Services +- **`rmax_ipo_receiver`**: An RX service for receiving RTP data using Rivermax Inline Packet Ordering (IPO) feature +- **`rmax_rtp_receiver`**: An RX service for receiving RTP data using Rivermax Striding protocol +- **`rmax_xstream_media_sender`**: High-performance media transmission service with multiple variants: + - `MediaSenderZeroCopyService`: True zero-copy path using application frame buffers directly + - `MediaSenderService`: Single-copy path with internal memory pool for generated data + +#### Key Capabilities +- **Hardware Acceleration**: Direct NIC hardware access for minimal latency +- **Memory Management**: Advanced memory allocation strategies (huge pages, GPU memory) +- **Protocol Support**: SMPTE 2110, RTP, custom protocols +- **Timing Control**: Precise packet timing for broadcast applications + +### Hardware Dependencies + +#### NVIDIA ConnectX NICs +- **ConnectX-6 or later**: Required for Rivermax functionality +- **Hardware Features**: RDMA, GPUDirect, hardware timestamping +- **Driver Requirements**: MOFED drivers for full functionality + +#### GPU Integration +- **GPUDirect Support**: Zero-copy GPU-to-NIC data paths +- **Memory Management**: Device memory allocation and management +- **Compute Integration**: GPU-based packet processing capabilities + +## Operational Flow + +### Initialization Sequence + +The Rivermax manager follows a carefully orchestrated initialization process to ensure optimal performance and proper resource allocation: + +```mermaid +flowchart TD + A["Application Startup"] --> B["RivermaxMgr Construction"] + B --> C["YAML Configuration Loading"] + + C --> D["Configuration Parsing
(rivermax_config_manager)"] + D --> E["Queue Configuration Building
(rivermax_queue_configs)"] + E --> F["Memory Region Validation"] + + F --> G{{"Service Type Detection"}} + G -->|RX Queue| H["RX Service Configuration"] + G -->|TX Queue| I["TX Service Configuration"] + + H --> J["RX Service Creation"] + I --> K["TX Service Creation"] + + J --> L{{"RX Service Type"}} + L -->|settings_type: ipo_receiver| M["IPOReceiverService
Instantiation"] + L -->|settings_type: rtp_receiver| N["RTPReceiverService
Instantiation"] + + K --> O{{"TX Service Type"}} + O -->|use_internal_memory_pool: false| P["MediaSenderZeroCopyService
Instantiation"] + O -->|use_internal_memory_pool: true| Q["MediaSenderService
Instantiation"] + O -->|dummy_sender: true| R["MediaSenderMockService
Instantiation"] + + M --> S["Memory Region Allocation
(CPU/GPU Buffers)"] + N --> S + P --> S + Q --> S + R --> S + + S --> T["Hardware Initialization
(ConnectX NIC Setup)"] + T --> U["Service Launch
(Background Threads)"] + U --> V["Ready for Operations
(Burst Processing)"] + + %% Styling + classDef initPhase fill:#e3f2fd + classDef configPhase fill:#f3e5f5 + classDef servicePhase fill:#e8f5e8 + classDef hardwarePhase fill:#e1f5fe + classDef readyPhase fill:#e8f5e8 + + class A,B,C initPhase + class D,E,F configPhase + class G,H,I,J,K,L,M,N,O,P,Q,R servicePhase + class S,T hardwarePhase + class U,V readyPhase +``` + +#### Detailed Initialization Steps + +1. **Configuration Parsing**: Parse YAML configuration into Rivermax-specific settings using ConfigBuilderContainer +2. **Service Creation**: Instantiate appropriate Rivermax services based on queue configuration and service types +3. **Memory Setup**: Allocate and configure memory regions (CPU/GPU) with proper alignment and access patterns +4. **Hardware Initialization**: Initialize NIC hardware, establish RDMA connections, and configure hardware queues +5. **Service Launch**: Start background services for packet processing with proper CPU affinity and threading + +### Receive Path (RX) + +The RX path implements a sophisticated pointer-based architecture for zero-copy packet processing: + +```mermaid +flowchart TD + A["Network Packets
(ConnectX NIC)"] --> B["Hardware DMA
(Direct Memory Access)"] + B --> C["Pre-allocated Memory Regions
(Host/Device Memory)"] + + C --> D{{"RDK Service Selection"}} + D -->|settings_type: ipo_receiver| E["IPO Receiver Service
(rmax_ipo_receiver)"] + D -->|settings_type: rtp_receiver| F["RTP Receiver Service
(rmax_rtp_receiver)"] + + E --> G["Packet Reception
(Hardware Acceleration)"] + F --> G + + G --> H["RivermaxMgr Processing
(burst_manager)"] + H --> I["Burst Assembly
(Packet Pointers Only)"] + + I --> J["AnoBurstExtendedInfo
Metadata Creation"] + J --> K["RivermaxBurst Container
(Pointers + Metadata)"] + + K --> L{{"Header-Data Split
(HDS) Configuration"}} + L -->|hds_on: true| M["Headers: CPU Memory
burst->pkts[0]
Payloads: GPU Memory
burst->pkts[1]"] + L -->|hds_on: false| N["Headers + Payloads
CPU Memory
burst->pkts[0] + offset"] + + M --> O["AnoBurstsQueue
(Thread-safe Distribution)"] + N --> O + + O --> P["Application Consumption
(get_rx_burst)"] + P --> Q["Burst Processing
(Advanced Network Media)"] + Q --> R["Pointer Lifecycle Tracking
(free_all_packets_and_burst_rx)"] + + %% Zero-copy emphasis + S["Zero-Copy Architecture
Emphasis"] -.-> I + S -.-> K + S -.-> O + T["No Data Copying
Only Pointer Management"] -.-> S + + %% Styling + classDef hardwareLayer fill:#e1f5fe + classDef rdkLayer fill:#e3f2fd + classDef managerLayer fill:#f3e5f5 + classDef burstLayer fill:#e8f5e8 + classDef appLayer fill:#fff3e0 + classDef zeroCopyEmphasis fill:#e8f5e8,stroke:#4caf50,stroke-width:3px + + class A,B,C hardwareLayer + class D,E,F,G rdkLayer + class H,I,J,K,L,M,N,O managerLayer + class P,Q,R appLayer + class S,T zeroCopyEmphasis +``` + +#### Key RX Path Characteristics + +1. **Packet Reception**: Rivermax service receives packets from NIC hardware directly into pre-allocated memory regions +2. **Burst Assembly**: Packet **pointers** are aggregated into `RivermaxBurst` containers with `AnoBurstExtendedInfo` metadata (no data copying) +3. **Metadata Extraction**: Extract timing, flow, and protocol information from packet headers +4. **HDS Configuration**: Headers and payloads placed in CPU/GPU memory based on Header-Data Split settings +5. **Queue Management**: Bursts (containing pointers) are queued for application consumption via `AnoBurstsQueue` +6. **Memory Management**: Coordinate buffer allocation, cleanup, and pointer lifecycle tracking + +### Transmit Path (TX) + +The TX path implements frame-level processing with configurable memory management strategies: + +```mermaid +flowchart TD + A["Application
(MediaFrame Input)"] --> B["BurstParams Creation
(custom_pkt_data attachment)"] + B --> C["RivermaxMgr Processing
(Service Coordination)"] + + C --> D{{"Service Selection
(Configuration Driven)"}} + D -->|use_internal_memory_pool: false| E["MediaSenderZeroCopyService
(True Zero-Copy Path)"] + D -->|use_internal_memory_pool: true| F["MediaSenderService
(Single Copy + Pool Path)"] + D -->|dummy_sender: true| G["MediaSenderMockService
(Testing Mode)"] + + %% Zero-Copy Path + E --> H["No Internal Memory Pool"] + H --> I["Direct Frame Reference
(custom_pkt_data → RDK)"] + I --> J["RDK MediaSenderApp
(Zero-Copy Processing)"] + J --> K["Frame Ownership Transfer
& Release After Processing"] + + %% Memory Pool Path + F --> L["Pre-allocated MediaFramePool
(MEDIA_FRAME_POOL_SIZE)"] + L --> M["Single Memory Copy
(Frame → Pool Buffer)"] + M --> N["RDK MediaSenderApp
(Pool Buffer Processing)"] + N --> O["Pool Buffer Reuse
(Returned to Pool)"] + + %% Mock Path + G --> P["Mock Processing
(Testing/Development)"] + + %% Common RDK Processing + J --> Q["RDK Internal Processing
(All Packet Operations)"] + N --> Q + P --> Q + + Q --> R["RTP Packetization
(SMPTE 2110 Standard)"] + R --> S["Protocol Headers
& Metadata Addition"] + S --> T["Precise Timing Control
& Scheduling"] + T --> U["Hardware Submission
(ConnectX NIC)"] + U --> V["Network Transmission"] + + %% Performance Paths + W["Zero-Copy Performance
Minimum Latency"] -.-> E + X["Memory Pool Performance
Sustained Throughput"] -.-> F + + %% Styling + classDef appLayer fill:#fff3e0 + classDef managerLayer fill:#f3e5f5 + classDef zeroCopyPath fill:#e8f5e8,stroke:#4caf50,stroke-width:3px + classDef poolPath fill:#fff8e1,stroke:#ff9800,stroke-width:3px + classDef mockPath fill:#f5f5f5,stroke:#757575,stroke-width:2px + classDef rdkLayer fill:#e3f2fd + classDef networkLayer fill:#e1f5fe + classDef performanceEmphasis fill:#e8f5e8,stroke:#4caf50,stroke-width:2px + + class A,B appLayer + class C,D managerLayer + class E,H,I,J,K zeroCopyPath + class F,L,M,N,O poolPath + class G,P mockPath + class Q,R,S,T rdkLayer + class U,V networkLayer + class W,X performanceEmphasis +``` + +#### Key TX Path Characteristics + +1. **Burst Preparation**: Applications provide frame data in burst format with MediaFrame references via `custom_pkt_data` +2. **Service Selection**: Route to appropriate MediaSender service based on configuration: + - `MediaSenderZeroCopyService`: Direct frame buffer access (zero-copy, minimum latency) + - `MediaSenderService`: Copy to internal memory pool (single-copy, sustained throughput) + - `MediaSenderMockService`: Testing mode with minimal functionality +3. **RDK Processing**: All packet formatting, RTP headers, and protocol handling performed by RDK services +4. **Timing Control**: Apply precise transmission timing within RDK for SMPTE 2110 compliance +5. **Hardware Submission**: Submit formatted packets to NIC hardware via Rivermax acceleration +6. **Resource Cleanup**: Free frame buffers and update statistics based on service type + + +### Memory Architecture Components + +#### Memory Region Types +- **Host Memory**: Standard system memory for headers and control data +- **Huge Pages**: Large page allocations for improved performance and reduced TLB misses +- **Device Memory**: GPU memory for zero-copy operations and GPU-accelerated processing +- **Pinned Memory**: Host memory accessible to GPU and NIC for efficient DMA transfers + +#### Buffer Management Strategy +- **Pre-allocation**: Buffers are allocated during initialization to avoid runtime allocation overhead +- **Pool Management**: Efficient buffer reuse through pool allocation reduces memory fragmentation +- **Lifecycle Tracking**: Careful buffer ownership and cleanup management prevents memory leaks +- **Burst Metadata**: `AnoBurstExtendedInfo` structure carries configuration details: + - **HDS Configuration**: `hds_on`, `header_stride_size`, `payload_stride_size` + - **Memory Location Flags**: `header_on_cpu`, `payload_on_cpu` + - **Segment Indices**: `header_seg_idx`, `payload_seg_idx` for memory region mapping + +#### HDS Memory Layout Optimization +- **HDS Enabled**: Headers stored in CPU memory, payloads in GPU memory for optimal processing +- **HDS Disabled**: Headers and payloads stored together in CPU memory with calculated offsets + +## Performance Optimizations + +The Rivermax manager implements multiple layers of performance optimization for ultra-low latency streaming: + +### Multi-Layer Optimization Strategy + +#### Zero-Copy Architecture +- **Pointer-Based Bursts**: Bursts contain only pointers to packet data, no copying at burst level +- **Direct Memory Access**: Minimize data copying between components through DMA and pointer management +- **GPU Integration**: Direct GPU-to-NIC data paths where supported through GPUDirect +- **Frame Reference Management**: MediaFrame objects reference original application buffers (TX zero-copy path) +- **Buffer Sharing**: Efficient buffer sharing between services and applications through pointer management + +#### Memory Copy Optimization +- **Strategy-Based Processing**: RX path uses optimal copy strategies (contiguous/strided) based on memory layout +- **Adaptive Strategy Selection**: Runtime detection of optimal copy strategy based on buffer alignment +- **Single Copy Principle**: When copying is necessary, ensure only one copy operation per data path + +### Thread Management +- **Service Threads**: Dedicated threads for each Rivermax service +- **CPU Affinity**: Thread pinning to isolated CPU cores +- **Lock-Free Queues**: High-performance inter-thread communication + +### Hardware Optimization +- **Batch Processing**: Process multiple packets per operation through burst containers +- **Hardware Queues**: Utilize multiple NIC queues for parallelism +- **Interrupt Mitigation**: Reduce interrupt overhead through batching +- **Memory Copy Optimization**: + - Contiguous packets: Single `cudaMemcpy` operation + - Strided packets: Optimized `cudaMemcpy2D` with detected stride parameters + - Adaptive strategy selection based on runtime memory layout analysis + +## Configuration Integration + +The Rivermax manager integrates seamlessly with the Advanced Network configuration system: + +### Supported Configurations +- **Network Interfaces**: Multiple NIC configuration support +- **Memory Regions**: Flexible memory allocation strategies +- **Queue Settings**: Per-queue configuration with protocol-specific options +- **Protocol Parameters**: RTP, IPO, and media streaming parameters + +### Validation and Error Handling +- **Configuration Validation**: Comprehensive parameter validation +- **Runtime Monitoring**: Service health monitoring and error reporting +- **Graceful Degradation**: Fallback strategies for partial failures + +## Future Extensibility + +The architecture is designed for future enhancements: + +### Protocol Support +- Additional protocol implementations can be added through the service interface +- New Rivermax SDK features can be integrated without changing the manager interface + +### Hardware Support +- Support for new ConnectX generations through configuration updates +- Enhanced GPU integration as hardware capabilities evolve + +### Performance Enhancements +- Additional optimization strategies can be implemented in the service layer +- New memory management techniques can be integrated transparently \ No newline at end of file diff --git a/operators/advanced_network/advanced_network/managers/rivermax/adv_network_rmax_mgr.h b/operators/advanced_network/advanced_network/managers/rivermax/adv_network_rivermax_mgr.h similarity index 89% rename from operators/advanced_network/advanced_network/managers/rivermax/adv_network_rmax_mgr.h rename to operators/advanced_network/advanced_network/managers/rivermax/adv_network_rivermax_mgr.h index 1e37326cc4..0a5e345c3f 100644 --- a/operators/advanced_network/advanced_network/managers/rivermax/adv_network_rmax_mgr.h +++ b/operators/advanced_network/advanced_network/managers/rivermax/adv_network_rivermax_mgr.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,16 +18,17 @@ #pragma once #include +#include #include #include "advanced_network/manager.h" namespace holoscan::advanced_network { -class RmaxMgr : public Manager { +class RivermaxMgr : public Manager { public: - RmaxMgr(); - ~RmaxMgr(); + RivermaxMgr(); + ~RivermaxMgr(); bool set_config_and_initialize(const NetworkConfig& cfg) override; void initialize() override; void run() override; @@ -58,7 +59,7 @@ class RmaxMgr : public Manager { void free_packet(BurstParams* burst, int pkt) override; void free_rx_burst(BurstParams* burst) override; void free_tx_burst(BurstParams* burst) override; - + Status get_rx_burst(BurstParams** burst, int port, int q, int stream) override; Status get_rx_burst(BurstParams** burst, int port, int q) override; using holoscan::advanced_network::Manager::get_rx_burst; // for overloads Status set_packet_tx_time(BurstParams* burst, int idx, uint64_t timestamp); @@ -73,8 +74,9 @@ class RmaxMgr : public Manager { Status get_mac_addr(int port, char* mac) override; private: - class RmaxMgrImpl; - std::unique_ptr pImpl; + class RivermaxMgrImpl; + std::unique_ptr pImpl; + std::mutex initialization_mutex_; }; }; // namespace holoscan::advanced_network diff --git a/operators/advanced_network/advanced_network/managers/rivermax/applications/CMakeLists.txt b/operators/advanced_network/advanced_network/managers/rivermax/applications/CMakeLists.txt new file mode 100644 index 0000000000..56ef014e8b --- /dev/null +++ b/operators/advanced_network/advanced_network/managers/rivermax/applications/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.20) + +message(STATUS "PROJECT_NAME: ${PROJECT_NAME}") + +enable_language(CUDA) + +target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + +target_sources(${PROJECT_NAME} + PRIVATE + ano_ipo_receiver.cpp + ano_rtp_receiver.cpp + ano_media_sender.cpp + PUBLIC + utils.h + ano_ipo_receiver.h + ano_rtp_receiver.h + ano_media_sender.h +) + +target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) +target_compile_options(${PROJECT_NAME} PUBLIC -std=c++17) diff --git a/operators/advanced_network/advanced_network/managers/rivermax/applications/ano_ipo_receiver.cpp b/operators/advanced_network/advanced_network/managers/rivermax/applications/ano_ipo_receiver.cpp new file mode 100644 index 0000000000..882fea2526 --- /dev/null +++ b/operators/advanced_network/advanced_network/managers/rivermax/applications/ano_ipo_receiver.cpp @@ -0,0 +1,188 @@ +/* + * 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 "ano_ipo_receiver.h" + +namespace holoscan::advanced_network { + +void ANOIPOReceiverSettings::init_default_values() +{ + AppSettings::init_default_values(); + app_memory_alloc = true; + max_path_differential_us = 50000; + is_extended_sequence_number = false; + num_of_packets_in_chunk = DEFAULT_NUM_OF_PACKETS_IN_CHUNK; + min_packets_in_rx_chunk = 0; + max_packets_in_rx_chunk = 0; +} + +ReturnStatus ANOIPOReceiverSettingsValidator::validate(const std::shared_ptr& settings) const +{ + if (settings->thread_settings.empty()) { + std::cerr << "Must be at least one thread" << std::endl; + return ReturnStatus::failure; + } + for (const auto& thread : settings->thread_settings) { + if (thread.stream_network_settings.empty()) { + std::cerr << "Must be at least one stream" << std::endl; + return ReturnStatus::failure; + } + for (const auto& stream : thread.stream_network_settings) { + if (stream.source_ips.empty()) { + std::cerr << "Must be at least one source IP" << std::endl; + return ReturnStatus::failure; + } + if (stream.destination_ips.size() != stream.source_ips.size()) { + std::cerr << "Must be the same number of destination IPs as number of source IPs" << std::endl; + return ReturnStatus::failure; + } + if (stream.local_ips.size() != stream.source_ips.size()) { + std::cerr << "Must be the same number of NIC addresses as number of source IPs" << std::endl; + return ReturnStatus::failure; + } + if (stream.destination_ports.size() != stream.source_ips.size()) { + std::cerr << "Must be the same number of destination ports as number of source IPs" << std::endl; + return ReturnStatus::failure; + } + } + } + return ReturnStatus::success; +} + +ANOIPOReceiverApp::ANOIPOReceiverApp(std::shared_ptr> settings_builder) : + RmaxReceiverBaseApp(), + m_settings_builder(std::move(settings_builder)), + m_thread_streams_flows(), + m_devices_ips() +{ +} + +ReturnStatus ANOIPOReceiverApp::initialize_connection_parameters() +{ + for (const auto& thread : m_ipo_receiver_settings->thread_settings) { + for (const auto& stream : thread.stream_network_settings) { + m_num_paths_per_stream = stream.local_ips.size(); // For now assume all streams have the same number of paths + for (size_t i = 0; i < m_num_paths_per_stream; ++i) { + in_addr device_address; + rmx_device_iface device_iface; + if (inet_pton(AF_INET, stream.local_ips[i].c_str(), &device_address) != 1) { + std::cerr << "Failed to parse address of device " << stream.local_ips[i] << std::endl; + return ReturnStatus::failure; + } + rmx_status status = rmx_retrieve_device_iface_ipv4(&device_iface, &device_address); + if (status != RMX_OK) { + std::cerr << "Failed to get device: " << stream.local_ips[i] << " with status: " << status << std::endl; + return ReturnStatus::failure; + } + m_devices_ips.push_back(stream.local_ips[i]); + m_device_interfaces.push_back(device_iface); + } + } + } + return ReturnStatus::success; +} + +ReturnStatus ANOIPOReceiverApp::initialize_app_settings() +{ + if (m_settings_builder == nullptr) { + std::cerr << "Settings builder is not initialized" << std::endl; + return ReturnStatus::failure; + } + m_ipo_receiver_settings = std::make_shared(); + ReturnStatus rc = m_settings_builder->build(m_ipo_receiver_settings); + if (rc == ReturnStatus::success) { + m_app_settings = m_ipo_receiver_settings; + return ReturnStatus::success; + } + if (rc != ReturnStatus::success_cli_help) { + std::cerr << "Failed to build settings" << std::endl; + } + m_obj_init_status = rc; + return rc; +} + +void ANOIPOReceiverApp::run_receiver_threads() +{ + run_threads(m_receivers); +} + +void ANOIPOReceiverApp::configure_network_flows() +{ + int thread_index = 0; + uint16_t source_port = 0; + for (const auto& thread : m_ipo_receiver_settings->thread_settings) { + std::vector> streams; + int internal_stream_index = 0; + for (const auto& stream : thread.stream_network_settings) { + size_t num_of_paths = stream.local_ips.size(); + std::vector paths; + for (size_t i = 0; i < num_of_paths; ++i) { + paths.emplace_back(stream.stream_id, stream.source_ips[i], source_port, stream.destination_ips[i], stream.destination_ports[i]); + } + streams.push_back(paths); + m_stream_id_map[stream.stream_id] = std::make_pair(thread_index, internal_stream_index); + internal_stream_index++; + } + m_thread_streams_flows.push_back(streams); + thread_index++; + } +} + +void ANOIPOReceiverApp::initialize_receive_io_nodes() +{ + int number_of_threads = m_ipo_receiver_settings->thread_settings.size(); + size_t streams_offset = 0; + for (int thread_index = 0; thread_index < number_of_threads; ++thread_index) { + int number_of_streams = m_thread_streams_flows[thread_index].size(); + m_receivers.push_back(std::unique_ptr(new IPOReceiverIONode( + *m_app_settings, + m_ipo_receiver_settings->max_path_differential_us, + m_ipo_receiver_settings->is_extended_sequence_number, + m_devices_ips, + thread_index, + m_ipo_receiver_settings->app_threads_cores[thread_index], + *m_memory_utils))); + static_cast(m_receivers[thread_index].get())->initialize_streams(streams_offset, m_thread_streams_flows[thread_index]); + streams_offset += number_of_streams; + } +} + +void ANOIPOReceiverApp::distribute_work_for_threads() +{ + m_streams_per_thread.reserve(m_ipo_receiver_settings->thread_settings.size()); + m_ipo_receiver_settings->num_of_total_streams = 0; + int thread_index = 0; + for (const auto& thread : m_ipo_receiver_settings->thread_settings) { + m_streams_per_thread[thread_index] = thread.stream_network_settings.size(); + m_ipo_receiver_settings->num_of_total_streams += thread.stream_network_settings.size(); + thread_index++; + } +} + +ReturnStatus ANOIPOReceiverApp::find_internal_stream_index(size_t external_stream_index, size_t& thread_index, size_t& internal_stream_index) { + if (m_stream_id_map.find(external_stream_index) == m_stream_id_map.end()) { + std::cerr << "Invalid stream index " << external_stream_index << std::endl; + return ReturnStatus::failure; + } + + thread_index = m_stream_id_map[external_stream_index].first; + internal_stream_index = m_stream_id_map[external_stream_index].second; + + return ReturnStatus::success; +} + +} // namespace holoscan::advanced_network diff --git a/operators/advanced_network/advanced_network/managers/rivermax/applications/ano_ipo_receiver.h b/operators/advanced_network/advanced_network/managers/rivermax/applications/ano_ipo_receiver.h new file mode 100644 index 0000000000..39794289c9 --- /dev/null +++ b/operators/advanced_network/advanced_network/managers/rivermax/applications/ano_ipo_receiver.h @@ -0,0 +1,115 @@ +/* + * 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 RIVERMAX_ANO_APPLICATIONS_IPO_RECEIVER_H_ +#define RIVERMAX_ANO_APPLICATIONS_IPO_RECEIVER_H_ + +#include + +#include "utils.h" +#include "rdk/apps/rmax_receiver_base.h" +#include "rdk/io_node/receivers/ipo_receiver_io_node.h" +#include "rdk/rivermax_dev_kit.h" + +namespace holoscan::advanced_network { + +using namespace rivermax::dev_kit::apps; +using namespace rivermax::dev_kit::io_node; +using namespace rivermax::dev_kit::services; +using namespace rivermax::dev_kit::core; + +struct ANOIPOReceiverSettings : AppSettings +{ +public: + static constexpr uint32_t DEFAULT_NUM_OF_PACKETS_IN_CHUNK = 262144; + void init_default_values() override; + + uint32_t max_path_differential_us; + bool is_extended_sequence_number; + size_t min_packets_in_rx_chunk; + size_t max_packets_in_rx_chunk; + std::vector thread_settings; +}; + +/** +* @brief: Validator for Rivermax IPO Receiver settings. +*/ +class ANOIPOReceiverSettingsValidator : public ISettingsValidator +{ +public: + ReturnStatus validate(const std::shared_ptr& settings) const override; +}; + + +class ANOIPOReceiverApp : public RmaxReceiverBaseApp +{ +private: + /* Settings builder pointer */ + std::shared_ptr> m_settings_builder; + /* Application settings pointer */ + std::shared_ptr m_ipo_receiver_settings; + /* Network recv flows */ + std::vector>> m_thread_streams_flows; + /* list of devices IPs */ + std::vector m_devices_ips; + /* map external stream ID to internal thread ID and stream ID */ + std::unordered_map> m_stream_id_map; +public: + /** + * @brief: ANOIPOReceiverApp class constructor. + * + * @param [in] settings_builder: Settings builder pointer. + */ + ANOIPOReceiverApp(std::shared_ptr> settings_builder); + /** + * @brief: ANOIPOReceiverApp class destructor. + */ + virtual ~ANOIPOReceiverApp() = default; + /** + * @brief: Get IPO streams total statistics. + * + * This function hides the base class function and provides default template arguments. + * + * @return: A vector of @ref IPORXStatistics. + */ + std::vector get_streams_total_statistics() const { + return RmaxReceiverBaseApp::get_streams_total_statistics(); + } + + /** + * @brief: Find internal thread and stream index for a given external stream index. + * + * @param [in] external_stream_index: The external stream index. + * @param [out] thread_index: The internal thread index. + * @param [out] internal_stream_index: The internal stream index. + * + * @return: Status of the operation. + */ + ReturnStatus find_internal_stream_index(size_t external_stream_index, size_t& thread_index, size_t& internal_stream_index) override; + +private: + ReturnStatus initialize_app_settings() final; + ReturnStatus initialize_connection_parameters() final; + void configure_network_flows() final; + void initialize_receive_io_nodes() final; + void run_receiver_threads() final; + void distribute_work_for_threads(); +}; + +} // namespace holoscan::advanced_network + +#endif // RIVERMAX_ANO_APPLICATIONS_IPO_RECEIVER_H_ diff --git a/operators/advanced_network/advanced_network/managers/rivermax/applications/ano_media_sender.cpp b/operators/advanced_network/advanced_network/managers/rivermax/applications/ano_media_sender.cpp new file mode 100644 index 0000000000..90314ea143 --- /dev/null +++ b/operators/advanced_network/advanced_network/managers/rivermax/applications/ano_media_sender.cpp @@ -0,0 +1,326 @@ +/* + * 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 "ano_media_sender.h" + +namespace holoscan::advanced_network { + +void ANOMediaSenderSettings::init_default_values() +{ + AppSettings::init_default_values(); + media.frames_fields_in_mem_block = 1; + media.resolution = { FHD_WIDTH, FHD_HEIGHT }; + num_of_packets_in_chunk = ANOMediaSenderSettings::DEFAULT_NUM_OF_PACKETS_IN_CHUNK_FHD; +} + +ReturnStatus ANOMediaSenderSettingsValidator::validate(const std::shared_ptr& settings) const +{ + if (settings->thread_settings.empty()) { + std::cerr << "Must be at least one thread" << std::endl; + return ReturnStatus::failure; + } + ReturnStatus rc; + for (const auto& thread : settings->thread_settings) { + if (thread.stream_network_settings.empty()) { + std::cerr << "Must be at least one stream" << std::endl; + return ReturnStatus::failure; + } + for (const auto& stream : thread.stream_network_settings) { + rc = ValidatorUtils::validate_ip4_address(stream.local_ip); + if (rc != ReturnStatus::success) { + return rc; + } + rc = ValidatorUtils::validate_ip4_address(stream.destination_ip); + if (rc != ReturnStatus::success) { + return rc; + } + rc = ValidatorUtils::validate_ip4_port(stream.destination_port); + if (rc != ReturnStatus::success) { + return rc; + } + } + } + + if (settings->register_memory && !settings->app_memory_alloc) { + std::cerr << "Register memory option is supported only with application memory allocation" << std::endl; + return ReturnStatus::failure; + } + return ReturnStatus::success; +} + +ANOMediaSenderApp::ANOMediaSenderApp(std::shared_ptr> settings_builder) : + RmaxBaseApp(), + m_settings_builder(std::move(settings_builder)), + m_device_interface{} +{ +} + +ReturnStatus ANOMediaSenderApp::post_load_settings() +{ + uint32_t default_packets_in_chunk; + + if (m_app_settings->media.resolution == Resolution(UHD_WIDTH, UHD_HEIGHT) || + m_app_settings->media.resolution == Resolution(UHD_HEIGHT, UHD_WIDTH)) { + default_packets_in_chunk = ANOMediaSenderSettings::DEFAULT_NUM_OF_PACKETS_IN_CHUNK_UHD; + } else { + default_packets_in_chunk = ANOMediaSenderSettings::DEFAULT_NUM_OF_PACKETS_IN_CHUNK_FHD; + } + + if (m_app_settings->num_of_packets_in_chunk != default_packets_in_chunk) { + m_app_settings->num_of_packets_in_chunk_specified = true; + } + auto rc = initialize_media_settings(*m_app_settings); + if (rc != ReturnStatus::success) { + std::cerr << "Failed to initialize media settings" << std::endl; + } + return rc; +} + +ReturnStatus ANOMediaSenderApp::initialize_app_settings() +{ + if (m_settings_builder == nullptr) { + std::cerr << "Settings builder is not initialized" << std::endl; + return ReturnStatus::failure; + } + m_media_sender_settings = std::make_shared(); + ReturnStatus rc = m_settings_builder->build(m_media_sender_settings); + if (rc == ReturnStatus::success) { + m_app_settings = m_media_sender_settings; + std::cout << "Successfully initialized ANO Media Sender application settings" << std::endl; + return ReturnStatus::success; + } + if (rc != ReturnStatus::success_cli_help) { + std::cerr << "Failed to build settings" << std::endl; + } + m_obj_init_status = rc; + return rc; +} + +ReturnStatus ANOMediaSenderApp::initialize() +{ + ReturnStatus rc = RmaxBaseApp::initialize(); + + if (rc != ReturnStatus::obj_init_success) { + return m_obj_init_status; + } + + try { + distribute_work_for_threads(); + configure_network_flows(); + initialize_sender_threads(); + rc = configure_memory_layout(); + if (rc == ReturnStatus::failure) { + std::cerr << "Failed to configure memory layout" << std::endl; + return rc; + } + } + catch (const std::exception & error) { + std::cerr << error.what() << std::endl; + return ReturnStatus::failure; + } + + m_obj_init_status = ReturnStatus::obj_init_success; + return m_obj_init_status; +} + +ReturnStatus ANOMediaSenderApp::initialize_connection_parameters() +{ + in_addr device_address; + if (inet_pton(AF_INET, m_app_settings->local_ip.c_str(), &device_address) != 1) { + std::cerr << "Failed to parse address of device " << m_app_settings->local_ip << std::endl; + return ReturnStatus::failure; + } + rmx_status status = rmx_retrieve_device_iface_ipv4(&m_device_interface, &device_address); + if (status != RMX_OK) { + std::cerr << "Failed to get device: " << m_app_settings->local_ip << " with status: " << status << std::endl; + return ReturnStatus::failure; + } + + return ReturnStatus::success; +} + +ReturnStatus ANOMediaSenderApp::initialize_memory_strategy() +{ + std::vector device_interfaces = {m_device_interface}; + auto base_memory_strategy = std::make_unique( + *m_header_allocator, *m_payload_allocator, + *m_memory_utils, + device_interfaces, + m_num_paths_per_stream, + m_app_settings->app_memory_alloc, + m_app_settings->register_memory); + + for (const auto& sender : m_senders) { + base_memory_strategy->add_memory_subcomponent(sender); + } + + m_memory_strategy.reset(base_memory_strategy.release()); + + return ReturnStatus::success; +} + +ReturnStatus ANOMediaSenderApp::run() +{ + if (m_obj_init_status != ReturnStatus::obj_init_success) { + return m_obj_init_status; + } + + ReturnStatus rc = run_stats_reader(); + if (rc == ReturnStatus::failure) { + return ReturnStatus::failure; + } + + try { + run_threads(m_senders); + } + catch (const std::exception & error) { + std::cerr << error.what() << std::endl; + return ReturnStatus::failure; + } + + return ReturnStatus::success; +} + +ReturnStatus ANOMediaSenderApp::set_rivermax_clock() +{ + ReturnStatus rc = set_rivermax_ptp_clock(&m_device_interface); + if(rc == ReturnStatus::success) { + uint64_t ptp_time = 0; + rc = get_rivermax_ptp_time_ns(ptp_time); + } + return rc; +} + +void ANOMediaSenderApp::distribute_work_for_threads() +{ + m_streams_per_thread.reserve(m_media_sender_settings->thread_settings.size()); + m_media_sender_settings->num_of_total_streams = 0; + m_media_sender_settings->num_of_threads = m_media_sender_settings->thread_settings.size(); + int thread_idx = 0; + for (const auto& thread : m_media_sender_settings->thread_settings) { + m_streams_per_thread[thread_idx] = thread.stream_network_settings.size(); + m_media_sender_settings->num_of_total_streams += thread.stream_network_settings.size(); + thread_idx++; + } +} + +void ANOMediaSenderApp::configure_network_flows() +{ + int thread_index = 0; + uint16_t source_port = 0; + for (const auto& thread : m_media_sender_settings->thread_settings) { + int internal_stream_index = 0; + std::vector streams; + for (const auto& stream : thread.stream_network_settings) { + streams.push_back(TwoTupleFlow(stream.stream_id, stream.destination_ip, stream.destination_port)); + m_stream_id_map[stream.stream_id] = std::make_pair(thread_index, internal_stream_index); + internal_stream_index++; + } + m_threads_streams.push_back(streams); + thread_index++; + } +} + +void ANOMediaSenderApp::initialize_sender_threads() +{ + // All streams with the same local IP and source port + m_app_settings->local_ip = m_media_sender_settings->thread_settings[0].stream_network_settings[0].local_ip; + m_app_settings->source_port = 0; + // These are not really used + m_app_settings->destination_ip = ""; + m_app_settings->destination_port = 0; + + for (int thread_idx = 0; thread_idx < m_media_sender_settings->num_of_threads; ++thread_idx) { + auto network_address = FourTupleFlow( + thread_idx, + m_app_settings->local_ip, + m_app_settings->source_port, + m_app_settings->destination_ip, + m_app_settings->destination_port + ); + m_senders.push_back(std::unique_ptr(new MediaSenderIONode( + network_address, + m_app_settings, + thread_idx, + m_streams_per_thread[thread_idx], + m_media_sender_settings->app_threads_cores[thread_idx], + *m_memory_utils, + ANOMediaSenderApp::get_time_ns))); + m_senders[thread_idx]->initialize_send_flows(m_threads_streams[thread_idx]); + m_senders[thread_idx]->initialize_streams(); + } +} + +ReturnStatus ANOMediaSenderApp::get_app_settings(const AppSettings*& settings) const { + if (m_obj_init_status != ReturnStatus::obj_init_success || !m_media_sender_settings) { + std::cerr << "Error: Application settings are not initialized" << std::endl; + settings = nullptr; + return ReturnStatus::failure; + } + settings = m_media_sender_settings.get(); + return ReturnStatus::success; +} + + +ReturnStatus ANOMediaSenderApp::find_internal_stream_index(size_t external_stream_index, size_t& thread_index, size_t& internal_stream_index) { + if (m_stream_id_map.find(external_stream_index) == m_stream_id_map.end()) { + std::cerr << "Invalid stream index " << external_stream_index << std::endl; + return ReturnStatus::failure; + } + + thread_index = m_stream_id_map[external_stream_index].first; + internal_stream_index = m_stream_id_map[external_stream_index].second; + + return ReturnStatus::success; +} + +ReturnStatus ANOMediaSenderApp::set_frame_provider(size_t stream_index, + std::shared_ptr frame_provider, MediaType media_type, bool contains_payload) +{ + size_t sender_thread_index = 0; + size_t sender_stream_index = 0; + + auto rc = find_internal_stream_index(stream_index, sender_thread_index, sender_stream_index); + if (rc != ReturnStatus::success) { + std::cerr << "Error setting frame provider, invalid stream index " << stream_index << std::endl; + return rc; + } + + rc = m_senders[sender_thread_index]->set_frame_provider( + sender_stream_index, std::move(frame_provider), media_type, contains_payload); + + if (rc != ReturnStatus::success) { + std::cerr << "Error setting frame provider for stream " + << sender_stream_index << " on sender " << sender_thread_index << std::endl; + } + + return rc; +} + +uint64_t ANOMediaSenderApp::get_time_ns(void* context) +{ + NOT_IN_USE(context); + uint64_t ptp_time = 0; + ReturnStatus rc = get_rivermax_ptp_time_ns(ptp_time); + if (rc != ReturnStatus::success) { + std::cerr << "Failed to get PTP time" << std::endl; + return 0; + } + return ptp_time; +} + +} // namespace holoscan::advanced_network diff --git a/operators/advanced_network/advanced_network/managers/rivermax/applications/ano_media_sender.h b/operators/advanced_network/advanced_network/managers/rivermax/applications/ano_media_sender.h new file mode 100644 index 0000000000..4ae19bdf13 --- /dev/null +++ b/operators/advanced_network/advanced_network/managers/rivermax/applications/ano_media_sender.h @@ -0,0 +1,177 @@ +/* + * 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 RIVERMAX_ANO_APPLICATIONS_MEDIA_SENDER_H_ +#define RIVERMAX_ANO_APPLICATIONS_MEDIA_SENDER_H_ + +#include "utils.h" +#include "rdk/apps/rmax_base_app.h" +#include "rdk/apps/rmax_base_memory_strategy.h" +#include "rdk/services/utils/defs.h" +#include "rdk/services/utils/clock.h" +#include "rdk/services/utils/enum_utils.h" +#include "rdk/services/sdp/sdp_defs.h" +#include "rdk/services/media/media.h" + +namespace holoscan::advanced_network { + +using namespace rivermax::dev_kit::apps; +using namespace rivermax::dev_kit::io_node; +using namespace rivermax::dev_kit::services; +using namespace rivermax::dev_kit::core; + +/** + * @brief: Configuration settings for Rivermax ANO Media Sender. + */ +struct ANOMediaSenderSettings : AppSettings +{ +public: + static constexpr uint32_t DEFAULT_NUM_OF_PACKETS_IN_CHUNK_FHD = 16; + static constexpr uint32_t DEFAULT_NUM_OF_PACKETS_IN_CHUNK_UHD = 32; + void init_default_values() override; + + std::vector thread_settings; +}; + +/** + * @brief: Validator for Rivermax ANO Media Sender settings. + */ +class ANOMediaSenderSettingsValidator : public ISettingsValidator +{ +public: + ReturnStatus validate(const std::shared_ptr& settings) const override; +}; + +/** + * @brief: ANO Media Sender application. + * + * This is an example of usage application for Rivermax media TX API. + */ +class ANOMediaSenderApp : public RmaxBaseApp +{ +private: + /* Settings builder pointer */ + std::shared_ptr> m_settings_builder; + /* Application settings pointer */ + std::shared_ptr m_media_sender_settings; + /* Sender objects container */ + std::vector> m_senders; + /* Network recv flows */ + std::vector> m_threads_streams; + /* NIC device interface */ + rmx_device_iface m_device_interface; + /* Number of paths per stream */ + size_t m_num_paths_per_stream = 1; + /* Network send flows */ + std::vector m_flows; + /* map external stream ID to internal thread ID and stream ID */ + std::unordered_map> m_stream_id_map; +public: + /** + * @brief: ANOMediaSenderApp class constructor. + * + * @param [in] settings_builder: Settings builder pointer. + */ + ANOMediaSenderApp(std::shared_ptr> settings_builder); + virtual ~ANOMediaSenderApp() = default; + ReturnStatus run() override; + ReturnStatus initialize() override; + /** + * @brief: Sets the frame provider for the specified stream index. + * + * @param [in] stream_index: Stream index. + * @param [in] frame_provider: Framer provider pointer. + * @param [in] media_type: Media type. + * @param [in] contains_payload: Flag indicating whether the frame provider contains payload. + * + * @return: Status of the operation. + */ + ReturnStatus set_frame_provider(size_t stream_index, std::shared_ptr frame_provider, + MediaType media_type = MediaType::Video, bool contains_payload = true); + + /** + * @brief: Find internal thread and stream index for a given external stream index. + * + * @param [in] external_stream_index: The external stream index. + * @param [out] thread_index: The internal thread index. + * @param [out] internal_stream_index: The internal stream index. + * + * @return: Status of the operation. + */ + ReturnStatus find_internal_stream_index(size_t external_stream_index, size_t& thread_index, size_t& internal_stream_index) override; + + /** + * @brief: Returns application settings. + * + * If the application was initialized successfully, this method will set the + * provided reference to the application settings and return success. + * Otherwise, it will return a failure status. + * + * @param [out] settings: A reference to store the application settings. + * + * @return: Status of the operation. + */ + ReturnStatus get_app_settings(const AppSettings*& settings) const override; +private: + ReturnStatus initialize_app_settings() final; + ReturnStatus post_load_settings() final; + ReturnStatus initialize_memory_strategy() override; + ReturnStatus set_rivermax_clock() override; + ReturnStatus initialize_connection_parameters() final; + /** + * @brief: Initializes network send flows. + * + * This method is responsible to initialize the send flows will be + * used in the application. Those flows will be distributed in + * @ref ANOMediaSenderApp::distribute_work_for_threads + * to the streams will be used in the application. + * The application supports unicast and multicast UDPv4 send flows. + */ + void configure_network_flows(); + /** + * @brief: Distributes work for threads. + * + * This method is responsible to distribute work to threads, by + * distributing number of streams per sender thread uniformly. + * In future development, this can be extended to different + * streams per thread distribution policies. + */ + void distribute_work_for_threads(); + /** + * @brief: Initializes sender threads. + * + * This method is responsible to initialize @ref MediaSenderIONode objects to work. + * It will initiate objects with the relevant parameters. + * The objects initialized in this method, will be the contexts to the std::thread objects + * will run in @ref RmaxBaseApp::run_threads method. + */ + void initialize_sender_threads(); + /** + * @brief: Returns current time in nanoseconds. + * + * This method uses @ref get_rivermax_ptp_time_ns to return the current PTP time. + * @note: PTP4l must be running on the system for time to be valid. + * + * @return: Current time in nanoseconds. + */ + static uint64_t get_time_ns(void* context = nullptr); +}; + +} // namespace holoscan::advanced_network + +#endif // RIVERMAX_ANO_APPLICATIONS_MEDIA_SENDER_H_ diff --git a/operators/advanced_network/advanced_network/managers/rivermax/applications/ano_rtp_receiver.cpp b/operators/advanced_network/advanced_network/managers/rivermax/applications/ano_rtp_receiver.cpp new file mode 100644 index 0000000000..e9f7fb6207 --- /dev/null +++ b/operators/advanced_network/advanced_network/managers/rivermax/applications/ano_rtp_receiver.cpp @@ -0,0 +1,177 @@ +/* + * 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 "ano_rtp_receiver.h" + +namespace holoscan::advanced_network { + +void ANORTPReceiverSettings::init_default_values() +{ + AppSettings::init_default_values(); + app_memory_alloc = true; + num_of_packets_in_chunk = DEFAULT_NUM_OF_PACKETS_IN_CHUNK; + is_extended_sequence_number = false; +} + +ReturnStatus ANORTPReceiverSettingsValidator::validate(const std::shared_ptr& settings) const +{ + if (settings->thread_settings.empty()) { + std::cerr << "Must be at least one thread" << std::endl; + return ReturnStatus::failure; + } + for (const auto& thread : settings->thread_settings) { + if (thread.stream_network_settings.empty()) { + std::cerr << "Must be at least one stream" << std::endl; + return ReturnStatus::failure; + } + for (const auto& stream : thread.stream_network_settings) { + ReturnStatus rc = ValidatorUtils::validate_ip4_address(stream.source_ip); + if (rc != ReturnStatus::success) { + return rc; + } + rc = ValidatorUtils::validate_ip4_address(stream.local_ip); + if (rc != ReturnStatus::success) { + return rc; + } + rc = ValidatorUtils::validate_ip4_address(stream.destination_ip); + if (rc != ReturnStatus::success) { + return rc; + } + rc = ValidatorUtils::validate_ip4_port(stream.destination_port); + if (rc != ReturnStatus::success) { + return rc; + } + } + } + return ReturnStatus::success; +} + +ANORTPReceiverApp::ANORTPReceiverApp(std::shared_ptr> settings_builder) : + RmaxReceiverBaseApp(), + m_settings_builder(std::move(settings_builder)), + m_threads_streams(), + m_devices_ips() +{ + m_num_paths_per_stream = 1; // RTP receiver supports only single path per stream +} + +ReturnStatus ANORTPReceiverApp::initialize_connection_parameters() +{ + for (const auto& thread : m_rtp_receiver_settings->thread_settings) { + for (const auto& stream : thread.stream_network_settings) { + in_addr device_address; + rmx_device_iface device_iface; + if (inet_pton(AF_INET, stream.local_ip.c_str(), &device_address) != 1) { + std::cerr << "Failed to parse address of device " << stream.local_ip << std::endl; + return ReturnStatus::failure; + } + rmx_status status = rmx_retrieve_device_iface_ipv4(&device_iface, &device_address); + if (status != RMX_OK) { + std::cerr << "Failed to get device: " << stream.local_ip << " with status: " << status << std::endl; + return ReturnStatus::failure; + } + m_devices_ips.push_back(stream.local_ip); + m_device_interfaces.push_back(device_iface); + return ReturnStatus::success; // RTP receiver application currently supports transmission on a single NIC only + } + } + return ReturnStatus::success; +} + +ReturnStatus ANORTPReceiverApp::initialize_app_settings() +{ + if (m_settings_builder == nullptr) { + std::cerr << "Settings builder is not initialized" << std::endl; + return ReturnStatus::failure; + } + m_rtp_receiver_settings = std::make_shared(); + ReturnStatus rc = m_settings_builder->build(m_rtp_receiver_settings); + if (rc == ReturnStatus::success) { + m_app_settings = m_rtp_receiver_settings; + return ReturnStatus::success; + } + if (rc != ReturnStatus::success_cli_help) { + std::cerr << "Failed to build settings" << std::endl; + } + m_obj_init_status = rc; + return rc; +} + +void ANORTPReceiverApp::run_receiver_threads() +{ + run_threads(m_receivers); +} + +void ANORTPReceiverApp::configure_network_flows() +{ + uint16_t source_port = 0; + int thread_index = 0; + for (const auto& thread : m_rtp_receiver_settings->thread_settings) { + std::vector streams; + int internal_stream_index = 0; + for (const auto& stream : thread.stream_network_settings) { + ReceiveFlow flow(stream.stream_id, stream.source_ip, source_port, stream.destination_ip, stream.destination_port); + streams.push_back(flow); + m_stream_id_map[stream.stream_id] = std::make_pair(thread_index, internal_stream_index); + internal_stream_index++; + } + m_threads_streams.push_back(streams); + thread_index++; + } +} + +void ANORTPReceiverApp::initialize_receive_io_nodes() +{ + m_rtp_receiver_settings->num_of_threads = m_rtp_receiver_settings->thread_settings.size(); + size_t streams_offset = 0; + for (int thread_index = 0; thread_index < m_rtp_receiver_settings->num_of_threads; ++thread_index) { + m_receivers.push_back(std::unique_ptr(new RTPReceiverIONode( + *m_app_settings, + m_rtp_receiver_settings->is_extended_sequence_number, + { m_devices_ips[0] }, // Pass a single device IP + thread_index, + m_rtp_receiver_settings->app_threads_cores[thread_index], + *m_memory_utils))); + static_cast(m_receivers[thread_index].get())->initialize_streams(streams_offset, m_threads_streams[thread_index]); + streams_offset += m_streams_per_thread[thread_index]; + } +} + +void ANORTPReceiverApp::distribute_work_for_threads() +{ + m_streams_per_thread.reserve(m_rtp_receiver_settings->thread_settings.size()); + m_rtp_receiver_settings->num_of_total_streams = 0; + int thread_index = 0; + for (const auto& thread : m_rtp_receiver_settings->thread_settings) { + m_streams_per_thread[thread_index] = thread.stream_network_settings.size(); + m_rtp_receiver_settings->num_of_total_streams += thread.stream_network_settings.size(); + thread_index++; + } +} + +ReturnStatus ANORTPReceiverApp::find_internal_stream_index(size_t external_stream_index, size_t& thread_index, size_t& internal_stream_index) { + if (m_stream_id_map.find(external_stream_index) == m_stream_id_map.end()) { + std::cerr << "Invalid stream index " << external_stream_index << std::endl; + return ReturnStatus::failure; + } + + thread_index = m_stream_id_map[external_stream_index].first; + internal_stream_index = m_stream_id_map[external_stream_index].second; + + return ReturnStatus::success; +} +} // namespace holoscan::advanced_network diff --git a/operators/advanced_network/advanced_network/managers/rivermax/applications/ano_rtp_receiver.h b/operators/advanced_network/advanced_network/managers/rivermax/applications/ano_rtp_receiver.h new file mode 100644 index 0000000000..9b137afe2d --- /dev/null +++ b/operators/advanced_network/advanced_network/managers/rivermax/applications/ano_rtp_receiver.h @@ -0,0 +1,112 @@ +/* + * 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 RIVERMAX_ANO_APPLICATIONS_RTP_RECEIVER_H_ +#define RIVERMAX_ANO_APPLICATIONS_RTP_RECEIVER_H_ + +#include + +#include "utils.h" +#include "rdk/apps/rmax_receiver_base.h" +#include "rdk/io_node/receivers/rtp_receiver_io_node.h" +#include "rdk/rivermax_dev_kit.h" + +namespace holoscan::advanced_network { + +using namespace rivermax::dev_kit::apps; +using namespace rivermax::dev_kit::io_node; +using namespace rivermax::dev_kit::services; +using namespace rivermax::dev_kit::core; + +struct ANORTPReceiverSettings : AppSettings +{ +public: + static constexpr uint32_t DEFAULT_NUM_OF_PACKETS_IN_CHUNK = 262144; + void init_default_values() override; + + bool is_extended_sequence_number; + std::vector thread_settings; +}; + +/** +* @brief: Validator for Rivermax RTP Receiver settings. +*/ +class ANORTPReceiverSettingsValidator : public ISettingsValidator +{ +public: + ReturnStatus validate(const std::shared_ptr& settings) const override; +}; + + +class ANORTPReceiverApp : public RmaxReceiverBaseApp +{ +private: + /* Settings builder pointer */ + std::shared_ptr> m_settings_builder; + /* Application settings pointer */ + std::shared_ptr m_rtp_receiver_settings; + /* Network recv flows */ + std::vector> m_threads_streams; + /* list of devices IPs */ + std::vector m_devices_ips; + /* map external stream ID to internal thread ID and stream ID */ + std::unordered_map> m_stream_id_map; +public: + /** + * @brief: ANORTPReceiverApp class constructor. + * + * @param [in] settings_builder: Settings builder pointer. + */ + ANORTPReceiverApp(std::shared_ptr> settings_builder); + /** + * @brief: ANORTPReceiverApp class destructor. + */ + virtual ~ANORTPReceiverApp() = default; + /** + * @brief: Get RTP streams total statistics. + * + * This function hides the base class function and provides default template arguments. + * + * @return: A vector of @ref RTPRXStatistics. + */ + std::vector get_streams_total_statistics() const { + return RmaxReceiverBaseApp::get_streams_total_statistics(); + } + + /** + * @brief: Find internal thread and stream index for a given external stream index. + * + * @param [in] external_stream_index: The external stream index. + * @param [out] thread_index: The internal thread index. + * @param [out] internal_stream_index: The internal stream index. + * + * @return: Status of the operation. + */ + ReturnStatus find_internal_stream_index(size_t external_stream_index, size_t& thread_index, size_t& internal_stream_index) override; + +private: + ReturnStatus initialize_app_settings() final; + ReturnStatus initialize_connection_parameters() final; + void configure_network_flows() final; + void initialize_receive_io_nodes() final; + void run_receiver_threads() final; + void distribute_work_for_threads(); +}; + +} // namespace holoscan::advanced_network + +#endif // RIVERMAX_ANO_APPLICATIONS_RTP_RECEIVER_H_ diff --git a/operators/advanced_network/advanced_network/managers/rivermax/applications/utils.h b/operators/advanced_network/advanced_network/managers/rivermax/applications/utils.h new file mode 100644 index 0000000000..d59deb7a6b --- /dev/null +++ b/operators/advanced_network/advanced_network/managers/rivermax/applications/utils.h @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023-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 RIVERMAX_ANO_APPLICATIONS_UTILS_H_ +#define RIVERMAX_ANO_APPLICATIONS_UTILS_H_ + +#include +#include +#include + +namespace holoscan::advanced_network { + +struct StreamNetworkSettings +{ + std::string local_ip; + std::vector local_ips; + std::string source_ip; + std::vector source_ips; + std::string destination_ip; + std::vector destination_ips; + uint16_t source_port; + std::vector destination_ports; + uint16_t destination_port; + uint32_t stream_id; +}; + +struct ThreadSettings +{ + std::vector stream_network_settings; + int thread_id; +}; + +} // namespace holoscan::advanced_network + +#endif // RIVERMAX_ANO_APPLICATIONS_UTILS_H_ diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rmax_ano_data_types.h b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_ano_data_types.h similarity index 73% rename from operators/advanced_network/advanced_network/managers/rivermax/rmax_ano_data_types.h rename to operators/advanced_network/advanced_network/managers/rivermax/rivermax_ano_data_types.h index 54a638852d..bf84e7e05a 100644 --- a/operators/advanced_network/advanced_network/managers/rivermax/rmax_ano_data_types.h +++ b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_ano_data_types.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,22 +15,61 @@ * limitations under the License. */ -#ifndef RMAX_ANO_DATA_TYPES_H_ -#define RMAX_ANO_DATA_TYPES_H_ +#ifndef RIVERMAX_ANO_DATA_TYPES_H_ +#define RIVERMAX_ANO_DATA_TYPES_H_ + +#include #include "advanced_network/types.h" namespace holoscan::advanced_network { -class RmaxBurst; - +class RivermaxBurst; +/** + * @brief Interface for a collection of bursts. + * + * This interface defines the basic operations for a collection of bursts, + * including enqueueing, dequeueing, checking the size, and clearing the collection. + */ class IAnoBurstsCollection { public: + /** + * @brief Virtual destructor for the IAnoBurstsCollection class. + */ virtual ~IAnoBurstsCollection() = default; - virtual bool enqueue_burst(std::shared_ptr burst) = 0; - virtual std::shared_ptr dequeue_burst() = 0; + /** + * @brief Enqueues a burst into the queue. + * + * @param burst The burst to put into the queue. + * @return True if the burst was successfully put into the queue, false otherwise. + */ + virtual bool enqueue_burst(std::shared_ptr burst) = 0; + + /** + * @brief Dequeues a burst from the queue. + * + * @return A shared pointer to the burst. + */ + virtual std::shared_ptr dequeue_burst() = 0; + + /** + * @brief Gets the number of available bursts in the queue. + * + * @return The number of available bursts. + */ virtual size_t available_bursts() = 0; + + /** + * @brief Checks if the queue is empty. + * + * @return True if the queue is empty, false otherwise. + */ virtual bool empty() = 0; + + /** + * @brief Clears the queue. + */ + virtual void stop() = 0; }; /** @@ -84,6 +123,10 @@ class QueueInterface { * @brief Clears the queue. */ virtual void clear() = 0; + /** + * @brief Stops the queue. + */ + virtual void stop() = 0; }; /** @@ -103,39 +146,17 @@ class AnoBurstsQueue : public IAnoBurstsCollection { */ AnoBurstsQueue(); - /** - * @brief Virtual destructor for the AnoBurstsQueue class. - */ virtual ~AnoBurstsQueue() = default; - /** - * @brief Enqueues a burst into the queue. - * - * @param burst The burst to put into the queue. - * @return True if the burst was successfully put into the queue, false otherwise. - */ - bool enqueue_burst(std::shared_ptr burst) override; + bool enqueue_burst(std::shared_ptr burst) override; - /** - * @brief Dequeues a burst from the queue. - * - * @return A shared pointer to the burst. - */ - std::shared_ptr dequeue_burst() override; + std::shared_ptr dequeue_burst() override; - /** - * @brief Gets the number of available bursts in the queue. - * - * @return The number of available bursts. - */ - size_t available_bursts() override { return m_queue->get_size(); }; + size_t available_bursts() override { return queue_->get_size(); }; - /** - * @brief Checks if the queue is empty. - * - * @return True if the queue is empty, false otherwise. - */ - bool empty() override { return m_queue->get_size() == 0; }; + bool empty() override { return queue_->get_size() == 0; }; + + void stop() override; /** * @brief Clears the queue. @@ -143,17 +164,18 @@ class AnoBurstsQueue : public IAnoBurstsCollection { void clear(); private: - std::unique_ptr>> m_queue; + std::unique_ptr>> queue_; + std::atomic stop_ = false; }; -enum BurstFlags : uint8_t { +enum BurstFlags : uint32_t { FLAGS_NONE = 0, INFO_PER_PACKET = 1, + FRAME_BUFFER_IS_OWNED = 2, }; struct AnoBurstExtendedInfo { uint32_t tag; - BurstFlags burst_flags; uint16_t burst_id; bool hds_on; uint16_t header_stride_size; @@ -164,23 +186,23 @@ struct AnoBurstExtendedInfo { uint16_t payload_seg_idx; }; -struct RmaxPacketExtendedInfo { +struct RivermaxPacketExtendedInfo { uint32_t flow_tag; uint64_t timestamp; }; -struct RmaxPacketData { +struct RivermaxPacketData { uint8_t* header_ptr; uint8_t* payload_ptr; size_t header_length; size_t payload_length; - RmaxPacketExtendedInfo extended_info; + RivermaxPacketExtendedInfo extended_info; }; /** - * @brief Class representing Rmax log levels. + * @brief Class representing Rivermax log levels. */ -class RmaxLogLevel { +class RivermaxLogLevel { public: enum Level { TRACE, @@ -193,9 +215,9 @@ class RmaxLogLevel { }; /** - * @brief Converts an Rmax log level to a description string. + * @brief Converts an Rivermax log level to a description string. * - * @param level The Rmax log level to convert. + * @param level The Rivermax log level to convert. * @return The string representation of the log level. */ static std::string to_description_string(Level level) { @@ -207,9 +229,9 @@ class RmaxLogLevel { } /** - * @brief Converts an Rmax log level to a command string. + * @brief Converts an Rivermax log level to a command string. * - * @param level The Rmax log level to convert. + * @param level The Rivermax log level to convert. * @return The string representation of the log level. */ static std::string to_cmd_string(Level level) { @@ -226,8 +248,8 @@ class RmaxLogLevel { * @return The corresponding Rmax log level. */ static Level from_adv_net_log_level(LogLevel::Level level) { - auto it = adv_net_to_rmax_log_level_map.find(level); - if (it != adv_net_to_rmax_log_level_map.end()) { return it->second; } + auto it = adv_net_to_rivermax_log_level_map.find(level); + if (it != adv_net_to_rivermax_log_level_map.end()) { return it->second; } return OFF; } @@ -237,11 +259,11 @@ class RmaxLogLevel { */ static const std::unordered_map> level_to_cmd_map; /** - * A map of advanced_network log level to Rmax log level. + * A map of advanced_network log level to Rivermax log level. */ - static const std::unordered_map adv_net_to_rmax_log_level_map; + static const std::unordered_map adv_net_to_rivermax_log_level_map; }; } // namespace holoscan::advanced_network -#endif // RMAX_ANO_DATA_TYPES_H_ +#endif // RIVERMAX_ANO_DATA_TYPES_H_ 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 new file mode 100644 index 0000000000..c3887bb42d --- /dev/null +++ b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/adv_network_rivermax_mgr.cpp @@ -0,0 +1,854 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023-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 +#include +#include +#include +#include +#include + +#include + +#include "rt_threads.h" +#include "rdk/rivermax_dev_kit.h" + +#include "adv_network_rivermax_mgr.h" +#include "rivermax_mgr_impl/rivermax_config_manager.h" +#include "rivermax_mgr_impl/burst_manager.h" +#include "rivermax_mgr_impl/packet_processor.h" +#include "rivermax_mgr_impl/rivermax_chunk_consumer_ano.h" +#include "rivermax_mgr_impl/rivermax_mgr_service.h" + +using namespace std::chrono; + +namespace holoscan::advanced_network { + +/** + * A map of log level to a tuple of the description and command strings. + */ +const std::unordered_map> + RivermaxLogLevel::level_to_cmd_map = { + {TRACE, {"Trace", "0"}}, + {DEBUG, {"Debug", "1"}}, + {INFO, {"Info", "2"}}, + {WARN, {"Warning", "3"}}, + {ERROR, {"Error", "4"}}, + {CRITICAL, {"Critical", "5"}}, + {OFF, {"Disabled", "6"}}, +}; + +/** + * A map of advanced_network log level to Rivermax log level. + */ +const std::unordered_map + RivermaxLogLevel::adv_net_to_rivermax_log_level_map = { + {LogLevel::TRACE, TRACE}, + {LogLevel::DEBUG, DEBUG}, + {LogLevel::INFO, INFO}, + {LogLevel::WARN, WARN}, + {LogLevel::ERROR, ERROR}, + {LogLevel::CRITICAL, CRITICAL}, + {LogLevel::OFF, OFF}, +}; + +/** + * @brief Implementation class for RivermaxMgr. + * + * This class contains the implementation details for RivermaxMgr, including + * methods for configuration, initialization, packet handling, and statistics. + */ +class RivermaxMgr::RivermaxMgrImpl { + public: + RivermaxMgrImpl() = default; + ~RivermaxMgrImpl(); + + bool set_config_and_initialize(const NetworkConfig& cfg); + void initialize(); + void run(); + + void* get_segment_packet_ptr(BurstParams* burst, int seg, int idx); + void* get_packet_ptr(BurstParams* burst, int idx); + uint16_t get_segment_packet_length(BurstParams* burst, int seg, int idx); + uint16_t get_packet_length(BurstParams* burst, int idx); + void* get_packet_extra_info(BurstParams* burst, int idx); + Status get_tx_packet_burst(BurstParams* burst); + Status set_eth_header(BurstParams* burst, int idx, char* dst_addr); + Status set_ipv4_header(BurstParams* burst, int idx, int ip_len, uint8_t proto, + unsigned int src_host, unsigned int dst_host); + Status set_udp_header(BurstParams* burst, int idx, int udp_len, uint16_t src_port, + uint16_t dst_port); + Status set_udp_payload(BurstParams* burst, int idx, void* data, int len); + bool is_tx_burst_available(BurstParams* burst); + + Status set_packet_lengths(BurstParams* burst, int idx, const std::initializer_list& lens); + void free_all_segment_packets(BurstParams* burst, int seg); + void free_all_packets(BurstParams* burst); + void free_packet_segment(BurstParams* burst, int seg, int pkt); + void free_packet(BurstParams* burst, int pkt); + void free_rx_burst(BurstParams* burst); + void free_tx_burst(BurstParams* burst); + void format_eth_addr(char* dst, std::string addr); + Status get_rx_burst(BurstParams** burst, int port, int q, int stream); + Status get_rx_burst(BurstParams** burst, int port, int q); + Status set_packet_tx_time(BurstParams* burst, int idx, uint64_t timestamp); + void free_rx_metadata(BurstParams* burst); + void free_tx_metadata(BurstParams* burst); + Status get_tx_metadata_buffer(BurstParams** burst); + Status send_tx_burst(BurstParams* burst); + void shutdown(); + void print_stats(); + uint64_t get_burst_tot_byte(BurstParams* burst); + BurstParams* create_tx_burst_params(); + Status get_mac_addr(int port, char* mac); + + private: + template + ConfigType* find_rivermax_config(uint32_t service_id, QueueConfigType expected_type); + template + std::unordered_map> + create_burst_queues_for_streams(const ConfigType& rivermax_config, uint32_t service_id); + template + bool common_initialize_rx_service(uint32_t service_id, + std::shared_ptr config_holder, + QueueConfigType expected_type, + const std::string& service_name); + bool initialize_rx_service(uint32_t service_id, + std::shared_ptr config_holder); + bool initialize_tx_service(uint32_t service_id, + std::shared_ptr config_holder); + + private: + static constexpr int DEFAULT_NUM_RX_BURST = 64; + static constexpr int MAX_TX_BURST = 128; + static constexpr int MAX_NUM_OF_FRAMES_IN_BURST = 1; + + const NetworkConfig* cfg_ = nullptr; + std::unordered_map>> rx_bursts_out_queues_map_; + std::vector rx_service_threads_; + std::vector tx_service_threads_; + std::unordered_map> rx_services_; + std::unordered_map> tx_services_; + BurstParams burst_tx_pool[MAX_TX_BURST]; + std::atomic burst_tx_idx{0}; + bool initialized_ = false; +}; + +std::atomic force_quit = false; + +bool RivermaxMgr::RivermaxMgrImpl::set_config_and_initialize(const NetworkConfig& cfg) { + if (this->initialized_) { + HOLOSCAN_LOG_INFO("Rivermax ANO Manager already initialized"); + return true; + } + cfg_ = &cfg; + + // Start Initialize in a separate thread so it doesn't set the affinity for the + // whole application + std::thread t(&RivermaxMgr::RivermaxMgrImpl::initialize, this); + t.join(); + + if (!this->initialized_) { + HOLOSCAN_LOG_ERROR("Failed to initialize Rivermax Advanced Network Manager"); + return false; + } + + run(); + + return true; +} + +void RivermaxMgr::RivermaxMgrImpl::initialize() { + RivermaxConfigContainer config_manager; + + bool res = config_manager.parse_configuration(*cfg_); + if (!res) { + HOLOSCAN_LOG_ERROR("Failed to parse configuration for Rivermax advanced_network Manager"); + return; + } + HOLOSCAN_LOG_INFO("Setting Rivermax Log Level to: {}", + holoscan::advanced_network::RivermaxLogLevel::to_description_string( + config_manager.get_rivermax_log_level())); + rivermax_setparam("RIVERMAX_LOG_LEVEL", + holoscan::advanced_network::RivermaxLogLevel::to_cmd_string( + config_manager.get_rivermax_log_level()), + true); + + auto rx_config_manager = std::dynamic_pointer_cast( + config_manager.get_config_manager(RivermaxConfigContainer::ConfigType::RX)); + + if (rx_config_manager) { + for (const auto& config : *rx_config_manager) { + res = initialize_rx_service(config.first, config.second); + if (!res) { + HOLOSCAN_LOG_ERROR("Failed to initialize RX service for config ID {}", config.first); + return; + } + } + } + + auto tx_config_manager = std::dynamic_pointer_cast( + config_manager.get_config_manager(RivermaxConfigContainer::ConfigType::TX)); + + if (tx_config_manager) { + for (const auto& config : *tx_config_manager) { + res = initialize_tx_service(config.first, config.second); + if (!res) { + HOLOSCAN_LOG_ERROR("Failed to initialize TX service for config ID {}", config.first); + return; + } + } + } + + for (int i = 0; i < MAX_TX_BURST; i++) { + 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].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]; + } + + this->initialized_ = true; +} + +// Extract configuration finding logic +template +ConfigType* RivermaxMgr::RivermaxMgrImpl::find_rivermax_config(uint32_t service_id, QueueConfigType expected_type) { + // Temporary solution to extract the specific queue configuration from the NetworkConfig + for (const auto& intf : cfg_->ifs_) { + for (const auto& rx_queue : intf.rx_.queues_) { + if (!rx_queue.common_.extra_queue_config_) continue; + + auto* config = dynamic_cast(rx_queue.common_.extra_queue_config_); + if (config && config->get_type() == expected_type && rx_queue.common_.id_ == service_id) { + return config; + } + } + } + return nullptr; +} + +template +std::unordered_map> +RivermaxMgr::RivermaxMgrImpl::create_burst_queues_for_streams(const ConfigType& rivermax_config, + uint32_t service_id) { + HOLOSCAN_LOG_INFO("Found {} threads in YAML configuration for service {}", + rivermax_config.thread_settings.size(), service_id); + + std::unordered_map> queue_map; + + for (const auto& thread : rivermax_config.thread_settings) { + HOLOSCAN_LOG_INFO("Thread {} has {} streams", + thread.thread_id, thread.stream_network_settings.size()); + + for (const auto& stream : thread.stream_network_settings) { + if (queue_map.find(stream.stream_id) != queue_map.end()) { + HOLOSCAN_LOG_WARN("Duplicate stream ID {} found in configuration for service {}. " + "Each stream ID must be unique. Skipping duplicate.", + stream.stream_id, service_id); + continue; + } + queue_map[stream.stream_id] = std::make_shared(); + } + } + + return queue_map; +} + +template +bool RivermaxMgr::RivermaxMgrImpl::common_initialize_rx_service( + uint32_t service_id, std::shared_ptr config_holder, + QueueConfigType expected_type, const std::string& service_name) { + auto typed_holder = std::dynamic_pointer_cast>(config_holder); + if (!typed_holder) { + HOLOSCAN_LOG_ERROR("Failed to cast to TypedConfigBuilderHolder<{}>", typeid(BuilderType).name()); + return false; + } + + auto builder = typed_holder->get_config_builder(); + if (!builder) { + HOLOSCAN_LOG_ERROR("Failed to get builder for {}", service_name); + return false; + } + + // Extract common configuration logic + auto rivermax_config = find_rivermax_config(service_id, expected_type); + if (!rivermax_config) { + HOLOSCAN_LOG_ERROR("Could not find {} configuration in YAML for service {}", + service_name, service_id); + return false; + } + + auto queue_map = create_burst_queues_for_streams(*rivermax_config, service_id); + rx_bursts_out_queues_map_[service_id] = std::move(queue_map); + + auto rx_service = std::make_shared( + service_id, builder, rx_bursts_out_queues_map_[service_id]); + + if (!rx_service->initialize()) { + HOLOSCAN_LOG_ERROR("Failed to initialize {} service", service_name); + return false; + } + + rx_services_[service_id] = std::move(rx_service); + return true; +} + +bool RivermaxMgr::RivermaxMgrImpl::initialize_rx_service( + uint32_t service_id, std::shared_ptr config_holder) { + if (config_holder == nullptr) { + HOLOSCAN_LOG_ERROR("Config holder is null"); + return false; + } + auto config_type = config_holder->get_type(); + + switch (config_type) { + case QueueConfigType::IPOReceiver: + HOLOSCAN_LOG_INFO("Initializing IPOReceiver:{}", service_id); + return common_initialize_rx_service< + RivermaxIPOReceiverQueueConfig, + RivermaxQueueToIPOReceiverSettingsBuilder, + IPOReceiverService>(service_id, config_holder, config_type, "IPOReceiver"); + case QueueConfigType::RTPReceiver: + HOLOSCAN_LOG_INFO("Initializing RTPReceiver:{}", service_id); + return common_initialize_rx_service< + RivermaxRTPReceiverQueueConfig, + RivermaxQueueToRTPReceiverSettingsBuilder, + RTPReceiverService>(service_id, config_holder, config_type, "RTPReceiver"); + default: + HOLOSCAN_LOG_ERROR("Unsupported Rx Service configuration type: {}", + queue_config_type_to_string(config_type)); + return false; + } +} + +bool RivermaxMgr::RivermaxMgrImpl::initialize_tx_service( + uint32_t service_id, std::shared_ptr config_holder) { + if (config_holder == nullptr) { + HOLOSCAN_LOG_ERROR("Config holder is null"); + return false; + } + + std::shared_ptr tx_service; + + auto config_type = config_holder->get_type(); + if (config_type == QueueConfigType::MediaFrameSender) { + HOLOSCAN_LOG_INFO("Initializing MediaSender:{}", service_id); + + auto typed_holder = std::dynamic_pointer_cast< + TypedConfigBuilderHolder>(config_holder); + + if (!typed_holder) { + HOLOSCAN_LOG_ERROR( + "Failed to cast to TypedConfigBuilderHolder"); + return false; + } + + auto media_sender_builder = typed_holder->get_config_builder(); + if (!media_sender_builder) { + HOLOSCAN_LOG_ERROR("Failed to get RivermaxQueueToMediaSenderSettingsBuilder"); + return false; + } + + auto dummy_sender = media_sender_builder->dummy_sender_; + if (dummy_sender) { + media_sender_builder->use_internal_memory_pool_ = true; + HOLOSCAN_LOG_INFO("Initializing Media Mock Sender :{}", service_id); + tx_service = std::make_shared(service_id, media_sender_builder); + } else { + if (!media_sender_builder->use_internal_memory_pool_) { + HOLOSCAN_LOG_INFO("Initializing Media Frame Zero Copy Sender :{}", service_id); + tx_service = std::make_shared(service_id, media_sender_builder); + } else { + HOLOSCAN_LOG_INFO("Initializing Media Frame Sender :{}", service_id); + tx_service = std::make_shared(service_id, media_sender_builder); + } + } + } else { + HOLOSCAN_LOG_ERROR("Unsupported Tx Service configuration type: {}", + queue_config_type_to_string(config_type)); + return false; + } + + if (!tx_service->initialize()) { + HOLOSCAN_LOG_ERROR("Failed to initialize TX service"); + return false; + } + + tx_services_[service_id] = std::move(tx_service); + return true; +} + +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); } + + for (const auto& entry : rx_services_) { + uint32_t key = entry.first; + auto& rx_service = entry.second; + rx_service_threads_.emplace_back( + [rx_service_ptr = rx_service.get()]() { rx_service_ptr->run(); }); + } + + num_services = tx_services_.size(); + if (num_services > 0) { HOLOSCAN_LOG_INFO("Starting {} TX Services", num_services); } + + for (const auto& entry : tx_services_) { + uint32_t key = entry.first; + auto& tx_service = entry.second; + tx_service_threads_.emplace_back( + [tx_service_ptr = tx_service.get()]() { tx_service_ptr->run(); }); + } + + HOLOSCAN_LOG_INFO("Done starting workers"); +} + +void* RivermaxMgr::RivermaxMgrImpl::get_segment_packet_ptr(BurstParams* burst, int seg, int idx) { + return burst->pkts[seg][idx]; +} + +void* RivermaxMgr::RivermaxMgrImpl::get_packet_ptr(BurstParams* burst, int idx) { + return burst->pkts[0][idx]; +} + +uint16_t RivermaxMgr::RivermaxMgrImpl::get_segment_packet_length(BurstParams* burst, int seg, + int idx) { + return burst->pkt_lens[seg][idx]; +} + +uint16_t RivermaxMgr::RivermaxMgrImpl::get_packet_length(BurstParams* burst, int idx) { + return burst->pkt_lens[0][idx]; +} + +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]; + return nullptr; +} + +Status RivermaxMgr::RivermaxMgrImpl::set_packet_tx_time(BurstParams* burst, int idx, + uint64_t timestamp) { + return Status::SUCCESS; +} + +Status RivermaxMgr::RivermaxMgrImpl::get_tx_packet_burst(BurstParams* burst) { + uint32_t key = + RivermaxBurst::burst_tag_from_port_and_queue_id(burst->hdr.hdr.port_id, burst->hdr.hdr.q_id); + + auto it = tx_services_.find(key); + if (it == tx_services_.end()) { + HOLOSCAN_LOG_ERROR( + "Invalid Port ID {}, Queue ID {} combination", burst->hdr.hdr.port_id, burst->hdr.hdr.q_id); + return Status::INVALID_PARAMETER; + } + + return it->second->get_tx_packet_burst(burst); +} + +Status RivermaxMgr::RivermaxMgrImpl::set_eth_header(BurstParams* burst, int idx, char* dst_addr) { + return Status::SUCCESS; +} + +Status RivermaxMgr::RivermaxMgrImpl::set_ipv4_header(BurstParams* burst, int idx, int ip_len, + uint8_t proto, unsigned int src_host, + unsigned int dst_host) { + return Status::SUCCESS; +} + +Status RivermaxMgr::RivermaxMgrImpl::set_udp_header(BurstParams* burst, int idx, int udp_len, + uint16_t src_port, uint16_t dst_port) { + return Status::SUCCESS; +} + +Status RivermaxMgr::RivermaxMgrImpl::set_udp_payload(BurstParams* burst, int idx, void* data, + int len) { + return Status::SUCCESS; +} + +bool RivermaxMgr::RivermaxMgrImpl::is_tx_burst_available(BurstParams* burst) { + uint32_t key = + RivermaxBurst::burst_tag_from_port_and_queue_id(burst->hdr.hdr.port_id, burst->hdr.hdr.q_id); + + auto it = tx_services_.find(key); + if (it == tx_services_.end()) { + HOLOSCAN_LOG_ERROR( + "Invalid Port ID {}, Queue ID {} combination", burst->hdr.hdr.port_id, burst->hdr.hdr.q_id); + return false; + } + + return it->second->is_tx_burst_available(burst); +} + +Status RivermaxMgr::RivermaxMgrImpl::set_packet_lengths(BurstParams* burst, int idx, + const std::initializer_list& lens) { + return Status::SUCCESS; +} + +void RivermaxMgr::RivermaxMgrImpl::free_all_segment_packets(BurstParams* burst, int seg) {} + +void RivermaxMgr::RivermaxMgrImpl::free_all_packets(BurstParams* burst) {} + +void RivermaxMgr::RivermaxMgrImpl::free_packet_segment(BurstParams* burst, int seg, int pkt) {} + +void RivermaxMgr::RivermaxMgrImpl::free_packet(BurstParams* burst, int pkt) {} + +void RivermaxMgr::RivermaxMgrImpl::free_rx_burst(BurstParams* burst) { + uint32_t key = + RivermaxBurst::burst_tag_from_port_and_queue_id(burst->hdr.hdr.port_id, burst->hdr.hdr.q_id); + + auto it = rx_services_.find(key); + if (it == rx_services_.end()) { + HOLOSCAN_LOG_ERROR("Rivermax Service is not initialized"); + return; + } + + it->second->free_rx_burst(burst); +} + +void RivermaxMgr::RivermaxMgrImpl::free_tx_burst(BurstParams* burst) { + uint32_t key = + RivermaxBurst::burst_tag_from_port_and_queue_id(burst->hdr.hdr.port_id, burst->hdr.hdr.q_id); + + auto it = tx_services_.find(key); + if (it == tx_services_.end()) { + HOLOSCAN_LOG_ERROR( + "Invalid Port ID {}, Queue ID {} combination", burst->hdr.hdr.port_id, burst->hdr.hdr.q_id); + return; + } + + it->second->free_tx_burst(burst); +} + +Status RivermaxMgr::RivermaxMgrImpl::get_rx_burst(BurstParams** burst, int port, int q, int stream) { + uint32_t service_id = RivermaxBurst::burst_tag_from_port_and_queue_id(port, q); + auto queue_it = rx_bursts_out_queues_map_.find(service_id); + + if (queue_it == rx_bursts_out_queues_map_.end()) { + HOLOSCAN_LOG_ERROR( + "No Rx queue found for Rivermax service (port {}, queue {}). " + "Check config.", + port, + q); + return Status::INVALID_PARAMETER; + } + if (queue_it->second.find(stream) == queue_it->second.end()) { + HOLOSCAN_LOG_ERROR("No Rx queue found for stream_id {}", stream); + return Status::INVALID_PARAMETER; + } + + auto out_burst_shared = queue_it->second[stream]->dequeue_burst(); + if (out_burst_shared == nullptr) { return Status::NULL_PTR; } + *burst = out_burst_shared.get(); + return Status::SUCCESS; +} + +Status RivermaxMgr::RivermaxMgrImpl::get_rx_burst(BurstParams** burst, int port, int q) { + // dequeue burst from any stream + uint32_t service_id = RivermaxBurst::burst_tag_from_port_and_queue_id(port, q); + auto queue_it = rx_bursts_out_queues_map_.find(service_id); + + if (queue_it == rx_bursts_out_queues_map_.end()) { + HOLOSCAN_LOG_ERROR( + "No Rx queue found for Rivermax service (port {}, queue {}). " + "Check config.", + port, + q); + return Status::INVALID_PARAMETER; + } + if (queue_it->second.empty()) { + HOLOSCAN_LOG_ERROR("No Rx queues found for service_id {}", service_id); + return Status::INVALID_PARAMETER; + } + // get burst from a random stream + auto number_of_streams = queue_it->second.size(); + if (number_of_streams == 0) { + HOLOSCAN_LOG_ERROR("No Rx queues found for service_id {}", service_id); + return Status::INVALID_PARAMETER; + } + auto it = queue_it->second.begin(); + std::advance(it, rand() % number_of_streams); + auto out_burst_shared = it->second->dequeue_burst(); + if (out_burst_shared == nullptr) { return Status::NULL_PTR; } + *burst = out_burst_shared.get(); + return Status::SUCCESS; +} + +void RivermaxMgr::RivermaxMgrImpl::free_rx_metadata(BurstParams* burst) {} + +void RivermaxMgr::RivermaxMgrImpl::free_tx_metadata(BurstParams* burst) {} + +Status RivermaxMgr::RivermaxMgrImpl::get_tx_metadata_buffer(BurstParams** burst) { + return Status::SUCCESS; +} + +Status RivermaxMgr::RivermaxMgrImpl::send_tx_burst(BurstParams* burst) { + uint32_t key = + RivermaxBurst::burst_tag_from_port_and_queue_id(burst->hdr.hdr.port_id, burst->hdr.hdr.q_id); + + auto it = tx_services_.find(key); + if (it == tx_services_.end()) { + HOLOSCAN_LOG_ERROR( + "Invalid Port ID {}, Queue ID {} combination", burst->hdr.hdr.port_id, burst->hdr.hdr.q_id); + return Status::INVALID_PARAMETER; + } + + return it->second->send_tx_burst(burst); +} + +void RivermaxMgr::RivermaxMgrImpl::shutdown() { + if (force_quit.load()) { return; } + HOLOSCAN_LOG_INFO("Advanced Network Rivermax manager shutting down"); + force_quit.store(true); + print_stats(); + kill(getpid(), SIGINT); + + // Shut down all services + for (auto& service_pair : rx_services_) { service_pair.second->shutdown(); } + + for (auto& service_pair : tx_services_) { service_pair.second->shutdown(); } + + for (auto& [service_id, rx_bursts_out_queue_service_map] : rx_bursts_out_queues_map_) { + for (auto& [stream_id, rx_bursts_out_queue] : rx_bursts_out_queue_service_map) { + rx_bursts_out_queue->stop(); + rx_bursts_out_queue->clear(); + } + } + + for (auto& rx_service_thread : rx_service_threads_) { + 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(); } + } + HOLOSCAN_LOG_INFO("All service threads finished"); + rx_bursts_out_queues_map_.clear(); + rx_services_.clear(); + + tx_services_.clear(); +} + +void RivermaxMgr::RivermaxMgrImpl::print_stats() { + std::stringstream ss; + ss << std::endl; + ss << "RIVERMAX Advanced Network Manager Statistics" << std::endl; + ss << "====================" << std::endl; + ss << "Service Statistics" << std::endl; + ss << "----------------" << std::endl; + + for (const auto& entry : rx_services_) { entry.second->print_stats(ss); } + + HOLOSCAN_LOG_INFO(ss.str()); +} + +uint64_t RivermaxMgr::RivermaxMgrImpl::get_burst_tot_byte(BurstParams* burst) { + return 0; +} + +BurstParams* RivermaxMgr::RivermaxMgrImpl::create_tx_burst_params() { + auto burst_idx = burst_tx_idx.fetch_add(1); + return &(burst_tx_pool[burst_idx % MAX_TX_BURST]); +} + +Status RivermaxMgr::RivermaxMgrImpl::get_mac_addr(int port, char* mac) { + return Status::NOT_SUPPORTED; +} + +RivermaxMgr::RivermaxMgr() : pImpl(std::make_unique()) {} + +RivermaxMgr::~RivermaxMgr() = default; + +bool RivermaxMgr::set_config_and_initialize(const NetworkConfig& cfg) { + std::lock_guard lock(initialization_mutex_); + + if (this->initialized_) { + HOLOSCAN_LOG_INFO("Rivermax Advanced Network Manager has been already initialized"); + return true; + } + + cfg_ = cfg; + + int port_id = 0; + for (auto& intf : cfg_.ifs_) { + intf.port_id_ = port_id++; + HOLOSCAN_LOG_INFO("{} ({}): assigned port ID {}", intf.name_, intf.address_, intf.port_id_); + } + + this->initialized_ = pImpl->set_config_and_initialize(cfg_); + + if (this->initialized_) { + HOLOSCAN_LOG_INFO("Rivermax ANO Manager initialized successfully"); + } else { + HOLOSCAN_LOG_ERROR("Failed to initialize Rivermax ANO Manager"); + } + + return this->initialized_; +} + +void RivermaxMgr::initialize() { + pImpl->initialize(); +} + +void RivermaxMgr::run() { + pImpl->run(); +} + +Status RivermaxMgr::parse_rx_queue_rivermax_config(const YAML::Node& q_item, RxQueueConfig& q) { + return RivermaxConfigParser::parse_rx_queue_rivermax_config(q_item, q); +} + +Status RivermaxMgr::parse_tx_queue_rivermax_config(const YAML::Node& q_item, TxQueueConfig& q) { + return RivermaxConfigParser::parse_tx_queue_rivermax_config(q_item, q); +} + +void* RivermaxMgr::get_segment_packet_ptr(BurstParams* burst, int seg, int idx) { + return pImpl->get_segment_packet_ptr(burst, seg, idx); +} + +void* RivermaxMgr::get_packet_ptr(BurstParams* burst, int idx) { + return pImpl->get_packet_ptr(burst, idx); +} + +uint16_t RivermaxMgr::get_segment_packet_length(BurstParams* burst, int seg, int idx) { + return pImpl->get_segment_packet_length(burst, seg, idx); +} + +uint16_t RivermaxMgr::get_packet_length(BurstParams* burst, int idx) { + return pImpl->get_packet_length(burst, idx); +} + +uint16_t RivermaxMgr::get_packet_flow_id(BurstParams* burst, int idx) { + return 0; +} + +void* RivermaxMgr::get_packet_extra_info(BurstParams* burst, int idx) { + return pImpl->get_packet_extra_info(burst, idx); +} + +Status RivermaxMgr::get_tx_packet_burst(BurstParams* burst) { + return pImpl->get_tx_packet_burst(burst); +} + +Status RivermaxMgr::set_eth_header(BurstParams* burst, int idx, char* dst_addr) { + return pImpl->set_eth_header(burst, idx, dst_addr); +} + +Status RivermaxMgr::set_ipv4_header(BurstParams* burst, int idx, int ip_len, uint8_t proto, + unsigned int src_host, unsigned int dst_host) { + return pImpl->set_ipv4_header(burst, idx, ip_len, proto, src_host, dst_host); +} + +Status RivermaxMgr::set_udp_header(BurstParams* burst, int idx, int udp_len, uint16_t src_port, + uint16_t dst_port) { + return pImpl->set_udp_header(burst, idx, udp_len, src_port, dst_port); +} + +Status RivermaxMgr::set_udp_payload(BurstParams* burst, int idx, void* data, int len) { + return pImpl->set_udp_payload(burst, idx, data, len); +} + +bool RivermaxMgr::is_tx_burst_available(BurstParams* burst) { + return pImpl->is_tx_burst_available(burst); +} + +Status RivermaxMgr::set_packet_lengths(BurstParams* burst, int idx, + const std::initializer_list& lens) { + return pImpl->set_packet_lengths(burst, idx, lens); +} + +void RivermaxMgr::free_all_segment_packets(BurstParams* burst, int seg) { + pImpl->free_all_segment_packets(burst, seg); +} + +void RivermaxMgr::free_all_packets(BurstParams* burst) { + pImpl->free_all_packets(burst); +} + +void RivermaxMgr::free_packet_segment(BurstParams* burst, int seg, int pkt) { + pImpl->free_packet_segment(burst, seg, pkt); +} + +void RivermaxMgr::free_packet(BurstParams* burst, int pkt) { + pImpl->free_packet(burst, pkt); +} + +void RivermaxMgr::free_rx_burst(BurstParams* burst) { + pImpl->free_rx_burst(burst); +} + +void RivermaxMgr::free_tx_burst(BurstParams* burst) { + pImpl->free_tx_burst(burst); +} + +Status RivermaxMgr::get_rx_burst(BurstParams** burst, int port, int q, int stream) { + return pImpl->get_rx_burst(burst, port, q, stream); +} + +Status RivermaxMgr::get_rx_burst(BurstParams** burst, int port, int q) { + return pImpl->get_rx_burst(burst, port, q); +} + +Status RivermaxMgr::set_packet_tx_time(BurstParams* burst, int idx, uint64_t timestamp) { + return pImpl->set_packet_tx_time(burst, idx, timestamp); +} + +void RivermaxMgr::free_rx_metadata(BurstParams* burst) { + pImpl->free_rx_metadata(burst); +} + +void RivermaxMgr::free_tx_metadata(BurstParams* burst) { + pImpl->free_tx_metadata(burst); +} + +Status RivermaxMgr::get_tx_metadata_buffer(BurstParams** burst) { + return pImpl->get_tx_metadata_buffer(burst); +} + +Status RivermaxMgr::send_tx_burst(BurstParams* burst) { + return pImpl->send_tx_burst(burst); +} + +void RivermaxMgr::shutdown() { + pImpl->shutdown(); +} + +void RivermaxMgr::print_stats() { + pImpl->print_stats(); +} + +uint64_t RivermaxMgr::get_burst_tot_byte(BurstParams* burst) { + return pImpl->get_burst_tot_byte(burst); +} + +BurstParams* RivermaxMgr::create_tx_burst_params() { + return pImpl->create_tx_burst_params(); +} + +Status RivermaxMgr::get_mac_addr(int port, char* mac) { + return pImpl->get_mac_addr(port, mac); +} + +}; // namespace holoscan::advanced_network 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 new file mode 100644 index 0000000000..00c834a998 --- /dev/null +++ b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/burst_manager.cpp @@ -0,0 +1,418 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023-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 +#include + +#include + +#include "rivermax_mgr_impl/rivermax_chunk_consumer_ano.h" + +#define USE_BLOCKING_QUEUE 0 +#define USE_BLOCKING_MEMPOOL 1 + +namespace holoscan::advanced_network { + +/** + * @brief A non-blocking queue implementation. + * + * @tparam T Type of elements stored in the queue. + */ +template +class NonBlockingQueue : public QueueInterface { + std::queue queue_; + mutable std::mutex mutex_; + std::atomic stop_{false}; + + public: + void enqueue(const T& value) override { + 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; } + value = queue_.front(); + queue_.pop(); + return true; + } + + bool try_dequeue(T& value, std::chrono::milliseconds timeout) override { + return try_dequeue(value); + } + + size_t get_size() const override { + std::lock_guard lock(mutex_); + return queue_.size(); + } + + void clear() override { + std::lock_guard lock(mutex_); + while (!queue_.empty()) { queue_.pop(); } + } + + void stop() override { + stop_ = true; + } +}; + +/** + * @brief A blocking queue implementation. + * + * @tparam T Type of elements stored in the queue. + */ +template +class BlockingQueue : public QueueInterface { + std::queue queue_; + mutable std::mutex mutex_; + std::condition_variable cond_; + std::atomic stop_{false}; + + public: + void enqueue(const T& value) override { + if (stop_) { return; } + std::lock_guard lock(mutex_); + queue_.push(value); + cond_.notify_one(); + } + + bool try_dequeue(T& value) override { + std::unique_lock lock(mutex_); + cond_.wait(lock, [this] { return !queue_.empty() || stop_; }); + if (stop_) { return false; } + value = queue_.front(); + queue_.pop(); + return true; + } + + 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; + } + if (stop_) { return false; } + value = queue_.front(); + queue_.pop(); + return true; + } + + size_t get_size() const override { + std::lock_guard lock(mutex_); + return queue_.size(); + } + + void clear() override { + std::lock_guard lock(mutex_); + while (!queue_.empty()) { queue_.pop(); } + } + + void stop() override { + stop_ = true; + cond_.notify_all(); + } +}; + +/** + * @brief Memory pool for managing bursts of packets. + */ +class AnoBurstsMemoryPool : public IAnoBurstsCollection { + public: + AnoBurstsMemoryPool() = delete; + + /** + * @brief Constructor with initial queue size. + * + * @param size Initial size of the memory pool. + * @param burst_handler Reference to the burst handler. + * @param tag Tag for the burst. + */ + AnoBurstsMemoryPool(size_t size, RivermaxBurst::BurstHandler& burst_handler, uint32_t tag); + + /** + * @brief Destructor for the AnoBurstsMemoryPool class. + * + * Frees all bursts in the queue and clears the burst map. + */ + ~AnoBurstsMemoryPool(); + + bool enqueue_burst(std::shared_ptr burst) override; + bool enqueue_burst(RivermaxBurst* burst); + std::shared_ptr dequeue_burst() override; + size_t available_bursts() override { return queue_->get_size(); }; + bool empty() override { return queue_->get_size() == 0; }; + void stop() override { queue_->stop(); } + + private: + std::unique_ptr>> queue_; + std::map> burst_map_; + size_t initial_size_; + mutable std::mutex burst_map_mutex_; + mutable std::mutex queue_mutex_; + uint32_t bursts_tag_ = 0; + RivermaxBurst::BurstHandler& burst_handler_; +}; + +AnoBurstsMemoryPool::AnoBurstsMemoryPool(size_t size, RivermaxBurst::BurstHandler& burst_handler, + uint32_t tag) + : initial_size_(size), bursts_tag_(tag), burst_handler_(burst_handler) { +#if USE_BLOCKING_MEMPOOL + queue_ = std::make_unique>>(); +#else + queue_ = std::make_unique>>(); +#endif + + for (uint16_t i = 0; i < size; i++) { + auto burst = burst_handler_.create_burst(i); + queue_->enqueue(burst); + burst_map_[i] = burst; + } +} + +bool AnoBurstsMemoryPool::enqueue_burst(RivermaxBurst* burst) { + if (burst == nullptr) { + HOLOSCAN_LOG_ERROR("Invalid burst"); + return false; + } + + uint16_t burst_id = burst->get_burst_id(); + + std::lock_guard lock(burst_map_mutex_); + auto it = burst_map_.find(burst_id); + if (it != burst_map_.end()) { + std::shared_ptr cur_burst = it->second; + return enqueue_burst(cur_burst); + } else { + HOLOSCAN_LOG_ERROR("Invalid burst ID: {}", burst_id); + return false; + } +} + +bool AnoBurstsMemoryPool::enqueue_burst(std::shared_ptr burst) { + if (burst == nullptr) { + HOLOSCAN_LOG_ERROR("Invalid burst"); + return false; + } + + std::lock_guard lock(queue_mutex_); + + if (queue_->get_size() < initial_size_) { + auto burst_tag = burst->get_burst_tag(); + if (bursts_tag_ != burst_tag) { + HOLOSCAN_LOG_ERROR("Invalid burst tag"); + return false; + } + queue_->enqueue(burst); + return true; + } else { + HOLOSCAN_LOG_ERROR("Burst pool is full burst_pool_tag {}", bursts_tag_); + } + return false; +} + +std::shared_ptr AnoBurstsMemoryPool::dequeue_burst() { + std::shared_ptr burst; + + if (queue_->try_dequeue(burst, + std::chrono::milliseconds(RxBurstsManager::GET_BURST_TIMEOUT_MS))) { + return burst; + } + return nullptr; +} + +AnoBurstsMemoryPool::~AnoBurstsMemoryPool() { + std::shared_ptr burst; + + while (queue_->get_size() > 0 && queue_->try_dequeue(burst)) { + burst_handler_.delete_burst(burst); + } + std::lock_guard lock(burst_map_mutex_); + burst_map_.clear(); +} + +AnoBurstsQueue::AnoBurstsQueue() { +#if USE_BLOCKING_QUEUE + queue_ = std::make_unique>>(); +#else + queue_ = std::make_unique>>(); +#endif +} + +bool AnoBurstsQueue::enqueue_burst(std::shared_ptr burst) { + queue_->enqueue(burst); + return true; +} + +void AnoBurstsQueue::clear() { + queue_->clear(); +} + +void AnoBurstsQueue::stop() { + queue_->stop(); +} + +std::shared_ptr AnoBurstsQueue::dequeue_burst() { + std::shared_ptr burst; + + if (queue_->try_dequeue(burst, + std::chrono::milliseconds(RxBurstsManager::GET_BURST_TIMEOUT_MS))) { + return burst; + } + return nullptr; +} + +RivermaxBurst::BurstHandler::BurstHandler(bool send_packet_ext_info, int port_id, int queue_id, + bool gpu_direct) + : send_packet_ext_info_(send_packet_ext_info), + port_id_(port_id), + queue_id_(queue_id), + gpu_direct_(gpu_direct) { + const uint32_t burst_tag = burst_tag_from_port_and_queue_id(port_id, queue_id); + + burst_info_.tag = burst_tag; + burst_info_.burst_id = 0; + burst_info_.hds_on = false; + burst_info_.header_on_cpu = false; + burst_info_.payload_on_cpu = false; + burst_info_.header_stride_size = 0; + burst_info_.payload_stride_size = 0; + burst_info_.header_seg_idx = 0; + burst_info_.payload_seg_idx = 0; +} + +std::shared_ptr RivermaxBurst::BurstHandler::create_burst(uint16_t burst_id) { + std::shared_ptr burst(new RivermaxBurst(port_id_, queue_id_, MAX_PKT_IN_BURST)); + + if (send_packet_ext_info_) { + burst->hdr.hdr.burst_flags = BurstFlags::INFO_PER_PACKET; + burst->pkt_extra_info = + reinterpret_cast(new RivermaxPacketExtendedInfo*[MAX_PKT_IN_BURST]); + for (int j = 0; j < MAX_PKT_IN_BURST; j++) { + burst->pkt_extra_info[j] = reinterpret_cast(new RivermaxPacketExtendedInfo()); + } + } else { + burst->hdr.hdr.burst_flags = BurstFlags::FLAGS_NONE; + burst->pkt_extra_info = nullptr; + } + + burst->pkts[0] = new void*[MAX_PKT_IN_BURST]; + burst->pkts[1] = new void*[MAX_PKT_IN_BURST]; + burst->pkt_lens[0] = new uint32_t[MAX_PKT_IN_BURST]; + burst->pkt_lens[1] = new uint32_t[MAX_PKT_IN_BURST]; + std::memset(burst->pkt_lens[0], 0, MAX_PKT_IN_BURST * sizeof(uint32_t)); + std::memset(burst->pkt_lens[1], 0, MAX_PKT_IN_BURST * sizeof(uint32_t)); + + burst_info_.burst_id = burst_id; + std::memcpy(burst->get_burst_info(), &burst_info_, sizeof(burst_info_)); + return burst; +} + +void RivermaxBurst::BurstHandler::delete_burst(std::shared_ptr burst) { + if (burst == nullptr) { + HOLOSCAN_LOG_ERROR("Invalid burst"); + return; + } + + if (send_packet_ext_info_ && burst->pkt_extra_info != nullptr) { + for (int i = 0; i < MAX_PKT_IN_BURST; i++) { + if (burst->pkt_extra_info[i] != nullptr) { + delete reinterpret_cast(burst->pkt_extra_info[i]); + burst->pkt_extra_info[i] = nullptr; + } + } + delete[] burst->pkt_extra_info; + burst->pkt_extra_info = nullptr; + } + + delete[] burst->pkts[0]; + burst->pkts[0] = nullptr; + delete[] burst->pkts[1]; + burst->pkts[1] = nullptr; + delete[] burst->pkt_lens[0]; + burst->pkt_lens[0] = nullptr; + delete[] burst->pkt_lens[1]; + burst->pkt_lens[1] = nullptr; +} + +void RxBurstsManager::rx_burst_done(RivermaxBurst* burst) { + if (burst == nullptr) { + HOLOSCAN_LOG_ERROR("Invalid burst"); + return; + } + + IAnoBurstsCollection* basePtr = rx_bursts_mempool_.get(); + + AnoBurstsMemoryPool* derivedPtr = dynamic_cast(basePtr); + + if (derivedPtr) { + bool rc = derivedPtr->enqueue_burst(burst); + if (!rc) { + HOLOSCAN_LOG_ERROR("Failed to push burst back to the pool. Port_id {}:{}, queue_id {}:{}", + burst->get_port_id(), + port_id_, + burst->get_queue_id(), + queue_id_); + } + } else { + HOLOSCAN_LOG_ERROR("Failed to push burst back to the pool, cast failed"); + } +} + +RxBurstsManager::RxBurstsManager(bool send_packet_ext_info, int port_id, int queue_id, + uint16_t burst_out_size, int gpu_id, + std::unordered_map> rx_bursts_out_queue) + : send_packet_ext_info_(send_packet_ext_info), + port_id_(port_id), + queue_id_(queue_id), + burst_out_size_(burst_out_size), + gpu_id_(gpu_id), + rx_bursts_out_queue_(rx_bursts_out_queue), + burst_handler_(std::make_unique( + send_packet_ext_info, port_id, queue_id, gpu_id != INVALID_GPU_ID)) { + const uint32_t burst_tag = RivermaxBurst::burst_tag_from_port_and_queue_id(port_id, queue_id); + gpu_direct_ = (gpu_id_ != INVALID_GPU_ID); + + rx_bursts_mempool_ = + std::make_unique(DEFAULT_NUM_RX_BURSTS, *burst_handler_, burst_tag); + + if (burst_out_size_ > RivermaxBurst::MAX_PKT_IN_BURST || burst_out_size_ == 0) + burst_out_size_ = RivermaxBurst::MAX_PKT_IN_BURST; +} + +RxBurstsManager::~RxBurstsManager() { + if (using_shared_out_queue_) { return; } + + std::shared_ptr burst; + // Get all bursts from the queue and return them to the memory pool + for (auto& [stream_id, rx_bursts_out_queue] : rx_bursts_out_queue_) { + while (rx_bursts_out_queue->available_bursts() > 0) { + burst = rx_bursts_out_queue->dequeue_burst(); + if (burst == nullptr) break; + rx_bursts_mempool_->enqueue_burst(burst); + } + } +} + +}; // namespace holoscan::advanced_network diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/burst_manager.h b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/burst_manager.h similarity index 68% rename from operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/burst_manager.h rename to operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/burst_manager.h index 88e0bd3d37..a272e603e8 100644 --- a/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/burst_manager.h +++ b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/burst_manager.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,20 +21,22 @@ #include #include -#include "rmax_ano_data_types.h" -#include "rmax_service/ipo_chunk_consumer_base.h" -#include "rmax_service/rmax_ipo_receiver_service.h" -#include "advanced_network/types.h" #include +#include "rdk/services/services.h" + +#include "advanced_network/types.h" +#include "rivermax_ano_data_types.h" + namespace holoscan::advanced_network { -using namespace ral::services; + +using namespace rivermax::dev_kit::services; /** - * @class RmaxBurst + * @class RivermaxBurst * @brief Represents a burst of packets in the advanced network. */ -class RmaxBurst : public BurstParams { +class RivermaxBurst : public BurstParams { public: static constexpr int MAX_PKT_IN_BURST = 9100; @@ -160,9 +162,7 @@ class RmaxBurst : public BurstParams { * @return The flags of the burst. */ inline BurstFlags get_burst_flags() const { - auto burst_info = get_burst_info(); - if (burst_info == nullptr) { return FLAGS_NONE; } - return burst_info->burst_flags; + return static_cast(hdr.hdr.burst_flags); } /** @@ -180,7 +180,7 @@ class RmaxBurst : public BurstParams { * * @return The maximum number of packets in the burst. */ - inline uint16_t get_max_num_packets() const { return m_max_num_packets; } + inline uint16_t get_max_num_packets() const { return max_num_packets_; } /** * @brief Gets the number of packets in a burst. @@ -196,10 +196,10 @@ class RmaxBurst : public BurstParams { * * @throws runtime_error If the maximum number of packets is exceeded. */ - inline void append_packet(const RmaxPacketData& packet_data) { - if (hdr.hdr.num_pkts >= m_max_num_packets) { + inline void append_packet(const RivermaxPacketData& packet_data) { + if (hdr.hdr.num_pkts >= max_num_packets_) { throw std::runtime_error("Maximum number of packets exceeded (num_packets: " + - std::to_string(m_max_num_packets) + ")"); + std::to_string(max_num_packets_) + ")"); } set_packet_data(get_num_packets(), packet_data); hdr.hdr.num_pkts += 1; @@ -213,12 +213,13 @@ class RmaxBurst : public BurstParams { * Index boundary checks are not performed in this function. * @param packet_data The data of the packet to append. */ - inline void set_packet_data(size_t packet_ind_in_out_burst, const RmaxPacketData& packet_data) { + inline void set_packet_data(size_t packet_ind_in_out_burst, + const RivermaxPacketData& packet_data) { auto burst_info = get_burst_info(); - if (burst_info->burst_flags & BurstFlags::INFO_PER_PACKET) { - RmaxPacketExtendedInfo* rx_packet_info = - reinterpret_cast(pkt_extra_info[packet_ind_in_out_burst]); + if (get_burst_flags() & BurstFlags::INFO_PER_PACKET) { + RivermaxPacketExtendedInfo* rx_packet_info = + reinterpret_cast(pkt_extra_info[packet_ind_in_out_burst]); rx_packet_info->timestamp = packet_data.extended_info.timestamp; rx_packet_info->flow_tag = packet_data.extended_info.flow_tag; } @@ -244,26 +245,27 @@ class RmaxBurst : public BurstParams { inline void set_num_packets(uint16_t num_pkts) { hdr.hdr.num_pkts = num_pkts; } /** - * @brief Constructs an RmaxBurst object. + * @brief Constructs an RivermaxBurst object. * * @param port_id The port ID. * @param queue_id The queue ID. * @param max_packets_in_burst The maximum number of packets in the burst. */ - RmaxBurst(uint16_t port_id, uint16_t queue_id, uint16_t max_packets_in_burst = MAX_PKT_IN_BURST) - : m_max_num_packets(max_packets_in_burst) { + RivermaxBurst(uint16_t port_id, uint16_t queue_id, + uint16_t max_packets_in_burst = MAX_PKT_IN_BURST) + : max_num_packets_(max_packets_in_burst) { hdr.hdr.port_id = port_id; hdr.hdr.q_id = queue_id; } private: - uint16_t m_max_num_packets = MAX_PKT_IN_BURST; + uint16_t max_num_packets_ = MAX_PKT_IN_BURST; }; /** * @brief Class responsible for handling burst operations. */ -class RmaxBurst::BurstHandler { +class RivermaxBurst::BurstHandler { public: /** * @brief Constructs a BurstHandler object. @@ -281,21 +283,21 @@ class RmaxBurst::BurstHandler { * @param burst_id The ID of the burst to create. * @return A shared pointer to the created burst. */ - std::shared_ptr create_burst(uint16_t burst_id); + std::shared_ptr create_burst(uint16_t burst_id); /** * @brief Deletes a burst and frees its associated resources. * * @param burst A shared pointer to the burst to delete. */ - void delete_burst(std::shared_ptr burst); + void delete_burst(std::shared_ptr burst); private: - bool m_send_packet_ext_info; - int m_port_id; - int m_queue_id; - bool m_gpu_direct; - AnoBurstExtendedInfo m_burst_info; + bool send_packet_ext_info_; + int port_id_; + int queue_id_; + bool gpu_direct_; + AnoBurstExtendedInfo burst_info_; }; /** @@ -303,7 +305,7 @@ class RmaxBurst::BurstHandler { * * The RxBurstsManager class is responsible for managing RX bursts in advanced networking * operations. It handles the creation, deletion, and processing of bursts, as well as - * managing the lifecycle of packets within bursts. This class interfaces with the Rmax + * managing the lifecycle of packets within bursts. This class interfaces with the Rivermax * framework to provide the necessary functionality for handling and transforming data * into a format suitable for advanced_network to process. */ @@ -323,10 +325,11 @@ class RxBurstsManager { * @param burst_out_size Size of the burst output. * @param gpu_id ID of the GPU. * @param rx_bursts_out_queue Shared pointer to the output queue for RX bursts. + * If not provided a local queue will be used. */ RxBurstsManager(bool send_packet_ext_info, int port_id, int queue_id, uint16_t burst_out_size = 0, int gpu_id = INVALID_GPU_ID, - std::shared_ptr rx_bursts_out_queue = nullptr); + std::unordered_map> rx_bursts_out_queue = {}); /** * @brief Destructor for the RxBurstsManager class. @@ -340,36 +343,36 @@ class RxBurstsManager { * @param hds_on Flag indicating if header data splitting (HDS) is enabled. * @param header_stride_size Stride size for the header data. * @param payload_stride_size Stride size for the payload data. - * @return ReturnStatus indicating the success or failure of the operation. - */ - inline ReturnStatus set_next_chunk_params(size_t chunk_size, bool hds_on, - size_t header_stride_size, size_t payload_stride_size) { - m_hds_on = hds_on; - m_header_stride_size = header_stride_size; - m_payload_stride_size = payload_stride_size; - return ReturnStatus::success; + * @return Status indicating the success or failure of the operation. + */ + inline Status set_next_chunk_params(size_t chunk_size, bool hds_on, size_t header_stride_size, + size_t payload_stride_size) { + hds_on_ = hds_on; + header_stride_size_ = header_stride_size; + payload_stride_size_ = payload_stride_size; + return Status::SUCCESS; } /** * @brief Submits the next packet to the burst manager. * * @param packet_data Extended information about the packet. - * @return ReturnStatus indicating the success or failure of the operation. + * @return Status indicating the success or failure of the operation. */ - inline ReturnStatus submit_next_packet(const RmaxPacketData& packet_data) { - get_or_allocate_current_burst(); - if (m_cur_out_burst == nullptr) { + inline Status submit_next_packet(int stream_id, const RivermaxPacketData& packet_data) { + get_or_allocate_current_burst(stream_id); + if (cur_out_burst_[stream_id] == nullptr) { HOLOSCAN_LOG_ERROR("Failed to allocate burst, running out of resources"); - return ReturnStatus::no_free_chunks; + return Status::NO_FREE_BURST_BUFFERS; } - m_cur_out_burst->append_packet(packet_data); + cur_out_burst_[stream_id]->append_packet(packet_data); - if (m_cur_out_burst->get_num_packets() >= m_burst_out_size) { - return enqueue_and_reset_current_burst(); + if (cur_out_burst_[stream_id]->get_num_packets() >= burst_out_size_) { + return enqueue_and_reset_current_burst(stream_id); } - return ReturnStatus::success; + return Status::SUCCESS; } /** @@ -377,17 +380,17 @@ class RxBurstsManager { * * @param burst Pointer to the burst parameters. * @throws logic_error If shared output queue is used. - * @return ReturnStatus indicating the success or failure of the operation. + * @return Status indicating the success or failure of the operation. */ - inline ReturnStatus get_rx_burst(BurstParams** burst) { - if (m_using_shared_out_queue) { + inline Status get_rx_burst(BurstParams** burst, int stream_id) { + if (using_shared_out_queue_) { throw std::logic_error("Cannot get RX burst when using shared output queue"); } - auto out_burst = m_rx_bursts_out_queue->dequeue_burst().get(); + auto out_burst = rx_bursts_out_queue_[stream_id]->dequeue_burst().get(); *burst = static_cast(out_burst); - if (*burst == nullptr) { return ReturnStatus::failure; } - return ReturnStatus::success; + if (*burst == nullptr) { return Status::NULL_PTR; } + return Status::SUCCESS; } /** @@ -395,7 +398,7 @@ class RxBurstsManager { * * @param burst Pointer to the burst parameters. */ - void rx_burst_done(RmaxBurst* burst); + void rx_burst_done(RivermaxBurst* burst); protected: /** @@ -403,66 +406,72 @@ class RxBurstsManager { * * @return Shared pointer to the allocated burst parameters. */ - inline std::shared_ptr allocate_burst() { - auto burst = m_rx_bursts_mempool->dequeue_burst(); + inline std::shared_ptr allocate_burst() { + auto burst = rx_bursts_mempool_->dequeue_burst(); return burst; } /** * @brief Gets or allocates the current burst. * + * This function checks if the current burst is null and allocates + * a new one if necessary. * @return Shared pointer to the current burst parameters. */ - inline std::shared_ptr get_or_allocate_current_burst() { - if (m_cur_out_burst == nullptr) { - m_cur_out_burst = allocate_burst(); - if (m_cur_out_burst == nullptr) { + inline std::shared_ptr get_or_allocate_current_burst(int stream_id) { + if (cur_out_burst_[stream_id] == nullptr) { + cur_out_burst_[stream_id] = allocate_burst(); + if (cur_out_burst_[stream_id] == nullptr) { HOLOSCAN_LOG_ERROR("Failed to allocate burst, running out of resources"); return nullptr; } - m_cur_out_burst->reset_burst_with_updated_params( - m_hds_on, m_header_stride_size, m_payload_stride_size, m_gpu_direct); + cur_out_burst_[stream_id]->reset_burst_with_updated_params( + hds_on_, header_stride_size_, payload_stride_size_, gpu_direct_); } - return m_cur_out_burst; + return cur_out_burst_[stream_id]; } - - inline ReturnStatus enqueue_and_reset_current_burst() { - if (m_cur_out_burst == nullptr) { + /** + * @brief Enqueues the current burst and resets it. + * + * @return Status indicating the success or failure of the operation. + */ + inline Status enqueue_and_reset_current_burst(int stream_id) { + if (cur_out_burst_[stream_id] == nullptr) { HOLOSCAN_LOG_ERROR("Trying to enqueue an empty burst"); - return ReturnStatus::failure; + return Status::NULL_PTR; } - bool res = m_rx_bursts_out_queue->enqueue_burst(m_cur_out_burst); - reset_current_burst(); + bool res = rx_bursts_out_queue_[stream_id]->enqueue_burst(cur_out_burst_[stream_id]); + reset_current_burst(stream_id); if (!res) { HOLOSCAN_LOG_ERROR("Failed to enqueue burst"); - return ReturnStatus::failure; + return Status::NO_SPACE_AVAILABLE; } - return ReturnStatus::success; + return Status::SUCCESS; } /** * @brief Resets the current burst. */ - inline void reset_current_burst() { m_cur_out_burst = nullptr; } + inline void reset_current_burst(int stream_id) { cur_out_burst_[stream_id] = nullptr; } protected: - bool m_send_packet_ext_info = false; - int m_port_id = 0; - int m_queue_id = 0; - uint16_t m_burst_out_size = 0; - int m_gpu_id = -1; - bool m_hds_on = false; - bool m_gpu_direct = false; - size_t m_header_stride_size = 0; - size_t m_payload_stride_size = 0; - bool m_using_shared_out_queue = true; - std::unique_ptr m_rx_bursts_mempool = nullptr; - std::shared_ptr m_rx_bursts_out_queue = nullptr; - std::shared_ptr m_cur_out_burst = nullptr; - AnoBurstExtendedInfo m_burst_info; - std::unique_ptr m_burst_handler; + bool send_packet_ext_info_ = false; + int port_id_ = 0; + int queue_id_ = 0; + uint16_t burst_out_size_ = 0; + int gpu_id_ = -1; + bool hds_on_ = false; + bool gpu_direct_ = false; + size_t header_stride_size_ = 0; + size_t payload_stride_size_ = 0; + bool using_shared_out_queue_ = true; + std::unique_ptr rx_bursts_mempool_ = nullptr; + std::unordered_map> rx_bursts_out_queue_; + std::unordered_map> cur_out_burst_; + AnoBurstExtendedInfo burst_info_; + std::unique_ptr burst_handler_; }; }; // namespace holoscan::advanced_network diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/packet_processor.h b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/packet_processor.h similarity index 70% rename from operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/packet_processor.h rename to operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/packet_processor.h index 45e9d29f3f..9e826371a3 100644 --- a/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/packet_processor.h +++ b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/packet_processor.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,12 +23,14 @@ #include #include -#include "rmax_ano_data_types.h" +#include "rdk/core/core.h" + +#include "rivermax_ano_data_types.h" #include "burst_manager.h" #include "advanced_network/types.h" namespace holoscan::advanced_network { -using namespace ral::services; +using namespace rivermax::dev_kit::core; /** * @brief Parameters for processing a chunk of packets. @@ -67,9 +69,10 @@ class IPacketProcessor { * status along with the number of processed packets. * * @param params Struct containing packet processing parameters. - * @return A tuple containing the status and the number of processed packets. + * @param processed_packets Sets the number of processed packets. + * @return Status indicating the success or failure of the operation. */ - virtual std::tuple process_packets(const PacketsChunkParams& params) = 0; + virtual Status process_packets(const PacketsChunkParams& params, size_t& processed_packets, int stream_id) = 0; }; /** @@ -88,39 +91,30 @@ class RxPacketProcessor : public IPacketProcessor { * @param rx_burst_manager Shared pointer to the burst manager. */ explicit RxPacketProcessor(std::shared_ptr rx_burst_manager) - : m_rx_burst_manager(rx_burst_manager) { - if (m_rx_burst_manager == nullptr) { + : rx_burst_manager_(rx_burst_manager) { + if (rx_burst_manager_ == nullptr) { throw std::invalid_argument("RxPacketProcessor: rx_burst_manager is nullptr"); } } - /** - * @brief Processes packets. - * - * This function processes the packets contained in the provided arrays and returns the - * status along with the number of processed packets. - * - * @param params Struct containing packet processing parameters. - * @return A tuple containing the status and the number of processed packets. - */ - std::tuple process_packets(const PacketsChunkParams& params) override { - size_t processed_packets = 0; - ReturnStatus status = ReturnStatus::success; + Status process_packets(const PacketsChunkParams& params, size_t& processed_packets, int stream_id) override { + processed_packets = 0; + Status status = Status::SUCCESS; - if (params.chunk_size == 0) { return {status, processed_packets}; } + if (params.chunk_size == 0) { return status; } auto remaining_packets = params.chunk_size; - status = m_rx_burst_manager->set_next_chunk_params( + status = rx_burst_manager_->set_next_chunk_params( params.chunk_size, params.hds_on, params.header_stride_size, params.payload_stride_size); - if (status != ReturnStatus::success) { return {status, processed_packets}; } + if (status != Status::SUCCESS) { return status; } auto header_ptr = params.header_ptr; auto payload_ptr = params.payload_ptr; while (remaining_packets > 0) { - RmaxPacketData rx_packet_data = { + RivermaxPacketData rx_packet_data = { header_ptr, payload_ptr, params.packet_info_array[processed_packets].get_packet_sub_block_size(0), @@ -128,9 +122,9 @@ class RxPacketProcessor : public IPacketProcessor { {params.packet_info_array[processed_packets].get_packet_flow_tag(), params.packet_info_array[processed_packets].get_packet_timestamp()}}; - status = m_rx_burst_manager->submit_next_packet(rx_packet_data); + status = rx_burst_manager_->submit_next_packet(stream_id, rx_packet_data); - if (status != ReturnStatus::success) { return {status, processed_packets}; } + if (status != Status::SUCCESS) { return status; } processed_packets++; remaining_packets--; @@ -138,11 +132,11 @@ class RxPacketProcessor : public IPacketProcessor { payload_ptr += params.payload_stride_size; } - return {status, processed_packets}; + return status; } private: - std::shared_ptr m_rx_burst_manager; + std::shared_ptr rx_burst_manager_; }; }; // namespace holoscan::advanced_network diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_chunk_consumer_ano.h b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_chunk_consumer_ano.h new file mode 100644 index 0000000000..1057b77efd --- /dev/null +++ b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_chunk_consumer_ano.h @@ -0,0 +1,128 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023-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 RIVERMAX_CHUNK_CONSUMER_ANO_H_ +#define RIVERMAX_CHUNK_CONSUMER_ANO_H_ + +#include +#include + +#include "rdk/core/core.h" +#include "rdk/services/services.h" + +#include "rivermax_ano_data_types.h" +#include "packet_processor.h" +#include "advanced_network/types.h" + +namespace holoscan::advanced_network { + +using namespace rivermax::dev_kit::services; +using namespace rivermax::dev_kit::core; + +/** + * @brief Consumer class for handling Rivermax chunks and providing advanced_network bursts. + * + * The RivermaxChunkConsumerAno class acts as an adapter that consumes Rmax chunks + * and produces advanced_network bursts. It processes the packets contained in the chunks, + * updates the consumed and unconsumed byte counts, and manages the lifecycle + * of the bursts. This class is designed to interface with the Rivermax framework + * and provide the necessary functionality to handle and transform the data + * into a format suitable for advanced_network to process. + */ +class RivermaxChunkConsumerAno : public IReceiveDataConsumer { + public: + /** + * @brief Constructor for the RivermaxChunkConsumerAno class. + * + * Initializes the chunk consumer with the specified packet processor. + * + * @param packet_processor Shared pointer to the packet processor. + */ + explicit RivermaxChunkConsumerAno(std::shared_ptr packet_processor, + size_t max_burst_size, int stream_id) + : packet_processor_(std::move(packet_processor)), + max_burst_size_(max_burst_size), + stream_id_(stream_id) { + if (max_burst_size_ >= RivermaxBurst::MAX_PKT_IN_BURST) { + max_burst_size_ = RivermaxBurst::MAX_PKT_IN_BURST; + } + packet_info_array_ = std::make_unique(RivermaxBurst::MAX_PKT_IN_BURST); + } + + /** + * @brief Destructor for the RivermaxChunkConsumerAno class. + * + * Ensures that all bursts are properly returned to the memory pool. + */ + virtual ~RivermaxChunkConsumerAno() = default; + + ReturnStatus consume_chunk(const ReceiveChunk& chunk, const IReceiveStream& stream, + size_t& consumed_packets) override; + + protected: + std::shared_ptr packet_processor_; + std::unique_ptr packet_info_array_; + size_t max_burst_size_ = 0; + int stream_id_ = 0; +}; + +inline ReturnStatus RivermaxChunkConsumerAno::consume_chunk(const ReceiveChunk& chunk, + const IReceiveStream& stream, + size_t& consumed_packets) { + consumed_packets = 0; + if (packet_processor_ == nullptr) { + HOLOSCAN_LOG_ERROR("Packet processor is not set"); + return ReturnStatus::failure; + } + + auto chunk_size = chunk.get_length(); + if (chunk_size == 0) { return ReturnStatus::success; } + if (chunk_size > max_burst_size_) { + HOLOSCAN_LOG_WARN("Chunk size {} exceeds maximum burst size {}, discarding packets", + chunk_size, max_burst_size_); + chunk_size = max_burst_size_; + } + + for (size_t i = 0; i < chunk_size; ++i) { packet_info_array_[i] = chunk.get_packet_info(i); } + PacketsChunkParams params = { + // header_ptr: Pointer to the header data + const_cast(reinterpret_cast(chunk.get_header_ptr())), + // payload_ptr: Pointer to the payload data + const_cast(reinterpret_cast(chunk.get_payload_ptr())), + // packet_info_array: Array of packet information + packet_info_array_.get(), + chunk_size, + // hds_on: Header data splitting enabled + chunk.is_header_data_split_on(), + // header_stride_size: Stride size for the header data + stream.get_header_stride_size(), + // payload_stride_size: Stride size for the payload data + stream.get_payload_stride_size(), + }; + + auto process_status = packet_processor_->process_packets(params, consumed_packets, stream_id_); + if (process_status != Status::SUCCESS) { + HOLOSCAN_LOG_ERROR("Packet processing failed"); + return ReturnStatus::failure; + } + + return ReturnStatus::success; +} + +}; // namespace holoscan::advanced_network + +#endif /* RIVERMAX_CHUNK_CONSUMER_ANO_H_ */ 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 new file mode 100644 index 0000000000..3bcb3407e7 --- /dev/null +++ b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_config_manager.cpp @@ -0,0 +1,911 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023-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 "rivermax_mgr_impl/burst_manager.h" +#include "rivermax_queue_configs.h" +#include "rivermax_config_manager.h" + +namespace holoscan::advanced_network { + +static constexpr int USECS_IN_SECOND = 1000000; + +/** + * @brief Factory class for creating configuration managers. + * + * The ConfigManagerFactory class provides a static method to create instances of + * configuration managers based on the specified configuration type + */ +class ConfigManagerFactory { + public: + /** + * @brief Creates a configuration manager. + * + * This static method creates and returns a shared pointer to a configuration manager + * based on the specified configuration type. + * + * @param type The type of configuration manager to create + * @return A shared pointer to the created configuration manager, or nullptr if the type is + * invalid. + */ + static std::shared_ptr create_manager(RivermaxConfigContainer::ConfigType type) { + switch (type) { + case RivermaxConfigContainer::ConfigType::RX: + return std::make_shared(); + case RivermaxConfigContainer::ConfigType::TX: + return std::make_shared(); + default: + return nullptr; + } + } +}; + +void RivermaxConfigContainer::add_config_manager(ConfigType type, + std::shared_ptr config_manager) { + config_managers_[type] = std::move(config_manager); +} + +void RivermaxConfigContainer::initialize_managers() { + add_config_manager(ConfigType::RX, ConfigManagerFactory::create_manager(ConfigType::RX)); + add_config_manager(ConfigType::TX, ConfigManagerFactory::create_manager(ConfigType::TX)); +} + +bool RivermaxConfigContainer::parse_configuration(const NetworkConfig& cfg) { + int rivermax_rx_config_found = 0; + int rivermax_tx_config_found = 0; + + is_configured_ = false; + cfg_ = cfg; + + for (const auto& intf : cfg.ifs_) { + HOLOSCAN_LOG_INFO("Rivermax init Port {} -- RX: {} TX: {}", + intf.port_id_, + intf.rx_.queues_.size() > 0 ? "ENABLED" : "DISABLED", + intf.tx_.queues_.size() > 0 ? "ENABLED" : "DISABLED"); + + auto parsed_rx_queues = parse_rx_queues(intf.port_id_, intf.rx_.queues_); + if (parsed_rx_queues < 0) { + HOLOSCAN_LOG_ERROR("Failed to parse RX queues for port {}", intf.port_id_); + return false; + } + rivermax_rx_config_found += parsed_rx_queues; + rivermax_tx_config_found += parse_tx_queues(intf.port_id_, intf.tx_.queues_); + } + + 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"); + return false; + } + + HOLOSCAN_LOG_INFO( + "Rivermax advanced_network settings were successfully parsed, " + "Found {} RX Queues and {} TX Queues " + "settings", + rivermax_rx_config_found, + rivermax_tx_config_found); + + is_configured_ = true; + return true; +} + +int RivermaxConfigContainer::parse_rx_queues(uint16_t port_id, + const std::vector& queues) { + int rivermax_rx_config_found = 0; + + auto rx_config_manager = std::dynamic_pointer_cast( + get_config_manager(RivermaxConfigContainer::ConfigType::RX)); + + 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)) { + HOLOSCAN_LOG_WARN("Failed to append RX queue configuration for queue - skipping"); + return -1; + } + rivermax_rx_config_found++; + } + + return rivermax_rx_config_found; +} + +bool RxConfigManager::append_candidate_for_rx_queue(uint16_t port_id, const RxQueueConfig& q) { + const auto& queue_id = q.common_.id_; + uint32_t key = RivermaxBurst::burst_tag_from_port_and_queue_id(port_id, queue_id); + if (config_builder_container_.has_config(key)) { + HOLOSCAN_LOG_ERROR("Rivermax RX ANO settings for: {} ({}) on port {} already exists", + q.common_.name_, + queue_id, + port_id); + return false; + } + + HOLOSCAN_LOG_INFO("Configuring RX queue: {} ({}) on port {}", q.common_.name_, queue_id, port_id); + + if (is_configuration_set_ == false) { + HOLOSCAN_LOG_ERROR("Configuration wasn't set for RxConfigManger"); + return false; + } + + // extra queue config_ contains Rivermax configuration. If it is not set, return false + if (!q.common_.extra_queue_config_) { + HOLOSCAN_LOG_ERROR("Extra queue config is not set for RX queue: {} ({}) on port {}", + q.common_.name_, + queue_id, + port_id); + return false; + } + + auto* base_rx_config_ptr = dynamic_cast(q.common_.extra_queue_config_); + if (!base_rx_config_ptr) { + HOLOSCAN_LOG_ERROR("Failed to cast extra queue config to BaseQueueConfig"); + return false; + } + + bool res = false; + auto config_type = base_rx_config_ptr->get_type(); + if (config_type == QueueConfigType::IPOReceiver) { + auto* rivermax_rx_config_ptr = + dynamic_cast(q.common_.extra_queue_config_); + if (!rivermax_rx_config_ptr) { + HOLOSCAN_LOG_ERROR("Failed to cast extra queue config to RivermaxIPOReceiverQueueConfig"); + return false; + } + + RivermaxIPOReceiverQueueConfig rivermax_rx_config(*rivermax_rx_config_ptr); + + res = append_ipo_receiver_candidate_for_rx_queue(key, q, rivermax_rx_config); + if (!res) { + HOLOSCAN_LOG_ERROR("Failed to append IPO receiver candidate for RX queue: {} ({}) on port {}", + q.common_.name_, + queue_id, + port_id); + return false; + } + } else if (config_type == QueueConfigType::RTPReceiver) { + auto* rivermax_rx_config_ptr = + dynamic_cast(q.common_.extra_queue_config_); + if (!rivermax_rx_config_ptr) { + HOLOSCAN_LOG_ERROR("Failed to cast extra queue config to RivermaxRTPReceiverQueueConfig"); + return false; + } + + RivermaxRTPReceiverQueueConfig rivermax_rx_config(*rivermax_rx_config_ptr); + + res = append_rtp_receiver_candidate_for_rx_queue(key, q, rivermax_rx_config); + } else { + HOLOSCAN_LOG_ERROR("Invalid configuration type for Rivermax RX queue: {}", + queue_config_type_to_string(config_type)); + return false; + } + + if (!res) { + HOLOSCAN_LOG_ERROR("Failed to append candidate for RX queue: {} ({}) on port {}", + q.common_.name_, + queue_id, + port_id); + return false; + } + + HOLOSCAN_LOG_INFO( + "Rivermax RX ANO settings for {} ({}) on port {}", q.common_.name_, queue_id, port_id); + + return true; +} + +bool RxConfigManager::append_ipo_receiver_candidate_for_rx_queue( + uint32_t config_index, const RxQueueConfig& q, + RivermaxIPOReceiverQueueConfig& rivermax_rx_config) { + if (!ConfigManagerUtilities::validate_memory_regions_config(q.common_.mrs_, cfg_.mrs_)) { + 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_; + + rivermax_rx_config.dump_parameters(); + RivermaxIPOReceiverQueueValidator rivermax_ano_settings_validator; + auto rivermax_rx_config_ptr = + std::make_shared(rivermax_rx_config); + auto rc = rivermax_ano_settings_validator.validate(rivermax_rx_config_ptr); + if (rc != ReturnStatus::success) { + HOLOSCAN_LOG_ERROR("Failed to validate source settings"); + return false; + } + auto rivermax_ipo_receiver_settings_validator = std::make_shared(); + auto settings_builder = std::make_shared( + std::move(rivermax_rx_config_ptr), std::move(rivermax_ipo_receiver_settings_validator)); + + config_builder_container_.add_config_builder( + config_index, QueueConfigType::IPOReceiver, settings_builder); + + return true; +} + +bool RxConfigManager::append_rtp_receiver_candidate_for_rx_queue( + uint32_t config_index, const RxQueueConfig& q, + RivermaxRTPReceiverQueueConfig& rivermax_rx_config) { + if (!ConfigManagerUtilities::validate_memory_regions_config(q.common_.mrs_, cfg_.mrs_)) { + 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_; + + rivermax_rx_config.dump_parameters(); + RivermaxRTPReceiverQueueValidator rivermax_ano_settings_validator; + auto rivermax_rx_config_ptr = + std::make_shared(rivermax_rx_config); + auto rc = rivermax_ano_settings_validator.validate(rivermax_rx_config_ptr); + if (rc != ReturnStatus::success) { + HOLOSCAN_LOG_ERROR("Failed to validate source settings"); + return false; + } + auto rivermax_rtp_receiver_settings_validator = std::make_shared(); + auto settings_builder = std::make_shared( + std::move(rivermax_rx_config_ptr), std::move(rivermax_rtp_receiver_settings_validator)); + + config_builder_container_.add_config_builder( + config_index, QueueConfigType::RTPReceiver, settings_builder); + + return true; +} + +int RivermaxConfigContainer::parse_tx_queues(uint16_t port_id, + const std::vector& queues) { + int rivermax_tx_config_found = 0; + + auto tx_config_manager = std::dynamic_pointer_cast( + get_config_manager(RivermaxConfigContainer::ConfigType::TX)); + + 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)) { + HOLOSCAN_LOG_WARN("Failed to append TX queue configuration for queue - Exiting"); + return 0; + } + rivermax_tx_config_found++; + } + + return rivermax_tx_config_found; +} + +bool TxConfigManager::append_candidate_for_tx_queue(uint16_t port_id, const TxQueueConfig& q) { + const auto& queue_id = q.common_.id_; + uint32_t key = RivermaxBurst::burst_tag_from_port_and_queue_id(port_id, queue_id); + if (config_builder_container_.has_config(key)) { + HOLOSCAN_LOG_ERROR("Rivermax TX ANO settings for: {} ({}) on port {} already exists", + q.common_.name_, + queue_id, + port_id); + return false; + } + + HOLOSCAN_LOG_INFO("Configuring TX queue: {} ({}) on port {}", q.common_.name_, queue_id, port_id); + + if (is_configuration_set_ == false) { + HOLOSCAN_LOG_ERROR("Configuration wasn't set for TxConfigManger"); + return false; + } + + // extra queue config_ contains Rivermax configuration. If it is not set, return false + if (!q.common_.extra_queue_config_) { + HOLOSCAN_LOG_ERROR("Extra queue config is not set for TX queue: {} ({}) on port {}", + q.common_.name_, + queue_id, + port_id); + return false; + } + + auto* base_tx_config_ptr = dynamic_cast(q.common_.extra_queue_config_); + if (!base_tx_config_ptr) { + HOLOSCAN_LOG_ERROR("Failed to cast extra queue config to BaseQueueConfig"); + return false; + } + + bool res; + auto config_type = base_tx_config_ptr->get_type(); + if (config_type == QueueConfigType::MediaFrameSender) { + auto* rivermax_tx_config_ptr = + dynamic_cast(q.common_.extra_queue_config_); + if (!rivermax_tx_config_ptr) { + HOLOSCAN_LOG_ERROR("Failed to cast extra queue config to RivermaxMediaSenderQueueConfig"); + return false; + } + + RivermaxMediaSenderQueueConfig rivermax_tx_config(*rivermax_tx_config_ptr); + + res = append_media_sender_candidate_for_tx_queue(key, q, rivermax_tx_config); + } else { + HOLOSCAN_LOG_ERROR("Invalid configuration type for Rivermax TX queue: {}", + queue_config_type_to_string(config_type)); + return false; + } + + if (!res) { + HOLOSCAN_LOG_ERROR("Failed to append candidate for TX queue: {} ({}) on port {}", + q.common_.name_, + queue_id, + port_id); + return false; + } + + HOLOSCAN_LOG_INFO( + "Rivermax RX ANO settings for {} ({}) on port {}", q.common_.name_, queue_id, port_id); + + return true; +} + +bool TxConfigManager::append_media_sender_candidate_for_tx_queue( + uint32_t config_index, const TxQueueConfig& q, + RivermaxMediaSenderQueueConfig& rivermax_tx_config) { + if (!ConfigManagerUtilities::validate_memory_regions_config(q.common_.mrs_, cfg_.mrs_)) { + 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_; + + rivermax_tx_config.dump_parameters(); + + RivermaxMediaSenderQueueValidator rivermax_ano_settings_validator; + auto rivermax_tx_config_ptr = + std::make_shared(rivermax_tx_config); + + auto rc = rivermax_ano_settings_validator.validate(rivermax_tx_config_ptr); + if (rc != ReturnStatus::success) { + HOLOSCAN_LOG_ERROR("Failed to validate source settings"); + return false; + } + if (!rivermax_tx_config.split_boundary && rivermax_tx_config.memory_pool_location == MemoryKind::DEVICE) { + HOLOSCAN_LOG_ERROR("GPU memory pool is supported only in header-data split mode"); + return false; + } + + auto rivermax_media_sender_settings_validator = std::make_shared(); + auto settings_builder = std::make_shared( + std::move(rivermax_tx_config_ptr), std::move(rivermax_media_sender_settings_validator)); + + settings_builder->dummy_sender_ = rivermax_tx_config.dummy_sender; + settings_builder->use_internal_memory_pool_ = rivermax_tx_config.use_internal_memory_pool; + settings_builder->memory_pool_location_ = rivermax_tx_config.memory_pool_location; + + config_builder_container_.add_config_builder( + config_index, QueueConfigType::MediaFrameSender, settings_builder); + + return true; +} + +bool RxConfigManager::config_memory_allocator(RivermaxCommonRxQueueConfig& rivermax_rx_config, + const RxQueueConfig& q) { + uint16_t num_of_mrs = q.common_.mrs_.size(); + HOLOSCAN_LOG_INFO( + "Configuring memory allocator for Rivermax RX queue: {}, number of memory regions: {}", + q.common_.name_, + num_of_mrs); + if (num_of_mrs == 1) { + return config_memory_allocator_from_single_mrs(rivermax_rx_config, + cfg_.mrs_[q.common_.mrs_[0]]); + } else if (num_of_mrs == 2) { + return config_memory_allocator_from_dual_mrs( + rivermax_rx_config, cfg_.mrs_[q.common_.mrs_[0]], cfg_.mrs_[q.common_.mrs_[1]]); + } else { + HOLOSCAN_LOG_ERROR("Incompatible number of memory regions for Rivermax RX queue: {} [1..{}]", + num_of_mrs, + MAX_RMAX_MEMORY_REGIONS); + return false; + } +} + +bool RxConfigManager::config_memory_allocator_from_single_mrs( + RivermaxCommonRxQueueConfig& rivermax_rx_config, const MemoryRegionConfig& mr) { + rivermax_rx_config.split_boundary = 0; + rivermax_rx_config.max_packet_size = mr.buf_size_; + rivermax_rx_config.packets_buffers_size = mr.num_bufs_; + + if (ConfigManagerUtilities::set_gpu_is_in_use_if_applicable(rivermax_rx_config, mr)) { + return true; + } + + ConfigManagerUtilities::set_gpu_is_not_in_use(rivermax_rx_config); + ConfigManagerUtilities::set_cpu_allocator_type(rivermax_rx_config, mr); + + return true; +} + +bool RxConfigManager::config_memory_allocator_from_dual_mrs( + RivermaxCommonRxQueueConfig& rivermax_rx_config, const MemoryRegionConfig& mr_header, + const MemoryRegionConfig& mr_payload) { + rivermax_rx_config.split_boundary = mr_header.buf_size_; + rivermax_rx_config.max_packet_size = mr_payload.buf_size_; + rivermax_rx_config.packets_buffers_size = mr_payload.num_bufs_; + + if (!ConfigManagerUtilities::set_gpu_is_in_use_if_applicable(rivermax_rx_config, mr_payload)) { + ConfigManagerUtilities::set_gpu_is_not_in_use(rivermax_rx_config); + } + + ConfigManagerUtilities::set_cpu_allocator_type(rivermax_rx_config, mr_header); + + return true; +} + +bool TxConfigManager::config_memory_allocator(RivermaxCommonTxQueueConfig& rivermax_tx_config, + const TxQueueConfig& q) { + uint16_t num_of_mrs = q.common_.mrs_.size(); + HOLOSCAN_LOG_INFO( + "Configuring memory allocator for Rivermax TX queue: {}, number of memory regions: {}", + q.common_.name_, + num_of_mrs); + if (num_of_mrs == 1) { + return config_memory_allocator_from_single_mrs(rivermax_tx_config, + cfg_.mrs_[q.common_.mrs_[0]]); + } else if (num_of_mrs == 2) { + return config_memory_allocator_from_dual_mrs( + rivermax_tx_config, cfg_.mrs_[q.common_.mrs_[0]], cfg_.mrs_[q.common_.mrs_[1]]); + } else { + HOLOSCAN_LOG_ERROR("Incompatible number of memory regions for Rivermax TX queue: {} [1..{}]", + num_of_mrs, + MAX_RMAX_MEMORY_REGIONS); + return false; + } +} + +bool TxConfigManager::config_memory_allocator_from_single_mrs( + RivermaxCommonTxQueueConfig& rivermax_tx_config, const MemoryRegionConfig& mr) { + rivermax_tx_config.split_boundary = 0; + + if (ConfigManagerUtilities::set_gpu_is_in_use_if_applicable(rivermax_tx_config, mr)) { + return true; + } + + ConfigManagerUtilities::set_gpu_is_not_in_use(rivermax_tx_config); + ConfigManagerUtilities::set_cpu_allocator_type(rivermax_tx_config, mr); + + return true; +} + +bool TxConfigManager::config_memory_allocator_from_dual_mrs( + RivermaxCommonTxQueueConfig& rivermax_tx_config, const MemoryRegionConfig& mr_header, + const MemoryRegionConfig& mr_payload) { + rivermax_tx_config.split_boundary = mr_header.buf_size_; + + if (!ConfigManagerUtilities::set_gpu_is_in_use_if_applicable(rivermax_tx_config, mr_payload)) { + ConfigManagerUtilities::set_gpu_is_not_in_use(rivermax_tx_config); + } + + ConfigManagerUtilities::set_cpu_allocator_type(rivermax_tx_config, mr_header); + + return true; +} + +Status RivermaxConfigParser::parse_rx_queue_rivermax_config(const YAML::Node& q_item, + RxQueueConfig& q) { + const auto& rivermax_rx_settings = q_item["rivermax_rx_settings"]; + + if (!rivermax_rx_settings) { + HOLOSCAN_LOG_ERROR("Rivermax RX settings not found"); + return Status::INVALID_PARAMETER; + } + + auto settings_type = rivermax_rx_settings["settings_type"].as("ipo_receiver"); + + if (settings_type == "ipo_receiver") { + q.common_.extra_queue_config_ = new RivermaxIPOReceiverQueueConfig(); + auto* ipo_rx_config = + static_cast(q.common_.extra_queue_config_); + + if (!parse_common_rx_settings(rivermax_rx_settings, q_item, *ipo_rx_config)) { + return Status::INVALID_PARAMETER; + } + + if (!parse_ipo_receiver_settings(rivermax_rx_settings, *ipo_rx_config)) { + return Status::INVALID_PARAMETER; + } + } else if (settings_type == "rtp_receiver") { + q.common_.extra_queue_config_ = new RivermaxRTPReceiverQueueConfig(); + auto* rtp_rx_config = + static_cast(q.common_.extra_queue_config_); + + if (!parse_common_rx_settings(rivermax_rx_settings, q_item, *rtp_rx_config)) { + return Status::INVALID_PARAMETER; + } + + if (!parse_rtp_receiver_settings(rivermax_rx_settings, *rtp_rx_config)) { + return Status::INVALID_PARAMETER; + } + } else { + HOLOSCAN_LOG_ERROR("Invalid settings type for Rivermax RX queue: {}", settings_type); + return Status::INVALID_PARAMETER; + } + + return Status::SUCCESS; +} + +bool RivermaxConfigParser::parse_common_rx_settings( + const YAML::Node& rx_settings, const YAML::Node& q_item, + RivermaxCommonRxQueueConfig& rivermax_rx_config) { + rivermax_rx_config.ext_seq_num = rx_settings["ext_seq_num"].as(true); + rivermax_rx_config.allocator_type = rx_settings["allocator_type"].as("auto"); + rivermax_rx_config.memory_registration = rx_settings["memory_registration"].as(true); + rivermax_rx_config.lock_gpu_clocks = rx_settings["lock_gpu_clocks"].as(true); + rivermax_rx_config.sleep_between_operations_us = + rx_settings["sleep_between_operations_us"].as(0); + rivermax_rx_config.print_parameters = rx_settings["verbose"].as(false); + rivermax_rx_config.send_packet_ext_info = rx_settings["send_packet_ext_info"].as(true); + rivermax_rx_config.max_chunk_size = q_item["batch_size"].as(1024); + rivermax_rx_config.stats_report_interval_ms = + rx_settings["stats_report_interval_ms"].as(1000); + + return true; +} + +bool RivermaxConfigParser::parse_ipo_receiver_settings( + const YAML::Node& rx_settings, RivermaxIPOReceiverQueueConfig& rivermax_rx_config) { + for (const auto& q_item_thread : rx_settings["rx_threads"]) { + ThreadSettings thread_settings; + for (const auto& q_item_stream : q_item_thread["network_settings"]) { + StreamNetworkSettings stream_settings; + for (const auto& q_item_ip : q_item_stream["local_ip_addresses"]) { + stream_settings.local_ips.emplace_back(q_item_ip.as()); + } + for (const auto& q_item_ip : q_item_stream["source_ip_addresses"]) { + stream_settings.source_ips.emplace_back(q_item_ip.as()); + } + for (const auto& q_item_ip : q_item_stream["destination_ip_addresses"]) { + stream_settings.destination_ips.emplace_back(q_item_ip.as()); + } + for (const auto& q_item_ip : q_item_stream["destination_ports"]) { + stream_settings.destination_ports.emplace_back(q_item_ip.as()); + } + stream_settings.stream_id = q_item_stream["stream_id"].as(0); + thread_settings.stream_network_settings.emplace_back(stream_settings); + } + thread_settings.thread_id = q_item_thread["thread_id"].as(0); + rivermax_rx_config.thread_settings.push_back(thread_settings); + } + rivermax_rx_config.max_path_differential_us = rx_settings["max_path_diff_us"].as(10000); + return true; +} + +bool RivermaxConfigParser::parse_rtp_receiver_settings( + const YAML::Node& rx_settings, RivermaxRTPReceiverQueueConfig& rivermax_rx_config) { + for (const auto& q_item_thread : rx_settings["rx_threads"]) { + ThreadSettings thread_settings; + for (const auto& q_item_stream : q_item_thread["network_settings"]) { + StreamNetworkSettings stream_settings; + stream_settings.local_ip = q_item_stream["local_ip_address"].as(""); + stream_settings.source_ip = q_item_stream["source_ip_address"].as(""); + stream_settings.destination_ip = q_item_stream["destination_ip_address"].as(""); + stream_settings.destination_port = q_item_stream["destination_port"].as(0); + stream_settings.stream_id = q_item_stream["stream_id"].as(0); + thread_settings.stream_network_settings.emplace_back(stream_settings); + } + thread_settings.thread_id = q_item_thread["thread_id"].as(0); + rivermax_rx_config.thread_settings.push_back(thread_settings); + } + return true; +} + +Status RivermaxConfigParser::parse_tx_queue_rivermax_config(const YAML::Node& q_item, + TxQueueConfig& q) { + const auto& rivermax_tx_settings = q_item["rivermax_tx_settings"]; + + if (!rivermax_tx_settings) { + HOLOSCAN_LOG_ERROR("Rivermax TX settings not found"); + return Status::INVALID_PARAMETER; + } + + auto settings_type = rivermax_tx_settings["settings_type"].as("media_sender"); + + if (settings_type == "media_sender") { + q.common_.extra_queue_config_ = new RivermaxMediaSenderQueueConfig(); + auto* media_tx_config = + static_cast(q.common_.extra_queue_config_); + + if (!parse_common_tx_settings(rivermax_tx_settings, q_item, *media_tx_config)) { + return Status::INVALID_PARAMETER; + } + + if (!parse_media_sender_settings(rivermax_tx_settings, *media_tx_config)) { + return Status::INVALID_PARAMETER; + } + } else if (settings_type == "generic_sender") { + HOLOSCAN_LOG_ERROR("Generic Sender is not supported"); + return Status::INVALID_PARAMETER; + } else { + HOLOSCAN_LOG_ERROR("Invalid settings type for Rivermax TX queue: {}", settings_type); + return Status::INVALID_PARAMETER; + } + + return Status::SUCCESS; +} + +bool RivermaxConfigParser::parse_common_tx_settings( + const YAML::Node& tx_settings, const YAML::Node& q_item, + RivermaxCommonTxQueueConfig& rivermax_tx_config) { + for (const auto& q_item_thread : tx_settings["tx_threads"]) { + ThreadSettings thread_settings; + for (const auto& q_item_stream : q_item_thread["network_settings"]) { + StreamNetworkSettings stream_settings; + stream_settings.local_ip = q_item_stream["local_ip_address"].as(""); + stream_settings.destination_ip = q_item_stream["destination_ip_address"].as(""); + stream_settings.destination_port = q_item_stream["destination_port"].as(0); + stream_settings.stream_id = q_item_stream["stream_id"].as(0); + thread_settings.stream_network_settings.push_back(stream_settings); + } + thread_settings.thread_id = q_item_thread["thread_id"].as(0); + rivermax_tx_config.thread_settings.push_back(thread_settings); + } + + rivermax_tx_config.allocator_type = tx_settings["allocator_type"].as("auto"); + rivermax_tx_config.memory_registration = tx_settings["memory_registration"].as(true); + rivermax_tx_config.memory_allocation = tx_settings["memory_allocation"].as(true); + rivermax_tx_config.lock_gpu_clocks = tx_settings["lock_gpu_clocks"].as(true); + + rivermax_tx_config.print_parameters = tx_settings["verbose"].as(false); + 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( + ANOMediaSenderSettings::DEFAULT_NUM_OF_PACKETS_IN_CHUNK_FHD); + rivermax_tx_config.sleep_between_operations = + tx_settings["sleep_between_operations"].as(true); + rivermax_tx_config.stats_report_interval_ms = + tx_settings["stats_report_interval_ms"].as(1000); + rivermax_tx_config.dummy_sender = tx_settings["dummy_sender"].as(false); + + return true; +} + +bool RivermaxConfigParser::parse_media_sender_settings( + const YAML::Node& tx_settings, RivermaxMediaSenderQueueConfig& rivermax_tx_config) { + rivermax_tx_config.video_format = tx_settings["video_format"].as(""); + rivermax_tx_config.bit_depth = tx_settings["bit_depth"].as(0); + rivermax_tx_config.frame_width = tx_settings["frame_width"].as(0); + rivermax_tx_config.frame_height = tx_settings["frame_height"].as(0); + rivermax_tx_config.frame_rate = tx_settings["frame_rate"].as(0); + 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")); + 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"); + } + } else { + rivermax_tx_config.memory_pool_location = MemoryKind::INVALID; + } + return true; +} + +template +void ConfigManagerUtilities::set_cpu_allocator_type(T& rivermax_config, + const MemoryRegionConfig& mr) { +#if RMAX_TEGRA + if (mr.kind_ == MemoryKind::HOST) { +#else + if (mr.kind_ == MemoryKind::HOST || mr.kind_ == MemoryKind::HOST_PINNED) { +#endif + rivermax_config.allocator_type = "malloc"; + } else if (mr.kind_ == MemoryKind::HUGE) { + if (rivermax_config.allocator_type != "huge_page_default" && + rivermax_config.allocator_type != "huge_page_2mb" && + rivermax_config.allocator_type != "huge_page_512mb" && + rivermax_config.allocator_type != "huge_page_1gb") { + rivermax_config.allocator_type = "huge_page_default"; + } // else the allocator type is already set + } +} + +template +void ConfigManagerUtilities::set_gpu_is_not_in_use(T& rivermax_config) { + rivermax_config.gpu_device_id = -1; + rivermax_config.gpu_direct = false; +} + +template +bool ConfigManagerUtilities::set_gpu_is_in_use_if_applicable(T& rivermax_config, + const MemoryRegionConfig& mr) { +#if RMAX_TEGRA + if (mr.kind_ == MemoryKind::DEVICE || mr.kind_ == MemoryKind::HOST_PINNED) { +#else + if (mr.kind_ == MemoryKind::DEVICE) { +#endif + rivermax_config.gpu_device_id = mr.affinity_; + rivermax_config.gpu_direct = true; + return true; + } + return false; +} + +bool ConfigManagerUtilities::parse_and_set_cores(std::vector& app_threads_cores, + const std::string& cores) { + std::istringstream iss(cores); + std::string coreStr; + bool to_reset_cores_vector = true; + while (std::getline(iss, coreStr, ',')) { + try { + int core = std::stoi(coreStr); + if (core < 0 || core >= std::thread::hardware_concurrency()) { + HOLOSCAN_LOG_ERROR("Invalid core number: {}", coreStr); + return false; + } else { + if (to_reset_cores_vector) { + app_threads_cores.clear(); + to_reset_cores_vector = false; + } + app_threads_cores.push_back(core); + } + } catch (const std::invalid_argument& e) { + HOLOSCAN_LOG_ERROR("Invalid core number: {}", coreStr); + return false; + } catch (const std::out_of_range& e) { + HOLOSCAN_LOG_ERROR("Core number out of range: {}", coreStr); + return false; + } + } + return true; +} + +int ConfigManagerUtilities::validate_cores(const std::string& cores) { + std::istringstream iss(cores); + std::string coreStr; + bool to_reset_cores_vector = true; + int num_cores = 0; + while (std::getline(iss, coreStr, ',')) { + try { + int core = std::stoi(coreStr); + if (core < 0 || core >= std::thread::hardware_concurrency()) { + HOLOSCAN_LOG_ERROR("Invalid core number: {}", coreStr); + return -1; + } + } catch (const std::invalid_argument& e) { + HOLOSCAN_LOG_ERROR("Invalid core number: {}", coreStr); + return -1; + } catch (const std::out_of_range& e) { + HOLOSCAN_LOG_ERROR("Core number out of range: {}", coreStr); + return -1; + } + num_cores++; + } + return num_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; } + }; + + app_settings_config.allocator_type = AllocatorTypeUI::Auto; + setAllocatorType("auto", AllocatorTypeUI::Auto); + setAllocatorType("huge_page_default", AllocatorTypeUI::HugePageDefault); + setAllocatorType("malloc", AllocatorTypeUI::Malloc); + setAllocatorType("huge_page_2mb", AllocatorTypeUI::HugePage2MB); + setAllocatorType("huge_page_512mb", AllocatorTypeUI::HugePage512MB); + setAllocatorType("huge_page_1gb", AllocatorTypeUI::HugePage1GB); + setAllocatorType("gpu", AllocatorTypeUI::GPU); +} + +VideoSampling ConfigManagerUtilities::convert_video_sampling(const std::string& sampling) { + static const std::map videoSamplingMap = { + {"YCbCr-4:4:4", VideoSampling::YCbCr_4_4_4}, + {"YCbCr-4:2:2", VideoSampling::YCbCr_4_2_2}, + {"YCbCr-4:2:0", VideoSampling::YCbCr_4_2_0}, + {"CLYCbCr-4:4:4", VideoSampling::CLYCbCr_4_4_4}, + {"CLYCbCr-4:2:2", VideoSampling::CLYCbCr_4_2_2}, + {"CLYCbCr-4:2:0", VideoSampling::CLYCbCr_4_2_0}, + {"ICtCp-4:4:4", VideoSampling::ICtCp_4_4_4}, + {"ICtCp-4:2:2", VideoSampling::ICtCp_4_2_2}, + {"ICtCp-4:2:0", VideoSampling::ICtCp_4_2_0}, + {"RGB", VideoSampling::RGB}, + {"XYZ", VideoSampling::XYZ}, + {"KEY", VideoSampling::KEY}}; + + auto it = videoSamplingMap.find(sampling); + if (it != videoSamplingMap.end()) { + return it->second; + } else { + return VideoSampling::Unknown; + } +} + +ColorBitDepth ConfigManagerUtilities::convert_bit_depth(uint16_t bit_depth) { + static const std::map bitDepthMap = {{8, ColorBitDepth::_8}, + {10, ColorBitDepth::_10}, + {12, ColorBitDepth::_12}, + {16, ColorBitDepth::_16}, + {165, ColorBitDepth::_16f}}; + + auto it = bitDepthMap.find(bit_depth); + if (it != bitDepthMap.end()) { + return it->second; + } else { + return ColorBitDepth::Unknown; + } +} + +bool ConfigManagerUtilities::validate_memory_regions_config( + const std::vector& queue_mr_names, + const std::unordered_map& memory_regions) { + uint16_t num_of_mrs = queue_mr_names.size(); + try { + if (num_of_mrs == 1) { + return validate_memory_regions_config_from_single_mrs(memory_regions.at(queue_mr_names[0])); + } else if (num_of_mrs == 2) { + return validate_memory_regions_config_from_dual_mrs(memory_regions.at(queue_mr_names[0]), + memory_regions.at(queue_mr_names[1])); + } else { + HOLOSCAN_LOG_ERROR("Incompatible number of memory regions for Rivermax RX queue: {} [1..2]", + num_of_mrs); + return false; + } + } catch (const std::out_of_range& e) { + if (num_of_mrs == 1) + HOLOSCAN_LOG_ERROR("Invalid memory region for Rivermax RX queue: {}", queue_mr_names[0]); + else + HOLOSCAN_LOG_ERROR("Invalid memory region for Rivermax RX queue: {} or {}", + queue_mr_names[0], + queue_mr_names[1]); + return false; + } + + return true; +} + +bool ConfigManagerUtilities::validate_memory_regions_config_from_single_mrs( + const MemoryRegionConfig& mr) { + return true; +} + +bool ConfigManagerUtilities::validate_memory_regions_config_from_dual_mrs( + const MemoryRegionConfig& mr_header, const MemoryRegionConfig& mr_payload) { + if (mr_payload.kind_ != MemoryKind::DEVICE && mr_header.kind_ != mr_payload.kind_) { + HOLOSCAN_LOG_ERROR( + "Memory region kind mismatch: {} != {}", (int)(mr_header.kind_), (int)mr_payload.kind_); + return false; + } + + if (mr_payload.kind_ == MemoryKind::DEVICE && mr_header.kind_ == MemoryKind::DEVICE) { + HOLOSCAN_LOG_ERROR("Both memory regions are device memory"); + return false; + } + + if (mr_payload.buf_size_ == 0) { + HOLOSCAN_LOG_ERROR("Invalid payload memory region size: {}", mr_payload.buf_size_); + return false; + } + + return true; +} + +} // namespace holoscan::advanced_network diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_config_manager.h b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_config_manager.h new file mode 100644 index 0000000000..5c9e2bdebd --- /dev/null +++ b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_config_manager.h @@ -0,0 +1,747 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023-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 RIVERMAX_CONFIG_MANAGER_H_ +#define RIVERMAX_CONFIG_MANAGER_H_ + +#include +#include +#include +#include +#include + +#include "advanced_network/manager.h" +#include "rivermax_ano_data_types.h" +#include "rivermax_queue_configs.h" + +namespace holoscan::advanced_network { + +/** + * @brief: Base configuration holder class. + * + * This class serves as the base for all configuration holder types + * and provides a common interface for identifying the configuration type. + */ +class ConfigBuilderHolder { + public: + /** + * @brief: Virtual destructor for ConfigBuilderHolder. + */ + virtual ~ConfigBuilderHolder() = default; + + /** + * @brief: Returns the type of the configuration. + * + * @return: The configuration type enum value. + */ + virtual QueueConfigType get_type() const = 0; +}; + +/** + * @brief: Typed configuration holder for specific builder types. + * + * This template class holds a builder of a specific type along with + * its type enum for identification purposes. + * + * @tparam ConfigBuilderType: The type of the configuration builder to hold. + */ +template +class TypedConfigBuilderHolder : public ConfigBuilderHolder { + public: + /** + * @brief: Constructor for TypedConfigBuilderHolder. + * + * @param [in] type: The configuration type enum value. + * @param [in] config_builder: A shared pointer to the configuration builder. + */ + TypedConfigBuilderHolder(QueueConfigType type, std::shared_ptr config_builder) + : type_(type), config_builder_(std::move(config_builder)) {} + + /** + * @brief: Returns the type of the configuration. + * + * @return: The configuration type enum value. + */ + QueueConfigType get_type() const override { return type_; } + + /** + * @brief: Gets the configuration builder pointer. + * + * @return: A shared pointer to the configuration builder. + */ + std::shared_ptr get_config_builder() const { return config_builder_; } + + private: + QueueConfigType type_; + std::shared_ptr config_builder_; +}; + +/** + * @brief: Container for multiple configuration builders. + * + * This class provides a mapping from configuration numbers to + * configuration builder holders, allowing for storage and retrieval + * of different types of configuration builders. + */ +class ConfigBuilderContainer { + public: + /** + * @brief: Iterator types for the container. + */ + using ConstIterator = + typename std::map>::const_iterator; + + /** + * @brief: Iterator access methods. + */ + ConstIterator begin() const { return holders_.begin(); } + ConstIterator end() const { return holders_.end(); } + ConstIterator cbegin() const { return holders_.cbegin(); } + ConstIterator cend() const { return holders_.cend(); } + + /** + * @brief: Adds a configuration builder to the container. + * + * @tparam ConfigBuilderType: The type of the configuration builder. + * @param [in] config_num: The configuration number (key). + * @param [in] type: The configuration type enum value. + * @param [in] config_builder: A shared pointer to the configuration builder. + */ + template + void add_config_builder(uint32_t config_num, QueueConfigType type, + std::shared_ptr config_builder) { + holders_[config_num] = std::make_shared>( + type, std::move(config_builder)); + } + + /** + * @brief: Gets a configuration holder by configuration number. + * + * @param [in] config_num: The configuration number (key). + * @return: A shared pointer to the ConfigBuilderHolder, or nullptr if not found. + */ + std::shared_ptr get_holder(uint32_t config_num) const { + auto it = holders_.find(config_num); + if (it != holders_.end()) { return it->second; } + return nullptr; + } + + /** + * @brief: Gets a configuration builder by configuration number and casts to specific type. + * + * @tparam ConfigBuilderType: The type of the configuration builder. + * @param [in] config_num: The configuration number (key). + * @return: A shared pointer to the specific type of configuration builder, or nullptr if not + * found. + */ + template + std::shared_ptr get_config_builder(uint32_t config_num) const { + auto holder = get_holder(config_num); + if (holder) { + auto typed_holder = dynamic_cast*>(holder.get()); + if (typed_holder) { return typed_holder->get_config_builder(); } + } + return nullptr; + } + + /** + * @brief: Gets configuration builders by type. + * + * @tparam ConfigBuilderType: The type of the configuration builder. + * @param [in] type: The configuration type enum value. + * @return: A vector of pairs containing config number and builder pointer. + */ + template + std::vector>> get_config_builders_by_type( + QueueConfigType type) const { + std::vector>> result; + for (const auto& pair : holders_) { + if (pair.second->get_type() == type) { + auto typed_holder = + dynamic_cast*>(pair.second.get()); + if (typed_holder) { result.emplace_back(pair.first, typed_holder->get_config_builder()); } + } + } + return result; + } + + /** + * @brief: Checks if the container has a configuration with the given number. + * + * @param [in] config_num: The configuration number to check. + * @return: True if the configuration exists, false otherwise. + */ + bool has_config(uint32_t config_num) const { + return holders_.find(config_num) != holders_.end(); + } + + /** + * @brief: Gets all configuration numbers in the container. + * + * @return: A vector of configuration numbers. + */ + std::vector get_config_nums() const { + std::vector keys; + keys.reserve(holders_.size()); + for (const auto& pair : holders_) { keys.push_back(pair.first); } + return keys; + } + + /** + * @brief: Gets the number of configurations in the container. + * + * @return: The number of configurations. + */ + size_t size() const { return holders_.size(); } + + /** + * @brief: Clears all configurations from the container. + */ + void clear() { holders_.clear(); } + + private: + std::map> holders_; +}; + +/** + * @brief Interface for configuration managers. + * + * The IConfigManager interface provides a common set of operations for + * managing configurations. It defines methods for setting configurations, + * iterating through configuration entries, and accessing specific configurations. + * This serves as the base interface for specialized configuration managers like + * RxConfigManager and TxConfigManager. + */ +class IConfigManager { + public: + /** + * @brief Maximum number of Rivermax memory regions supported. + */ + static constexpr uint16_t MAX_RMAX_MEMORY_REGIONS = 2; + + /** + * @brief Type definition for constant iterators used to traverse configurations. + */ + using ConstIterator = typename ConfigBuilderContainer::ConstIterator; + + /** + * @brief Virtual destructor to ensure proper cleanup of derived classes. + */ + virtual ~IConfigManager() = default; + + /** + * @brief Sets the network configuration. + * + * @param [in] cfg The network configuration to set. + * @return true if the configuration was set successfully, false otherwise. + */ + virtual bool set_configuration(const NetworkConfig& cfg) = 0; + + /** + * @brief Gets an iterator to the beginning of the configuration collection. + * + * @return A constant iterator pointing to the beginning of the collection. + */ + virtual ConstIterator begin() const = 0; + + /** + * @brief Gets an iterator to the end of the configuration collection. + * + * @return A constant iterator pointing to the end of the collection. + */ + virtual ConstIterator end() const = 0; + + /** + * @brief Gets a constant iterator to the beginning of the configuration collection. + * + * This method explicitly returns a constant iterator, useful when the const + * nature of the access needs to be emphasized. + * + * @return A constant iterator pointing to the beginning of the collection. + */ + virtual ConstIterator cbegin() const = 0; + + /** + * @brief Gets a constant iterator to the end of the configuration collection. + * + * This method explicitly returns a constant iterator, useful when the const + * nature of the access needs to be emphasized. + * + * @return A constant iterator pointing to the end of the collection. + */ + virtual ConstIterator cend() const = 0; +}; + +/** + * @brief Interface for RX configuration managers. + * + * The IRxConfigManager interface extends IConfigManager and defines additional operations + * specific to RX configuration managers in Rivermax. + */ +class IRxConfigManager : public IConfigManager { + public: + /** + * @brief Appends a candidate for RX queue configuration. + * + * @param port_id The port ID. + * @param q The RX queue configuration. + * @return True if the configuration was appended successfully, false otherwise. + */ + virtual bool append_candidate_for_rx_queue(uint16_t port_id, const RxQueueConfig& q) = 0; +}; + +/** + * @brief Interface for TX configuration managers. + * + * The ITxConfigManager interface extends IConfigManager and defines additional operations + * specific to TX configuration managers in Rivermax. + */ +class ITxConfigManager : public IConfigManager { + public: + /** + * @brief Appends a candidate for TX queue configuration. + * + * @param port_id The port ID. + * @param q The TX queue configuration. + * @return True if the configuration was appended successfully, false otherwise. + */ + virtual bool append_candidate_for_tx_queue(uint16_t port_id, const TxQueueConfig& q) = 0; +}; + +/** + * @brief Manages the RX configuration for Rivermax. + * + * The RxConfigManager class is responsible for managing the configuration settings + * for RX queues in Rivermax. It validates and appends RX queue configurations for given ports. + */ +class RxConfigManager : public IRxConfigManager { + public: + // Regular const iterators + ConstIterator begin() const override { return config_builder_container_.begin(); } + ConstIterator end() const override { return config_builder_container_.end(); } + // Explicit const iterators + ConstIterator cbegin() const override { return config_builder_container_.cbegin(); } + ConstIterator cend() const override { return config_builder_container_.cend(); } + + bool set_configuration(const NetworkConfig& cfg) override { + cfg_ = cfg; + is_configuration_set_ = true; + return true; + } + + bool append_candidate_for_rx_queue(uint16_t port_id, const RxQueueConfig& q) override; + + private: + /** + * @brief Appends a candidate for RX queue configuration. + * + * @param config_index The configuration index. + * @param q The RX queue configuration. + * @return True if the configuration was appended successfully, false otherwise. + */ + bool append_ipo_receiver_candidate_for_rx_queue( + uint32_t config_index, const RxQueueConfig& q, + RivermaxIPOReceiverQueueConfig& rivermax_rx_config); + /** + * @brief Appends a candidate for RX queue configuration. + * + * @param config_index The configuration index. + * @param q The RX queue configuration. + * @return True if the configuration was appended successfully, false otherwise. + */ + bool append_rtp_receiver_candidate_for_rx_queue( + uint32_t config_index, const RxQueueConfig& q, + RivermaxRTPReceiverQueueConfig& rivermax_rx_config); + /** + * @brief Configures the memory allocator for the Rivermax RX queue. + * + * @param rivermax_rx_config The Rivermax RX queue configuration. + * @param q The RX queue configuration. + * @return true if the configuration is successful, false otherwise. + */ + bool config_memory_allocator(RivermaxCommonRxQueueConfig& rivermax_rx_config, + const RxQueueConfig& q); + + /** + * @brief Configures the memory allocator for a single memory region. + * + * Configures the memory allocator for a single memory region. + * The allocator will be used for both the header and payload memory. + * + * @param rivermax_rx_config The Rivermax RX queue configuration. + * @param mr The memory region. + * @return true if the configuration is successful, false otherwise. + */ + bool config_memory_allocator_from_single_mrs(RivermaxCommonRxQueueConfig& rivermax_rx_config, + const MemoryRegionConfig& mr); + + /** + * @brief Configures the memory allocator for dual memory regions. + * + * Configures the memory allocator for dual memory regions. + * If GPU is in use, it will be used for the payload memory region, + * and the CPU allocator will be used for the header memory region. + * Otherwise, the function expects that the same allocator is configured + * for both memory regions. + * + * @param rivermax_rx_config The Rivermax RX queue configuration. + * @param mr_header The header memory region. + * @param mr_payload The payload memory region. + * @return true if the configuration is successful, false otherwise. + */ + + bool config_memory_allocator_from_dual_mrs(RivermaxCommonRxQueueConfig& rivermax_rx_config, + const MemoryRegionConfig& mr_header, + const MemoryRegionConfig& mr_payload); + + private: + ConfigBuilderContainer config_builder_container_; + NetworkConfig cfg_; + bool is_configuration_set_ = false; +}; + +/** + * @brief Manages the TX configuration for Rivermax. + * + * The TxConfigManager class is responsible for managing the configuration settings + * for TX queues in Rivermax. It validates and appends TX queue configurations for given ports. + */ +class TxConfigManager : public ITxConfigManager { + public: + // Regular const iterators + ConstIterator begin() const override { return config_builder_container_.begin(); } + ConstIterator end() const override { return config_builder_container_.end(); } + // Explicit const iterators + ConstIterator cbegin() const override { return config_builder_container_.cbegin(); } + ConstIterator cend() const override { return config_builder_container_.cend(); } + + bool set_configuration(const NetworkConfig& cfg) override { + cfg_ = cfg; + is_configuration_set_ = true; + return true; + } + + bool append_candidate_for_tx_queue(uint16_t port_id, const TxQueueConfig& q) override; + + private: + /** + * @brief Appends a candidate for TX queue configuration. + * + * @param config_index The configuration index. + * @param q The TX queue configuration. + * @return True if the configuration was appended successfully, false otherwise. + */ + bool append_media_sender_candidate_for_tx_queue( + uint32_t config_index, const TxQueueConfig& q, + RivermaxMediaSenderQueueConfig& rivermax_tx_config); + /** + * @brief Configures the memory allocator for the Rivermax TX queue. + * + * @param rivermax_rx_config The Rivermax TX queue configuration. + * @param q The TX queue configuration. + * @return true if the configuration is successful, false otherwise. + */ + bool config_memory_allocator(RivermaxCommonTxQueueConfig& rivermax_tx_config, + const TxQueueConfig& q); + + /** + * @brief Configures the memory allocator for a single memory region. + * + * Configures the memory allocator for a single memory region. + * The allocator will be used for both the header and payload memory. + * + * @param rivermax_rx_config The Rivermax TX queue configuration. + * @param mr The memory region. + * @return true if the configuration is successful, false otherwise. + */ + bool config_memory_allocator_from_single_mrs(RivermaxCommonTxQueueConfig& rivermax_tx_config, + const MemoryRegionConfig& mr); + + /** + * @brief Configures the memory allocator for dual memory regions. + * + * Configures the memory allocator for dual memory regions. + * If GPU is in use, it will be used for the payload memory region, + * and the CPU allocator will be used for the header memory region. + * Otherwise, the function expects that the same allocator is configured + * for both memory regions. + * + * @param rivermax_rx_config The Rivermax TX queue configuration. + * @param mr_header The header memory region. + * @param mr_payload The payload memory region. + * @return true if the configuration is successful, false otherwise. + */ + + bool config_memory_allocator_from_dual_mrs(RivermaxCommonTxQueueConfig& rivermax_tx_config, + const MemoryRegionConfig& mr_header, + const MemoryRegionConfig& mr_payload); + + private: + ConfigBuilderContainer config_builder_container_; + NetworkConfig cfg_; + bool is_configuration_set_ = false; +}; + +/** + * @brief Manages the configuration for Rivermax. + * + * The RivermaxConfigContainer class is responsible for parsing and managing the configuration + * settings for Rivermax via dedicated configuration managers. + */ +class RivermaxConfigContainer { + public: + enum class ConfigType { RX, TX }; + + /** + * @brief Constructs a new RivermaxConfigContainer object. + */ + RivermaxConfigContainer() { initialize_managers(); } + + /** + * @brief Parses the configuration from the YAML file. + * + * This function iterates over the interfaces and their respective RX an TX queues + * defined in the configuration YAML, extracting and validating the necessary + * settings for each queue. It then populates the RX and TX service configuration + * structures with these settings. The parsing is done via dedicated configuration managers. + * + * @param cfg The configuration YAML. + * @return True if the configuration was successfully parsed, false otherwise. + */ + bool parse_configuration(const NetworkConfig& cfg); + + std::shared_ptr get_config_manager(ConfigType type) const { + auto it = config_managers_.find(type); + if (it != config_managers_.end()) { return it->second; } + return nullptr; + } + + /** + * @brief Gets the current log level for Rivermax. + * + * @return The current log level. + */ + RivermaxLogLevel::Level get_rivermax_log_level() const { return rivermax_log_level_; } + + private: + /** + * @brief Initializes the configuration managers. + * + * This function initializes the configuration managers for RX and TX services. + */ + void initialize_managers(); + + /** + * @brief Adds a configuration manager. + * + * This function adds a configuration manager for the specified type. + * + * @param type The type of configuration manager to add. + * @param config_manager The shared pointer to the configuration manager. + */ + void add_config_manager(ConfigType type, std::shared_ptr config_manager); + + /** + * @brief Parses the RX queues configuration. + * + * This function parses the configuration for RX queues for the specified port ID. + * + * @param port_id The port ID for which to parse the RX queues. + * @param queues The vector of RX queue configurations. + * @return An integer indicating the success or failure of the parsing operation. + */ + int parse_rx_queues(uint16_t port_id, const std::vector& queues); + + /** + * @brief Parses the TX queues configuration. + * + * This function parses the configuration for TX queues for the specified port ID. + * + * @param port_id The port ID for which to parse the TX queues. + * @param queues The vector of TX queue configurations. + * @return An integer indicating the success or failure of the parsing operation. + */ + int parse_tx_queues(uint16_t port_id, const std::vector& queues); + + /** + * @brief Sets the Rivermax log level based on the provided advanced_network log level. + * + * This function converts the provided advanced_network log level to the corresponding + * Rivermax log level and sets it as the current log level for Rivermax. + * + * @param level The advanced_network log level to be converted and set. + */ + void set_rivermax_log_level(LogLevel::Level level) { + rivermax_log_level_ = RivermaxLogLevel::from_adv_net_log_level(level); + } + + private: + RivermaxLogLevel::Level rivermax_log_level_ = RivermaxLogLevel::OFF; + std::unordered_map> config_managers_; + NetworkConfig cfg_; + bool is_configured_ = false; +}; + +/** + * @brief Parses the configuration for Rivermax queues from YAML. + * + * The RivermaxConfigParser class is a static utility class responsible for parsing + * configuration settings for Rivermax from YAML nodes. It handles both RX and TX + * queue configurations, supporting multiple receiver types (IPO, RTP) and sender types + * (media sender, generic sender). + * + * This class separates the parsing logic into common settings shared across all queue types + * and specialized settings for specific receiver/sender implementations. It works together + * with RxConfigManager and TxConfigManager which use the parsed configurations to build + * the actual Rivermax service configurations. + * + * All methods in this class are static and do not require instantiation of the class. + */ +class RivermaxConfigParser { + public: + /** + * @brief Parses the RX queue configuration from a YAML node. + * + * This function extracts the RX queue configuration settings from the provided YAML node + * and populates the RxQueueConfig structure with the extracted values. + * + * @param q_item The YAML node containing the RX queue configuration. + * @param q The RxQueueConfig structure to be populated. + * @return Status indicating the success or failure of the operation. + */ + static Status parse_rx_queue_rivermax_config(const YAML::Node& q_item, RxQueueConfig& q); + + /** + * @brief Parses common RX settings from a YAML node. + * + * This function extracts common RX configuration settings from the provided YAML node + * and populates the RivermaxCommonRxQueueConfig structure with the extracted values. + * These settings are shared across all RX queue types. + * + * @param [in] rx_settings The YAML node containing the RX settings. + * @param [in] q_item The YAML node containing the queue item. + * @param [out] rivermax_rx_config The common RX queue configuration to be populated. + * @return true if parsing was successful, false otherwise. + */ + static bool parse_common_rx_settings(const YAML::Node& rx_settings, const YAML::Node& q_item, + RivermaxCommonRxQueueConfig& rivermax_rx_config); + + /** + * @brief Parses IPO receiver specific settings from a YAML node. + * + * This function extracts IPO receiver specific configuration settings from the provided YAML node + * and populates the RivermaxIPOReceiverQueueConfig structure with the extracted values. + * These settings include network addresses, ports, and IPO-specific parameters. + * + * @param [in] rx_settings The YAML node containing the RX settings. + * @param [out] rivermax_rx_config The IPO receiver RX queue configuration to be populated. + * @return true if parsing was successful, false otherwise. + */ + static bool parse_ipo_receiver_settings(const YAML::Node& rx_settings, + RivermaxIPOReceiverQueueConfig& rivermax_rx_config); + + /** + * @brief Parses RTP receiver specific settings from a YAML node. + * + * This function extracts RTP receiver specific configuration settings from the provided YAML node + * and populates the RivermaxRTPReceiverQueueConfig structure with the extracted values. + * These settings include network addresses and port parameters specific to RTP. + * + * @param [in] rx_settings The YAML node containing the RX settings. + * @param [out] rivermax_rx_config The RTP receiver RX queue configuration to be populated. + * @return true if parsing was successful, false otherwise. + */ + static bool parse_rtp_receiver_settings(const YAML::Node& rx_settings, + RivermaxRTPReceiverQueueConfig& rivermax_rx_config); + + /** + * @brief Parses the TX queue Rivermax configuration. + * + * This function extracts the TX queue configuration settings from the provided YAML node + * and populates the TxQueueConfig structure with the extracted values. + * + * @param q_item The YAML node containing the queue item. + * @param q The TX queue configuration to be populated. + * @return Status indicating the success or failure of the operation. + */ + static Status parse_tx_queue_rivermax_config(const YAML::Node& q_item, TxQueueConfig& q); + + /** + * @brief Parses common TX settings from a YAML node. + * + * This function extracts common TX configuration settings from the provided YAML node + * and populates the RivermaxCommonTxQueueConfig structure with the extracted values. + * These settings are shared across all TX queue types. + * + * @param [in] tx_settings The YAML node containing the TX settings. + * @param [in] q_item The YAML node containing the queue item. + * @param [out] rivermax_tx_config The common TX queue configuration to be populated. + * @return true if parsing was successful, false otherwise. + */ + static bool parse_common_tx_settings(const YAML::Node& tx_settings, const YAML::Node& q_item, + RivermaxCommonTxQueueConfig& rivermax_tx_config); + + /** + * @brief Parses media sender specific settings from a YAML node. + * + * This function extracts media sender specific configuration settings from the provided YAML node + * and populates the RivermaxMediaSenderQueueConfig structure with the extracted values. + * These settings include video format, bit depth, frame dimensions, and frame rate. + * + * @param [in] tx_settings The YAML node containing the TX settings. + * @param [out] rivermax_tx_config The media sender TX queue configuration to be populated. + * @return true if parsing was successful, false otherwise. + */ + static bool parse_media_sender_settings(const YAML::Node& tx_settings, + RivermaxMediaSenderQueueConfig& rivermax_tx_config); +}; + +class ConfigManagerUtilities { + public: + template + static void set_cpu_allocator_type(T& rivermax_config, const MemoryRegionConfig& mr); + + template + static void set_gpu_is_not_in_use(T& rivermax_config); + + template + static bool set_gpu_is_in_use_if_applicable(T& rivermax_config, const MemoryRegionConfig& mr); + + static bool parse_and_set_cores(std::vector& app_threads_cores, const std::string& cores); + + static int validate_cores(const std::string& cores); + + static void set_allocator_type(AppSettings& app_settings_config, + const std::string& allocator_type); + + static VideoSampling convert_video_sampling(const std::string& sampling); + + static ColorBitDepth convert_bit_depth(uint16_t bit_depth); + + static bool validate_memory_regions_config( + const std::vector& queue_mr_names, + const std::unordered_map& memory_regions); + + static bool validate_memory_regions_config_from_single_mrs(const MemoryRegionConfig& mr); + + static bool validate_memory_regions_config_from_dual_mrs(const MemoryRegionConfig& mr_header, + const MemoryRegionConfig& mr_payload); +}; + +} // namespace holoscan::advanced_network + +#endif // RIVERMAX_CONFIG_MANAGER_H_ 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 new file mode 100644 index 0000000000..f91636dd05 --- /dev/null +++ b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_mgr_service.cpp @@ -0,0 +1,678 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023-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 "rivermax_mgr_impl/rivermax_chunk_consumer_ano.h" +#include "rivermax_mgr_service.h" + +namespace holoscan::advanced_network { + +RivermaxManagerRxService::RivermaxManagerRxService( + uint32_t service_id, std::unordered_map> rx_bursts_out_queue) + : RivermaxManagerService(service_id), rx_bursts_out_queue_(std::move(rx_bursts_out_queue)) { + port_id_ = RivermaxBurst::burst_port_id_from_burst_tag(service_id); + queue_id_ = RivermaxBurst::burst_queue_id_from_burst_tag(service_id); +} + +bool RivermaxManagerRxService::initialize() { + HOLOSCAN_LOG_INFO("Initializing Receiver:{}", service_id_); + + rx_service_ = create_service(); + if (!rx_service_) { + HOLOSCAN_LOG_ERROR("Failed to create receiver service"); + return false; + } + + auto init_status = rx_service_->initialize(); + if (init_status != ReturnStatus::obj_init_success) { + HOLOSCAN_LOG_ERROR("Failed to initialize RX service, status: {}", (int)init_status); + return false; + } + + HOLOSCAN_LOG_INFO("Receiver:{} was successfully initialized", service_id_); + + if (!configure_service()) { + HOLOSCAN_LOG_ERROR("Failed to configure service"); + return false; + } + + rx_burst_manager_ = std::make_shared( + send_packet_ext_info_, port_id_, queue_id_, max_chunk_size_, gpu_id_, rx_bursts_out_queue_); + + rx_packet_processor_ = std::make_shared(rx_burst_manager_); + + for (const auto& [stream_id, rx_bursts_out_queue] : rx_bursts_out_queue_) { + auto rivermax_chunk_consumer = std::make_unique(rx_packet_processor_, + max_chunk_size_, stream_id); + + auto status = rx_service_->set_receive_data_consumer(stream_id, std::move(rivermax_chunk_consumer)); + if (status != ReturnStatus::success) { + HOLOSCAN_LOG_ERROR("Failed to set receive data consumer, status: {}", (int)status); + return false; + } + } + + initialized_ = true; + return true; +} + +void RivermaxManagerRxService::free_rx_burst(BurstParams* burst) { + if (!rx_burst_manager_) { + HOLOSCAN_LOG_ERROR("RX burst manager not initialized"); + return; + } + + rx_burst_manager_->rx_burst_done(static_cast(burst)); +} + +void RivermaxManagerRxService::run() { + if (!initialized_) { + HOLOSCAN_LOG_ERROR("Cannot run uninitialized receiver service"); + return; + } + + HOLOSCAN_LOG_INFO("Starting Receiver:{}", service_id_); + ReturnStatus status = rx_service_->run(); + + if (status != ReturnStatus::success) { + HOLOSCAN_LOG_ERROR("Failed to run receiver service, status: {}", (int)status); + } +} + +void RivermaxManagerRxService::shutdown() { + if (rx_service_) { HOLOSCAN_LOG_INFO("Shutting down Receiver:{}", service_id_); } + initialized_ = false; +} + +Status RivermaxManagerRxService::get_rx_burst(BurstParams** burst, int stream_id) { + if (rx_bursts_out_queue_.count(stream_id) == 0) { + HOLOSCAN_LOG_ERROR("RX bursts out queue not initialized for stream_id: {}", stream_id); + return Status::NOT_READY; + } + auto out_burst = rx_bursts_out_queue_[stream_id]->dequeue_burst().get(); + *burst = static_cast(out_burst); + if (*burst == nullptr) { return Status::NOT_READY; } + return Status::SUCCESS; +} + +IPOReceiverService::IPOReceiverService( + uint32_t service_id, + std::shared_ptr ipo_receiver_builder, + std::unordered_map> rx_bursts_out_queue) + : RivermaxManagerRxService(service_id, rx_bursts_out_queue), + ipo_receiver_builder_(ipo_receiver_builder) {} + +bool IPOReceiverService::configure_service() { + if (!ipo_receiver_builder_) { + HOLOSCAN_LOG_ERROR("IPO receiver builder is null"); + return false; + } + 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; + return true; +} + +void IPOReceiverService::print_stats(std::stringstream& ss) const { + if (!rx_service_) { + ss << "IPO Receiver Service ID: " << service_id_ << " (not initialized)" << std::endl; + return; + } + + ANOIPOReceiverApp& ipo_service = static_cast(*rx_service_); + auto stream_stats = ipo_service.get_streams_total_statistics(); + + ss << "IPO Receiver Service ID: " << service_id_ << std::endl; + + for (uint32_t i = 0; i < stream_stats.size(); ++i) { + ss << "[stream_index " << std::setw(3) << i << "]" + << " Got " << std::setw(7) << stream_stats[i].rx_count << " packets | "; + + if (stream_stats[i].received_bytes >= RivermaxManagerRxService::GIGABYTE) { + ss << std::fixed << std::setprecision(2) + << (stream_stats[i].received_bytes / RivermaxManagerRxService::GIGABYTE) << " GB |"; + } else if (stream_stats[i].received_bytes >= RivermaxManagerRxService::MEGABYTE) { + ss << std::fixed << std::setprecision(2) + << (stream_stats[i].received_bytes / RivermaxManagerRxService::MEGABYTE) << " MB |"; + } else { + ss << stream_stats[i].received_bytes << " bytes |"; + } + + ss << " dropped: "; + for (uint32_t s_index = 0; s_index < stream_stats[i].path_stats.size(); ++s_index) { + if (s_index > 0) { ss << ", "; } + ss << stream_stats[i].path_stats[s_index].rx_dropped + stream_stats[i].rx_dropped; + } + ss << " |" + << " consumed: " << stream_stats[i].consumed_packets << " |" + << " unconsumed: " << stream_stats[i].unconsumed_packets << " |" + << " lost: " << stream_stats[i].rx_dropped << " |" + << " exceed MD: " << stream_stats[i].rx_exceed_md << " |" + << " bad RTP hdr: " << stream_stats[i].rx_corrupt_header << " | "; + + for (uint32_t s_index = 0; s_index < stream_stats[i].path_stats.size(); ++s_index) { + if (stream_stats[i].rx_count > 0) { + uint32_t number = static_cast( + floor(100 * static_cast(stream_stats[i].path_stats[s_index].rx_count) / + static_cast(stream_stats[i].rx_count))); + ss << " " << std::setw(3) << number << "%"; + } else { + ss << " 0%"; + } + } + ss << std::endl; + } +} + +RTPReceiverService::RTPReceiverService( + uint32_t service_id, + std::shared_ptr rtp_receiver_builder, + std::unordered_map> rx_bursts_out_queue) + : RivermaxManagerRxService(service_id, rx_bursts_out_queue), + rtp_receiver_builder_(rtp_receiver_builder) {} + +bool RTPReceiverService::configure_service() { + if (!rtp_receiver_builder_) { + HOLOSCAN_LOG_ERROR("RTP receiver builder is null"); + return false; + } + 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_; + return true; +} + +void RTPReceiverService::print_stats(std::stringstream& ss) const { + if (!rx_service_) { + ss << "RTP Receiver Service ID: " << service_id_ << " (not initialized)" << std::endl; + return; + } + + ANORTPReceiverApp& rtp_service = static_cast(*rx_service_); + auto stream_stats = rtp_service.get_streams_total_statistics(); + + ss << "RTP Receiver Service ID: " << service_id_ << std::endl; + + for (uint32_t i = 0; i < stream_stats.size(); ++i) { + ss << "[stream_index " << std::setw(3) << i << "]" + << " Got " << std::setw(7) << stream_stats[i].rx_count << " packets | "; + + if (stream_stats[i].received_bytes >= RivermaxManagerRxService::GIGABYTE) { + ss << std::fixed << std::setprecision(2) + << (stream_stats[i].received_bytes / RivermaxManagerRxService::GIGABYTE) << " GB |"; + } else if (stream_stats[i].received_bytes >= RivermaxManagerRxService::MEGABYTE) { + ss << std::fixed << std::setprecision(2) + << (stream_stats[i].received_bytes / RivermaxManagerRxService::MEGABYTE) << " MB |"; + } else { + ss << stream_stats[i].received_bytes << " bytes |"; + } + ss << " |" + << " consumed: " << stream_stats[i].consumed_packets << " |" + << " unconsumed: " << stream_stats[i].unconsumed_packets << " |" + << " lost: " << stream_stats[i].rx_dropped << " |" + << " bad RTP hdr: " << stream_stats[i].rx_corrupt_header << " | "; + ss << std::endl; + } +} + +RivermaxManagerTxService::RivermaxManagerTxService(uint32_t service_id) + : RivermaxManagerService(service_id) { + port_id_ = RivermaxBurst::burst_port_id_from_burst_tag(service_id); + queue_id_ = RivermaxBurst::burst_queue_id_from_burst_tag(service_id); +} + +bool RivermaxManagerTxService::initialize() { + HOLOSCAN_LOG_INFO("Initializing TX Service:{}", service_id_); + + tx_service_ = create_service(); + if (!tx_service_) { + HOLOSCAN_LOG_ERROR("Failed to create TX service"); + return false; + } + + if (!configure_service()) { + HOLOSCAN_LOG_ERROR("Failed to configure TX service"); + return false; + } + + auto init_status = tx_service_->initialize(); + if (init_status != ReturnStatus::obj_init_success) { + HOLOSCAN_LOG_ERROR("Failed to initialize TX service, status: {}", (int)init_status); + return false; + } + + HOLOSCAN_LOG_INFO("TX Service:{} was successfully initialized", service_id_); + + if (!post_init_setup()) { + HOLOSCAN_LOG_ERROR("Failed in post-initialization setup"); + return false; + } + + initialized_ = true; + return true; +} + +void RivermaxManagerTxService::run() { + if (!initialized_) { + HOLOSCAN_LOG_ERROR("Cannot run uninitialized TX service"); + return; + } + + HOLOSCAN_LOG_INFO("Starting TX Service:{}", service_id_); + ReturnStatus status = tx_service_->run(); + + if (status != ReturnStatus::success) { + HOLOSCAN_LOG_ERROR("Failed to run TX service, status: {}", (int)status); + } +} + +void RivermaxManagerTxService::shutdown() { + if (tx_service_) { HOLOSCAN_LOG_INFO("Shutting down TX Service:{}", service_id_); } + initialized_ = false; +} + +BurstParams* RivermaxManagerTxService::prepare_burst_params(BurstParams* burst) { + burst->hdr.hdr.port_id = port_id_; + burst->hdr.hdr.q_id = queue_id_; + return burst; +} + +MediaSenderBaseService::MediaSenderBaseService( + uint32_t service_id, + std::shared_ptr media_sender_builder) + : RivermaxManagerTxService(service_id), media_sender_builder_(media_sender_builder) {} + +bool MediaSenderBaseService::configure_service() { + if (!media_sender_builder_) { + HOLOSCAN_LOG_ERROR("Media sender builder is null"); + return false; + } + return true; +} + +void* MediaSenderBaseService::allocate_pool_memory(size_t pool_buffer_size, + const AppSettings* settings) { + void* pool_buffer = nullptr; + if (!media_sender_builder_->use_internal_memory_pool_) { + HOLOSCAN_LOG_ERROR("Media sender service is configured not to use internal memory pool"); + return nullptr; + } + + if (media_sender_builder_->memory_pool_location_ == MemoryKind::DEVICE) { + cudaError_t cuda_status = cudaMalloc(&pool_buffer, pool_buffer_size); + if (cuda_status != cudaSuccess) { + HOLOSCAN_LOG_ERROR("Failed to allocate GPU memory: {}", cudaGetErrorString(cuda_status)); + return nullptr; + } + HOLOSCAN_LOG_INFO("Allocated GPU memory for frames memory pool at address: {}", pool_buffer); + } else if (media_sender_builder_->memory_pool_location_ == MemoryKind::HOST_PINNED) { + cudaError_t cuda_status = cudaHostAlloc(&pool_buffer, pool_buffer_size, cudaHostAllocDefault); + if (cuda_status != cudaSuccess) { + HOLOSCAN_LOG_ERROR("Failed to allocate pinned host memory: {}", + cudaGetErrorString(cuda_status)); + cudaFree(pool_buffer); + return nullptr; + } + HOLOSCAN_LOG_INFO("Allocated pinned host memory for frames memory pool at address: {}", + pool_buffer); + } else if (media_sender_builder_->memory_pool_location_ == MemoryKind::HUGE) { + RivermaxDevKitFacade& rivermax_dev_kit(RivermaxDevKitFacade::get_instance()); + std::shared_ptr app_settings(const_cast(settings), + [](AppSettings*) {}); + allocator_ = + rivermax_dev_kit.get_memory_allocator(AllocatorType::HugePageDefault, app_settings); + if (allocator_ == nullptr) { + HOLOSCAN_LOG_ERROR("Failed to create header memory allocator"); + return nullptr; + } + pool_buffer_size = allocator_->align_length(pool_buffer_size); + pool_buffer = allocator_->allocate_aligned(pool_buffer_size, allocator_->get_page_size()); + if (pool_buffer == nullptr) { + HOLOSCAN_LOG_ERROR("Failed to allocate huge memory"); + return nullptr; + } + HOLOSCAN_LOG_INFO("Allocated huge memory for frames memory pool at address: {}", pool_buffer); + } else { + pool_buffer = new byte_t[pool_buffer_size]; + if (pool_buffer == nullptr) { + HOLOSCAN_LOG_ERROR("Failed to allocate host memory"); + return nullptr; + } + HOLOSCAN_LOG_INFO("Allocated host memory for frames memory pool at address: {}", pool_buffer); + } + return pool_buffer; +} + +MediaSenderMockService::MediaSenderMockService( + uint32_t service_id, + std::shared_ptr media_sender_builder) + : MediaSenderBaseService(service_id, media_sender_builder) {} + +bool MediaSenderMockService::post_init_setup() { + const AppSettings* settings = nullptr; + auto status = tx_service_->get_app_settings(settings); + + if (status != ReturnStatus::success) { + HOLOSCAN_LOG_ERROR("Failed to get settings from TX service"); + return false; + } + size_t pool_buffer_size = settings->media.bytes_per_frame * 1; + void* pool_buffer = allocate_pool_memory(pool_buffer_size, settings); + if (pool_buffer == nullptr) { + HOLOSCAN_LOG_ERROR("Failed to allocate memory for media frame pool"); + return false; + } + + processing_frame_ = std::make_unique(static_cast(pool_buffer), + settings->media.bytes_per_frame); + return true; +} + +Status MediaSenderMockService::get_tx_packet_burst(BurstParams* burst) { + if (!initialized_) { + HOLOSCAN_LOG_ERROR("Media Sender service not initialized"); + return Status::INVALID_PARAMETER; + } + + prepare_burst_params(burst); + burst->pkts[0][0] = processing_frame_->data->get(); + burst->hdr.hdr.max_pkt = 1; // Single packet in burst + + return Status::SUCCESS; +} + +Status MediaSenderMockService::send_tx_burst(BurstParams* burst) { + if (!initialized_) { + HOLOSCAN_LOG_ERROR("Media Sender service not initialized"); + return Status::INVALID_PARAMETER; + } + + if (!processing_frame_) { + HOLOSCAN_LOG_ERROR("MediaSenderMockService{}:{}::send_tx_burst(): No frame in processing", + port_id_, + queue_id_); + return Status::INVALID_PARAMETER; + } + + return Status::SUCCESS; +} + +MediaSenderService::MediaSenderService( + uint32_t service_id, + std::shared_ptr media_sender_builder) + : MediaSenderBaseService(service_id, media_sender_builder) {} + +bool MediaSenderService::post_init_setup() { + const AppSettings* settings = nullptr; + auto status = tx_service_->get_app_settings(settings); + const ANOMediaSenderSettings* media_settings = + dynamic_cast(settings); + if (!media_settings) { + HOLOSCAN_LOG_ERROR("Failed to cast AppSettings to ANOMediaSenderSettings"); + return false; + } + + + if (status != ReturnStatus::success) { + HOLOSCAN_LOG_ERROR("Failed to get settings from TX service"); + return false; + } + size_t pool_buffer_size = settings->media.bytes_per_frame * MEDIA_FRAME_POOL_SIZE; + void* pool_buffer = allocate_pool_memory(pool_buffer_size, settings); + if (pool_buffer == nullptr) { + HOLOSCAN_LOG_ERROR("Failed to allocate memory for media frame pool"); + return false; + } + HOLOSCAN_LOG_INFO("Creating memory pool at address: {}", pool_buffer); + tx_media_frame_pool_ = std::make_unique(MEDIA_FRAME_POOL_SIZE, + settings->media.bytes_per_frame, + static_cast(pool_buffer), + pool_buffer_size); + + tx_media_frame_provider_ = + std::make_shared(MEDIA_FRAME_PROVIDER_SIZE); + // Set the media frame provider to the TX service + for (auto& thread : media_settings->thread_settings) { + for (auto& stream : thread.stream_network_settings) { + status = tx_service_->set_frame_provider(stream.stream_id, tx_media_frame_provider_); + if (status != ReturnStatus::success) { + HOLOSCAN_LOG_ERROR("Failed to set frame provider to TX service for stream {}", stream.stream_id); + return false; + } + } + } + return true; +} + +Status MediaSenderService::get_tx_packet_burst(BurstParams* burst) { + if (!initialized_) { + HOLOSCAN_LOG_ERROR("Media Sender service not initialized"); + return Status::INVALID_PARAMETER; + } + std::lock_guard lock(mutex_); + if (processing_frame_) { + HOLOSCAN_LOG_ERROR( + "MediaSenderService{}:{}::get_tx_packet_burst(): Frame is already in process", + port_id_, + queue_id_); + return Status::INVALID_PARAMETER; + } + if (burst->hdr.hdr.q_id != queue_id_ || burst->hdr.hdr.port_id != port_id_) { + HOLOSCAN_LOG_ERROR("MediaSenderService{}:{}::get_tx_packet_burst(): Burst queue ID mismatch", + port_id_, + queue_id_); + return Status::INVALID_PARAMETER; + } + + auto frame = tx_media_frame_pool_->get_frame(); + if (!frame) { + HOLOSCAN_LOG_ERROR( + "MediaSenderService{}:{}::get_tx_packet_burst(): Failed to get frame from pool", + port_id_, + queue_id_); + return Status::NO_FREE_BURST_BUFFERS; + } + + prepare_burst_params(burst); + burst->pkts[0][0] = frame->data->get(); + burst->pkt_lens[0][0] = frame->data->get_size(); + burst->hdr.hdr.max_pkt = 1; // Single packet in burst + + processing_frame_ = std::move(frame); + HOLOSCAN_LOG_TRACE( + "MediaSenderService{}:{}::get_tx_packet_burst(): Processing Frame is set," + "frame address: {} with size {}", + port_id_, + queue_id_, + static_cast(processing_frame_->data->get()), + processing_frame_->data->get_size()); + return Status::SUCCESS; +} + +Status MediaSenderService::send_tx_burst(BurstParams* burst) { + if (!initialized_) { + HOLOSCAN_LOG_ERROR("Media Sender service not initialized"); + return Status::INVALID_PARAMETER; + } + std::lock_guard lock(mutex_); + if (!processing_frame_) { + HOLOSCAN_LOG_ERROR( + "MediaSenderService{}:{}::send_tx_burst(): No frame in processing", port_id_, queue_id_); + return Status::INVALID_PARAMETER; + } + HOLOSCAN_LOG_TRACE("MediaSenderService{}:{}::send_tx_burst(): Out frame size is {}", + port_id_, + queue_id_, + tx_media_frame_provider_->get_queue_size()); + auto status = tx_media_frame_provider_->add_frame(std::move(processing_frame_)); + if (status != ReturnStatus::success) { + HOLOSCAN_LOG_ERROR( + "MediaSenderService{}:{}::send_tx_burst(): Failed to add frame to provider! Frame will be " + "dropped", + port_id_, + queue_id_); + processing_frame_.reset(); // Clear the frame to avoid leaks + return Status::NO_SPACE_AVAILABLE; + } + HOLOSCAN_LOG_TRACE( + "MediaSenderService{}:{}::send_tx_burst(): Frame was sent", port_id_, queue_id_); + processing_frame_.reset(); // Clear the reference as it's now managed by the provider + return Status::SUCCESS; +} + +bool MediaSenderService::is_tx_burst_available(BurstParams* burst) { + 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_); +} + +void MediaSenderService::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 to avoid leaks + 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(); } +} + +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(); } +} + +MediaSenderZeroCopyService::MediaSenderZeroCopyService( + uint32_t service_id, + std::shared_ptr media_sender_builder) + : MediaSenderBaseService(service_id, media_sender_builder) {} + +bool MediaSenderZeroCopyService::post_init_setup() { + tx_media_frame_provider_ = + std::make_shared(MEDIA_FRAME_PROVIDER_SIZE); + // Set the media frame provider to the TX service + auto status = tx_service_->set_frame_provider(0, tx_media_frame_provider_); + if (status != ReturnStatus::success) { + HOLOSCAN_LOG_ERROR("Failed to set frame provider to TX service"); + return false; + } + return true; +} + +Status MediaSenderZeroCopyService::get_tx_packet_burst(BurstParams* burst) { + if (!initialized_) { + HOLOSCAN_LOG_ERROR("Media Sender service not initialized"); + return Status::INVALID_PARAMETER; + } + std::lock_guard lock(mutex_); + if (is_frame_in_process_) { + HOLOSCAN_LOG_ERROR( + "MediaSenderZeroCopyService{}:{}::get_tx_packet_burst(): Frame is already in process", + port_id_, + queue_id_); + 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_); + return Status::INVALID_PARAMETER; + } + + prepare_burst_params(burst); + burst->hdr.hdr.max_pkt = 1; // Single packet in burst + burst->hdr.hdr.burst_flags = FLAGS_NONE; + burst->custom_pkt_data = nullptr; + + is_frame_in_process_ = true; + HOLOSCAN_LOG_TRACE( + "MediaSenderZeroCopyService{}:{}::get_tx_packet_burst(): Processing Frame is set,", + port_id_, + queue_id_); + return Status::SUCCESS; +} + +Status MediaSenderZeroCopyService::send_tx_burst(BurstParams* burst) { + if (!initialized_) { + HOLOSCAN_LOG_ERROR("Media Sender service not initialized"); + return Status::INVALID_PARAMETER; + } + std::lock_guard lock(mutex_); + if ((burst->hdr.hdr.burst_flags & FRAME_BUFFER_IS_OWNED)) { + HOLOSCAN_LOG_ERROR( + "MediaSenderZeroCopyService{}:{}::send_tx_burst(): Illegal burst flags." + "Buffer is owned by frame", + port_id_, + queue_id_); + return Status::INVALID_PARAMETER; + } + std::shared_ptr out_frame = + std::static_pointer_cast(burst->custom_pkt_data); + burst->custom_pkt_data.reset(); + if (!out_frame) { + HOLOSCAN_LOG_ERROR( + "MediaSenderZeroCopyService{}:{}::send_tx_burst(): No frame was provided in the burst", + port_id_, + queue_id_); + return Status::INVALID_PARAMETER; + } + HOLOSCAN_LOG_TRACE("MediaSenderZeroCopyService{}:{}::send_tx_burst(): Out frame size is {}", + port_id_, + queue_id_, + tx_media_frame_provider_->get_queue_size()); + + auto status = tx_media_frame_provider_->add_frame(std::move(out_frame)); + if (status != ReturnStatus::success) { + HOLOSCAN_LOG_ERROR( + "MediaSenderZeroCopyService{}:{}::send_tx_burst(): Failed to add frame to provider! Frame " + "will be dropped", + port_id_, + queue_id_); + is_frame_in_process_ = false; + return Status::NO_SPACE_AVAILABLE; + } + HOLOSCAN_LOG_TRACE( + "MediaSenderZeroCopyService{}:{}::send_tx_burst(): Frame was sent", port_id_, queue_id_); + is_frame_in_process_ = false; + return Status::SUCCESS; +} + +bool MediaSenderZeroCopyService::is_tx_burst_available(BurstParams* burst) { + if (!initialized_) { return false; } + return (!is_frame_in_process_ && + 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_); + is_frame_in_process_ = false; +} + +void MediaSenderZeroCopyService::shutdown() { + 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 new file mode 100644 index 0000000000..350fdf1e2b --- /dev/null +++ b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_mgr_service.h @@ -0,0 +1,511 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023-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 RIVERMAX_MANAGER_SERVICE_H_ +#define RIVERMAX_MANAGER_SERVICE_H_ + +#include +#include + +#include + +#include "rdk/rivermax_dev_kit.h" + +#include "adv_network_rivermax_mgr.h" +#include "rivermax_mgr_impl/rivermax_config_manager.h" +#include "rivermax_mgr_impl/burst_manager.h" +#include "rivermax_mgr_impl/packet_processor.h" + +namespace holoscan::advanced_network { + +/** + * @brief Base class for all Rivermax manager services. + * + * This class defines the common interface and functionality for all Rivermax manager services, + * providing lifecycle management methods and service identification. + */ +class RivermaxManagerService { + public: + /** + * @brief Constructor for the RivermaxManagerService class. + * + * @param service_id Unique identifier for the service. + */ + explicit RivermaxManagerService(uint32_t service_id) : service_id_(service_id) {} + + /** + * @brief Virtual destructor for the RivermaxManagerService class. + */ + virtual ~RivermaxManagerService() = default; + + /** + * @brief Returns the service ID. + * + * @return The unique identifier for this service. + */ + uint32_t service_id() const { return service_id_; } + + /** + * @brief Initializes the service. + * + * This method must be implemented by derived classes to perform service-specific initialization. + * + * @return True if initialization was successful, false otherwise. + */ + virtual bool initialize() = 0; + + /** + * @brief Runs the service. + * + * This method must be implemented by derived classes to start the service operation. + */ + virtual void run() = 0; + + /** + * @brief Shuts down the service. + * + * This method must be implemented by derived classes to perform clean shutdown of the service. + */ + virtual void shutdown() = 0; + + protected: + uint32_t service_id_; ///< Unique identifier for the service + uint16_t port_id_; ///< Port ID derived from the service ID + uint16_t queue_id_; ///< Queue ID derived from the service ID + bool initialized_ = false; ///< Flag indicating whether the service is initialized +}; + +/** + * @brief Base class for all Rivermax receiver services. + * + * This class provides common functionality for Rivermax receiver services, + * including burst management, packet processing, and service lifecycle management. + */ +class RivermaxManagerRxService : public RivermaxManagerService { + public: + /** + * @brief Constructor for the RivermaxManagerRxService class. + * + * @param service_id Unique identifier for the service. + * @param rx_bursts_out_queue Shared queue for outgoing received bursts. + */ + RivermaxManagerRxService(uint32_t service_id, + std::unordered_map> rx_bursts_out_queue); + + /** + * @brief Virtual destructor for the RivermaxManagerRxService class. + */ + virtual ~RivermaxManagerRxService() = default; + + /** + * @brief Frees a burst buffer that was previously obtained. + * + * @param burst Pointer to the burst to be freed. + */ + virtual void free_rx_burst(BurstParams* burst); + + /** + * @brief Gets a received burst. + * + * @param burst Pointer to a pointer where the burst will be stored. + * @return Status indicating the success or failure of the operation. + */ + virtual Status get_rx_burst(BurstParams** burst, int stream_id); + + /** + * @brief Prints service statistics. + * + * @param ss Stream to which statistics should be printed. + */ + virtual void print_stats(std::stringstream& ss) const {} + + bool initialize() override; + void run() override; + void shutdown() override; + + protected: + /** + * @brief Creates a service instance. + * + * This method must be implemented by derived classes to create the specific receiver service. + * + * @return Unique pointer to the created service. + */ + virtual std::unique_ptr create_service() = 0; + + /** + * @brief Configures the service with service-specific settings. + * + * This method must be implemented by derived classes to configure the service. + * + * @return True if configuration was successful, false otherwise. + */ + virtual bool configure_service() = 0; + + protected: + static constexpr double GIGABYTE = 1073741824.0; ///< Constant for gigabyte conversion + static constexpr double MEGABYTE = 1048576.0; ///< Constant for megabyte conversion + std::unique_ptr rx_service_; ///< The receiver service instance + std::shared_ptr rx_burst_manager_; ///< Manager for received bursts + std::shared_ptr rx_packet_processor_; ///< Processor for received packets + std::unordered_map> rx_bursts_out_queue_; ///< Output queue for received bursts + 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 +}; + +/** + * @brief IPO receiver service implementation. + * + * This class provides functionality for receiving and processing IPO protocol data. + */ +class IPOReceiverService : public RivermaxManagerRxService { + public: + /** + * @brief Constructor for the IPOReceiverService class. + * + * @param service_id Unique identifier for the service. + * @param ipo_receiver_builder Builder for IPO receiver settings. + * @param rx_bursts_out_queue Shared queue for outgoing received bursts. + */ + IPOReceiverService( + uint32_t service_id, + std::shared_ptr ipo_receiver_builder, + std::unordered_map> rx_bursts_out_queue); + + /** + * @brief Virtual destructor for the IPOReceiverService class. + */ + virtual ~IPOReceiverService() = default; + + void print_stats(std::stringstream& ss) const override; + + protected: + std::unique_ptr create_service() override { + return std::make_unique(ipo_receiver_builder_); + } + bool configure_service() override; + + private: + ///< Builder for IPO receiver settings + std::shared_ptr ipo_receiver_builder_; +}; + +/** + * @brief RTP receiver service implementation. + * + * This class provides functionality for receiving and processing RTP protocol data. + */ +class RTPReceiverService : public RivermaxManagerRxService { + public: + /** + * @brief Constructor for the RTPReceiverService class. + * + * @param service_id Unique identifier for the service. + * @param rtp_receiver_builder Builder for RTP receiver settings. + * @param rx_bursts_out_queue Shared queue for outgoing received bursts. + */ + RTPReceiverService( + uint32_t service_id, + std::shared_ptr rtp_receiver_builder, + std::unordered_map> rx_bursts_out_queue); + + /** + * @brief Virtual destructor for the RTPReceiverService class. + */ + virtual ~RTPReceiverService() = default; + + void print_stats(std::stringstream& ss) const override; + + protected: + std::unique_ptr create_service() override { + return std::make_unique(rtp_receiver_builder_); + } + bool configure_service() override; + + private: + ///< Builder for RTP receiver settings + std::shared_ptr rtp_receiver_builder_; +}; + +/** + * @brief Base class for all Rivermax transmitter services. + * + * This class provides common functionality for Rivermax transmitter services, + * including burst management and service lifecycle management. + */ +class RivermaxManagerTxService : public RivermaxManagerService { + public: + /** + * @brief Constructor for the RivermaxManagerTxService class. + * + * @param service_id Unique identifier for the service. + */ + explicit RivermaxManagerTxService(uint32_t service_id); + + /** + * @brief Virtual destructor for the RivermaxManagerTxService class. + */ + virtual ~RivermaxManagerTxService() = default; + + /** + * @brief Gets a burst buffer for transmission. + * + * @param burst Pointer to the burst buffer. + * @return Status indicating the success or failure of the operation. + */ + virtual Status get_tx_packet_burst(BurstParams* burst) = 0; + + /** + * @brief Sends a burst. + * + * @param burst Pointer to the burst to be sent. + * @return Status indicating the success or failure of the operation. + */ + virtual Status send_tx_burst(BurstParams* burst) = 0; + + /** + * @brief Checks if a burst is available for transmission. + * + * @param burst Pointer to the burst to check. + * @return True if the burst is available, false otherwise. + */ + virtual bool is_tx_burst_available(BurstParams* burst) = 0; + + /** + * @brief Frees a burst buffer that was previously obtained. + * + * @param burst Pointer to the burst to be freed. + */ + virtual void free_tx_burst(BurstParams* burst) = 0; + + /** + * @brief Prints service statistics. + * + * @param ss Stream to which statistics should be printed. + */ + virtual void print_stats(std::stringstream& ss) const {} + + bool initialize() override; + void run() override; + void shutdown() override; + + protected: + /** + * @brief Creates a service instance. + * + * This method must be implemented by derived classes to create the specific transmitter service. + * + * @return Unique pointer to the created service. + */ + virtual std::unique_ptr create_service() = 0; + + /** + * @brief Configures the service with service-specific settings. + * + * This method must be implemented by derived classes to configure the service. + * + * @return True if configuration was successful, false otherwise. + */ + virtual bool configure_service() = 0; + + /** + * @brief Performs additional setup after initialization. + * + * This method must be implemented by derived classes to perform any additional setup + * required after the service is initialized. + * + * @return True if post-initialization setup was successful, false otherwise. + */ + virtual bool post_init_setup() = 0; + + /** + * @brief Prepares burst parameters for transmission. + * + * @param burst Pointer to the burst to prepare. + * @return Pointer to the prepared burst. + */ + BurstParams* prepare_burst_params(BurstParams* burst); + + protected: + std::unique_ptr tx_service_; ///< The transmitter service instance +}; + +/** + * @brief Base class for media sender services. + * + * This class provides common functionality for media sender services. + */ +class MediaSenderBaseService : public RivermaxManagerTxService { + public: + /** + * @brief Constructor for the MediaSenderBaseService class. + * + * @param service_id Unique identifier for the service. + * @param media_sender_builder Builder for media sender settings. + */ + MediaSenderBaseService( + uint32_t service_id, + std::shared_ptr media_sender_builder); + + /** + * @brief Virtual destructor for the MediaSenderBaseService class. + */ + virtual ~MediaSenderBaseService() = default; + + protected: + std::unique_ptr create_service() override { + return std::make_unique(media_sender_builder_); + } + + bool configure_service() override; + + /** @brief Allocates memory for a pool buffer. + * + * @param pool_buffer_size Size of the buffer to allocate. + * @param settings Application settings. + * @return Pointer to the allocated memory. + */ + void* allocate_pool_memory(size_t pool_buffer_size, const AppSettings* settings); + + protected: + ///< Builder for media sender settings + std::shared_ptr media_sender_builder_; + std::shared_ptr allocator_; +}; + +/** + * @brief Mock implementation of media sender service. + * + * This class provides a mock implementation of the media sender service for testing purposes. + */ +class MediaSenderMockService : public MediaSenderBaseService { + public: + /** + * @brief Constructor for the MediaSenderMockService class. + * + * @param service_id Unique identifier for the service. + * @param media_sender_builder Builder for media sender settings. + */ + MediaSenderMockService( + uint32_t service_id, + std::shared_ptr media_sender_builder); + + /** + * @brief Virtual destructor for the MediaSenderMockService class. + */ + virtual ~MediaSenderMockService() = default; + + Status get_tx_packet_burst(BurstParams* burst) override; + Status send_tx_burst(BurstParams* burst) override; + bool is_tx_burst_available(BurstParams* burst) override { return true; } + void free_tx_burst(BurstParams* burst) override {} + + protected: + bool post_init_setup() override; + + private: + std::unique_ptr processing_frame_; ///< Currently processing media frame +}; + +/** + * @brief Implementation of media sender service. + * + * This class provides functionality for sending media data. + */ +class MediaSenderService : public MediaSenderBaseService { + public: + /** + * @brief Constructor for the MediaSenderService class. + * + * @param service_id Unique identifier for the service. + * @param media_sender_builder Builder for media sender settings. + */ + MediaSenderService( + uint32_t service_id, + std::shared_ptr media_sender_builder); + + /** + * @brief Virtual destructor for the MediaSenderService class. + */ + virtual ~MediaSenderService() = default; + + Status get_tx_packet_burst(BurstParams* burst) override; + Status send_tx_burst(BurstParams* burst) override; + bool is_tx_burst_available(BurstParams* burst) override; + void free_tx_burst(BurstParams* burst) override; + void shutdown() override; + + protected: + bool post_init_setup() override; + + private: + std::unique_ptr tx_media_frame_pool_; ///< Pool for media frames + std::shared_ptr + tx_media_frame_provider_; ///< Provider for buffered media frames + std::shared_ptr processing_frame_; ///< Currently processing media frame + mutable std::mutex mutex_; + + static constexpr int MEDIA_FRAME_POOL_SIZE = 16; ///< Size of the media frame pool + static constexpr int MEDIA_FRAME_PROVIDER_SIZE = 32; ///< Size of the media frame provider +}; + +/** + * @brief Implementation of media sender service without internal memory pool. + * + * This class provides functionality for sending media data. + * It doesn't have an internal memory pool and forwards media frames to the Rivermax + * via @ref MediaFrameProvider. + */ +class MediaSenderZeroCopyService : public MediaSenderBaseService { + public: + /** + * @brief Constructor for the MediaSenderZeroCopyService class. + * + * @param service_id Unique identifier for the service. + * @param media_sender_builder Builder for media sender settings. + */ + MediaSenderZeroCopyService( + uint32_t service_id, + std::shared_ptr media_sender_builder); + + /** + * @brief Virtual destructor for the MediaSenderZeroCopyService class. + */ + virtual ~MediaSenderZeroCopyService() = default; + + Status get_tx_packet_burst(BurstParams* burst) override; + Status send_tx_burst(BurstParams* burst) override; + bool is_tx_burst_available(BurstParams* burst) override; + void free_tx_burst(BurstParams* burst) override; + void shutdown() override; + + protected: + bool post_init_setup() override; + + private: + std::shared_ptr + tx_media_frame_provider_; ///< Provider for buffered media frames + bool is_frame_in_process_ = false; + mutable std::mutex mutex_; + + static constexpr int MEDIA_FRAME_PROVIDER_SIZE = 32; ///< Size of the media frame provider +}; + +} // namespace holoscan::advanced_network + +#endif /* RIVERMAX_MANAGER_SERVICE_H_ */ 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 new file mode 100644 index 0000000000..e56f87f25f --- /dev/null +++ b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_queue_configs.cpp @@ -0,0 +1,623 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023-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 "rt_threads.h" +#include "rdk/services/services.h" + +#include "rivermax_config_manager.h" +#include "rivermax_queue_configs.h" + +namespace holoscan::advanced_network { + +using namespace rivermax::dev_kit::services; + +RivermaxCommonRxQueueConfig::RivermaxCommonRxQueueConfig(const RivermaxCommonRxQueueConfig& other) + : BaseQueueConfig(other), + max_packet_size(other.max_packet_size), + max_chunk_size(other.max_chunk_size), + packets_buffers_size(other.packets_buffers_size), + gpu_direct(other.gpu_direct), + gpu_device_id(other.gpu_device_id), + lock_gpu_clocks(other.lock_gpu_clocks), + split_boundary(other.split_boundary), + print_parameters(other.print_parameters), + sleep_between_operations_us(other.sleep_between_operations_us), + allocator_type(other.allocator_type), + ext_seq_num(other.ext_seq_num), + memory_registration(other.memory_registration), + 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), + thread_settings(other.thread_settings) {} + +RivermaxCommonRxQueueConfig& RivermaxCommonRxQueueConfig::operator=( + const RivermaxCommonRxQueueConfig& other) { + if (this == &other) { return *this; } + BaseQueueConfig::operator=(other); + max_packet_size = other.max_packet_size; + max_chunk_size = other.max_chunk_size; + packets_buffers_size = other.packets_buffers_size; + gpu_direct = other.gpu_direct; + gpu_device_id = other.gpu_device_id; + lock_gpu_clocks = other.lock_gpu_clocks; + split_boundary = other.split_boundary; + print_parameters = other.print_parameters; + sleep_between_operations_us = other.sleep_between_operations_us; + allocator_type = other.allocator_type; + ext_seq_num = other.ext_seq_num; + memory_registration = other.memory_registration; + 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; + thread_settings = other.thread_settings; + return *this; +} + +RivermaxIPOReceiverQueueConfig::RivermaxIPOReceiverQueueConfig( + const RivermaxIPOReceiverQueueConfig& other) + : RivermaxCommonRxQueueConfig(other), + max_path_differential_us(other.max_path_differential_us) {} + +RivermaxIPOReceiverQueueConfig& RivermaxIPOReceiverQueueConfig::operator=( + const RivermaxIPOReceiverQueueConfig& other) { + if (this == &other) { return *this; } + RivermaxCommonRxQueueConfig::operator=(other); + max_path_differential_us = other.max_path_differential_us; + return *this; +} + +RivermaxRTPReceiverQueueConfig::RivermaxRTPReceiverQueueConfig( + const RivermaxRTPReceiverQueueConfig& other) + : RivermaxCommonRxQueueConfig(other) {} + +RivermaxRTPReceiverQueueConfig& RivermaxRTPReceiverQueueConfig::operator=( + const RivermaxRTPReceiverQueueConfig& other) { + if (this == &other) { return *this; } + RivermaxCommonRxQueueConfig::operator=(other); + return *this; +} + +RivermaxCommonTxQueueConfig::RivermaxCommonTxQueueConfig(const RivermaxCommonTxQueueConfig& other) + : gpu_direct(other.gpu_direct), + gpu_device_id(other.gpu_device_id), + lock_gpu_clocks(other.lock_gpu_clocks), + split_boundary(other.split_boundary), + local_ip(other.local_ip), + print_parameters(other.print_parameters), + sleep_between_operations(other.sleep_between_operations), + allocator_type(other.allocator_type), + memory_allocation(other.memory_allocation), + memory_registration(other.memory_registration), + send_packet_ext_info(other.send_packet_ext_info), + num_of_packets_in_chunk(other.num_of_packets_in_chunk), + stats_report_interval_ms(other.stats_report_interval_ms), + cpu_cores(other.cpu_cores), + master_core(other.master_core), + dummy_sender(other.dummy_sender), + thread_settings(other.thread_settings) {} + +RivermaxCommonTxQueueConfig& RivermaxCommonTxQueueConfig::operator=( + const RivermaxCommonTxQueueConfig& other) { + if (this == &other) { return *this; } + gpu_direct = other.gpu_direct; + gpu_device_id = other.gpu_device_id; + lock_gpu_clocks = other.lock_gpu_clocks; + split_boundary = other.split_boundary; + local_ip = other.local_ip; + print_parameters = other.print_parameters; + sleep_between_operations = other.sleep_between_operations; + allocator_type = other.allocator_type; + memory_allocation = other.memory_allocation; + memory_registration = other.memory_registration; + send_packet_ext_info = other.send_packet_ext_info; + num_of_packets_in_chunk = other.num_of_packets_in_chunk; + stats_report_interval_ms = other.stats_report_interval_ms; + cpu_cores = other.cpu_cores; + master_core = other.master_core; + dummy_sender = other.dummy_sender; + thread_settings = other.thread_settings; + return *this; +} + +RivermaxMediaSenderQueueConfig::RivermaxMediaSenderQueueConfig( + const RivermaxMediaSenderQueueConfig& other) + : RivermaxCommonTxQueueConfig(other), + video_format(other.video_format), + bit_depth(other.bit_depth), + frame_width(other.frame_width), + frame_height(other.frame_height), + frame_rate(other.frame_rate), + use_internal_memory_pool(other.use_internal_memory_pool), + memory_pool_location((other.memory_pool_location)) {} + +RivermaxMediaSenderQueueConfig& RivermaxMediaSenderQueueConfig::operator=( + const RivermaxMediaSenderQueueConfig& other) { + if (this == &other) { return *this; } + RivermaxCommonTxQueueConfig::operator=(other); + video_format = other.video_format; + bit_depth = other.bit_depth; + frame_width = other.frame_width; + frame_height = other.frame_height; + frame_rate = other.frame_rate; + use_internal_memory_pool = other.use_internal_memory_pool; + memory_pool_location = other.memory_pool_location; + return *this; +} + +void RivermaxRTPReceiverQueueConfig::dump_parameters() const { + if (this->print_parameters) { + HOLOSCAN_LOG_INFO("Rivermax RX Queue Config:"); + HOLOSCAN_LOG_INFO("\tNetwork settings:"); + for (const auto& thread : thread_settings) { + HOLOSCAN_LOG_INFO("\t\tthread_id: {}", thread.thread_id); + for (const auto& stream : thread.stream_network_settings) { + HOLOSCAN_LOG_INFO("\t\t\tstream_id: {}", stream.stream_id); + HOLOSCAN_LOG_INFO("\t\t\t\tlocal_ip: {}", stream.local_ip); + HOLOSCAN_LOG_INFO("\t\t\t\tsource_ip: {}", stream.source_ip); + HOLOSCAN_LOG_INFO("\t\t\t\tdestination_ip: {}", stream.destination_ip); + HOLOSCAN_LOG_INFO("\t\t\t\tdestination_port: {}", stream.destination_port); + } + } + HOLOSCAN_LOG_INFO("\tGPU settings:"); + HOLOSCAN_LOG_INFO("\t\tGPU ID: {}", gpu_device_id); + HOLOSCAN_LOG_INFO("\t\tGPU Direct: {}", gpu_direct); + HOLOSCAN_LOG_INFO("\t\tlock_gpu_clocks: {}", lock_gpu_clocks); + HOLOSCAN_LOG_INFO("\tMemory config settings:"); + HOLOSCAN_LOG_INFO("\t\tallocator_type: {}", allocator_type); + HOLOSCAN_LOG_INFO("\t\tmemory_registration: {}", memory_registration); + HOLOSCAN_LOG_INFO("\tPacket settings:"); + HOLOSCAN_LOG_INFO("\t\tbatch_size/max_chunk_size: {}", max_chunk_size); + HOLOSCAN_LOG_INFO("\t\tsplit_boundary/header_size: {}", split_boundary); + HOLOSCAN_LOG_INFO("\t\tmax_packet_size: {}", max_packet_size); + HOLOSCAN_LOG_INFO("\t\tpackets_buffers_size: {}", packets_buffers_size); + HOLOSCAN_LOG_INFO("\tRMAX RTP settings:"); + HOLOSCAN_LOG_INFO("\t\text_seq_num: {}", ext_seq_num); + HOLOSCAN_LOG_INFO("\t\tsleep_between_operations_us: {}", sleep_between_operations_us); + HOLOSCAN_LOG_INFO("\t\tsend_packet_ext_info: {}", send_packet_ext_info); + HOLOSCAN_LOG_INFO("\t\tstats_report_interval_ms: {}", stats_report_interval_ms); + } +} + +void RivermaxIPOReceiverQueueConfig::dump_parameters() const { + if (this->print_parameters) { + HOLOSCAN_LOG_INFO("Rivermax RX Queue Config:"); + HOLOSCAN_LOG_INFO("\tNetwork settings:"); + for (const auto& thread : thread_settings) { + HOLOSCAN_LOG_INFO("\t\tthread_id: {}", thread.thread_id); + for (const auto& stream : thread.stream_network_settings) { + HOLOSCAN_LOG_INFO("\t\t\tstream_id: {}", stream.stream_id); + HOLOSCAN_LOG_INFO("\t\t\t\tlocal_ips: {}", stream.local_ips); + HOLOSCAN_LOG_INFO("\t\t\t\tsource_ips: {}", stream.source_ips); + HOLOSCAN_LOG_INFO("\t\t\t\tdestination_ips: {}", stream.destination_ips); + HOLOSCAN_LOG_INFO("\t\t\t\tdestination_ports: {}", stream.destination_ports); + } + } + HOLOSCAN_LOG_INFO("\tGPU settings:"); + HOLOSCAN_LOG_INFO("\t\tGPU ID: {}", gpu_device_id); + HOLOSCAN_LOG_INFO("\t\tGPU Direct: {}", gpu_direct); + HOLOSCAN_LOG_INFO("\t\tlock_gpu_clocks: {}", lock_gpu_clocks); + HOLOSCAN_LOG_INFO("\tMemory config settings:"); + HOLOSCAN_LOG_INFO("\t\tallocator_type: {}", allocator_type); + HOLOSCAN_LOG_INFO("\t\tmemory_registration: {}", memory_registration); + HOLOSCAN_LOG_INFO("\tPacket settings:"); + HOLOSCAN_LOG_INFO("\t\tbatch_size/max_chunk_size: {}", max_chunk_size); + HOLOSCAN_LOG_INFO("\t\tsplit_boundary/header_size: {}", split_boundary); + HOLOSCAN_LOG_INFO("\t\tmax_packet_size: {}", max_packet_size); + HOLOSCAN_LOG_INFO("\t\tpackets_buffers_size: {}", packets_buffers_size); + HOLOSCAN_LOG_INFO("\tRMAX IPO settings:"); + HOLOSCAN_LOG_INFO("\t\text_seq_num: {}", ext_seq_num); + HOLOSCAN_LOG_INFO("\t\tsleep_between_operations_us: {}", sleep_between_operations_us); + HOLOSCAN_LOG_INFO("\t\tmax_path_differential_us: {}", max_path_differential_us); + HOLOSCAN_LOG_INFO("\t\tsend_packet_ext_info: {}", send_packet_ext_info); + HOLOSCAN_LOG_INFO("\t\tstats_report_interval_ms: {}", stats_report_interval_ms); + } +} + +void RivermaxMediaSenderQueueConfig::dump_parameters() const { + if (this->print_parameters) { + HOLOSCAN_LOG_INFO("Rivermax TX Queue Config:"); + HOLOSCAN_LOG_INFO("\tNetwork settings:"); + for (const auto& thread : thread_settings) { + HOLOSCAN_LOG_INFO("\t thread_id: {}", thread.thread_id); + for (const auto& stream : thread.stream_network_settings) { + HOLOSCAN_LOG_INFO("\t stream_id: {}", stream.stream_id); + HOLOSCAN_LOG_INFO("\t local_ip: {}", stream.local_ip); + HOLOSCAN_LOG_INFO("\t destination_ip: {}", stream.destination_ip); + HOLOSCAN_LOG_INFO("\t destination_port: {}", stream.destination_port); + } + } + HOLOSCAN_LOG_INFO("\tGPU settings:"); + HOLOSCAN_LOG_INFO("\t GPU ID: {}", gpu_device_id); + HOLOSCAN_LOG_INFO("\t GPU Direct: {}", gpu_direct); + HOLOSCAN_LOG_INFO("\t lock_gpu_clocks: {}", lock_gpu_clocks); + HOLOSCAN_LOG_INFO("\tMemory config settings:"); + HOLOSCAN_LOG_INFO("\t allocator_type: {}", allocator_type); + HOLOSCAN_LOG_INFO("\t memory_registration: {}", memory_registration); + HOLOSCAN_LOG_INFO("\t memory_allocation: {}", memory_allocation); + HOLOSCAN_LOG_INFO("\t use_internal_memory_pool: {}", use_internal_memory_pool); + if (use_internal_memory_pool) { + HOLOSCAN_LOG_INFO("\t memory_pool_location: {}", (int)memory_pool_location); + } + HOLOSCAN_LOG_INFO("\tPacket settings:"); + HOLOSCAN_LOG_INFO("\t split_boundary: {}", split_boundary); + HOLOSCAN_LOG_INFO("\t num_of_packets_in_chunk: {}", num_of_packets_in_chunk); + HOLOSCAN_LOG_INFO("\tSender settings:"); + HOLOSCAN_LOG_INFO("\t dummy_sender: {}", dummy_sender); + HOLOSCAN_LOG_INFO("\t send_packet_ext_info: {}", send_packet_ext_info); + HOLOSCAN_LOG_INFO("\t sleep_between_operations: {}", sleep_between_operations); + HOLOSCAN_LOG_INFO("\t stats_report_interval_ms: {}", stats_report_interval_ms); + HOLOSCAN_LOG_INFO("\tVideo settings:"); + HOLOSCAN_LOG_INFO("\t video_format: {}", video_format); + HOLOSCAN_LOG_INFO("\t bit_depth: {}", bit_depth); + HOLOSCAN_LOG_INFO("\t frame_width: {}", frame_width); + HOLOSCAN_LOG_INFO("\t frame_height: {}", frame_height); + HOLOSCAN_LOG_INFO("\t frame_rate: {}", frame_rate); + } +} + +ReturnStatus RivermaxCommonRxQueueValidator::validate( + const std::shared_ptr& settings) const { + ReturnStatus rc = ValidatorUtils::validate_core(settings->master_core); + if (rc != ReturnStatus::success) { return rc; } + int cpu_cores = ConfigManagerUtilities::validate_cores(settings->cpu_cores); + if (cpu_cores < 0 || cpu_cores != settings->thread_settings.size()) { + HOLOSCAN_LOG_ERROR("Number of CPU cores must match number of threads"); + return ReturnStatus::failure; + } + + if (settings->split_boundary == 0 && settings->memory_registration) { + HOLOSCAN_LOG_ERROR("Memory registration is supported only in header-data split mode"); + return ReturnStatus::failure; + } + if (settings->split_boundary == 0 && settings->gpu_direct) { + HOLOSCAN_LOG_ERROR("GPU Direct is supported only in header-data split mode"); + return ReturnStatus::failure; + } + + if(settings->sleep_between_operations_us < 0) { + HOLOSCAN_LOG_ERROR("Sleep between operations must be non-negative"); + return ReturnStatus::failure; + } + + return ReturnStatus::success; +} + +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; } + + int thread_id = 0; + int stream_id = 0; + for (const auto& thread : settings->thread_settings) { + if (thread.thread_id != thread_id) { + HOLOSCAN_LOG_ERROR("Thread ID must be sequential starting from 0"); + return ReturnStatus::failure; + } + if (thread.stream_network_settings.empty()) { + HOLOSCAN_LOG_ERROR("No stream network settings provided for thread ID {}", thread.thread_id); + return ReturnStatus::failure; + } + thread_id++; + for (const auto& stream : thread.stream_network_settings) { + // if (stream.stream_id != stream_id) { + // HOLOSCAN_LOG_ERROR("Stream ID must be sequential starting from 0"); + // return ReturnStatus::failure; + // } + stream_id++; + if (stream.source_ips.empty()) { + HOLOSCAN_LOG_ERROR( + "Source IPs are not set for stream in thread ID {}", thread.thread_id); + return ReturnStatus::failure; + } + rc = ValidatorUtils::validate_ip4_address(stream.source_ips); + if (rc != ReturnStatus::success) { return rc; } + if (stream.destination_ips.size() != stream.source_ips.size()) { + HOLOSCAN_LOG_ERROR( + "Must be the same number of destination multicast IPs as number of source IPs " + "for stream in thread ID {}", + thread.thread_id); + return ReturnStatus::failure; + } + rc = ValidatorUtils::validate_ip4_address(stream.destination_ips); + if (rc != ReturnStatus::success) { return rc; } + if (stream.local_ips.size() != stream.source_ips.size()) { + HOLOSCAN_LOG_ERROR( + "Must be the same number of NIC addresses as number of source IPs for stream in " + "thread ID {}", + thread.thread_id); + return ReturnStatus::failure; + } + rc = ValidatorUtils::validate_ip4_address(stream.local_ips); + if (rc != ReturnStatus::success) { return rc; } + if (stream.destination_ports.size() != stream.source_ips.size()) { + HOLOSCAN_LOG_ERROR( + "Must be the same number of destination ports as number of source IPs for stream in " + "thread ID {}", + thread.thread_id); + return ReturnStatus::failure; + } + rc = ValidatorUtils::validate_ip4_port(stream.destination_ports); + if (rc != ReturnStatus::success) { return rc; } + } + } + + return ReturnStatus::success; +} + +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; } + + int thread_id = 0; + int stream_id = 0; + for (const auto& thread : settings->thread_settings) { + if (thread.thread_id != thread_id) { + HOLOSCAN_LOG_ERROR("Thread ID must be sequential starting from 0"); + return ReturnStatus::failure; + } + thread_id++; + if (thread.stream_network_settings.empty()) { + HOLOSCAN_LOG_ERROR("No stream network settings provided for thread ID {}", thread.thread_id); + return ReturnStatus::failure; + } + for (const auto& stream : thread.stream_network_settings) { + if (stream.stream_id != stream_id) { + HOLOSCAN_LOG_ERROR("Stream ID must be sequential starting from 0"); + return ReturnStatus::failure; + } + stream_id++; + rc = ValidatorUtils::validate_ip4_address(stream.source_ip); + if (rc != ReturnStatus::success) { return rc; } + rc = ValidatorUtils::validate_ip4_address(stream.destination_ip); + if (rc != ReturnStatus::success) { return rc; } + rc = ValidatorUtils::validate_ip4_address(stream.local_ip); + if (rc != ReturnStatus::success) { return rc; } + rc = ValidatorUtils::validate_ip4_port(stream.destination_port); + 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; } + int thread_id = 0; + int stream_id = 0; + for (const auto& thread : settings->thread_settings) { + thread_id++; + if (thread.stream_network_settings.empty()) { + HOLOSCAN_LOG_ERROR("No stream network settings provided for thread ID {}", thread.thread_id); + return ReturnStatus::failure; + } + for (const auto& stream : thread.stream_network_settings) { + // if (stream.stream_id != stream_id) { + // HOLOSCAN_LOG_ERROR("Stream ID must be sequential starting from 0"); + // return ReturnStatus::failure; + // } + // stream_id++; + rc = ValidatorUtils::validate_ip4_address(stream.destination_ip); + if (rc != ReturnStatus::success) { return rc; } + rc = ValidatorUtils::validate_ip4_address(stream.local_ip); + if (rc != ReturnStatus::success) { return rc; } + rc = ValidatorUtils::validate_ip4_port(stream.destination_port); + 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"); + return ReturnStatus::failure; + } + if (settings->gpu_direct && settings->split_boundary == 0) { + HOLOSCAN_LOG_ERROR("GPU Direct is supported only in header-data split mode"); + return ReturnStatus::failure; + } + + return ReturnStatus::success; +} + +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; } + + return ReturnStatus::success; +} + +ReturnStatus RivermaxQueueToIPOReceiverSettingsBuilder::convert_settings( + const std::shared_ptr& source_settings, + std::shared_ptr& target_settings) { + target_settings->thread_settings = source_settings->thread_settings; + target_settings->num_of_threads = target_settings->thread_settings.size(); + + if (source_settings->gpu_direct) { + target_settings->gpu_id = source_settings->gpu_device_id; + } else { + target_settings->gpu_id = INVALID_GPU_ID; + } + + ConfigManagerUtilities::set_allocator_type(*target_settings, source_settings->allocator_type); + if (source_settings->master_core < 0) { + target_settings->internal_thread_core = CPU_NONE; + target_settings->lock_gpu_clocks = source_settings->lock_gpu_clocks; + } else { + target_settings->internal_thread_core = source_settings->master_core; + } + bool res = ConfigManagerUtilities::parse_and_set_cores(target_settings->app_threads_cores, + source_settings->cpu_cores); + if (!res) { + HOLOSCAN_LOG_ERROR("Failed to parse CPU cores"); + return ReturnStatus::failure; + } + + target_settings->print_parameters = source_settings->print_parameters; + 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->num_of_packets_in_chunk = + std::pow(2, std::ceil(std::log2(source_settings->packets_buffers_size))); + target_settings->is_extended_sequence_number = source_settings->ext_seq_num; + target_settings->max_path_differential_us = source_settings->max_path_differential_us; + if (target_settings->max_path_differential_us >= USECS_IN_SECOND) { + HOLOSCAN_LOG_ERROR("Max path differential must be less than 1 second"); + target_settings->max_path_differential_us = USECS_IN_SECOND; + } + + target_settings->stats_report_interval_ms = source_settings->stats_report_interval_ms; + target_settings->register_memory = source_settings->memory_registration; + target_settings->max_packets_in_rx_chunk = source_settings->max_chunk_size; + + send_packet_ext_info_ = source_settings->send_packet_ext_info; + settings_built_ = true; + built_settings_ = *target_settings; + + return ReturnStatus::success; +} + +ReturnStatus RivermaxQueueToRTPReceiverSettingsBuilder::convert_settings( + const std::shared_ptr& source_settings, + std::shared_ptr& target_settings) { + target_settings->thread_settings = source_settings->thread_settings; + target_settings->num_of_threads = target_settings->thread_settings.size(); + + if (source_settings->gpu_direct) { + target_settings->gpu_id = source_settings->gpu_device_id; + } else { + target_settings->gpu_id = INVALID_GPU_ID; + } + + ConfigManagerUtilities::set_allocator_type(*target_settings, source_settings->allocator_type); + if (source_settings->master_core < 0) { + target_settings->internal_thread_core = CPU_NONE; + target_settings->lock_gpu_clocks = source_settings->lock_gpu_clocks; + } else { + target_settings->internal_thread_core = source_settings->master_core; + } + bool res = ConfigManagerUtilities::parse_and_set_cores(target_settings->app_threads_cores, + source_settings->cpu_cores); + if (!res) { + HOLOSCAN_LOG_ERROR("Failed to parse CPU cores"); + return ReturnStatus::failure; + } + + target_settings->print_parameters = source_settings->print_parameters; + 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->num_of_packets_in_chunk = + std::pow(2, std::ceil(std::log2(source_settings->packets_buffers_size))); + target_settings->is_extended_sequence_number = source_settings->ext_seq_num; + + target_settings->stats_report_interval_ms = source_settings->stats_report_interval_ms; + target_settings->register_memory = source_settings->memory_registration; + max_chunk_size_ = source_settings->max_chunk_size; + send_packet_ext_info_ = source_settings->send_packet_ext_info; + + settings_built_ = true; + built_settings_ = *target_settings; + + return ReturnStatus::success; +} + +ReturnStatus RivermaxQueueToMediaSenderSettingsBuilder::convert_settings( + const std::shared_ptr& source_settings, + std::shared_ptr& target_settings) { + target_settings->thread_settings = source_settings->thread_settings; + target_settings->num_of_threads = target_settings->thread_settings.size(); + // Same local IP for all streams of the first thread + // Next - This will be taken from the adv_network settings `address` + target_settings->local_ip = source_settings->thread_settings[0].stream_network_settings[0].local_ip; + + if (source_settings->gpu_direct) { + target_settings->gpu_id = source_settings->gpu_device_id; + target_settings->lock_gpu_clocks = source_settings->lock_gpu_clocks; + } else { + target_settings->gpu_id = INVALID_GPU_ID; + } + + ConfigManagerUtilities::set_allocator_type(*target_settings, source_settings->allocator_type); + if (source_settings->master_core < 0) { + target_settings->internal_thread_core = CPU_NONE; + } else { + target_settings->internal_thread_core = source_settings->master_core; + } + bool res = ConfigManagerUtilities::parse_and_set_cores(target_settings->app_threads_cores, + source_settings->cpu_cores); + if (!res) { + HOLOSCAN_LOG_ERROR("Failed to parse CPU cores"); + return ReturnStatus::failure; + } + + 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->stats_report_interval_ms = source_settings->stats_report_interval_ms; + target_settings->register_memory = source_settings->memory_registration; + target_settings->app_memory_alloc = source_settings->memory_allocation; + target_settings->num_of_packets_in_chunk = source_settings->num_of_packets_in_chunk; + + target_settings->media.resolution.height = source_settings->frame_height; + target_settings->media.resolution.width = source_settings->frame_width; + target_settings->media.frame_rate = FrameRate(source_settings->frame_rate); + target_settings->media.bit_depth = + ConfigManagerUtilities::convert_bit_depth(source_settings->bit_depth); + target_settings->media.sampling_type = + ConfigManagerUtilities::convert_video_sampling(source_settings->video_format); + + dummy_sender_ = source_settings->dummy_sender; + settings_built_ = true; + built_settings_ = *target_settings; + + return ReturnStatus::success; +} + +std::string queue_config_type_to_string(QueueConfigType type) { + static const std::map queueTypeMap = { + {QueueConfigType::IPOReceiver, "IPOReceiver"}, + {QueueConfigType::RTPReceiver, "RTPReceiver"}, + {QueueConfigType::MediaFrameSender, "MediaFrameSender"}, + {QueueConfigType::GenericPacketSender, "GenericPacketSender"}}; + + auto it = queueTypeMap.find(type); + if (it != queueTypeMap.end()) { + return it->second; + } else { + return "Unknown"; + } +} + +} // namespace holoscan::advanced_network 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 new file mode 100644 index 0000000000..3d34fdad0f --- /dev/null +++ b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_mgr_impl/rivermax_queue_configs.h @@ -0,0 +1,301 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023-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 RIVERMAX_QUEUE_CONFIGS_H_ +#define RIVERMAX_QUEUE_CONFIGS_H_ + +#include +#include +#include +#include + +#include "rdk/rivermax_dev_kit.h" + +#include "advanced_network/manager.h" +#include "rivermax_ano_data_types.h" +#include "ano_ipo_receiver.h" +#include "ano_rtp_receiver.h" +#include "ano_media_sender.h" + +namespace holoscan::advanced_network { + +enum class QueueConfigType { IPOReceiver, RTPReceiver, MediaFrameSender, GenericPacketSender }; + +/** @brief Converts QueueConfigType to string. + * + * This function converts the QueueConfigType enum value to its corresponding + * string representation. + * + * @param type The QueueConfigType enum value to convert. + * @return The string representation of the QueueConfigType. + */ +std::string queue_config_type_to_string(QueueConfigType type); + +/** @brief Base class for additional queue configuration. + * + * This class serves as a base for all additional queue configurations. + */ +class BaseQueueConfig : public ManagerExtraQueueConfig { + public: + virtual ~BaseQueueConfig() = default; + /** + * @brief Dumps the additional queue configuration. + * + * This function must be implemented by derived classes to dump the additional + * queue configuration to the log. + */ + virtual void dump_parameters() const {} + /** + * @brief Returns the type of the queue configuration. + * + * This function must be implemented by derived classes to return the type of + * the queue configuration. + * + * @return The type of the queue configuration. + */ + virtual QueueConfigType get_type() const = 0; +}; + +/** + * @brief Configuration structure for Rivermax RX queue. + * + * This structure holds the configuration settings for an Rivermax RX queue, + * including packet size, chunk size, IP addresses, ports, and other parameters. + */ +struct RivermaxCommonRxQueueConfig : public BaseQueueConfig { + public: + RivermaxCommonRxQueueConfig() = default; + virtual ~RivermaxCommonRxQueueConfig() = default; + + RivermaxCommonRxQueueConfig(const RivermaxCommonRxQueueConfig& other); + RivermaxCommonRxQueueConfig& operator=(const RivermaxCommonRxQueueConfig& other); + + public: + uint16_t max_packet_size = 0; + size_t max_chunk_size; + size_t packets_buffers_size; + bool gpu_direct; + int gpu_device_id; + bool lock_gpu_clocks; + uint16_t split_boundary; + bool print_parameters; + int sleep_between_operations_us; + std::string allocator_type; + bool ext_seq_num; + bool memory_registration; + bool send_packet_ext_info; + uint32_t stats_report_interval_ms; + std::string cpu_cores; + int master_core; + std::vector thread_settings; +}; + +/** + * @brief Configuration structure for Rivermax IPO receiver queue. + * + * This structure holds the configuration settings for an Rivermax IPO receiver queue, + * including packet size, chunk size, IP addresses, ports, and other parameters. + */ +struct RivermaxIPOReceiverQueueConfig : public RivermaxCommonRxQueueConfig { + public: + RivermaxIPOReceiverQueueConfig() = default; + virtual ~RivermaxIPOReceiverQueueConfig() = default; + + RivermaxIPOReceiverQueueConfig(const RivermaxIPOReceiverQueueConfig& other); + RivermaxIPOReceiverQueueConfig& operator=(const RivermaxIPOReceiverQueueConfig& other); + + QueueConfigType get_type() const override { return QueueConfigType::IPOReceiver; } + void dump_parameters() const override; + + public: + uint32_t max_path_differential_us; +}; + +/** + * @brief Configuration structure for Rivermax RTP receiver queue. + * + * This structure holds the configuration settings for an Rivermax RTP receiver queue, + * including packet size, chunk size, IP addresses, ports, and other parameters. + */ +struct RivermaxRTPReceiverQueueConfig : public RivermaxCommonRxQueueConfig { + public: + RivermaxRTPReceiverQueueConfig() = default; + virtual ~RivermaxRTPReceiverQueueConfig() = default; + + RivermaxRTPReceiverQueueConfig(const RivermaxRTPReceiverQueueConfig& other); + RivermaxRTPReceiverQueueConfig& operator=(const RivermaxRTPReceiverQueueConfig& other); + + QueueConfigType get_type() const override { return QueueConfigType::RTPReceiver; } + void dump_parameters() const override; +}; + +/** + * @brief Configuration structure for Rivermax TX queue. + * + * This structure holds the configuration settings for an Rivermax TX queue, + * including packet size, chunk size, IP addresses, ports, and other parameters. + */ +struct RivermaxCommonTxQueueConfig : public BaseQueueConfig { + public: + RivermaxCommonTxQueueConfig() = default; + virtual ~RivermaxCommonTxQueueConfig() = default; + + RivermaxCommonTxQueueConfig(const RivermaxCommonTxQueueConfig& other); + RivermaxCommonTxQueueConfig& operator=(const RivermaxCommonTxQueueConfig& other); + + public: + bool gpu_direct; + int gpu_device_id; + bool lock_gpu_clocks; + uint16_t split_boundary; + std::string local_ip; + bool print_parameters; + bool sleep_between_operations; + std::string allocator_type; + bool memory_allocation; + bool memory_registration; + bool send_packet_ext_info; + size_t num_of_packets_in_chunk; + uint32_t stats_report_interval_ms; + std::string cpu_cores; + int master_core; + bool dummy_sender; + std::vector thread_settings; +}; + +struct RivermaxMediaSenderQueueConfig : public RivermaxCommonTxQueueConfig { + public: + RivermaxMediaSenderQueueConfig() = default; + virtual ~RivermaxMediaSenderQueueConfig() = default; + + RivermaxMediaSenderQueueConfig(const RivermaxMediaSenderQueueConfig& other); + RivermaxMediaSenderQueueConfig& operator=(const RivermaxMediaSenderQueueConfig& other); + + QueueConfigType get_type() const override { return QueueConfigType::MediaFrameSender; } + void dump_parameters() const override; + + public: + std::string video_format; + uint16_t bit_depth; + uint16_t frame_width; + uint16_t frame_height; + uint16_t frame_rate; + bool use_internal_memory_pool; + MemoryKind memory_pool_location; +}; + +class RivermaxCommonRxQueueValidator : public ISettingsValidator { + public: + ReturnStatus validate( + const std::shared_ptr& settings) const override; +}; + +class RivermaxIPOReceiverQueueValidator + : public ISettingsValidator { + public: + ReturnStatus validate( + const std::shared_ptr& settings) const override; +}; + +class RivermaxRTPReceiverQueueValidator + : public ISettingsValidator { + public: + ReturnStatus validate( + const std::shared_ptr& settings) const override; +}; + +class RivermaxCommonTxQueueValidator : public ISettingsValidator { + public: + ReturnStatus validate( + const std::shared_ptr& settings) const override; +}; + +class RivermaxMediaSenderQueueValidator + : public ISettingsValidator { + public: + ReturnStatus validate( + const std::shared_ptr& settings) const override; +}; + +class RivermaxQueueToIPOReceiverSettingsBuilder + : public ConversionSettingsBuilder { + public: + RivermaxQueueToIPOReceiverSettingsBuilder( + std::shared_ptr source_settings, + std::shared_ptr> validator) + : ConversionSettingsBuilder( + source_settings, validator) {} + + protected: + ReturnStatus convert_settings( + const std::shared_ptr& source_settings, + std::shared_ptr& target_settings) override; + + public: + static constexpr int USECS_IN_SECOND = 1000000; + bool send_packet_ext_info_ = false; + ANOIPOReceiverSettings built_settings_; + bool settings_built_ = false; +}; + +class RivermaxQueueToRTPReceiverSettingsBuilder + : public ConversionSettingsBuilder { + public: + RivermaxQueueToRTPReceiverSettingsBuilder( + std::shared_ptr source_settings, + std::shared_ptr> validator) + : ConversionSettingsBuilder( + source_settings, validator) {} + + protected: + ReturnStatus convert_settings( + const std::shared_ptr& source_settings, + std::shared_ptr& target_settings) override; + + public: + static constexpr int USECS_IN_SECOND = 1000000; + bool send_packet_ext_info_ = false; + size_t max_chunk_size_ = 0; + ANORTPReceiverSettings built_settings_; + bool settings_built_ = false; +}; + +class RivermaxQueueToMediaSenderSettingsBuilder + : public ConversionSettingsBuilder { + public: + RivermaxQueueToMediaSenderSettingsBuilder( + std::shared_ptr source_settings, + std::shared_ptr> validator) + : ConversionSettingsBuilder( + source_settings, validator) {} + + protected: + ReturnStatus convert_settings( + const std::shared_ptr& source_settings, + std::shared_ptr& target_settings) override; + + public: + bool dummy_sender_ = false; + bool use_internal_memory_pool_ = false; + MemoryKind memory_pool_location_ = MemoryKind::DEVICE; + ANOMediaSenderSettings built_settings_; + bool settings_built_ = false; +}; + +} // namespace holoscan::advanced_network + +#endif // RIVERMAX_QUEUE_CONFIGS_H_ diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rivermax_service/CMakeLists.txt b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_service/CMakeLists.txt new file mode 100644 index 0000000000..1af5d2e5c3 --- /dev/null +++ b/operators/advanced_network/advanced_network/managers/rivermax/rivermax_service/CMakeLists.txt @@ -0,0 +1,88 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023-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(rivermax_service) + +add_library(${PROJECT_NAME} INTERFACE) + +target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_17) + +set(RMAX_CUDA TRUE) + +# Check if the environment variable RMAX_TEGRA is set +if(NOT DEFINED ENV{RMAX_TEGRA}) + message(STATUS "Environment variable RMAX_TEGRA was not defined. Defaulting to FALSE") + set(RMAX_TEGRA FALSE) +else() + # Convert the environment variable to CMake boolean TRUE or FALSE + if("$ENV{RMAX_TEGRA}" STREQUAL "TRUE" OR "$ENV{RMAX_TEGRA}" STREQUAL "ON") + set(RMAX_TEGRA TRUE) + else() + set(RMAX_TEGRA FALSE) + endif() +endif() + +unset(RMAX_TEGRA CACHE) +message(STATUS "RMAX_TEGRA is set to: ${RMAX_TEGRA}") + +if(RMAX_TEGRA) + target_compile_definitions(${PROJECT_NAME} INTERFACE RMAX_TEGRA=1) +else() + target_compile_definitions(${PROJECT_NAME} INTERFACE RMAX_TEGRA=0) +endif() + +# Clear any previously cached value +unset(RIVERMAX_DEV_KIT_REPO_URL CACHE) + +# Configure FetchContent for Rivermax library +include(FetchContent) + +# Force position-independent code for all targets to avoid linker issues +set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) + +# For fine-grained tokens, use the token directly in the URL +set(RIVERMAX_DEV_KIT_REPO_URL "https://github.com/NVIDIA/rivermax-dev-kit.git" CACHE STRING "Repository URL" FORCE) + +# Clear and set with FORCE to prevent overrides +unset(RIVERMAX_DEV_KIT_TAG CACHE) +set(RIVERMAX_DEV_KIT_TAG "ef340f769869d75080f63f91a0a1d7e36ccc4688" CACHE STRING "Git tag, branch or branch hash to use for rivermax-dev-kit" FORCE) +message(STATUS "Using Rivermax Hash: ${RIVERMAX_DEV_KIT_TAG}") + +FetchContent_Declare( + rivermax-dev-kit + GIT_REPOSITORY ${RIVERMAX_DEV_KIT_REPO_URL} + GIT_TAG ${RIVERMAX_DEV_KIT_TAG} + GIT_PROGRESS TRUE +) + +FetchContent_MakeAvailable(rivermax-dev-kit) + +# Will be removed once RDK makes the change +file(READ "${rivermax-dev-kit_SOURCE_DIR}/source/apps/include/rdk/apps/rmax_base_app.h" CONTENTS) +if(NOT CONTENTS MATCHES "virtual ReturnStatus find_internal_stream_index\\(") + string(REPLACE "ReturnStatus find_internal_stream_index(" "virtual ReturnStatus find_internal_stream_index(" NEW_CONTENTS "${CONTENTS}") + file(WRITE "${rivermax-dev-kit_SOURCE_DIR}/source/apps/include/rdk/apps/rmax_base_app.h" "${NEW_CONTENTS}") +endif() +if (NOT CONTENTS MATCHES "virtual ReturnStatus get_app_settings\\(") + string(REPLACE "ReturnStatus get_app_settings(" "virtual ReturnStatus get_app_settings(" NEW_CONTENTS2 "${NEW_CONTENTS}") + file(WRITE "${rivermax-dev-kit_SOURCE_DIR}/source/apps/include/rdk/apps/rmax_base_app.h" "${NEW_CONTENTS2}") +endif() + +# Link external RDK libraries through INTERFACE +target_link_libraries(${PROJECT_NAME} INTERFACE + rivermax-dev-kit-app-base + rivermax-dev-kit +) diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/adv_network_rmax_mgr.cpp b/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/adv_network_rmax_mgr.cpp deleted file mode 100644 index 75c4c4304a..0000000000 --- a/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/adv_network_rmax_mgr.cpp +++ /dev/null @@ -1,1106 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2023 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 -#include -#include -#include -#include -#include - -#include "adv_network_rmax_mgr.h" -#include "rmax_mgr_impl/rmax_config_manager.h" -#include "rmax_mgr_impl/burst_manager.h" -#include "rmax_mgr_impl/packet_processor.h" -#include "rmax_mgr_impl/rmax_chunk_consumer_ano.h" -#include "rmax_mgr_impl/stats_printer.h" -#include "rmax_ipo_receiver_service.h" -#include -#include "rt_threads.h" - -using namespace std::chrono; - -namespace holoscan::advanced_network { - -using namespace ral::services::rmax_ipo_receiver; - -/** - * A map of log level to a tuple of the description and command strings. - */ -const std::unordered_map> - RmaxLogLevel::level_to_cmd_map = { - {TRACE, {"Trace", "0"}}, - {DEBUG, {"Debug", "1"}}, - {INFO, {"Info", "2"}}, - {WARN, {"Warning", "3"}}, - {ERROR, {"Error", "4"}}, - {CRITICAL, {"Critical", "5"}}, - {OFF, {"Disabled", "6"}}, -}; - -/** - * A map of advanced_network log level to Rmax log level. - */ -const std::unordered_map - RmaxLogLevel::adv_net_to_rmax_log_level_map = { - {LogLevel::TRACE, TRACE}, - {LogLevel::DEBUG, DEBUG}, - {LogLevel::INFO, INFO}, - {LogLevel::WARN, WARN}, - {LogLevel::ERROR, ERROR}, - {LogLevel::CRITICAL, CRITICAL}, - {LogLevel::OFF, OFF}, -}; - -/** - * @brief Implementation class for RmaxMgr. - * - * This class contains the implementation details for RmaxMgr, including - * methods for configuration, initialization, packet handling, and statistics. - */ -class RmaxMgr::RmaxMgrImpl { - public: - RmaxMgrImpl() = default; - ~RmaxMgrImpl(); - - bool set_config_and_initialize(const NetworkConfig& cfg); - void initialize(); - void run(); - - void* get_segment_packet_ptr(BurstParams* burst, int seg, int idx); - void* get_packet_ptr(BurstParams* burst, int idx); - uint16_t get_segment_packet_length(BurstParams* burst, int seg, int idx); - uint16_t get_packet_length(BurstParams* burst, int idx); - void* get_packet_extra_info(BurstParams* burst, int idx); - Status get_tx_packet_burst(BurstParams* burst); - Status set_eth_header(BurstParams* burst, int idx, char* dst_addr); - Status set_ipv4_header(BurstParams* burst, int idx, int ip_len, uint8_t proto, - unsigned int src_host, unsigned int dst_host); - Status set_udp_header(BurstParams* burst, int idx, int udp_len, uint16_t src_port, - uint16_t dst_port); - Status set_udp_payload(BurstParams* burst, int idx, void* data, int len); - bool is_tx_burst_available(BurstParams* burst); - - Status set_packet_lengths(BurstParams* burst, int idx, const std::initializer_list& lens); - void free_all_segment_packets(BurstParams* burst, int seg); - void free_all_packets(BurstParams* burst); - void free_packet_segment(BurstParams* burst, int seg, int pkt); - void free_packet(BurstParams* burst, int pkt); - void free_rx_burst(BurstParams* burst); - void free_tx_burst(BurstParams* burst); - void format_eth_addr(char* dst, std::string addr); - Status get_rx_burst(BurstParams** burst, int port, int q); - Status set_packet_tx_time(BurstParams* burst, int idx, uint64_t timestamp); - void free_rx_metadata(BurstParams* burst); - void free_tx_metadata(BurstParams* burst); - Status get_tx_metadata_buffer(BurstParams** burst); - Status send_tx_burst(BurstParams* burst); - void shutdown(); - void print_stats(); - uint64_t get_burst_tot_byte(BurstParams* burst); - BurstParams* create_tx_burst_params(); - Status get_mac_addr(int port, char* mac); - - private: - 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); - void initialize_rx_service(uint32_t service_id, const ExtRmaxIPOReceiverConfig& config); - - private: - static constexpr int DEFAULT_NUM_RX_BURST = 64; - - NetworkConfig cfg_; - std::unordered_map> - rx_services; - std::unordered_map> rmax_chunk_consumers; - std::unordered_map> rx_burst_managers; - std::unordered_map> rx_packet_processors; - std::unordered_map> rx_bursts_out_queues_map_; - std::vector rx_service_threads; - bool initialized_ = false; - std::shared_ptr rmax_apps_lib = nullptr; -}; - -std::atomic force_quit = false; - -/** - * @brief Sets the configuration and initializes the Rmax manager. - * - * This function sets the configuration and initializes the Rmax manager. - * It starts the initialization in a separate thread to avoid setting the - * affinity for the whole application. Once initialized, it runs the manager. - * - * @param cfg The configuration YAML. - * @return True if the initialization was successful, false otherwise. - */ -bool RmaxMgr::RmaxMgrImpl::set_config_and_initialize(const NetworkConfig& cfg) { - if (!this->initialized_) { - cfg_ = cfg; - - // Start Initialize in a separate thread so it doesn't set the affinity for the - // whole application - std::thread t(&RmaxMgr::RmaxMgrImpl::initialize, this); - t.join(); - - if (!this->initialized_) { - HOLOSCAN_LOG_ERROR("Failed to initialize Rivermax advanced_network Manager"); - return false; - } - run(); - } - - return true; -} - -/** - * @brief Initializes the Rmax manager. - * - * This function initializes the Rmax manager by setting up the RX bursts queue, - * creating the Rmax applications library facade, parsing the configuration, - * and initializing the RX services and chunk consumers. - */ -void RmaxMgr::RmaxMgrImpl::initialize() { - int port_id = 0; - for (auto& intf : cfg_.ifs_) { - intf.port_id_ = port_id++; - HOLOSCAN_LOG_INFO("{} ({}): assigned port ID {}", intf.name_, intf.address_, intf.port_id_); - } - - rmax_apps_lib = std::make_shared(); - RmaxConfigContainer config_manager(rmax_apps_lib); - - bool res = config_manager.parse_configuration(cfg_); - if (!res) { - HOLOSCAN_LOG_ERROR("Failed to parse configuration for Rivermax advanced_network Manager"); - return; - } - HOLOSCAN_LOG_INFO("Setting Rivermax Log Level to: {}", - holoscan::advanced_network::RmaxLogLevel::to_description_string( - config_manager.get_rmax_log_level())); - rivermax_setparam( - "RIVERMAX_LOG_LEVEL", - holoscan::advanced_network::RmaxLogLevel::to_cmd_string(config_manager.get_rmax_log_level()), - true); - - auto rx_config_manager = std::dynamic_pointer_cast( - config_manager.get_config_manager(RmaxConfigContainer::ConfigType::RX)); - - if (rx_config_manager) { - for (const auto& config : *rx_config_manager) { - initialize_rx_service(config.first, config.second); - } - } - - this->initialized_ = true; -} - -/** - * @brief Initializes an RX service with the given configuration. - * - * This method creates and initializes an RX service based on the provided configuration. - * It also sets up the necessary burst managers, packet processors, and chunk consumers. - * - * @param service_id The unique service id identifying the RX service. - * @param config The configuration for the RX service. - */ -void RmaxMgr::RmaxMgrImpl::initialize_rx_service(uint32_t service_id, - const ExtRmaxIPOReceiverConfig& config) { - uint16_t port_id = RmaxBurst::burst_port_id_from_burst_tag(service_id); - uint16_t queue_id = RmaxBurst::burst_queue_id_from_burst_tag(service_id); - - auto rx_service = std::make_unique(config); - auto init_status = rx_service->get_init_status(); - if (init_status != ReturnStatus::obj_init_success) { - HOLOSCAN_LOG_ERROR("Failed to initialize RX service, status: {}", (int)init_status); - return; - } - - // Create a dedicated queue for this service_id - auto queue = std::make_shared(); - rx_bursts_out_queues_map_[service_id] = queue; - - rx_services[service_id] = std::move(rx_service); - rx_burst_managers[service_id] = std::make_shared(config.send_packet_ext_info, - port_id, - queue_id, - config.max_chunk_size, - config.app_settings->gpu_id, - queue); - - rx_packet_processors[service_id] = - std::make_shared(rx_burst_managers[service_id]); - - rmax_chunk_consumers[service_id] = - std::make_unique(rx_packet_processors[service_id]); - - rx_services[service_id]->set_chunk_consumer(rmax_chunk_consumers[service_id].get()); -} - -/** - * @brief Destructor for RmaxMgrImpl. - * - * This destructor prints the statistics and joins all RX service threads - * before destroying the Rmax manager implementation. - */ -RmaxMgr::RmaxMgrImpl::~RmaxMgrImpl() { - for (auto& rx_service_thread : rx_service_threads) { - if (rx_service_thread.joinable()) { rx_service_thread.join(); } - } - for (auto& [service_id, rx_service] : rx_services) { - if (rx_service) { rx_service->set_chunk_consumer(nullptr); } - } - rx_services.clear(); - - rx_burst_managers.clear(); - - rx_packet_processors.clear(); - - rmax_chunk_consumers.clear(); - - for (auto& [service_id, rx_bursts_out_queue] : rx_bursts_out_queues_map_) { - rx_bursts_out_queue->clear(); - } - rx_bursts_out_queues_map_.clear(); -} - -class RmaxServicesSynchronizer : public IRmaxServicesSynchronizer { - public: - explicit RmaxServicesSynchronizer(std::size_t count) - : threshold(count), count(count), generation(0), ready(false), waiting_threads(0) {} - - void wait_for_start() override { - wait_at_barrier(); - wait_for_signal_to_run(); - } - - void start_all() { - std::lock_guard lock(mtx); - ready = true; - cv.notify_all(); - } - - void wait_until_all_ready_to_run() { - std::unique_lock lock(mtx); - cv.wait(lock, [this] { return waiting_threads == threshold; }); - } - - void wait_until_all_are_running() { - std::unique_lock lock(mtx); - cv.wait(lock, [this] { return waiting_threads == 0; }); - } - - private: - void wait_at_barrier() { - std::unique_lock lock(mtx); - auto gen = generation; - if (--count == 0) { - generation++; - count = threshold; - cv.notify_all(); - } else { - cv.wait(lock, [this, gen] { return gen != generation; }); - } - } - - void wait_for_signal_to_run() { - std::unique_lock lock(mtx); - waiting_threads++; - cv.notify_all(); - cv.wait(lock, [this] { return ready; }); - waiting_threads--; - if (waiting_threads == 0) { cv.notify_all(); } - } - - private: - std::mutex mtx; - std::condition_variable cv; - std::size_t threshold; - std::size_t count; - std::size_t generation; - bool ready; - std::size_t waiting_threads; -}; - -/** - * @brief Runs the Rmax manager. - * - * This function starts the RX services by iterating over the RX services map - * and launching each service in a separate thread. It ensures that each service - * is properly initialized before running. - */ -void RmaxMgr::RmaxMgrImpl::run() { - HOLOSCAN_LOG_INFO("Starting RX Services"); - - std::size_t num_services = rx_services.size(); - RmaxServicesSynchronizer services_sync(num_services); - - for (const auto& entry : rx_services) { - uint32_t key = entry.first; - auto& rx_service = entry.second; - if (rx_service->get_init_status() != ReturnStatus::obj_init_success) { - HOLOSCAN_LOG_ERROR("Rx Service failed to initialize, cannot run"); - return; - } - rx_service_threads.emplace_back([rx_service_ptr = rx_service.get(), &services_sync]() { - ReturnStatus status = rx_service_ptr->run(&services_sync); - // TODO: Handle the status if needed - }); - } - services_sync.wait_until_all_ready_to_run(); - services_sync.start_all(); - services_sync.wait_until_all_are_running(); - - HOLOSCAN_LOG_INFO("Done starting workers"); -} - -/** - * @brief Flushes packets on a specific port. - * - * @param port The port number on which to flush packets. - */ -void RmaxMgr::RmaxMgrImpl::flush_packets(int port) { - HOLOSCAN_LOG_INFO("Flushing packet on port {}", port); -} - -/** - * @brief Gets the pointer to a specific packet in a segment. - * - * @param burst The burst parameters. - * @param seg The segment index. - * @param idx The packet index within the segment. - * @return Pointer to the packet. - */ -void* RmaxMgr::RmaxMgrImpl::get_segment_packet_ptr(BurstParams* burst, int seg, int idx) { - return burst->pkts[seg][idx]; -} - -/** - * @brief Gets the pointer to a specific packet. - * - * @param burst The burst parameters. - * @param idx The packet index. - * @return Pointer to the packet. - */ -void* RmaxMgr::RmaxMgrImpl::get_packet_ptr(BurstParams* burst, int idx) { - return burst->pkts[0][idx]; -} - -/** - * @brief Gets the length of a specific packet in a segment. - * - * @param burst The burst parameters. - * @param seg The segment index. - * @param idx The packet index within the segment. - * @return Length of the packet. - */ -uint16_t RmaxMgr::RmaxMgrImpl::get_segment_packet_length(BurstParams* burst, int seg, int idx) { - return burst->pkt_lens[seg][idx]; -} - -/** - * @brief Gets the length of a specific packet. - * - * @param burst The burst parameters. - * @param idx The packet index. - * @return Length of the packet. - */ -uint16_t RmaxMgr::RmaxMgrImpl::get_packet_length(BurstParams* burst, int idx) { - return burst->pkt_lens[0][idx]; -} - -/** - * @brief Gets the extra information of a specific packet. - * - * @param burst The burst parameters. - * @param idx The packet index. - * @return Pointer to the extra information. - */ -void* RmaxMgr::RmaxMgrImpl::get_packet_extra_info(BurstParams* burst, int idx) { - RmaxBurst* rmax_burst = static_cast(burst); - if (rmax_burst->is_packet_info_per_packet()) return burst->pkt_extra_info[idx]; - return nullptr; -} - -/** - * @brief Sets the transmission time for a specific packet. - * - * @param burst The burst parameters. - * @param idx The packet index. - * @param timestamp The transmission timestamp. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::RmaxMgrImpl::set_packet_tx_time(BurstParams* burst, int idx, uint64_t timestamp) { - return Status::SUCCESS; -} - -/** - * @brief Gets a burst of TX packets. - * - * @param burst The burst parameters. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::RmaxMgrImpl::get_tx_packet_burst(BurstParams* burst) { - return Status::NO_FREE_BURST_BUFFERS; -} - -/** - * @brief Sets the Ethernet header for a specific packet. - * - * @param burst The burst parameters. - * @param idx The packet index. - * @param dst_addr The destination address. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::RmaxMgrImpl::set_eth_header(BurstParams* burst, int idx, char* dst_addr) { - return Status::SUCCESS; -} - -/** - * @brief Sets the IPv4 header for a specific packet. - * - * @param burst The burst parameters. - * @param idx The packet index. - * @param ip_len The length of the IP packet. - * @param proto The protocol. - * @param src_host The source host address. - * @param dst_host The destination host address. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::RmaxMgrImpl::set_ipv4_header(BurstParams* burst, int idx, int ip_len, uint8_t proto, - unsigned int src_host, unsigned int dst_host) { - return Status::SUCCESS; -} - -/** - * @brief Sets the UDP header for a specific packet. - * - * @param burst The burst parameters. - * @param idx The packet index. - * @param udp_len The length of the UDP packet. - * @param src_port The source port. - * @param dst_port The destination port. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::RmaxMgrImpl::set_udp_header(BurstParams* burst, int idx, int udp_len, - uint16_t src_port, uint16_t dst_port) { - return Status::SUCCESS; -} - -/** - * @brief Sets the UDP payload for a specific packet. - * - * @param burst The burst parameters. - * @param idx The packet index. - * @param data The payload data. - * @param len The length of the payload data. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::RmaxMgrImpl::set_udp_payload(BurstParams* burst, int idx, void* data, int len) { - return Status::SUCCESS; -} - -/** - * @brief Checks if a TX burst is available. - * - * @param burst The burst parameters. - * @return True if a TX burst is available, false otherwise. - */ -bool RmaxMgr::RmaxMgrImpl::is_tx_burst_available(BurstParams* burst) { - return false; -} - -/** - * @brief Sets the packet lengths for a specific packet. - * - * @param burst The burst parameters. - * @param idx The packet index. - * @param lens The list of lengths. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::RmaxMgrImpl::set_packet_lengths(BurstParams* burst, int idx, - const std::initializer_list& lens) { - return Status::SUCCESS; -} - -/** - * @brief Frees all packets in a specific segment. - * - * @param burst The burst parameters. - * @param seg The segment index. - */ -void RmaxMgr::RmaxMgrImpl::free_all_segment_packets(BurstParams* burst, int seg) {} - -/** - * @brief Frees all packets. - * - * @param burst The burst parameters. - */ -void RmaxMgr::RmaxMgrImpl::free_all_packets(BurstParams* burst) {} - -/** - * @brief Frees a specific packet in a segment. - * - * @param burst The burst parameters. - * @param seg The segment index. - * @param pkt The packet index within the segment. - */ -void RmaxMgr::RmaxMgrImpl::free_packet_segment(BurstParams* burst, int seg, int pkt) {} - -/** - * @brief Frees a specific packet. - * - * @param burst The burst parameters. - * @param pkt The packet index. - */ -void RmaxMgr::RmaxMgrImpl::free_packet(BurstParams* burst, int pkt) {} - -/** - * @brief Frees the RX burst. - * - * @param burst The burst parameters. - */ -void RmaxMgr::RmaxMgrImpl::free_rx_burst(BurstParams* burst) { - uint32_t key = - RmaxBurst::burst_tag_from_port_and_queue_id(burst->hdr.hdr.port_id, burst->hdr.hdr.q_id); - - if (rx_services.find(key) == rx_services.end() || - rx_burst_managers.find(key) == rx_burst_managers.end()) { - HOLOSCAN_LOG_ERROR("Rmax Service is not initialized"); - return; - } - - auto& rx_service = rx_services[key]; - auto& rmax_bursts_manager = rx_burst_managers[key]; - - if (!(rx_service->is_alive())) { - HOLOSCAN_LOG_ERROR("Rmax Service is not initialized"); - return; - } - - rmax_bursts_manager->rx_burst_done(static_cast(burst)); -} - -/** - * @brief Frees the TX burst. - * - * @param burst The burst parameters. - */ -void RmaxMgr::RmaxMgrImpl::free_tx_burst(BurstParams* burst) {} - -/** - * @brief Dequeues an RX burst. - * - * @param burst Pointer to the burst parameters. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::RmaxMgrImpl::get_rx_burst(BurstParams** burst, int port, int q) { - uint32_t service_id = RmaxBurst::burst_tag_from_port_and_queue_id(port, q); - auto queue_it = rx_bursts_out_queues_map_.find(service_id); - - if (queue_it == rx_bursts_out_queues_map_.end()) { - HOLOSCAN_LOG_ERROR("No Rx queue found for Rivermax service (port {}, queue {}). " - "Check config.", port, q); - return Status::INVALID_PARAMETER; - } - - auto out_burst_shared = queue_it->second->dequeue_burst(); - if (out_burst_shared == nullptr) { - return Status::NULL_PTR; - } - *burst = out_burst_shared.get(); - return Status::SUCCESS; -} - -/** - * @brief Frees the RX metadata. - * - * @param burst The burst parameters. - */ -void RmaxMgr::RmaxMgrImpl::free_rx_metadata(BurstParams* burst) {} - -/** - * @brief Frees the TX metadata. - * - * @param burst The burst parameters. - */ -void RmaxMgr::RmaxMgrImpl::free_tx_metadata(BurstParams* burst) {} - -/** - * @brief Gets the TX metadata buffer. - * - * @param burst Pointer to the burst parameters. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::RmaxMgrImpl::get_tx_metadata_buffer(BurstParams** burst) { - return Status::SUCCESS; -} - -/** - * @brief Sends a TX burst. - * - * @param burst The burst parameters. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::RmaxMgrImpl::send_tx_burst(BurstParams* burst) { - return Status::SUCCESS; -} - -/** - * @brief Shuts down the Rmax manager. - */ -void RmaxMgr::RmaxMgrImpl::shutdown() { - if (!force_quit.load()) { - print_stats(); - - HOLOSCAN_LOG_INFO("advanced_network Rivermax manager shutting down"); - force_quit.store(false); - std::raise(SIGINT); - } -} - -/** - * @brief Prints the statistics of the Rmax manager. - */ -void RmaxMgr::RmaxMgrImpl::print_stats() { - std::stringstream ss; - IpoRxStatsPrinter::print_total_stats(ss, rx_services); - HOLOSCAN_LOG_INFO(ss.str()); -} - -/** - * @brief Gets the total byte count of a burst. - * - * @param burst The burst parameters. - * @return Total byte count of the burst. - */ -uint64_t RmaxMgr::RmaxMgrImpl::get_burst_tot_byte(BurstParams* burst) { - return 0; -} - -/** - * @brief Creates burst parameters. - * - * @return Pointer to the created burst parameters. - */ -BurstParams* RmaxMgr::RmaxMgrImpl::create_tx_burst_params() { - return new BurstParams(); -} - -/** - * @brief Gets the MAC address for a specific port. - * - * @param port The port number. - * @param mac Pointer to the MAC address buffer. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::RmaxMgrImpl::get_mac_addr(int port, char* mac) { - return Status::NOT_SUPPORTED; -} - -/** - * @brief Constructor for RmaxMgr. - */ -RmaxMgr::RmaxMgr() : pImpl(std::make_unique()) {} - -/** - * @brief Destructor for RmaxMgr. - */ -RmaxMgr::~RmaxMgr() = default; - -/** - * @brief Sets the configuration and initializes the Rmax manager. - * - * @param cfg The configuration YAML. - * @return True if the initialization was successful, false otherwise. - */ -bool RmaxMgr::set_config_and_initialize(const NetworkConfig& cfg) { - bool res = true; - if (!this->initialized_) { - res = pImpl->set_config_and_initialize(cfg); - this->initialized_ = res; - } - return res; -} - -/** - * @brief Initializes the Rmax manager. - */ -void RmaxMgr::initialize() { - pImpl->initialize(); -} - -/** - * @brief Runs the Rmax manager. - */ -void RmaxMgr::run() { - pImpl->run(); -} - -/** - * @brief Parses the RX queue configuration from a YAML node. - * - * This function extracts the RX queue configuration settings from the provided YAML node - * and populates the RxQueueConfig structure with the extracted values. - * - * @param q_item The YAML node containing the RX queue configuration. - * @param q The RxQueueConfig structure to be populated. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::parse_rx_queue_rivermax_config(const YAML::Node& q_item, RxQueueConfig& q) { - return RmaxConfigParser::parse_rx_queue_rivermax_config(q_item, q); -} - -/** - * @brief Parses the TX queue Rivermax configuration. - * - * @param q_item The YAML node containing the queue item. - * @param q The TX queue configuration to be populated. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::parse_tx_queue_rivermax_config(const YAML::Node& q_item, TxQueueConfig& q) { - return RmaxConfigParser::parse_tx_queue_rivermax_config(q_item, q); -} - -/** - * @brief Gets the pointer to a specific packet in a segment. - * - * @param burst The burst parameters. - * @param seg The segment index. - * @param idx The packet index within the segment. - * @return Pointer to the packet. - */ -void* RmaxMgr::get_segment_packet_ptr(BurstParams* burst, int seg, int idx) { - return pImpl->get_segment_packet_ptr(burst, seg, idx); -} - -/** - * @brief Gets the pointer to a specific packet. - * - * @param burst The burst parameters. - * @param idx The packet index. - * @return Pointer to the packet. - */ -void* RmaxMgr::get_packet_ptr(BurstParams* burst, int idx) { - return pImpl->get_packet_ptr(burst, idx); -} - -/** - * @brief Gets the length of a specific packet in a segment. - * - * @param burst The burst parameters. - * @param seg The segment index. - * @param idx The packet index within the segment. - * @return Length of the packet. - */ -uint16_t RmaxMgr::get_segment_packet_length(BurstParams* burst, int seg, int idx) { - return pImpl->get_segment_packet_length(burst, seg, idx); -} - -/** - * @brief Gets the length of a specific packet. - * - * @param burst The burst parameters. - * @param idx The packet index. - * @return Length of the packet. - */ -uint16_t RmaxMgr::get_packet_length(BurstParams* burst, int idx) { - return pImpl->get_packet_length(burst, idx); -} - -/** - * @brief Gets the flow ID of a packet. Currently returns 0 for the Rivermax backend - * - * @param burst The burst parameters. - * @param idx The packet index. - * @return Flow ID of the packet - */ -uint16_t RmaxMgr::get_packet_flow_id(BurstParams* burst, int idx) { - return 0; -} - -/** - * @brief Gets the extra information of a specific packet. - * - * @param burst The burst parameters. - * @param idx The packet index. - * @return Pointer to the extra information. - */ -void* RmaxMgr::get_packet_extra_info(BurstParams* burst, int idx) { - return pImpl->get_packet_extra_info(burst, idx); -} - -/** - * @brief Gets a burst of TX packets. - * - * @param burst The burst parameters. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::get_tx_packet_burst(BurstParams* burst) { - return pImpl->get_tx_packet_burst(burst); -} - -/** - * @brief Sets the Ethernet header for a specific packet. - * - * @param burst The burst parameters. - * @param idx The packet index. - * @param dst_addr The destination address. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::set_eth_header(BurstParams* burst, int idx, char* dst_addr) { - return pImpl->set_eth_header(burst, idx, dst_addr); -} - -/** - * @brief Sets the IPv4 header for a specific packet. - * - * @param burst The burst parameters. - * @param idx The packet index. - * @param ip_len The length of the IP packet. - * @param proto The protocol. - * @param src_host The source host address. - * @param dst_host The destination host address. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::set_ipv4_header(BurstParams* burst, int idx, int ip_len, uint8_t proto, - unsigned int src_host, unsigned int dst_host) { - return pImpl->set_ipv4_header(burst, idx, ip_len, proto, src_host, dst_host); -} - -/** - * @brief Sets the UDP header for a specific packet. - * - * @param burst The burst parameters. - * @param idx The packet index. - * @param udp_len The length of the UDP packet. - * @param src_port The source port. - * @param dst_port The destination port. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::set_udp_header(BurstParams* burst, int idx, int udp_len, uint16_t src_port, - uint16_t dst_port) { - return pImpl->set_udp_header(burst, idx, udp_len, src_port, dst_port); -} - -/** - * @brief Sets the UDP payload for a specific packet. - * - * @param burst The burst parameters. - * @param idx The packet index. - * @param data The payload data. - * @param len The length of the payload data. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::set_udp_payload(BurstParams* burst, int idx, void* data, int len) { - return pImpl->set_udp_payload(burst, idx, data, len); -} - -/** - * @brief Checks if a TX burst is available. - * - * @param burst The burst parameters. - * @return True if a TX burst is available, false otherwise. - */ -bool RmaxMgr::is_tx_burst_available(BurstParams* burst) { - return pImpl->is_tx_burst_available(burst); -} - -/** - * @brief Sets the packet lengths for a specific packet. - * - * @param burst The burst parameters. - * @param idx The packet index. - * @param lens The list of lengths. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::set_packet_lengths(BurstParams* burst, int idx, - const std::initializer_list& lens) { - return pImpl->set_packet_lengths(burst, idx, lens); -} - -/** - * @brief Frees all packets in a specific segment. - * - * @param burst The burst parameters. - * @param seg The segment index. - */ -void RmaxMgr::free_all_segment_packets(BurstParams* burst, int seg) { - pImpl->free_all_segment_packets(burst, seg); -} - -/** - * @brief Frees all packets. - * - * @param burst The burst parameters. - */ -void RmaxMgr::free_all_packets(BurstParams* burst) { - pImpl->free_all_packets(burst); -} - -/** - * @brief Frees a specific packet in a segment. - * - * @param burst The burst parameters. - * @param seg The segment index. - * @param pkt The packet index within the segment. - */ -void RmaxMgr::free_packet_segment(BurstParams* burst, int seg, int pkt) { - pImpl->free_packet_segment(burst, seg, pkt); -} - -/** - * @brief Frees a specific packet. - * - * @param burst The burst parameters. - * @param pkt The packet index. - */ -void RmaxMgr::free_packet(BurstParams* burst, int pkt) { - pImpl->free_packet(burst, pkt); -} - -/** - * @brief Frees the RX burst. - * - * @param burst The burst parameters. - */ -void RmaxMgr::free_rx_burst(BurstParams* burst) { - pImpl->free_rx_burst(burst); -} - -/** - * @brief Frees the TX burst. - * - * @param burst The burst parameters. - */ -void RmaxMgr::free_tx_burst(BurstParams* burst) { - pImpl->free_tx_burst(burst); -} - -/** - * @brief Gets an RX burst. - * - * @param burst Pointer to the burst parameters. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::get_rx_burst(BurstParams** burst, int port, int q) { - return pImpl->get_rx_burst(burst, port, q); -} - -/** - * @brief Sets the transmission time for a specific packet. - * - * @param burst The burst parameters. - * @param idx The packet index. - * @param timestamp The transmission timestamp. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::set_packet_tx_time(BurstParams* burst, int idx, uint64_t timestamp) { - return pImpl->set_packet_tx_time(burst, idx, timestamp); -} - -/** - * @brief Frees the RX metadata. - * - * @param burst The burst parameters. - */ -void RmaxMgr::free_rx_metadata(BurstParams* burst) { - pImpl->free_rx_metadata(burst); -} - -/** - * @brief Frees the TX metadata. - * - * @param burst The burst parameters. - */ -void RmaxMgr::free_tx_metadata(BurstParams* burst) { - pImpl->free_tx_metadata(burst); -} - -/** - * @brief Gets the TX metadata buffer. - * - * @param burst Pointer to the burst parameters. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::get_tx_metadata_buffer(BurstParams** burst) { - return pImpl->get_tx_metadata_buffer(burst); -} - -/** - * @brief Sends a TX burst. - * - * @param burst The burst parameters. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::send_tx_burst(BurstParams* burst) { - return pImpl->send_tx_burst(burst); -} - -/** - * @brief Shuts down the Rmax manager. - */ -void RmaxMgr::shutdown() { - pImpl->shutdown(); -} - -/** - * @brief Prints the statistics of the Rmax manager. - */ -void RmaxMgr::print_stats() { - pImpl->print_stats(); -} - -/** - * @brief Gets the total byte count of a burst. - * - * @param burst The burst parameters. - * @return Total byte count of the burst. - */ -uint64_t RmaxMgr::get_burst_tot_byte(BurstParams* burst) { - return pImpl->get_burst_tot_byte(burst); -} - -/** - * @brief Creates burst parameters. - * - * @return Pointer to the created burst parameters. - */ -BurstParams* RmaxMgr::create_tx_burst_params() { - return pImpl->create_tx_burst_params(); -} - -/** - * @brief Gets the MAC address for a specific port. - * - * @param port The port number. - * @param mac Pointer to the MAC address buffer. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxMgr::get_mac_addr(int port, char* mac) { - return pImpl->get_mac_addr(port, mac); -} - -}; // namespace holoscan::advanced_network diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/burst_manager.cpp b/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/burst_manager.cpp deleted file mode 100644 index 40f3b39ebb..0000000000 --- a/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/burst_manager.cpp +++ /dev/null @@ -1,539 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2023 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 - -#include -#include "api/rmax_apps_lib_api.h" -#include "rmax_service/rmax_ipo_receiver_service.h" -#include "rmax_mgr_impl/rmax_chunk_consumer_ano.h" -#include - -#define USE_BLOCKING_QUEUE 0 -#define USE_BLOCKING_MEMPOOL 1 - -using namespace ral::lib::core; -using namespace ral::lib::services; - -namespace holoscan::advanced_network { - -/** - * @brief A non-blocking queue implementation. - * - * @tparam T Type of elements stored in the queue. - */ -template -class NonBlockingQueue : public QueueInterface { - std::queue queue_; - mutable std::mutex mutex_; - - public: - /** - * @brief Enqueues an element into the queue. - * - * @param value The element to be enqueued. - */ - void enqueue(const T& value) override { - std::lock_guard lock(mutex_); - queue_.push(value); - } - - /** - * @brief Tries to dequeue an element from the queue. - * - * @param value Reference to store the dequeued element. - * @return true if an element was dequeued, false otherwise. - */ - bool try_dequeue(T& value) override { - std::lock_guard lock(mutex_); - if (queue_.empty()) { return false; } - value = queue_.front(); - queue_.pop(); - return true; - } - - /** - * @brief Tries to dequeue an element from the queue. - * - * @param value Reference to store the dequeued element. - * @param timeout Timeout for the dequeue operation (ignored). - * @return true if an element was dequeued, false otherwise. - */ - bool try_dequeue(T& value, std::chrono::milliseconds timeout) override { - return try_dequeue(value); - } - - /** - * @brief Gets the size of the queue. - * - * @return The number of elements in the queue. - */ - size_t get_size() const override { - std::lock_guard lock(mutex_); - return queue_.size(); - } - - /** - * @brief Clears all elements from the queue. - */ - void clear() override { - std::lock_guard lock(mutex_); - while (!queue_.empty()) { queue_.pop(); } - } -}; - -/** - * @brief A blocking queue implementation. - * - * @tparam T Type of elements stored in the queue. - */ -template -class BlockingQueue : public QueueInterface { - std::queue queue_; - mutable std::mutex mutex_; - std::condition_variable cond_; - - public: - /** - * @brief Enqueues an element into the queue. - * - * @param value The element to be enqueued. - */ - void enqueue(const T& value) override { - std::lock_guard lock(mutex_); - queue_.push(value); - cond_.notify_one(); - } - - /** - * @brief Tries to dequeue an element from the queue (blocks forever). - * - * @param value Reference to store the dequeued element. - * @return true if an element was dequeued, false otherwise. - */ - bool try_dequeue(T& value) override { - std::unique_lock lock(mutex_); - cond_.wait(lock, [this] { return !queue_.empty(); }); - value = queue_.front(); - queue_.pop(); - return true; - } - - /** - * @brief Tries to dequeue an element from the queue. - * - * @param value Reference to store the dequeued element. - * @param timeout Timeout for the dequeue operation. - * @return true if an element was dequeued, false otherwise. - */ - 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(); })) { return false; } - value = queue_.front(); - queue_.pop(); - return true; - } - - /** - * @brief Gets the size of the queue. - * - * @return The number of elements in the queue. - */ - size_t get_size() const override { - std::lock_guard lock(mutex_); - return queue_.size(); - } - - /** - * @brief Clears all elements from the queue. - */ - void clear() override { - std::lock_guard lock(mutex_); - while (!queue_.empty()) { queue_.pop(); } - } -}; - -/** - * @brief Memory pool for managing bursts of packets. - */ -class AnoBurstsMemoryPool : public IAnoBurstsCollection { - public: - AnoBurstsMemoryPool() = delete; - - /** - * @brief Constructor with initial queue size. - * - * @param size Initial size of the memory pool. - * @param burst_handler Reference to the burst handler. - * @param tag Tag for the burst. - */ - AnoBurstsMemoryPool(size_t size, RmaxBurst::BurstHandler& burst_handler, uint32_t tag); - - /** - * @brief Destructor to clean up resources. - */ - ~AnoBurstsMemoryPool(); - - bool enqueue_burst(std::shared_ptr burst) override; - bool enqueue_burst(RmaxBurst* burst); - std::shared_ptr dequeue_burst() override; - size_t available_bursts() override { return m_queue->get_size(); }; - bool empty() override { return m_queue->get_size() == 0; }; - - private: - std::unique_ptr>> m_queue; - std::map> m_burst_map; - size_t m_initial_size; - mutable std::mutex m_burst_map_mutex; - mutable std::mutex m_queue_mutex; - uint32_t m_bursts_tag = 0; - RmaxBurst::BurstHandler& m_burst_handler; -}; - -/** - * @brief Constructor for AnoBurstsMemoryPool. - * - * @param size Initial size of the memory pool. - * @param burst_handler Reference to the burst handler. - * @param tag Tag for the burst. - */ -AnoBurstsMemoryPool::AnoBurstsMemoryPool(size_t size, RmaxBurst::BurstHandler& burst_handler, - uint32_t tag) - : m_initial_size(size), m_bursts_tag(tag), m_burst_handler(burst_handler) { -#if USE_BLOCKING_MEMPOOL - m_queue = std::make_unique>>(); -#else - m_queue = std::make_unique>>(); -#endif - - for (uint16_t i = 0; i < size; i++) { - auto burst = m_burst_handler.create_burst(i); - m_queue->enqueue(burst); - m_burst_map[i] = burst; - } -} - -/** - * @brief Puts a burst back into the memory pool. - * - * @param burst Pointer to the burst to be put back. - * @return true if the burst was successfully put back, false otherwise. - */ -bool AnoBurstsMemoryPool::enqueue_burst(RmaxBurst* burst) { - if (burst == nullptr) { - HOLOSCAN_LOG_ERROR("Invalid burst"); - return false; - } - - uint16_t burst_id = burst->get_burst_id(); - - std::lock_guard lock(m_burst_map_mutex); - auto it = m_burst_map.find(burst_id); - if (it != m_burst_map.end()) { - std::shared_ptr cur_burst = it->second; - return enqueue_burst(cur_burst); - } else { - HOLOSCAN_LOG_ERROR("Invalid burst ID: {}", burst_id); - return false; - } -} - -/** - * @brief Puts a burst back into the memory pool. - * - * @param burst Shared pointer to the burst to be put back. - * @return true if the burst was successfully put back, false otherwise. - */ -bool AnoBurstsMemoryPool::enqueue_burst(std::shared_ptr burst) { - if (burst == nullptr) { - HOLOSCAN_LOG_ERROR("Invalid burst"); - return false; - } - - std::lock_guard lock(m_queue_mutex); - - if (m_queue->get_size() < m_initial_size) { - auto burst_tag = burst->get_burst_tag(); - if (m_bursts_tag != burst_tag) { - HOLOSCAN_LOG_ERROR("Invalid burst tag"); - return false; - } - m_queue->enqueue(burst); - return true; - } else { - HOLOSCAN_LOG_ERROR("Burst pool is full burst_pool_tag {}", m_bursts_tag); - } - return false; -} - -/** - * @brief Retrieves a burst from the memory pool. - * - * @return A shared pointer to the retrieved burst, or nullptr if no burst is available. - */ -std::shared_ptr AnoBurstsMemoryPool::dequeue_burst() { - std::shared_ptr burst; - - if (m_queue->try_dequeue(burst, - std::chrono::milliseconds(RxBurstsManager::GET_BURST_TIMEOUT_MS))) { - return burst; - } - return nullptr; -} - -/** - * @brief Destructor for the AnoBurstsMemoryPool class. - * - * Frees all bursts in the queue and clears the burst map. - */ -AnoBurstsMemoryPool::~AnoBurstsMemoryPool() { - std::shared_ptr burst; - - while (m_queue->get_size() > 0 && m_queue->try_dequeue(burst)) { - m_burst_handler.delete_burst(burst); - } - std::lock_guard lock(m_burst_map_mutex); - m_burst_map.clear(); -} - -/** - * @brief Constructor for the AnoBurstsQueue class. - * - * Initializes the queue based on the USE_BLOCKING_QUEUE macro. - */ -AnoBurstsQueue::AnoBurstsQueue() { -#if USE_BLOCKING_QUEUE - m_queue = std::make_unique>>(); -#else - m_queue = std::make_unique>>(); -#endif -} - -/** - * @brief Enqueues a burst into the queue. - * - * @param burst A shared pointer to the burst to be enqueued. - * @return True if the burst was successfully enqueued. - */ -bool AnoBurstsQueue::enqueue_burst(std::shared_ptr burst) { - m_queue->enqueue(burst); - return true; -} - -/** - * @brief Clears all bursts from the queue. - */ -void AnoBurstsQueue::clear() { - m_queue->clear(); -} - -/** - * @brief Retrieves a burst from the queue. - * - * @return A shared pointer to the retrieved burst, or nullptr if no burst is available. - */ -std::shared_ptr AnoBurstsQueue::dequeue_burst() { - std::shared_ptr burst; - - if (m_queue->try_dequeue(burst, - std::chrono::milliseconds(RxBurstsManager::GET_BURST_TIMEOUT_MS))) { - return burst; - } - return nullptr; -} - -/** - * @brief Constructs a BurstHandler object. - * - * @param send_packet_ext_info Flag indicating whether to send packet info. - * @param port_id The port ID. - * @param queue_id The queue ID. - * @param gpu_direct Flag indicating whether GPU direct is enabled. - */ -RmaxBurst::BurstHandler::BurstHandler(bool send_packet_ext_info, int port_id, int queue_id, - bool gpu_direct) - : m_send_packet_ext_info(send_packet_ext_info), - m_port_id(port_id), - m_queue_id(queue_id), - m_gpu_direct(gpu_direct) { - const uint32_t burst_tag = burst_tag_from_port_and_queue_id(port_id, queue_id); - - m_burst_info.tag = burst_tag; - m_burst_info.burst_flags = - (m_send_packet_ext_info ? BurstFlags::INFO_PER_PACKET : BurstFlags::FLAGS_NONE); - m_burst_info.burst_id = 0; - m_burst_info.hds_on = false; - m_burst_info.header_on_cpu = false; - m_burst_info.payload_on_cpu = false; - m_burst_info.header_stride_size = 0; - m_burst_info.payload_stride_size = 0; - m_burst_info.header_seg_idx = 0; - m_burst_info.payload_seg_idx = 0; -} - -/** - * @brief Creates and initializes a new burst with the given burst ID - * - * @param burst_id The ID of the burst to create. - * @return A shared pointer to the created burst. - */ -std::shared_ptr RmaxBurst::BurstHandler::create_burst(uint16_t burst_id) { - std::shared_ptr burst(new RmaxBurst(m_port_id, m_queue_id, MAX_PKT_IN_BURST)); - - if (m_send_packet_ext_info) { - burst->pkt_extra_info = reinterpret_cast(new RmaxPacketExtendedInfo*[MAX_PKT_IN_BURST]); - for (int j = 0; j < MAX_PKT_IN_BURST; j++) { - burst->pkt_extra_info[j] = reinterpret_cast(new RmaxPacketExtendedInfo()); - } - } - - burst->pkts[0] = new void*[MAX_PKT_IN_BURST]; - burst->pkts[1] = new void*[MAX_PKT_IN_BURST]; - burst->pkt_lens[0] = new uint32_t[MAX_PKT_IN_BURST]; - burst->pkt_lens[1] = new uint32_t[MAX_PKT_IN_BURST]; - std::memset(burst->pkt_lens[0], 0, MAX_PKT_IN_BURST * sizeof(uint32_t)); - std::memset(burst->pkt_lens[1], 0, MAX_PKT_IN_BURST * sizeof(uint32_t)); - - m_burst_info.burst_id = burst_id; - std::memcpy(burst->get_burst_info(), &m_burst_info, sizeof(m_burst_info)); - return burst; -} - -/** - * @brief Deletes a burst and frees its associated resources. - * - * @param burst A shared pointer to the burst to delete. - */ -void RmaxBurst::BurstHandler::delete_burst(std::shared_ptr burst) { - if (burst == nullptr) { - HOLOSCAN_LOG_ERROR("Invalid burst"); - return; - } - - if (m_send_packet_ext_info && burst->pkt_extra_info != nullptr) { - for (int i = 0; i < MAX_PKT_IN_BURST; i++) { - if (burst->pkt_extra_info[i] != nullptr) { - delete reinterpret_cast(burst->pkt_extra_info[i]); - burst->pkt_extra_info[i] = nullptr; - } - } - delete[] burst->pkt_extra_info; - burst->pkt_extra_info = nullptr; - } - - delete[] burst->pkts[0]; - burst->pkts[0] = nullptr; - delete[] burst->pkts[1]; - burst->pkts[1] = nullptr; - delete[] burst->pkt_lens[0]; - burst->pkt_lens[0] = nullptr; - delete[] burst->pkt_lens[1]; - burst->pkt_lens[1] = nullptr; -} - -/** - * @brief Marks a burst as done and returns it to the memory pool. - * - * @param burst Pointer to the burst that is done. - */ -void RxBurstsManager::rx_burst_done(RmaxBurst* burst) { - if (burst == nullptr) { - HOLOSCAN_LOG_ERROR("Invalid burst"); - return; - } - - IAnoBurstsCollection* basePtr = m_rx_bursts_mempool.get(); - - AnoBurstsMemoryPool* derivedPtr = dynamic_cast(basePtr); - - if (derivedPtr) { - bool rc = derivedPtr->enqueue_burst(burst); - if (!rc) { - HOLOSCAN_LOG_ERROR("Failed to push burst back to the pool. Port_id {}:{}, queue_id {}:{}", - burst->get_port_id(), - m_port_id, - burst->get_queue_id(), - m_queue_id); - } - } else { - HOLOSCAN_LOG_ERROR("Failed to push burst back to the pool, cast failed"); - } -} - -/** - * @brief Constructor for the RxBurstsManager class. - * - * Initializes the chunk consumer with the specified parameters. - * - * @param send_packet_ext_info Flag indicating if packet information should be sent. - * @param port_id The port ID. - * @param queue_id The queue ID. - * @param burst_out_size The minimum output burst size. - * @param gpu_id The GPU ID. - * @param rx_bursts_out_queue Shared pointer to the output queue for received bursts. - */ -RxBurstsManager::RxBurstsManager(bool send_packet_ext_info, int port_id, int queue_id, - uint16_t burst_out_size, int gpu_id, - std::shared_ptr rx_bursts_out_queue) - : m_send_packet_ext_info(send_packet_ext_info), - m_port_id(port_id), - m_queue_id(queue_id), - m_burst_out_size(burst_out_size), - m_gpu_id(gpu_id), - m_rx_bursts_out_queue(rx_bursts_out_queue), - m_burst_handler(std::make_unique( - send_packet_ext_info, port_id, queue_id, gpu_id != INVALID_GPU_ID)) { - const uint32_t burst_tag = RmaxBurst::burst_tag_from_port_and_queue_id(port_id, queue_id); - m_gpu_direct = (m_gpu_id != INVALID_GPU_ID); - - m_rx_bursts_mempool = - std::make_unique(DEFAULT_NUM_RX_BURSTS, *m_burst_handler, burst_tag); - - if (!m_rx_bursts_out_queue) { - m_rx_bursts_out_queue = std::make_shared(); - m_using_shared_out_queue = false; - } - - if (m_burst_out_size > RmaxBurst::MAX_PKT_IN_BURST || m_burst_out_size == 0) - m_burst_out_size = RmaxBurst::MAX_PKT_IN_BURST; -} - -/** - * @brief Destructor for the RxBurstsManager class. - * - * Ensures that all bursts are properly returned to the memory pool. - */ -RxBurstsManager::~RxBurstsManager() { - if (m_using_shared_out_queue) { return; } - - std::shared_ptr burst; - // Get all bursts from the queue and return them to the memory pool - while (m_rx_bursts_out_queue->available_bursts() > 0) { - burst = m_rx_bursts_out_queue->dequeue_burst(); - if (burst == nullptr) break; - m_rx_bursts_mempool->enqueue_burst(burst); - } -} - -}; // namespace holoscan::advanced_network diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/rmax_chunk_consumer_ano.h b/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/rmax_chunk_consumer_ano.h deleted file mode 100644 index 930da5d76b..0000000000 --- a/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/rmax_chunk_consumer_ano.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2023 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 RMAX_CHUNK_CONSUMER_ANO_H_ -#define RMAX_CHUNK_CONSUMER_ANO_H_ - -#include -#include - -#include "rmax_ano_data_types.h" -#include "rmax_service/ipo_chunk_consumer_base.h" -#include "rmax_service/rmax_ipo_receiver_service.h" -#include "packet_processor.h" -#include "advanced_network/types.h" - -namespace holoscan::advanced_network { -using namespace ral::services; - -/** - * @brief Consumer class for handling Rmax chunks and providing advanced_network bursts. - * - * The RmaxChunkConsumerAno class acts as an adapter that consumes Rmax chunks - * and produces advanced_network bursts. It processes the packets contained in the chunks, - * updates the consumed and unconsumed byte counts, and manages the lifecycle - * of the bursts. This class is designed to interface with the Rmax framework - * and provide the necessary functionality to handle and transform the data - * into a format suitable for advanced_network to process. - */ -class RmaxChunkConsumerAno : public IIPOChunkConsumer { - public: - /** - * @brief Constructor for the RmaxChunkConsumerAno class. - * - * Initializes the chunk consumer with the specified packet processor. - * - * @param packet_processor Shared pointer to the packet processor. - */ - explicit RmaxChunkConsumerAno(std::shared_ptr packet_processor) - : m_packet_processor(packet_processor) {} - - /** - * @brief Destructor for the RmaxChunkConsumerAno class. - * - * Ensures that all bursts are properly returned to the memory pool. - */ - virtual ~RmaxChunkConsumerAno() = default; - - /** - * @brief Consumes and processes packets from a given chunk. - * - * This function processes the packets contained in the provided chunk and returns a tuple - * containing the return status, the number of consumed packets, and the number of unconsumed - * packets. - * - * @param chunk Reference to the IPOReceiveChunk containing the packets. - * @param stream Reference to the IPOReceiveStream associated with the chunk. - * @return std::tuple containing the return status, the number of - * consumed packets, and the number of unconsumed packets. - */ - std::tuple consume_chunk_packets(IPOReceiveChunk& chunk, - IPOReceiveStream& stream) override; - - protected: - std::shared_ptr m_packet_processor; -}; - -/** - * @brief Consumes and processes packets from a given chunk. - * - * This function processes the packets contained in the provided chunk and returns a tuple - * containing the return status, the number of consumed packets, and the number of unconsumed - * packets. - * - * @param chunk Reference to the IPOReceiveChunk containing the packets. - * @param stream Reference to the IPOReceiveStream associated with the chunk. - * @return std::tuple containing the return status, the number of - * consumed packets, and the number of unconsumed packets. - */ -inline std::tuple RmaxChunkConsumerAno::consume_chunk_packets( - IPOReceiveChunk& chunk, IPOReceiveStream& stream) { - if (m_packet_processor == nullptr) { - HOLOSCAN_LOG_ERROR("Packet processor is not set"); - return {ReturnStatus::failure, 0, 0}; - } - - const auto chunk_size = chunk.get_completion_chunk_size(); - if (chunk_size == 0) { return {ReturnStatus::success, 0, 0}; } - - PacketsChunkParams params = { - // header_ptr: Pointer to the header data - reinterpret_cast(chunk.get_completion_header_ptr()), - // payload_ptr: Pointer to the payload data - reinterpret_cast(chunk.get_completion_payload_ptr()), - // packet_info_array: Array of packet information - chunk.get_completion_info_ptr(), - chunk_size, - // hds_on: Header data splitting enabled - (chunk_size > 0) ? (chunk.get_packet_header_size(0) > 0) : false, - // header_stride_size: Stride size for the header data - stream.get_header_stride_size(), - // payload_stride_size: Stride size for the payload data - stream.get_payload_stride_size(), - }; - - auto [status, processed_packets] = m_packet_processor->process_packets(params); - - return {status, processed_packets, chunk_size - processed_packets}; -} - -}; // namespace holoscan::advanced_network - -#endif /* RMAX_CHUNK_CONSUMER_ANO_H_ */ diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/rmax_config_manager.cpp b/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/rmax_config_manager.cpp deleted file mode 100644 index 664db7eca0..0000000000 --- a/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/rmax_config_manager.cpp +++ /dev/null @@ -1,803 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2023 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 "rt_threads.h" -#include "rmax_ipo_receiver_service.h" -#include "rmax_mgr_impl/burst_manager.h" -#include - -#include "rmax_config_manager.h" - -namespace holoscan::advanced_network { - -static constexpr int USECS_IN_SECOND = 1000000; - -/** - * @brief Factory class for creating configuration managers. - * - * The ConfigManagerFactory class provides a static method to create instances of - * configuration managers based on the specified configuration type - */ -class ConfigManagerFactory { - public: - /** - * @brief Creates a configuration manager. - * - * This static method creates and returns a shared pointer to a configuration manager - * based on the specified configuration type. - * - * @param type The type of configuration manager to create - * @return A shared pointer to the created configuration manager, or nullptr if the type is - * invalid. - */ - static std::shared_ptr create_manager(RmaxConfigContainer::ConfigType type) { - switch (type) { - case RmaxConfigContainer::ConfigType::RX: - return std::make_shared(); - case RmaxConfigContainer::ConfigType::TX: - return std::make_shared(); - default: - return nullptr; - } - } -}; - -/** - * @brief Adds a configuration manager. - * - * This function adds a configuration manager for the specified type. - * - * @param type The type of configuration manager to add. - * @param config_manager The shared pointer to the configuration manager. - */ -void RmaxConfigContainer::add_config_manager(ConfigType type, - std::shared_ptr config_manager) { - config_managers_[type] = config_manager; -} - -/** - * @brief Initializes the configuration managers. - * - * This function initializes the configuration managers for RX and TX services. - */ -void RmaxConfigContainer::initialize_managers() { - add_config_manager(ConfigType::RX, ConfigManagerFactory::create_manager(ConfigType::RX)); - add_config_manager(ConfigType::TX, ConfigManagerFactory::create_manager(ConfigType::TX)); -} - -/** - * @brief Parses the RX queues configuration. - * - * This function parses the configuration for RX queues for the specified port ID. - * - * @param port_id The port ID for which to parse the RX queues. - * @param queues The vector of RX queue configurations. - * @return An integer indicating the success or failure of the parsing operation. - */ -int RmaxConfigContainer::parse_rx_queues(uint16_t port_id, - const std::vector& queues) { - int rmax_rx_config_found = 0; - - auto rx_manager = std::dynamic_pointer_cast( - get_config_manager(RmaxConfigContainer::ConfigType::RX)); - - if (!rx_manager) { return 0; } - - rx_manager->set_configuration(cfg_, rmax_apps_lib_); - - for (const auto& q : queues) { - if (!rx_manager->append_candidate_for_rx_queue(port_id, q)) { continue; } - rmax_rx_config_found++; - } - - return rmax_rx_config_found; -} - -/** - * @brief Parses the TX queues configuration. - * - * This function parses the configuration for TX queues for the specified port ID. - * - * @param port_id The port ID for which to parse the TX queues. - * @param queues The vector of TX queue configurations. - * @return An integer indicating the success or failure of the parsing operation. - */ -int RmaxConfigContainer::parse_tx_queues(uint16_t port_id, - const std::vector& queues) { - int rmax_tx_config_found = 0; - - auto tx_manager = std::dynamic_pointer_cast( - get_config_manager(RmaxConfigContainer::ConfigType::TX)); - - if (!tx_manager) { return 0; } - - tx_manager->set_configuration(cfg_, rmax_apps_lib_); - - for (const auto& q : queues) { - if (!tx_manager->append_candidate_for_tx_queue(port_id, q)) { continue; } - rmax_tx_config_found++; - } - - return rmax_tx_config_found; -} - -/** - * @brief Parses the configuration from the YAML file. - * - * This function iterates over the interfaces and their respective RX an TX queues - * defined in the configuration YAML, extracting and validating the necessary - * settings for each queue. It then populates the RX and TX service configuration - * structures with these settings. The parsing is done via dedicated configuration managers. - * - * @param cfg The configuration YAML. - * @return True if the configuration was successfully parsed, false otherwise. - */ -bool RmaxConfigContainer::parse_configuration(const NetworkConfig& cfg) { - int rmax_rx_config_found = 0; - int rmax_tx_config_found = 0; - - is_configured_ = false; - cfg_ = cfg; - - for (const auto& intf : cfg.ifs_) { - HOLOSCAN_LOG_INFO("Rmax init Port {} -- RX: {} TX: {}", - intf.port_id_, - intf.rx_.queues_.size() > 0 ? "ENABLED" : "DISABLED", - intf.tx_.queues_.size() > 0 ? "ENABLED" : "DISABLED"); - - rmax_rx_config_found += parse_rx_queues(intf.port_id_, intf.rx_.queues_); - rmax_tx_config_found += parse_tx_queues(intf.port_id_, intf.tx_.queues_); - } - - set_rmax_log_level(cfg.log_level_); - - if (rmax_rx_config_found == 0 && rmax_tx_config_found == 0) { - HOLOSCAN_LOG_ERROR("Failed to parse Rivermax advanced_network settings. " - "No valid settings found"); - return false; - } - - HOLOSCAN_LOG_INFO( - "Rivermax advanced_network settings were successfully parsed, " - "Found {} RX Queues and {} TX Queues " - "settings", - rmax_rx_config_found, - rmax_tx_config_found); - - is_configured_ = true; - return true; -} - -/** - * @brief Sets the default configuration for an RX service. - * - * @param rx_service_cfg The RX service configuration to be set. - */ -void RxConfigManager::set_default_config(ExtRmaxIPOReceiverConfig& rx_service_cfg) const { - rx_service_cfg.app_settings->destination_ip = DESTINATION_IP_DEFAULT; - rx_service_cfg.app_settings->destination_port = DESTINATION_PORT_DEFAULT; - rx_service_cfg.app_settings->num_of_threads = NUM_OF_THREADS_DEFAULT; - rx_service_cfg.app_settings->num_of_total_streams = NUM_OF_TOTAL_STREAMS_DEFAULT; - rx_service_cfg.app_settings->num_of_total_flows = NUM_OF_TOTAL_FLOWS_DEFAULT; - rx_service_cfg.app_settings->internal_thread_core = CPU_NONE; - rx_service_cfg.app_settings->app_threads_cores = - std::vector(rx_service_cfg.app_settings->num_of_threads, CPU_NONE); - rx_service_cfg.app_settings->rate = {0, 0}; - rx_service_cfg.app_settings->num_of_chunks = NUM_OF_CHUNKS_DEFAULT; - rx_service_cfg.app_settings->num_of_packets_in_chunk = NUM_OF_PACKETS_IN_CHUNK_DEFAULT; - rx_service_cfg.app_settings->packet_payload_size = PACKET_PAYLOAD_SIZE_DEFAULT; - rx_service_cfg.app_settings->packet_app_header_size = PACKET_APP_HEADER_SIZE_DEFAULT; - rx_service_cfg.app_settings->sleep_between_operations_us = SLEEP_BETWEEN_OPERATIONS_US_DEFAULT; - rx_service_cfg.app_settings->sleep_between_operations = false; - rx_service_cfg.app_settings->print_parameters = false; - rx_service_cfg.app_settings->use_checksum_header = false; - rx_service_cfg.app_settings->hw_queue_full_sleep_us = 0; - rx_service_cfg.app_settings->gpu_id = INVALID_GPU_ID; - rx_service_cfg.app_settings->allocator_type = AllocatorTypeUI::Auto; - rx_service_cfg.app_settings->statistics_reader_core = INVALID_CORE_NUMBER; - rx_service_cfg.app_settings->session_id_stats = UINT_MAX; - rx_service_cfg.is_extended_sequence_number = true; - rx_service_cfg.max_path_differential_us = 0; - rx_service_cfg.register_memory = false; - rx_service_cfg.max_chunk_size = 0; - rx_service_cfg.rmax_apps_lib = nullptr; - rx_service_cfg.rx_stats_period_report_ms = 1000; -} - -/** - * @brief Validates RX queue for Rmax Queue configuration. If valid, appends the RX queue for a - * given port. - * - * @param port_id The port ID. - * @param q The RX queue configuration. - * @return True if the configuration was appended successfully, false otherwise. - */ -bool RxConfigManager::append_candidate_for_rx_queue(uint16_t port_id, const RxQueueConfig& q) { - HOLOSCAN_LOG_INFO( - "Configuring RX queue: {} ({}) on port {}", q.common_.name_, q.common_.id_, port_id); - - if (is_configuration_set_ == false) { - HOLOSCAN_LOG_ERROR("Configuration wasn't set for RxConfigManger"); - return false; - } - - // extra queue config_ contains RMAX configuration. If it is not set, return false - if (!q.common_.extra_queue_config_) return false; - - auto* rmax_rx_config_ptr = dynamic_cast(q.common_.extra_queue_config_); - if (!rmax_rx_config_ptr) { - HOLOSCAN_LOG_ERROR("Failed to cast extra queue config to RmaxRxQueueConfig"); - return false; - } - - RmaxRxQueueConfig rmax_rx_config(*rmax_rx_config_ptr); - - if (!validate_rx_queue_config(rmax_rx_config)) { return false; } - - if (!validate_memory_regions_config(q, rmax_rx_config)) { return false; } - - if (config_memory_allocator(rmax_rx_config, q) == false) { return false; } - - rmax_rx_config.dump_parameters(); - - ExtRmaxIPOReceiverConfig rx_service_cfg; - - if (!build_rmax_ipo_receiver_config(rx_service_cfg, rmax_rx_config, q)) { return false; } - - add_new_rx_service_config(rx_service_cfg, port_id, q.common_.id_); - - return true; -} - -/** - * @brief Configures the memory allocator for the RMAX RX queue. - * - * @param rmax_rx_config The RMAX RX queue configuration. - * @param q The RX queue configuration. - * @return true if the configuration is successful, false otherwise. - */ -bool RxConfigManager::config_memory_allocator(RmaxRxQueueConfig& rmax_rx_config, - const RxQueueConfig& q) { - uint16_t num_of_mrs = q.common_.mrs_.size(); - HOLOSCAN_LOG_INFO( - "Configuring memory allocator for RMAX RX queue: {}, number of memory regions: {}", - q.common_.name_, - num_of_mrs); - if (num_of_mrs == 1) { - return config_memory_allocator_from_single_mrs(rmax_rx_config, q, cfg_.mrs_[q.common_.mrs_[0]]); - } else if (num_of_mrs == 2) { - return config_memory_allocator_from_dual_mrs( - rmax_rx_config, q, cfg_.mrs_[q.common_.mrs_[0]], cfg_.mrs_[q.common_.mrs_[1]]); - } else { - HOLOSCAN_LOG_ERROR("Incompatible number of memory regions for Rivermax RX queue: {} [1..{}]", - num_of_mrs, - MAX_RMAX_MEMORY_REGIONS); - return false; - } -} - -/** - * @brief Configures the memory allocator for a single memory region. - * The allocator will be used for both the header and payload memory. - * - * @param rmax_rx_config The RMAX RX queue configuration. - * @param q The RX queue configuration. - * @param mr The memory region. - * @return true if the configuration is successful, false otherwise. - */ -bool RxConfigManager::config_memory_allocator_from_single_mrs(RmaxRxQueueConfig& rmax_rx_config, - const RxQueueConfig& q, - const MemoryRegionConfig& mr) { - rmax_rx_config.split_boundary = 0; - rmax_rx_config.max_packet_size = mr.buf_size_; - rmax_rx_config.packets_buffers_size = mr.num_bufs_; - - if (set_gpu_is_in_use_if_applicable(rmax_rx_config, mr)) { return true; } - - set_gpu_is_not_in_use(rmax_rx_config); - set_cpu_allocator_type(rmax_rx_config, mr); - - return true; -} - -/** - * @brief Configures the memory allocator for dual memory regions. - * If GPU is in use, it will be used for the payload memory region, - * and the CPU allocator will be used for the header memory region. - * Otherwise, the function expects that the same allocator is configured - * for both memory regions. - * - * @param rmax_rx_config The RMAX RX queue configuration. - * @param q The RX queue configuration. - * @param mr_header The header memory region. - * @param mr_payload The payload memory region. - * @return true if the configuration is successful, false otherwise. - */ -bool RxConfigManager::config_memory_allocator_from_dual_mrs(RmaxRxQueueConfig& rmax_rx_config, - const RxQueueConfig& q, - const MemoryRegionConfig& mr_header, - const MemoryRegionConfig& mr_payload) { - rmax_rx_config.split_boundary = mr_header.buf_size_; - rmax_rx_config.max_packet_size = mr_payload.buf_size_; - rmax_rx_config.packets_buffers_size = mr_payload.num_bufs_; - - if (!set_gpu_is_in_use_if_applicable(rmax_rx_config, mr_payload)) { - set_gpu_is_not_in_use(rmax_rx_config); - } - - set_cpu_allocator_type(rmax_rx_config, mr_header); - - return true; -} - -/** - * @brief Sets the GPU memory configuration if applicable. - * - * @param rmax_rx_config The RMAX RX queue configuration. - * @param mr The memory region. - * @return true if the GPU memory configuration is set, false otherwise. - */ -bool RxConfigManager::set_gpu_is_in_use_if_applicable(RmaxRxQueueConfig& rmax_rx_config, - const MemoryRegionConfig& mr) { -#if RMAX_TEGRA - if (mr.kind_ == MemoryKind::DEVICE || mr.kind_ == MemoryKind::HOST_PINNED) { -#else - if (mr.kind_ == MemoryKind::DEVICE) { -#endif - rmax_rx_config.gpu_device_id = mr.affinity_; - rmax_rx_config.gpu_direct = true; - return true; - } - return false; -} - -/** - * @brief Sets the CPU memory configuration. - * - * @param rmax_rx_config The RMAX RX queue configuration. - */ -void RxConfigManager::set_gpu_is_not_in_use(RmaxRxQueueConfig& rmax_rx_config) { - rmax_rx_config.gpu_device_id = -1; - rmax_rx_config.gpu_direct = false; -} - -/** - * @brief Sets the allocator type based on the memory region. - * - * @param rmax_rx_config The RMAX RX queue configuration. - * @param mr The memory region. - */ -void RxConfigManager::set_cpu_allocator_type(RmaxRxQueueConfig& rmax_rx_config, - const MemoryRegionConfig& mr) { -#if RMAX_TEGRA - if (mr.kind_ == MemoryKind::HOST) { -#else - if (mr.kind_ == MemoryKind::HOST || mr.kind_ == MemoryKind::HOST_PINNED) { -#endif - - rmax_rx_config.allocator_type = "malloc"; - } else if (mr.kind_ == MemoryKind::HUGE) { - if (rmax_rx_config.allocator_type != "huge_page_default" && - rmax_rx_config.allocator_type != "huge_page_2mb" && - rmax_rx_config.allocator_type != "huge_page_512mb" && - rmax_rx_config.allocator_type != "huge_page_1gb") { - rmax_rx_config.allocator_type = "huge_page_default"; - } // else the allocator type is already set - } -} - -/** - * @brief Validates the RX queue memory regions configuration. - * - * @param q The RX queue configuration. - * @param rmax_rx_config The Rmax RX queue configuration. - * @return True if the configuration is valid, false otherwise. - */ -bool RxConfigManager::validate_memory_regions_config(const RxQueueConfig& q, - const RmaxRxQueueConfig& rmax_rx_config) { - uint16_t num_of_mrs = q.common_.mrs_.size(); - try { - if (num_of_mrs == 1) { - return validate_memory_regions_config_from_single_mrs( - q, rmax_rx_config, cfg_.mrs_.at(q.common_.mrs_[0])); - } else if (num_of_mrs == 2) { - return validate_memory_regions_config_from_dual_mrs( - q, rmax_rx_config, cfg_.mrs_.at(q.common_.mrs_[0]), cfg_.mrs_.at(q.common_.mrs_[1])); - } else { - HOLOSCAN_LOG_ERROR("Incompatible number of memory regions for Rivermax RX queue: {} [1..{}]", - num_of_mrs, - MAX_RMAX_MEMORY_REGIONS); - return false; - } - } catch (const std::out_of_range& e) { - if (num_of_mrs == 1) - HOLOSCAN_LOG_ERROR("Invalid memory region for Rivermax RX queue: {}", q.common_.mrs_[0]); - else - HOLOSCAN_LOG_ERROR("Invalid memory region for Rivermax RX queue: {} or {}", - q.common_.mrs_[0], - q.common_.mrs_[1]); - return false; - } - - return true; -} - -/** - * @brief Validates the RX queue memory regions configuration for a single memory region. - * - * @param q The RX queue configuration. - * @param rmax_rx_config The Rmax RX queue configuration. - * @param mr The memory region. - * @return True if the configuration is valid, false otherwise. - */ -bool RxConfigManager::validate_memory_regions_config_from_single_mrs( - const RxQueueConfig& q, const RmaxRxQueueConfig& rmax_rx_config, const MemoryRegionConfig& mr) { - return true; -} - -/** - * @brief Validates the RX queue memory regions configuration for dual memory regions. - * - * @param q The RX queue configuration. - * @param rmax_rx_config The Rmax RX queue configuration. - * @param mr_header The header memory region. - * @param mr_payload The payload memory region. - * @return True if the configuration is valid, false otherwise. - */ -bool RxConfigManager::validate_memory_regions_config_from_dual_mrs( - const RxQueueConfig& q, const RmaxRxQueueConfig& rmax_rx_config, - const MemoryRegionConfig& mr_header, const MemoryRegionConfig& mr_payload) { - if (mr_payload.kind_ != MemoryKind::DEVICE && mr_header.kind_ != mr_payload.kind_) { - HOLOSCAN_LOG_ERROR( - "Memory region kind mismatch: {} != {}", (int)(mr_header.kind_), (int)mr_payload.kind_); - return false; - } - - if (mr_payload.kind_ == MemoryKind::DEVICE && mr_header.kind_ == MemoryKind::DEVICE) { - HOLOSCAN_LOG_ERROR("Both memory regions are device memory"); - return false; - } - - if (mr_payload.buf_size_ == 0) { - HOLOSCAN_LOG_ERROR("Invalid payload memory region size: {}", mr_payload.buf_size_); - return false; - } - - return true; -} - -/** - * @brief Builds the Rmax IPO receiver configuration. - * - * @param rx_service_cfg The RX service configuration to be built. -s * @param rmax_rx_config The Rmax RX queue configuration. - * @param q The RX queue configuration. - * @return True if the configuration is successful, false otherwise. - */ -bool RxConfigManager::build_rmax_ipo_receiver_config(ExtRmaxIPOReceiverConfig& rx_service_cfg, - const RmaxRxQueueConfig& rmax_rx_config, - const RxQueueConfig& q) { - rx_service_cfg.app_settings = std::make_shared(); - set_default_config(rx_service_cfg); - - auto& app_settings_config = *(rx_service_cfg.app_settings); - - set_rx_service_common_app_settings(app_settings_config, rmax_rx_config); - - if (!parse_and_set_cores(app_settings_config, q.common_.cpu_core_)) { return false; } - - set_rx_service_ipo_receiver_settings(rx_service_cfg, rmax_rx_config); - - return true; -} - -/** - * @brief Validates the RX queue configuration. - * - * @param rmax_rx_config The Rmax RX queue configuration. - * @return True if the configuration is valid, false otherwise. - */ -bool RxConfigManager::validate_rx_queue_config(const RmaxRxQueueConfig& rmax_rx_config) { - if (rmax_rx_config.source_ips.empty()) { - HOLOSCAN_LOG_ERROR("Source IP addresses are not set for RTP stream"); - return false; - } - - if (rmax_rx_config.local_ips.empty()) { - HOLOSCAN_LOG_ERROR("Local IP addresses are not set for RTP stream"); - return false; - } - - if (rmax_rx_config.destination_ips.empty()) { - HOLOSCAN_LOG_ERROR("Destination IP addresses are not set for RTP stream"); - return false; - } - - if (rmax_rx_config.destination_ports.empty()) { - HOLOSCAN_LOG_ERROR("Destination ports are not set for RTP stream"); - return false; - } - - if ((rmax_rx_config.local_ips.size() != rmax_rx_config.source_ips.size()) || - (rmax_rx_config.local_ips.size() != rmax_rx_config.destination_ips.size()) || - (rmax_rx_config.local_ips.size() != rmax_rx_config.destination_ports.size())) { - HOLOSCAN_LOG_ERROR( - "Local/Source/Destination IP addresses and ports sizes are not equal for RTP stream"); - return false; - } - - return true; -} - -/** - * @brief Sets the common application settings for the RX service. - * - * @param app_settings_config The application settings configuration. - * @param rmax_rx_config The Rmax RX queue configuration. - * @param split_boundary The split boundary value. - */ -void RxConfigManager::set_rx_service_common_app_settings(AppSettings& app_settings_config, - const RmaxRxQueueConfig& rmax_rx_config) { - app_settings_config.local_ips = rmax_rx_config.local_ips; - app_settings_config.source_ips = rmax_rx_config.source_ips; - app_settings_config.destination_ips = rmax_rx_config.destination_ips; - app_settings_config.destination_ports = rmax_rx_config.destination_ports; - - if (rmax_rx_config.gpu_direct) { - app_settings_config.gpu_id = rmax_rx_config.gpu_device_id; - } else { - app_settings_config.gpu_id = INVALID_GPU_ID; - } - - set_allocator_type(app_settings_config, rmax_rx_config.allocator_type); - - if (cfg_.common_.master_core_ >= 0 && - cfg_.common_.master_core_ < std::thread::hardware_concurrency()) { - app_settings_config.internal_thread_core = cfg_.common_.master_core_; - } else { - app_settings_config.internal_thread_core = CPU_NONE; - } - app_settings_config.num_of_threads = rmax_rx_config.num_of_threads; - app_settings_config.print_parameters = rmax_rx_config.print_parameters; - app_settings_config.sleep_between_operations_us = rmax_rx_config.sleep_between_operations_us; - app_settings_config.packet_payload_size = rmax_rx_config.max_packet_size; - app_settings_config.packet_app_header_size = rmax_rx_config.split_boundary; - app_settings_config.num_of_packets_in_chunk = - std::pow(2, std::ceil(std::log2(rmax_rx_config.packets_buffers_size))); -} - -/** - * @brief Sets the allocator type for the application settings. - * - * @param app_settings_config The application settings configuration. - * @param allocator_type The allocator type string. - */ -void RxConfigManager::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; } - }; - - app_settings_config.allocator_type = AllocatorTypeUI::Auto; - setAllocatorType("auto", AllocatorTypeUI::Auto); - setAllocatorType("huge_page_default", AllocatorTypeUI::HugePageDefault); - setAllocatorType("malloc", AllocatorTypeUI::Malloc); - setAllocatorType("huge_page_2mb", AllocatorTypeUI::HugePage2MB); - setAllocatorType("huge_page_512mb", AllocatorTypeUI::HugePage512MB); - setAllocatorType("huge_page_1gb", AllocatorTypeUI::HugePage1GB); - setAllocatorType("gpu", AllocatorTypeUI::Gpu); -} - -/** - * @brief Parses and sets the cores for the application settings. - * - * @param app_settings_config The application settings configuration. - * @param cores The cores configuration string. - * @return True if the cores are successfully parsed and set, false otherwise. - */ -bool RxConfigManager::parse_and_set_cores(AppSettings& app_settings_config, - const std::string& cores) { - std::istringstream iss(cores); - std::string coreStr; - bool to_reset_cores_vector = true; - while (std::getline(iss, coreStr, ',')) { - try { - int core = std::stoi(coreStr); - if (core < 0 || core >= std::thread::hardware_concurrency()) { - HOLOSCAN_LOG_ERROR("Invalid core number: {}", coreStr); - return false; - } else { - if (to_reset_cores_vector) { - app_settings_config.app_threads_cores.clear(); - to_reset_cores_vector = false; - } - app_settings_config.app_threads_cores.push_back(core); - } - } catch (const std::invalid_argument& e) { - HOLOSCAN_LOG_ERROR("Invalid core number: {}", coreStr); - return false; - } catch (const std::out_of_range& e) { - HOLOSCAN_LOG_ERROR("Core number out of range: {}", coreStr); - return false; - } - } - return true; -} - -/** - * @brief Sets the IPO receiver settings for the RX service. - * - * @param rx_service_cfg The RX service configuration. - * @param rmax_rx_config The Rmax RX queue configuration. - */ -void RxConfigManager::set_rx_service_ipo_receiver_settings( - ExtRmaxIPOReceiverConfig& rx_service_cfg, const RmaxRxQueueConfig& rmax_rx_config) { - rx_service_cfg.is_extended_sequence_number = rmax_rx_config.ext_seq_num; - rx_service_cfg.max_path_differential_us = rmax_rx_config.max_path_differential_us; - if (rx_service_cfg.max_path_differential_us >= USECS_IN_SECOND) { - HOLOSCAN_LOG_ERROR("Max path differential must be less than 1 second"); - rx_service_cfg.max_path_differential_us = USECS_IN_SECOND; - } - - rx_service_cfg.rx_stats_period_report_ms = rmax_rx_config.rx_stats_period_report_ms; - rx_service_cfg.register_memory = rmax_rx_config.memory_registration; - rx_service_cfg.max_chunk_size = rmax_rx_config.max_chunk_size; - rx_service_cfg.rmax_apps_lib = this->rmax_apps_lib_; - - rx_service_cfg.send_packet_ext_info = rmax_rx_config.send_packet_ext_info; -} - -/** - * @brief Adds a new RX service configuration to the configuration map. - * - * @param rx_service_cfg The RX service configuration. - * @param port_id The port ID. - * @param queue_id The queue ID. - */ -void RxConfigManager::add_new_rx_service_config(const ExtRmaxIPOReceiverConfig& rx_service_cfg, - uint16_t port_id, uint16_t queue_id) { - uint32_t key = RmaxBurst::burst_tag_from_port_and_queue_id(port_id, queue_id); - if (rx_service_configs_.find(key) != rx_service_configs_.end()) { - HOLOSCAN_LOG_ERROR("Rivermax advanced_network settings for port {} and queue {} already exists", - port_id, - queue_id); - return; - } - HOLOSCAN_LOG_INFO("Rivermax advanced_network settings for port {} and queue {} added", - port_id, - queue_id); - - rx_service_configs_[key] = rx_service_cfg; -} - -/** - * @brief Validates TX queue for Rmax Queue configuration. If valid, appends the TX queue for a - * given port. - * - * @param port_id The port ID. - * @param q The TX queue configuration. - * @return True if the configuration was appended successfully, false otherwise. - */ -bool TxConfigManager::append_candidate_for_tx_queue(uint16_t port_id, const TxQueueConfig& q) { - HOLOSCAN_LOG_INFO( - "Configuring TX queue: {} ({}) on port {}", q.common_.name_, q.common_.id_, port_id); - - if (is_configuration_set_ == false) { - HOLOSCAN_LOG_ERROR("Configuration wasn't set for TxConfigManger"); - return false; - } - - // TODO: Implement when TX is ready - return false; -} - -/** - * @brief Parses the RX queue configuration from a YAML node. - * - * This function extracts the RX queue configuration settings from the provided YAML node - * and populates the RxQueueConfig structure with the extracted values. - * - * @param q_item The YAML node containing the RX queue configuration. - * @param q The RxQueueConfig structure to be populated. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxConfigParser::parse_rx_queue_rivermax_config(const YAML::Node& q_item, - RxQueueConfig& q) { - const auto& rmax_rx_settings = q_item["rmax_rx_settings"]; - - if (!rmax_rx_settings) { - HOLOSCAN_LOG_ERROR("Rmax RX settings not found"); - return Status::INVALID_PARAMETER; - } - - q.common_.extra_queue_config_ = new RmaxRxQueueConfig(); - auto& rmax_rx_config = *(reinterpret_cast(q.common_.extra_queue_config_)); - - for (const auto& q_item_ip : rmax_rx_settings["local_ip_addresses"]) { - rmax_rx_config.local_ips.emplace_back(q_item_ip.as()); - } - - for (const auto& q_item_ip : rmax_rx_settings["source_ip_addresses"]) { - rmax_rx_config.source_ips.emplace_back(q_item_ip.as()); - } - - for (const auto& q_item_ip : rmax_rx_settings["destination_ip_addresses"]) { - rmax_rx_config.destination_ips.emplace_back(q_item_ip.as()); - } - - for (const auto& q_item_ip : rmax_rx_settings["destination_ports"]) { - rmax_rx_config.destination_ports.emplace_back(q_item_ip.as()); - } - - rmax_rx_config.ext_seq_num = rmax_rx_settings["ext_seq_num"].as(true); - rmax_rx_config.max_path_differential_us = rmax_rx_settings["max_path_diff_us"].as(0); - rmax_rx_config.allocator_type = rmax_rx_settings["allocator_type"].as("auto"); - rmax_rx_config.memory_registration = rmax_rx_settings["memory_registration"].as(false); - rmax_rx_config.sleep_between_operations_us = - rmax_rx_settings["sleep_between_operations_us"].as(0); - rmax_rx_config.print_parameters = rmax_rx_settings["verbose"].as(false); - rmax_rx_config.num_of_threads = rmax_rx_settings["num_of_threads"].as(1); - rmax_rx_config.send_packet_ext_info = rmax_rx_settings["send_packet_ext_info"].as(true); - rmax_rx_config.max_chunk_size = q_item["batch_size"].as(1024); - rmax_rx_config.rx_stats_period_report_ms = - rmax_rx_settings["rx_stats_period_report_ms"].as(0); - return Status::SUCCESS; -} - -/** - * @brief Parses the TX queue Rivermax configuration. - * - * @param q_item The YAML node containing the queue item. - * @param q The TX queue configuration to be populated. - * @return Status indicating the success or failure of the operation. - */ -Status RmaxConfigParser::parse_tx_queue_rivermax_config(const YAML::Node& q_item, - TxQueueConfig& q) { - return Status::SUCCESS; -} - -void RmaxRxQueueConfig::dump_parameters() const { - if (this->print_parameters) { - HOLOSCAN_LOG_INFO("Rivermax RX Queue Config:"); - // print gpu settings - HOLOSCAN_LOG_INFO("\tNetwork settings:"); - HOLOSCAN_LOG_INFO("\t\tlocal_ips: {}", fmt::join(local_ips, ", ")); - HOLOSCAN_LOG_INFO("\t\tsource_ips: {}", fmt::join(source_ips, ", ")); - HOLOSCAN_LOG_INFO("\t\tdestination_ips: {}", fmt::join(destination_ips, ", ")); - HOLOSCAN_LOG_INFO("\t\tdestination_ports: {}", fmt::join(destination_ports, ", ")); - HOLOSCAN_LOG_INFO("\tGPU settings:"); - HOLOSCAN_LOG_INFO("\t\tGPU ID: {}", gpu_device_id); - HOLOSCAN_LOG_INFO("\t\tGPU Direct: {}", gpu_direct); - HOLOSCAN_LOG_INFO("\tMemory config settings:"); - HOLOSCAN_LOG_INFO("\t\tallocator_type: {}", allocator_type); - HOLOSCAN_LOG_INFO("\t\tmemory_registration: {}", memory_registration); - HOLOSCAN_LOG_INFO("\tPacket settings:"); - HOLOSCAN_LOG_INFO("\t\tbatch_size/max_chunk_size: {}", max_chunk_size); - HOLOSCAN_LOG_INFO("\t\tsplit_boundary/header_size: {}", split_boundary); - HOLOSCAN_LOG_INFO("\t\tmax_packet_size: {}", max_packet_size); - HOLOSCAN_LOG_INFO("\t\tpackets_buffers_size: {}", packets_buffers_size); - HOLOSCAN_LOG_INFO("\tRMAX IPO settings:"); - HOLOSCAN_LOG_INFO("\t\text_seq_num: {}", ext_seq_num); - HOLOSCAN_LOG_INFO("\t\tsleep_between_operations_us: {}", sleep_between_operations_us); - HOLOSCAN_LOG_INFO("\t\tmax_path_differential_us: {}", max_path_differential_us); - HOLOSCAN_LOG_INFO("\t\tnum_of_threads: {}", num_of_threads); - HOLOSCAN_LOG_INFO("\t\tsend_packet_ext_info: {}", send_packet_ext_info); - HOLOSCAN_LOG_INFO("\t\trx_stats_period_report_ms: {}", rx_stats_period_report_ms); - } -} - -} // namespace holoscan::advanced_network diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/rmax_config_manager.h b/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/rmax_config_manager.h deleted file mode 100644 index 7c733c682e..0000000000 --- a/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/rmax_config_manager.h +++ /dev/null @@ -1,618 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2023 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 RMAX_CONFIG_MANAGER_H_ -#define RMAX_CONFIG_MANAGER_H_ - -#include -#include -#include -#include -#include - -#include "advanced_network/manager.h" -#include "rmax_ano_data_types.h" -#include "rmax_ipo_receiver_service.h" - -namespace holoscan::advanced_network { - -using namespace ral::services::rmax_ipo_receiver; - -/** - * @brief Configuration structure for Rmax RX queue. - * - * This structure holds the configuration settings for an Rmax RX queue, - * including packet size, chunk size, IP addresses, ports, and other parameters. - */ -struct RmaxRxQueueConfig : public ManagerExtraQueueConfig { - uint16_t max_packet_size = 0; - size_t max_chunk_size; - size_t packets_buffers_size; - bool gpu_direct; - int gpu_device_id; - uint16_t split_boundary; - std::vector local_ips; - std::vector source_ips; - std::vector destination_ips; - std::vector destination_ports; - size_t num_of_threads; - bool print_parameters; - uint32_t max_path_differential_us; - int sleep_between_operations_us; - std::string allocator_type; - bool ext_seq_num; - bool memory_registration; - bool send_packet_ext_info; - uint32_t rx_stats_period_report_ms; - - public: - RmaxRxQueueConfig() = default; - ~RmaxRxQueueConfig() = default; - - RmaxRxQueueConfig(const RmaxRxQueueConfig& other) - : ManagerExtraQueueConfig(other), - max_packet_size(other.max_packet_size), - max_chunk_size(other.max_chunk_size), - packets_buffers_size(other.packets_buffers_size), - gpu_direct(other.gpu_direct), - gpu_device_id(other.gpu_device_id), - split_boundary(other.split_boundary), - local_ips(other.local_ips), - source_ips(other.source_ips), - destination_ips(other.destination_ips), - destination_ports(other.destination_ports), - num_of_threads(other.num_of_threads), - print_parameters(other.print_parameters), - max_path_differential_us(other.max_path_differential_us), - sleep_between_operations_us(other.sleep_between_operations_us), - allocator_type(other.allocator_type), - ext_seq_num(other.ext_seq_num), - memory_registration(other.memory_registration), - send_packet_ext_info(other.send_packet_ext_info), - rx_stats_period_report_ms(other.rx_stats_period_report_ms) {} - - RmaxRxQueueConfig& operator=(const RmaxRxQueueConfig& other) { - if (this != &other) { - ManagerExtraQueueConfig::operator=(other); - max_packet_size = other.max_packet_size; - max_chunk_size = other.max_chunk_size; - packets_buffers_size = other.packets_buffers_size; - gpu_direct = other.gpu_direct; - gpu_device_id = other.gpu_device_id; - split_boundary = other.split_boundary; - local_ips = other.local_ips; - source_ips = other.source_ips; - destination_ips = other.destination_ips; - destination_ports = other.destination_ports; - num_of_threads = other.num_of_threads; - print_parameters = other.print_parameters; - max_path_differential_us = other.max_path_differential_us; - sleep_between_operations_us = other.sleep_between_operations_us; - allocator_type = other.allocator_type; - ext_seq_num = other.ext_seq_num; - memory_registration = other.memory_registration; - send_packet_ext_info = other.send_packet_ext_info; - rx_stats_period_report_ms = other.rx_stats_period_report_ms; - } - return *this; - } - - void dump_parameters() const; -}; - -/** - * @brief Extended configuration for Rmax IPO Receiver. - */ -struct ExtRmaxIPOReceiverConfig : RmaxIPOReceiverConfig { - bool send_packet_ext_info; -}; - -/** - * @brief Base interface for configuration managers. - * - * The IConfigManager interface defines the basic operations for configuration managers - * in Rmax. It provides a template for iterators and a method to set the configuration. - */ -class IConfigManager { - public: - static constexpr uint16_t MAX_RMAX_MEMORY_REGIONS = 2; - - template - using ConstIterator = typename std::unordered_map::const_iterator; - - virtual ~IConfigManager() = default; - - /** - * @brief Sets the configuration. - * - * @param cfg The configuration object parsed from YAML. - * @param rmax_apps_lib Shared pointer to the RmaxAppsLibFacade. - * @return True if the configuration was successfully set, false otherwise. - */ - virtual bool set_configuration(const NetworkConfig& cfg, - std::shared_ptr rmax_apps_lib) = 0; -}; - -/** - * @brief Interface for RX configuration managers. - * - * The IRxConfigManager interface extends IConfigManager and defines additional operations - * specific to RX configuration managers in Rmax. - */ -class IRxConfigManager : public IConfigManager { - public: - using ConstIterator = IConfigManager::ConstIterator; - - /** - * @brief Gets the beginning iterator for RX configurations. - * - * @return The beginning iterator. - */ - virtual ConstIterator begin() const = 0; - - /** - * @brief Gets the ending iterator for RX configurations. - * - * @return The ending iterator. - */ - virtual ConstIterator end() const = 0; - - /** - * @brief Appends a candidate for RX queue configuration. - * - * @param port_id The port ID. - * @param q The RX queue configuration. - * @return True if the configuration was appended successfully, false otherwise. - */ - virtual bool append_candidate_for_rx_queue(uint16_t port_id, const RxQueueConfig& q) = 0; -}; - -/** - * @brief Interface for TX configuration managers. - * - * The ITxConfigManager interface extends IConfigManager and defines additional operations - * specific to TX configuration managers in Rmax. - */ -class ITxConfigManager : public IConfigManager { - public: - using ConstIterator = IConfigManager::ConstIterator; - - /** - * @brief Gets the beginning iterator for TX configurations. - * - * @return The beginning iterator. - */ - virtual ConstIterator begin() const = 0; - - /** - * @brief Gets the ending iterator for TX configurations. - * - * @return The ending iterator. - */ - virtual ConstIterator end() const = 0; - - /** - * @brief Appends a candidate for TX queue configuration. - * - * @param port_id The port ID. - * @param q The TX queue configuration. - * @return True if the configuration was appended successfully, false otherwise. - */ - virtual bool append_candidate_for_tx_queue(uint16_t port_id, const TxQueueConfig& q) = 0; -}; - -/** - * @brief Manages the RX configuration for Rmax. - * - * The RxConfigManager class is responsible for managing the configuration settings - * for RX queues in Rmax. It validates and appends RX queue configurations for given ports. - */ -class RxConfigManager : public IRxConfigManager { - public: - using ConstIterator = IConfigManager::ConstIterator; - - ConstIterator begin() const override { return rx_service_configs_.begin(); } - ConstIterator end() const override { return rx_service_configs_.end(); } - - /** - * @brief Sets the configuration for RX queues. - * - * @param cfg The configuration object parsed from YAML. - * @param rmax_apps_lib Shared pointer to the RmaxAppsLibFacade. - * @return True if the configuration was successfully set, false otherwise. - */ - bool set_configuration(const NetworkConfig& cfg, - std::shared_ptr rmax_apps_lib) override { - cfg_ = cfg; - rmax_apps_lib_ = rmax_apps_lib; - is_configuration_set_ = true; - return true; - } - - /** - * @brief Validates RX queue for Rmax Queue configuration. If valid, appends the RX queue for a - * given port. - * - * @param port_id The port ID. - * @param q The RX queue configuration. - * @return True if the configuration was appended successfully, false otherwise. - */ - bool append_candidate_for_rx_queue(uint16_t port_id, const RxQueueConfig& q) override; - - private: - /** - * @brief Sets the default configuration for an RX service. - * - * @param rx_service_cfg The RX service configuration to set defaults for. - */ - void set_default_config(ExtRmaxIPOReceiverConfig& rx_service_cfg) const; - - /** - * @brief Builds the Rmax IPO receiver configuration. - * - * @param rx_service_cfg The RX service configuration to build. - * @param rmax_rx_config The Rmax RX queue configuration. - * @param q The RX queue configuration. - * @return True if the configuration was successfully built, false otherwise. - */ - bool build_rmax_ipo_receiver_config(ExtRmaxIPOReceiverConfig& rx_service_cfg, - const RmaxRxQueueConfig& rmax_rx_config, - const RxQueueConfig& q); - - /** - * @brief Validates the RX queue configuration. - * - * @param rmax_rx_config The Rmax RX queue configuration to validate. - * @return True if the configuration is valid, false otherwise. - */ - bool validate_rx_queue_config(const RmaxRxQueueConfig& rmax_rx_config); - - /** - * @brief Configures the memory allocator for the RMAX RX queue. - * - * @param rmax_rx_config The RMAX RX queue configuration. - * @param q The RX queue configuration. - * @return true if the configuration is successful, false otherwise. - */ - bool config_memory_allocator(RmaxRxQueueConfig& rmax_rx_config, const RxQueueConfig& q); - - /** - * @brief Configures the memory allocator for a single memory region. - * The allocator will be used for both the header and payload memory. - * - * @param rmax_rx_config The RMAX RX queue configuration. - * @param q The RX queue configuration. - * @param mr The memory region. - * @return true if the configuration is successful, false otherwise. - */ - bool config_memory_allocator_from_single_mrs(RmaxRxQueueConfig& rmax_rx_config, - const RxQueueConfig& q, - const MemoryRegionConfig& mr); - - /** - * @brief Configures the memory allocator for dual memory regions. - * If GPU is in use, it will be used for the payload memory region, - * and the CPU allocator will be used for the header memory region. - * Otherwise, the function expects that the same allocator is configured - * for both memory regions. - * - * @param rmax_rx_config The RMAX RX queue configuration. - * @param q The RX queue configuration. - * @param mr_header The header memory region. - * @param mr_payload The payload memory region. - * @return true if the configuration is successful, false otherwise. - */ - - bool config_memory_allocator_from_dual_mrs(RmaxRxQueueConfig& rmax_rx_config, - const RxQueueConfig& q, - const MemoryRegionConfig& mr_header, - const MemoryRegionConfig& mr_payload); - /** - * @brief Sets the GPU memory configuration if applicable. - * - * @param rmax_rx_config The RMAX RX queue configuration. - * @param mr The memory region. - * @return true if the GPU memory configuration is set, false otherwise. - */ - bool set_gpu_is_in_use_if_applicable(RmaxRxQueueConfig& rmax_rx_config, - const MemoryRegionConfig& mr); - - /** - * @brief Sets the CPU memory configuration. - * - * @param rmax_rx_config The RMAX RX queue configuration. - */ - void set_gpu_is_not_in_use(RmaxRxQueueConfig& rmax_rx_config); - - /** - * @brief Sets the allocator type based on the memory region. - * - * @param rmax_rx_config The RMAX RX queue configuration. - * @param mr The memory region. - */ - void set_cpu_allocator_type(RmaxRxQueueConfig& rmax_rx_config, const MemoryRegionConfig& mr); - - /** - * @brief Validates the RX queue memory regions configuration. - * - * @param q The RX queue configuration. - * @param rmax_rx_config The Rmax RX queue configuration. - * @return True if the configuration is valid, false otherwise. - */ - bool validate_memory_regions_config(const RxQueueConfig& q, - const RmaxRxQueueConfig& rmax_rx_config); - - /** - * @brief Validates the RX queue memory regions configuration for a single memory region. - * - * @param q The RX queue configuration. - * @param rmax_rx_config The Rmax RX queue configuration. - * @param mr The memory region. - * @return True if the configuration is valid, false otherwise. - */ - bool validate_memory_regions_config_from_single_mrs(const RxQueueConfig& q, - const RmaxRxQueueConfig& rmax_rx_config, - const MemoryRegionConfig& mr); - - /** - * @brief Validates the RX queue memory regions configuration for dual memory regions. - * - * @param q The RX queue configuration. - * @param rmax_rx_config The Rmax RX queue configuration. - * @param mr_header The header memory region. - * @param mr_payload The payload memory region. - * @return True if the configuration is valid, false otherwise. - */ - bool validate_memory_regions_config_from_dual_mrs(const RxQueueConfig& q, - const RmaxRxQueueConfig& rmax_rx_config, - const MemoryRegionConfig& mr_header, - const MemoryRegionConfig& mr_payload); - - /** - * @brief Sets common application settings for an RX service. - * - * @param app_settings_config The application settings configuration to set. - * @param rmax_rx_config The Rmax RX queue configuration. - */ - void set_rx_service_common_app_settings(AppSettings& app_settings_config, - const RmaxRxQueueConfig& rmax_rx_config); - - /** - * @brief Sets the allocator type for the application settings. - * - * @param app_settings_config The application settings configuration to set. - * @param allocator_type The allocator type to set. - */ - void set_allocator_type(AppSettings& app_settings_config, const std::string& allocator_type); - - /** - * @brief Parses and sets the cores for the application settings. - * - * @param app_settings_config The application settings configuration to set. - * @param cores The cores to parse and set. - * @return True if the cores were successfully parsed and set, false otherwise. - */ - bool parse_and_set_cores(AppSettings& app_settings_config, const std::string& cores); - - /** - * @brief Sets the IPO receiver settings for an RX service. - * - * @param rx_service_cfg The RX service configuration to set. - * @param rmax_rx_config The Rmax RX queue configuration. - */ - void set_rx_service_ipo_receiver_settings(ExtRmaxIPOReceiverConfig& rx_service_cfg, - const RmaxRxQueueConfig& rmax_rx_config); - - /** - * @brief Adds a new RX service configuration. - * - * @param rx_service_cfg The RX service configuration to add. - * @param port_id The port ID. - * @param queue_id The queue ID. - */ - void add_new_rx_service_config(const ExtRmaxIPOReceiverConfig& rx_service_cfg, uint16_t port_id, - uint16_t queue_id); - - private: - std::unordered_map rx_service_configs_; - NetworkConfig cfg_; - std::shared_ptr rmax_apps_lib_ = nullptr; - bool is_configuration_set_ = false; -}; - -/** - * @brief Manages the TX configuration for Rmax. - * - * The TxConfigManager class is responsible for managing the configuration settings - * for TX queues in Rmax. It validates and appends TX queue configurations for given ports. - */ -class TxConfigManager : public ITxConfigManager { - public: - using ConstIterator = IConfigManager::ConstIterator; - - ConstIterator begin() const override { return tx_service_configs_.begin(); } - ConstIterator end() const override { return tx_service_configs_.end(); } - - /** - * @brief Sets the configuration for TX queues. - * - * @param cfg The configuration object parsed from YAML. - * @param rmax_apps_lib Shared pointer to the RmaxAppsLibFacade. - * @return True if the configuration was successfully set, false otherwise. - */ - bool set_configuration(const NetworkConfig& cfg, - std::shared_ptr rmax_apps_lib) override { - cfg_ = cfg; - rmax_apps_lib_ = rmax_apps_lib; - is_configuration_set_ = true; - return true; - } - - /** - * @brief Validates TX queue for Rmax Queue configuration. If valid, appends the TX queue for a - * given port. - * - * @param port_id The port ID. - * @param q The TX queue configuration. - * @return True if the configuration was appended successfully, false otherwise. - */ - bool append_candidate_for_tx_queue(uint16_t port_id, const TxQueueConfig& q) override; - - private: - std::unordered_map tx_service_configs_; - NetworkConfig cfg_; - std::shared_ptr rmax_apps_lib_ = nullptr; - bool is_configuration_set_ = false; -}; - -/** - * @brief Manages the configuration for Rmax. - * - * The RmaxConfigContainer class is responsible for parsing and managing the configuration - * settings for Rmax via dedicated configuration managers. - */ -class RmaxConfigContainer { - public: - enum class ConfigType { RX, TX }; - - /** - * @brief Constructs a new RmaxConfigContainer object. - * @param rmax_apps_lib Optional shared pointer to the RmaxAppsLibFacade. - */ - explicit RmaxConfigContainer(std::shared_ptr rmax_apps_lib = nullptr) - : rmax_apps_lib_(rmax_apps_lib) { - initialize_managers(); - } - - /** - * @brief Parses the configuration from the YAML file. - * - * This function iterates over the interfaces and their respective RX an TX queues - * defined in the configuration YAML, extracting and validating the necessary - * settings for each queue. It then populates the RX and TX service configuration - * structures with these settings. The parsing is done via dedicated configuration managers. - * - * @param cfg The configuration YAML. - * @return True if the configuration was successfully parsed, false otherwise. - */ - bool parse_configuration(const NetworkConfig& cfg); - - std::shared_ptr get_config_manager(ConfigType type) const { - auto it = config_managers_.find(type); - if (it != config_managers_.end()) { return it->second; } - return nullptr; - } - - /** - * @brief Gets the current log level for Rmax. - * - * @return The current log level. - */ - RmaxLogLevel::Level get_rmax_log_level() const { return rmax_log_level_; } - - private: - /** - * @brief Initializes the configuration managers. - * - * This function initializes the configuration managers for RX and TX services. - */ - void initialize_managers(); - - /** - * @brief Adds a configuration manager. - * - * This function adds a configuration manager for the specified type. - * - * @param type The type of configuration manager to add. - * @param config_manager The shared pointer to the configuration manager. - */ - void add_config_manager(ConfigType type, std::shared_ptr config_manager); - - /** - * @brief Parses the RX queues configuration. - * - * This function parses the configuration for RX queues for the specified port ID. - * - * @param port_id The port ID for which to parse the RX queues. - * @param queues The vector of RX queue configurations. - * @return An integer indicating the success or failure of the parsing operation. - */ - int parse_rx_queues(uint16_t port_id, const std::vector& queues); - - /** - * @brief Parses the TX queues configuration. - * - * This function parses the configuration for TX queues for the specified port ID. - * - * @param port_id The port ID for which to parse the TX queues. - * @param queues The vector of TX queue configurations. - * @return An integer indicating the success or failure of the parsing operation. - */ - int parse_tx_queues(uint16_t port_id, const std::vector& queues); - - /** - * @brief Sets the Rmax log level based on the provided advanced_network log level. - * - * This function converts the provided advanced_network log level to the corresponding - * Rmax log level and sets it as the current log level for Rmax. - * - * @param level The advanced_network log level to be converted and set. - */ - void set_rmax_log_level(LogLevel::Level level) { - rmax_log_level_ = RmaxLogLevel::from_adv_net_log_level(level); - } - - private: - RmaxLogLevel::Level rmax_log_level_ = RmaxLogLevel::OFF; - std::unordered_map> config_managers_; - std::shared_ptr rmax_apps_lib_ = nullptr; - NetworkConfig cfg_; - bool is_configured_ = false; -}; - -/** - * @brief Parses the configuration for Rmax. - * - * The RmaxConfigParser class is responsible for parsing the configuration settings for Rmax. - */ -class RmaxConfigParser { - public: - /** - * @brief Parses the RX queue configuration from a YAML node. - * - * This function extracts the RX queue configuration settings from the provided YAML node - * and populates the RxQueueConfig structure with the extracted values. - * - * @param q_item The YAML node containing the RX queue configuration. - * @param q The RxQueueConfig structure to be populated. - * @return Status indicating the success or failure of the operation. - */ - static Status parse_rx_queue_rivermax_config(const YAML::Node& q_item, RxQueueConfig& q); - - /** - * @brief Parses the TX queue Rivermax configuration. - * - * @param q_item The YAML node containing the queue item. - * @param q The TX queue configuration to be populated. - * @return Status indicating the success or failure of the operation. - */ - static Status parse_tx_queue_rivermax_config(const YAML::Node& q_item, TxQueueConfig& q); -}; - -} // namespace holoscan::advanced_network - -#endif // RMAX_CONFIG_MANAGER_H_ diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/stats_printer.h b/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/stats_printer.h deleted file mode 100644 index b937065cce..0000000000 --- a/operators/advanced_network/advanced_network/managers/rivermax/rmax_mgr_impl/stats_printer.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2023 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 STATS_PRINTER_H_ -#define STATS_PRINTER_H_ - -#include -#include -#include - -#include "advanced_network/manager.h" -#include "rmax_ipo_receiver_service.h" - -namespace holoscan::advanced_network { - -class IpoRxStatsPrinter { - public: - static constexpr double GIGABYTE = 1073741824.0; - static constexpr double MEGABYTE = 1048576.0; - - static void print_stream_stats(std::stringstream& ss, uint32_t stream_index, - IPORXStatistics stream_stats, - std::vector stream_path_stats) { - ss << "[stream_index " << std::setw(3) << stream_index << "]" - << " Got " << std::setw(7) << stream_stats.rx_counter << " packets | "; - - if (stream_stats.received_bytes >= GIGABYTE) { - ss << std::fixed << std::setprecision(2) << (stream_stats.received_bytes / GIGABYTE) - << " GB |"; - } else if (stream_stats.received_bytes >= MEGABYTE) { - ss << std::fixed << std::setprecision(2) << (stream_stats.received_bytes / MEGABYTE) - << " MB |"; - } else { - ss << stream_stats.received_bytes << " bytes |"; - } - - ss << " dropped: "; - for (uint32_t s_index = 0; s_index < stream_path_stats.size(); ++s_index) { - if (s_index > 0) { ss << ", "; } - ss << stream_path_stats[s_index].rx_dropped + stream_stats.rx_dropped; - } - ss << " |" - << " consumed: " << stream_stats.consumed_packets << " |" - << " unconsumed: " << stream_stats.unconsumed_packets << " |" - << " lost: " << stream_stats.rx_dropped << " |" - << " exceed MD: " << stream_stats.rx_exceed_md << " |" - << " bad RTP hdr: " << stream_stats.rx_corrupt_rtp_header << " | "; - - for (uint32_t s_index = 0; s_index < stream_path_stats.size(); ++s_index) { - if (stream_stats.rx_counter > 0) { - uint32_t number = static_cast( - floor(100 * static_cast(stream_path_stats[s_index].rx_count) / - static_cast(stream_stats.rx_counter))); - ss << " " << std::setw(3) << number << "%"; - } else { - ss << " 0%"; - } - } - ss << "\n"; - } - - /** - * @brief Prints the statistics of the Rmax manager. - */ - static void print_total_stats( - std::stringstream& ss, - std::unordered_map>& - rx_services) { - uint32_t stream_id = 0; - - ss << "RIVERMAX advanced_network Statistics\n"; - ss << "====================================\n"; - ss << "Total Statistics\n"; - ss << "----------------\n"; - for (const auto& entry : rx_services) { - uint32_t key = entry.first; - auto& rx_service = entry.second; - auto [stream_stats, path_stats] = rx_service->get_streams_statistics(); - for (uint32_t i = 0; i < stream_stats.size(); ++i) { - print_stream_stats(ss, stream_id++, stream_stats[i], path_stats[i]); - } - } - } -}; - -}; // namespace holoscan::advanced_network - -#endif // STATS_PRINTER_H diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/CMakeLists.txt b/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/CMakeLists.txt deleted file mode 100644 index 4effb035c7..0000000000 --- a/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/CMakeLists.txt +++ /dev/null @@ -1,82 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2023 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(rmax_service) - -add_library(${PROJECT_NAME} SHARED - rmax_base_service.cpp - rmax_ipo_receiver_service.cpp - ipo_receiver_io_node.cpp -) - -target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) - -#get_target_property(CORE_COMPILE_FEATURES holoscan::core INTERFACE_COMPILE_FEATURES) -#message("Holoscan Core compile features: ${CORE_COMPILE_FEATURES}") -#target_compile_features(${PROJECT_NAME} PUBLIC ${CORE_COMPILE_FEATURES}) - - -target_compile_features(${PROJECT_NAME} -PUBLIC cxx_std_17 -) -target_compile_definitions(${PROJECT_NAME} PUBLIC RMAX_APPS_LIB_FLAT_STRUCTURE) - -set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) - - -# add rmax_apps_lib with absolute path as a library -if(NOT DEFINED RAL_DIR_PATH) - set(RAL_DIR_PATH "/opt/rivermax_sdk/rmax_apps_lib" CACHE STRING "Rmax Apps Lib absolute path") -endif() - -set(RMAX_CUDA TRUE) - -# Check if the environment variable RMAX_TEGRA is set -if(NOT DEFINED ENV{RMAX_TEGRA}) - message(STATUS "Environment variable RMAX_TEGRA was not defined. Defaulting to FALSE") - set(RMAX_TEGRA FALSE) -else() - # Convert the environment variable to CMake boolean TRUE or FALSE - if("$ENV{RMAX_TEGRA}" STREQUAL "TRUE" OR "$ENV{RMAX_TEGRA}" STREQUAL "ON") - set(RMAX_TEGRA TRUE) - else() - set(RMAX_TEGRA FALSE) - endif() -endif() - -unset(RMAX_TEGRA CACHE) -message(STATUS "RMAX_TEGRA is set to: ${RMAX_TEGRA}") - -if(RMAX_TEGRA) - target_compile_definitions(${PROJECT_NAME} PUBLIC RMAX_TEGRA=1) -else() - target_compile_definitions(${PROJECT_NAME} PUBLIC RMAX_TEGRA=0) -endif() - -add_subdirectory(${RAL_DIR_PATH} ${CMAKE_BINARY_DIR}/external_build) - - -target_link_libraries(${PROJECT_NAME} PUBLIC rmax-ral-build rmax-ral-lib) - -install(TARGETS ${PROJECT_NAME} COMPONENT advanced_network-rivermax) - -#find_package(fmt) - -#if(fmt_FOUND) -# target_link_libraries(${PROJECT_NAME} PUBLIC fmt::fmt) -#else() -# message(FATAL_ERROR "fmt library not found") -#endif() diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/ipo_chunk_consumer_base.h b/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/ipo_chunk_consumer_base.h deleted file mode 100644 index 55ccdc53a4..0000000000 --- a/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/ipo_chunk_consumer_base.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2023 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 RMAX_APPS_LIB_SERVICES_IPO_CHUNK_CONSUMER_BASE_H_ -#define RMAX_APPS_LIB_SERVICES_IPO_CHUNK_CONSUMER_BASE_H_ - -#include -#include -#include -#include -#include -#include - -#include - -#include "api/rmax_apps_lib_api.h" - -using namespace ral::lib::core; -using namespace ral::lib::services; - -namespace ral { -namespace io_node { - -/** - * @brief: IIPOChunkConsumer class. - * - * The interface to the IPO Chunk Consumer Class. - */ -class IIPOChunkConsumer { - public: - virtual ~IIPOChunkConsumer() = default; - /** - * @brief Consumes and processes packets from a given chunk. - * - * This function processes the packets contained in the provided chunk and returns a tuple - * containing the return status, the number of consumed packets, and the number of unconsumed - * packets. - * - * @param chunk Reference to the IPOReceiveChunk containing the packets. - * @param stream Reference to the IPOReceiveStream associated with the chunk. - * @return std::tuple containing the return status, the number of - * consumed packets, and the number of unconsumed packets. - */ - virtual std::tuple consume_chunk_packets( - IPOReceiveChunk& chunk, IPOReceiveStream& stream) = 0; -}; - -} // namespace io_node -} // namespace ral - -#endif /* RMAX_APPS_LIB_SERVICES_IPO_CHUNK_CONSUMER_BASE_H_ */ diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/ipo_receiver_io_node.cpp b/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/ipo_receiver_io_node.cpp deleted file mode 100644 index 5ee18053b7..0000000000 --- a/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/ipo_receiver_io_node.cpp +++ /dev/null @@ -1,483 +0,0 @@ -/* - * 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 - -#include "rt_threads.h" - -#ifdef RMAX_APPS_LIB_FLAT_STRUCTURE -#include "ipo_receiver_io_node.h" -#include "ipo_chunk_consumer_base.h" -#else -#include "receivers/ipo_receiver_io_node.h" -#include "receivers/ipo_chunk_consumer_base.h" -#endif - -#include "services/utils/cpu.h" - -#define BREAK_ON_FAILURE(rc) \ - if (unlikely(rc == ReturnStatus::failure)) { break; } -#define BREAK_ON_NO_SUCCESS(rc) \ - if (unlikely(rc != ReturnStatus::success)) { break; } - -using namespace ral::io_node; -using namespace ral::lib::core; -using namespace ral::lib::services; - -namespace { -static constexpr uint8_t RTP_HEADER_CSRC_GRANULARITY_BYTES = 4; -static constexpr uint32_t SEQUENCE_NUMBER_MASK_16BIT = 0xFFFF; -static constexpr uint32_t SEQUENCE_NUMBER_MASK_32BIT = 0xFFFFFFFF; -} // namespace - -AppIPOReceiveStream::AppIPOReceiveStream(size_t id, const ipo_stream_settings_t& settings, - bool extended_sequence_number, - const std::vector& paths) - : IPOReceiveStream(id, settings, paths, extended_sequence_number), - m_is_extended_sequence_number(extended_sequence_number), - m_sequence_number_mask((extended_sequence_number) ? SEQUENCE_NUMBER_MASK_32BIT - : SEQUENCE_NUMBER_MASK_16BIT) { - m_path_stats.resize(paths.size()); - m_path_packets.resize(settings.num_of_packets_in_chunk, std::vector(paths.size(), 0)); - m_path_stats_totals.resize(paths.size()); -} - -ReturnStatus AppIPOReceiveStream::get_next_chunk(IPOReceiveChunk* chunk) { - ReturnStatus status = IPOReceiveStream::get_next_chunk(chunk); - if (status == ReturnStatus::success) { - m_statistic.rx_counter += chunk->get_completion_chunk_size(); - m_statistic_totals.rx_counter += chunk->get_completion_chunk_size(); - - const auto packet_info_array = chunk->get_completion_info_ptr(); - for (uint32_t stride_index = 0; stride_index < chunk->get_completion_chunk_size(); - ++stride_index) { - auto sub_block_size_0 = packet_info_array[stride_index].get_packet_sub_block_size(0); - m_statistic.received_bytes += sub_block_size_0; - m_statistic_totals.received_bytes += sub_block_size_0; - if (m_header_data_split) { - auto sub_block_size_1 = packet_info_array[stride_index].get_packet_sub_block_size(1); - m_statistic.received_bytes += sub_block_size_1; - m_statistic_totals.received_bytes += sub_block_size_1; - } - } - } - return status; -} - -void AppIPOReceiveStream::print_statistics( - std::ostream& out, const std::chrono::high_resolution_clock::duration& duration) const { - std::stringstream ss; - double duration_sec = - static_cast(std::chrono::duration_cast(duration).count()) / - 1.e6; - - ss << "[stream_index " << std::setw(3) << get_id() << "]" - << " Got " << std::setw(7) << m_statistic.rx_counter << " packets during " << std::fixed - << std::setprecision(1) << std::setw(4) << duration_sec << " sec | "; - - ss << std::fixed << std::setprecision(2); - double bitrate_Mbps = m_statistic.get_bitrate_Mbps(); - - if (bitrate_Mbps > 1000.) { - ss << std::setw(4) << (bitrate_Mbps / 1000.) / duration_sec << " Gbps |"; - } else { - ss << std::setw(4) << bitrate_Mbps / duration_sec << " Mbps |"; - } - - ss << " dropped: "; - for (uint32_t s_index = 0; s_index < m_paths.size(); ++s_index) { - if (s_index > 0) { ss << ", "; } - ss << m_path_stats[s_index].rx_dropped + m_statistic.rx_dropped; - } - ss << " |" - << " consumed: " << m_statistic.consumed_packets << " |" - << " unconsumed: " << m_statistic.unconsumed_packets << " |" - << " lost: " << m_statistic.rx_dropped << " |" - << " exceed MD: " << m_statistic.rx_exceed_md << " |" - << " bad RTP hdr: " << m_statistic.rx_corrupt_rtp_header << " | "; - - for (uint32_t s_index = 0; s_index < m_paths.size(); ++s_index) { - ss << " | " << m_paths[s_index].flow.get_destination_ip() << ":" - << m_paths[s_index].flow.get_destination_port(); - if (m_statistic.rx_counter) { - uint32_t number = - static_cast(floor(100 * static_cast(m_path_stats[s_index].rx_count) / - static_cast(m_statistic.rx_counter))); - ss << " " << std::setw(3) << number << "%"; - } else { - ss << " 0%"; - } - } - - if (m_statistic.rx_dropped) { - ss << std::endl << "ERROR !!! Lost Packets - count: " << m_statistic.rx_dropped; - } - if (m_statistic.rx_corrupt_rtp_header) { - ss << std::endl << "ERROR !!! Corrupted Packets - count: " << m_statistic.rx_corrupt_rtp_header; - } - - out << ss.str() << std::endl; -} - -void AppIPOReceiveStream::reset_statistics() { - for (auto& stat : m_path_stats) { stat.reset(); } - m_statistic.reset(); -} - -void AppIPOReceiveStream::reset_statistics_totals() { - for (auto& stat : m_path_stats_totals) { stat.reset(); } - m_statistic_totals.reset(); -} - -std::pair> AppIPOReceiveStream::get_statistics() - const { - return {m_statistic_totals, m_path_stats_totals}; -} - -void AppIPOReceiveStream::handle_corrupted_packet(size_t index, - const ReceivePacketInfo& packet_info) { - IPOReceiveStream::handle_corrupted_packet(index, packet_info); - - ++m_statistic.rx_corrupt_rtp_header; - ++m_statistic_totals.rx_corrupt_rtp_header; -} - -void AppIPOReceiveStream::handle_late_packet(size_t index, uint32_t sequence_number, - const ReceivePacketInfo& packet_info) { - IPOReceiveStream::handle_late_packet(index, sequence_number, packet_info); - - ++m_statistic.rx_exceed_md; - ++m_statistic_totals.rx_exceed_md; -} - -void AppIPOReceiveStream::handle_packet(size_t index, uint32_t sequence_number, - const ReceivePacketInfo& packet_info) { - IPOReceiveStream::handle_packet(index, sequence_number, packet_info); - - auto& by_paths = m_path_packets.at(sequence_number % get_sequence_number_wrap_around()); - - for (size_t i = 0; i < by_paths.size(); ++i) { by_paths[i] = (i == index) ? 1 : 0; } -} - -void AppIPOReceiveStream::handle_redundant_packet(size_t index, uint32_t sequence_number, - const ReceivePacketInfo& packet_info) { - IPOReceiveStream::handle_redundant_packet(index, sequence_number, packet_info); - - auto& by_paths = m_path_packets.at(sequence_number % get_sequence_number_wrap_around()); - - by_paths[index] = 1; -} - -void AppIPOReceiveStream::complete_packet(uint32_t sequence_number) { - IPOReceiveStream::complete_packet(sequence_number); - - auto& by_paths = m_path_packets.at(sequence_number % get_sequence_number_wrap_around()); - - for (size_t i = 0; i < by_paths.size(); ++i) { - m_path_stats[i].rx_count += by_paths[i]; - m_path_stats[i].rx_dropped += 1 - by_paths[i]; - m_path_stats_totals[i].rx_count += by_paths[i]; - m_path_stats_totals[i].rx_dropped += 1 - by_paths[i]; - } - - // count dropped packets by sequence number - if (m_initialized) { - uint32_t expected = m_last_sequence_number + 1; - uint32_t num_dropped = (sequence_number - expected) & get_sequence_number_mask(); - m_statistic.rx_dropped += num_dropped; - m_statistic_totals.rx_dropped += num_dropped; - } - m_initialized = true; - m_last_sequence_number = sequence_number; -} - -void AppIPOReceiveStream::handle_sender_restart() { - std::cout << "Sender restart detected" << std::endl; - m_initialized = false; -} - -bool AppIPOReceiveStream::get_sequence_number(const byte_t* header, size_t length, - uint32_t& sequence_number) const { - if (length < 4 || (header[0] & 0xC0) != 0x80) { return false; } - - sequence_number = header[3] | header[2] << 8; - if (m_is_extended_sequence_number) { - uint8_t cc = 0x0F & header[0]; - uint8_t offset = cc * RTP_HEADER_CSRC_GRANULARITY_BYTES; - if (length < offset + 14) { return false; } - sequence_number |= (header[offset + 12] << 24) | (header[offset + 13] << 16); - } - return true; -} - -void AppIPOReceiveStream::update_consumed_packets_stats(size_t packets_consumed, - size_t packets_unconsumed) { - m_statistic.consumed_packets += packets_consumed; - m_statistic.unconsumed_packets += packets_unconsumed; - m_statistic_totals.consumed_packets += packets_consumed; - m_statistic_totals.unconsumed_packets += packets_unconsumed; -} - -IPOReceiverIONode::IPOReceiverIONode(const AppSettings& app_settings, - uint64_t max_path_differential_us, - bool extended_sequence_number, size_t max_chunk_size, - const std::vector& devices, size_t index, - int cpu_core_affinity, IIPOChunkConsumer* chunk_consumer) - : m_app_settings(app_settings), - m_is_extended_sequence_number(extended_sequence_number), - m_devices(devices), - m_index(index), - m_print_parameters(app_settings.print_parameters), - m_cpu_core_affinity(cpu_core_affinity), - m_sleep_between_operations( - std::chrono::microseconds(app_settings.sleep_between_operations_us)), - m_chunk_consumer(chunk_consumer) { - m_stream_settings.stream_options.insert(RMX_INPUT_STREAM_CREATE_INFO_PER_PACKET); - m_stream_settings.packet_payload_size = m_app_settings.packet_payload_size; - m_stream_settings.packet_app_header_size = m_app_settings.packet_app_header_size; - m_stream_settings.num_of_packets_in_chunk = m_app_settings.num_of_packets_in_chunk; - // m_stream_settings.max_chunk_size = DEFAULT_MAX_CHUNK_SIZE; - m_stream_settings.max_chunk_size = max_chunk_size; - m_stream_settings.max_path_differential_us = max_path_differential_us; -} - -std::ostream& IPOReceiverIONode::print(std::ostream& out) const { - out << "+#############################################\n" - << "| Sender index: " << m_index << "\n" - << "| Thread ID: 0x" << std::hex << std::this_thread::get_id() << std::dec << "\n" - << "| CPU core affinity: " << m_cpu_core_affinity << "\n" - << "| Number of streams in this thread: " << m_streams.size() << "\n" - << "+#############################################\n"; - for (const auto& stream : m_streams) { stream->print(out); } - return out; -} - -std::pair, std::vector>> -IPOReceiverIONode::get_streams_statistics() const { - std::vector streams_stats; - std::vector> streams_path_stats; - - // Populate streams_stats and streams_path_stats - for (const auto& stream : m_streams) { - auto [stream_stats, stream_path_stats] = stream->get_statistics(); - streams_stats.push_back(stream_stats); - streams_path_stats.push_back(stream_path_stats); - } - - return {streams_stats, streams_path_stats}; -} - -void IPOReceiverIONode::initialize_streams(size_t start_id, - const std::vector>& flows) { - size_t id = start_id; - m_streams.reserve(flows.size()); - for (const auto& flow_list : flows) { - std::vector paths; - - paths.reserve(flow_list.size()); - assert(flow_list.size() == m_devices.size()); - for (size_t i = 0; i < flow_list.size(); ++i) { - paths.emplace_back(m_devices[i], flow_list[i]); - } - m_streams.emplace_back(new AppIPOReceiveStream( - id, m_stream_settings, m_is_extended_sequence_number, std::move(paths))); - ++id; - } -} - -void IPOReceiverIONode::print_parameters() { - if (!m_print_parameters) { return; } - - std::stringstream receiver_parameters; - print(receiver_parameters); - std::cout << receiver_parameters.str() << std::endl; -} - -void IPOReceiverIONode::print_statistics_settings(bool print_stats, uint32_t print_interval_ms) { - m_print_stats = print_stats; - m_print_interval_ms = print_interval_ms; -} - -ReturnStatus IPOReceiverIONode::sync_streams() { - ReturnStatus rc = ReturnStatus::success; - for (auto& stream : m_streams) { - rc = stream->sync_paths(); - BREAK_ON_FAILURE(rc); - } - return rc; -} - -ReturnStatus IPOReceiverIONode::wait_first_packet() { - IPOReceiveChunk chunk(m_stream_settings.packet_app_header_size != 0); - ReturnStatus rc = ReturnStatus::success; - bool initialized = false; - while (likely(!initialized && rc != ReturnStatus::failure && - SignalHandler::get_received_signal() < 0)) { - for (auto& stream : m_streams) { - rc = stream->get_next_chunk(&chunk); - BREAK_ON_FAILURE(rc); - if (chunk.get_completion_chunk_size() > 0) { - initialized = true; - if (m_chunk_consumer) { - auto [status, packets_consumed, packets_unconsumed] = - m_chunk_consumer->consume_chunk_packets(chunk, *(stream.get())); - stream->update_consumed_packets_stats(packets_consumed, packets_unconsumed); - } - break; - } - } - } - return rc; -} - -void IPOReceiverIONode::operator()() { - set_cpu_resources(); - ReturnStatus rc = create_streams(); - if (rc == ReturnStatus::failure) { - std::cerr << "Failed to create receiver (" << m_index << ") streams" << std::endl; - return; - } - rc = attach_flows(); - if (rc == ReturnStatus::failure) { - std::cerr << "Failed to attach flows to receiver (" << m_index << ") streams" << std::endl; - return; - } - print_parameters(); - - IPOReceiveChunk chunk(m_stream_settings.packet_app_header_size != 0); - rc = ReturnStatus::success; - - rc = sync_streams(); - if (rc == ReturnStatus::failure) { std::cerr << "Error during initial sync" << std::endl; } - start(); - rc = wait_first_packet(); - if (rc == ReturnStatus::failure) { - std::cerr << "Error during waiting for a first packet" << std::endl; - } - // main receive loop - auto start_time = std::chrono::high_resolution_clock::now(); - - while (likely(rc != ReturnStatus::failure && SignalHandler::get_received_signal() < 0)) { - for (auto& stream : m_streams) { - rc = stream->get_next_chunk(&chunk); - if (unlikely(rc == ReturnStatus::failure)) { - std::cerr << "Error getting next chunk of packets" << std::endl; - break; - } - if (m_chunk_consumer) { - auto [status, packets_consumed, packets_unconsumed] = - m_chunk_consumer->consume_chunk_packets(chunk, *(stream.get())); - stream->update_consumed_packets_stats(packets_consumed, packets_unconsumed); - BREAK_ON_NO_SUCCESS(status); - } - } - - if (m_print_stats) { - auto now = std::chrono::high_resolution_clock::now(); - auto duration = now - start_time; - if (duration >= std::chrono::milliseconds(m_print_interval_ms)) { - for (auto& stream : m_streams) { - stream->print_statistics(std::cout, duration); - stream->reset_statistics(); - } - start_time = now; - } - } - if (m_sleep_between_operations.count() > 0) { - std::this_thread::sleep_for(m_sleep_between_operations); - } - } - - rc = destroy_streams(); - if (rc == ReturnStatus::failure) { - std::cerr << "Failed to destroy sender (" << m_index << ") streams" << std::endl; - return; - } -} - -ReturnStatus IPOReceiverIONode::create_streams() { - ReturnStatus rc; - - for (auto& stream : m_streams) { - rc = stream->create_stream(); - if (rc == ReturnStatus::failure) { - std::cerr << "Failed to create stream (" << stream->get_id() << ")" << std::endl; - return rc; - } - } - - return ReturnStatus::success; -} - -ReturnStatus IPOReceiverIONode::attach_flows() { - ReturnStatus rc; - - for (auto& stream : m_streams) { - rc = stream->attach_flow(); - if (rc == ReturnStatus::failure) { - std::cerr << "Failed to attach flow to stream (" << stream->get_id() << ")" << std::endl; - return rc; - } - } - - return ReturnStatus::success; -} - -void IPOReceiverIONode::start() { - for (auto& stream : m_streams) { stream->start(); } -} - -ReturnStatus IPOReceiverIONode::detach_flows() { - ReturnStatus rc; - - for (auto& stream : m_streams) { - rc = stream->detach_flow(); - if (rc == ReturnStatus::failure) { - std::cerr << "Failed to detach flow from stream (" << stream->get_id() << ")" << std::endl; - return rc; - } - } - - return ReturnStatus::success; -} - -ReturnStatus IPOReceiverIONode::destroy_streams() { - ReturnStatus rc; - - for (auto& stream : m_streams) { - rc = stream->destroy_stream(); - if (rc == ReturnStatus::failure) { - std::cerr << "Failed to destroy stream (" << stream->get_id() << ")" << std::endl; - return rc; - } - } - - return ReturnStatus::success; -} - -void IPOReceiverIONode::set_cpu_resources() { - set_current_thread_affinity(m_cpu_core_affinity); - rt_set_thread_priority(RMAX_THREAD_PRIORITY_TIME_CRITICAL - 1); -} diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/ipo_receiver_io_node.h b/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/ipo_receiver_io_node.h deleted file mode 100644 index 4b99e4a8c3..0000000000 --- a/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/ipo_receiver_io_node.h +++ /dev/null @@ -1,431 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2023 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 RMAX_APPS_LIB_IO_NODE_RECEIVERS_IPO_RECEIVER_IO_NODE_H_ - -#include -#include -#include -#include -#include -#include - -#include - -#include "api/rmax_apps_lib_api.h" -#include "ipo_chunk_consumer_base.h" - -using namespace ral::lib::core; -using namespace ral::lib::services; - -namespace ral { -namespace io_node { - -/** - * @brief: Receiving statistics struct. - * - * This struct will hold run time statistics of a stream. - */ -struct IPORXStatistics { - size_t rx_counter = 0; - size_t rx_dropped = 0; - size_t rx_corrupt_rtp_header = 0; - size_t rx_exceed_md = 0; - size_t received_bytes = 0; - size_t consumed_packets = 0; - size_t unconsumed_packets = 0; - - /** - * @brief: Resets values to zero. - */ - void reset() { - rx_counter = 0; - rx_dropped = 0; - rx_corrupt_rtp_header = 0; - rx_exceed_md = 0; - received_bytes = 0; - consumed_packets = 0; - unconsumed_packets = 0; - } - - /** - * @return: Bitrate in Mbits per second. - */ - double get_bitrate_Mbps() const { return ((received_bytes * 8) / 1.e6); } -}; - -/** - * @brief: Receives path statistics. - */ -struct IPOPathStatistics { - uint32_t rx_count = 0; - uint32_t rx_dropped = 0; - - /** - * @brief: Resets values to zero. - */ - void reset() { - rx_count = 0; - rx_dropped = 0; - } -}; - -/** - * @brief: Application IPO receive stream specialized to parse RTP streams. - * - * This class implements and extends @ref ral::lib::core::IPOReceiveStream operations. - */ -class AppIPOReceiveStream : public IPOReceiveStream { - private: - const bool m_is_extended_sequence_number; - const uint32_t m_sequence_number_mask; - - IPORXStatistics m_statistic; - std::vector m_path_stats; - IPORXStatistics m_statistic_totals; - std::vector m_path_stats_totals; - std::vector> m_path_packets; - bool m_initialized = false; - uint32_t m_last_sequence_number = 0; - - public: - /** - * @brief: Constructs Inline Packet Ordering stream wrapper. - * - * @param [in] id: Stream identifier. - * @param [in] settings: Stream settings. - * @param [in] extended_sequence_number: Parse extended sequence number. - * @param [in] paths: List of redundant data receive paths. - */ - AppIPOReceiveStream(size_t id, const ipo_stream_settings_t& settings, - bool extended_sequence_number, const std::vector& paths); - virtual ~AppIPOReceiveStream() = default; - - /** - * @brief: Prints stream statistics. - * - * @param [out] out: Output stream to print statistics to. - * @param [in] duration: Statistics interval duration. - */ - void print_statistics(std::ostream& out, - const std::chrono::high_resolution_clock::duration& duration) const; - /** - * @brief: Resets statistics. - */ - void reset_statistics(); - /** - * @brief: Resets totals statistics. - */ - void reset_statistics_totals(); - /** - * @brief: Receives next chunk from input stream. - * - * @param [out] chunk: Pointer to the returned chunk structure. - * - * @return: Status of the operation: - * @ref ral::lib::services::ReturnStatus::success - In case of success. - * @ref ral::lib::services::ReturnStatus::signal_received - If operation was interrupted - * by an OS signal. - * @ref ral::lib::services::ReturnStatus::failure - In case of failure, Rivermax status - * will be logged. - */ - ReturnStatus get_next_chunk(IPOReceiveChunk* chunk) final; - /** - * @brief: Returns stream statistics. - * - * @return: Stream statistics. - */ - std::pair> get_statistics() const; - - /** - * @brief Updates the statistics for consumed and unconsumed packets. - * - * This function updates the internal statistics for the number of packets that have been - * consumed and unconsumed. It is used to keep track of packet processing within the system. - * - * @param packets_consumed The number of packets that have been successfully consumed. - * @param packets_unconsumed The number of packets that have not been consumed. - */ - void update_consumed_packets_stats(size_t packets_consumed, size_t packets_unconsumed); - - private: - /** - * @brief: Handles packet with corrupt RTP header. - * - * @param [in] index: Redundant stream index (0-based). - * @param [in] packet_info: Detailed packet information. - */ - void handle_corrupted_packet(size_t index, const ReceivePacketInfo& packet_info) final; - /** - * @brief: Handles packet that arrived too late. - * - * @param [in] index: Redundant stream index (0-based). - * @param [in] sequence_number: RTP sequence number. - * @param [in] packet_info: Detailed packet information. - */ - virtual void handle_late_packet(size_t index, uint32_t sequence_number, - const ReceivePacketInfo& packet_info); - /** - * @brief: Handles received packet. - * - * This function is called only for the first packet, for redundant packets - * copies received from another streams @ref handle_redundant_packet will - * be called. - * - * @param [in] index: Redundant stream index (0-based). - * @param [in] sequence_number: Sequence number. - * @param [in] packet_info: Detailed packet information. - */ - void handle_packet(size_t index, uint32_t sequence_number, - const ReceivePacketInfo& packet_info) final; - /** - * @brief: Handles received redundant packet. - * - * This function is called only for redundant packet(s), for the first - * received packet @ref handle_packet will be called. - * - * @param [in] index: Redundant stream index (0-based). - * @param [in] sequence_number: Sequence number. - * @param [in] packet_info: Detailed packet information. - */ - void handle_redundant_packet(size_t index, uint32_t sequence_number, - const ReceivePacketInfo& packet_info) final; - /** - * @brief: Handles packet before returning it to caller. - * - * This function is called when packet is transferred from cache buffer to - * the caller. - * - * @param [in] sequence_number: Sequence number. - */ - void complete_packet(uint32_t sequence_number) final; - /** - * @brief: Handles sender restart. - * - * This function is called once receiver detects that the sender restarted streaming. - */ - virtual void handle_sender_restart(); - - protected: - /** - * @brief: Extracts sequence number from RTP packet and (if needed) payload header. - * - * If @ref m_is_extended_sequence_number is set then parse the 16 high - * order bits of the extended 32-bit sequence number from the start of RTP - * payload. - * - * @param [in] header: Pointer to start of RTP header. - * @param [in] length: Header length. - * @param [out] sequence_number: Sequence number. - * - * @return: true if packet header is valid. - */ - bool get_sequence_number(const byte_t* header, size_t length, - uint32_t& sequence_number) const final; - /** - * @brief: Gets sequence number mask. - * - * @return: Sequence number mask. - */ - uint32_t get_sequence_number_mask() const final { return m_sequence_number_mask; } -}; - -/** - * @brief: IPOReceiverIONode class. - * - * This class implements the required operations in order to be a IPO receiver. - * The sender class will be the context that will be run under a std::thread by - * overriding the operator (). Each receiver will be able to run multiple - * streams. - */ -class IPOReceiverIONode { - private: - static constexpr size_t DEFAULT_MAX_CHUNK_SIZE = 1024; - const AppSettings m_app_settings; - const bool m_is_extended_sequence_number; - const std::vector m_devices; - const size_t m_index; - const bool m_print_parameters; - const int m_cpu_core_affinity; - const std::chrono::microseconds m_sleep_between_operations; - - ipo_stream_settings_t m_stream_settings; - std::vector> m_streams; - - IIPOChunkConsumer* m_chunk_consumer = nullptr; - bool m_print_stats = true; - uint32_t m_print_interval_ms = 1000; - - public: - /** - * @brief: IPOReceiverNode constructor. - * - * @param [in] app_settings: Application settings. - * @param [in] max_path_differential_us: Maximum Path Differential value. - * @param [in] extended_sequence_number: Parse extended sequence number. - * @param [in] devices: List of NICs to receive data. - * @param [in] index: Receiver index. - * @param [in] cpu_core_affinity: CPU core affinity the sender will run on. - */ - IPOReceiverIONode(const AppSettings& app_settings, uint64_t max_path_differential_us, - bool extended_sequence_number, size_t max_chunk_size, - const std::vector& devices, size_t index, int cpu_core_affinity, - IIPOChunkConsumer* chunk_consumer = nullptr); - - virtual ~IPOReceiverIONode() = default; - /** - * @brief: Returns receiver's streams container. - * - * @return: Receiver's streams container. - */ - std::vector>& get_streams() { return m_streams; } - /** - * @brief: Prints receiver's parameters to a output stream. - * - * The method prints the parameters of the receiver to be shown to the user - * to a output stream. - * - * @param [out] out: Output stream parameter print to. - * - * @return: Output stream. - */ - std::ostream& print(std::ostream& out) const; - /** - * @brief: Overrides operator << for @ref ral::io_node::IPOReceiverIONode reference. - */ - friend std::ostream& operator<<(std::ostream& out, const IPOReceiverIONode& receiver) { - receiver.print(out); - return out; - } - /** - * @brief: Overrides operator << for @ref ral::io_node::IPOReceiverIONode pointer. - */ - friend std::ostream& operator<<(std::ostream& out, IPOReceiverIONode* receiver) { - receiver->print(out); - return out; - } - /** - * @brief: Initializes receive streams. - * - * @param [in] start_id: Starting identifier for streams list. - * @param [in] flows: Vector of vectors of flows to be received by streams. - * Each item in outer level vector must contain vector - * of the same number of items as in devices list passed - * into constructor. Each item in the inner vector will - * be mapped to a corresponding device. - */ - void initialize_streams(size_t start_id, const std::vector>& flows); - /** - * @brief: Prints receiver's parameters. - * - * @note: The information will be printed if the receiver was initialized with - * @ref app_settings->print_parameters parameter of set to true. - */ - void print_parameters(); - /** - * @brief: Gets receiver index. - * - * @return: Receiver index. - */ - size_t get_index() const { return m_index; } - /** - * @brief: Receiver's worker. - * - * This method is the worker method of the std::thread will run with this - * object as it's context. The user of @ref ral::io_node::IPOReceiverIONode - * class can initialize the object in advance and run std::thread when - * needed. - */ - void operator()(); - - /** - * @brief: Prints statistics settings. - * - * @param [in] print_stats: Print statistics flag. - * @param [in] print_interval_ms: Print interval in milliseconds. - */ - void print_statistics_settings(bool print_stats, uint32_t print_interval_ms); - - /** - * @brief: Returns streams statistics. - * - * @return: Pair of vectors of statistics. First vector contains stream - * statistics, second vector contains path statistics. - */ - std::pair, std::vector>> - get_streams_statistics() const; - - private: - /** - * @brief: Creates receiver's streams. - * - * This method is responsible to go over receiver's stream objects and - * create the appropriate Rivermax streams. - * - * @return: Status of the operation. - */ - ReturnStatus create_streams(); - /** - * @brief: Attaches flows to receiver's streams. - * - * @return: Status of the operation. - */ - ReturnStatus attach_flows(); - /** - * @brief: Sync all streams. - * - * Flushes input buffers of all streams. - */ - ReturnStatus sync_streams(); - /** - * @brief: Start all streams. - */ - void start(); - /** - * @brief: Wait for a first input packet. - * - * @return: Status code. - */ - ReturnStatus wait_first_packet(); - /** - * @brief: Detaches flows from receiver's streams. - * - * @return: Status of the operation. - */ - ReturnStatus detach_flows(); - /** - * @brief: Destroys receiver's streams. - * - * This method is responsible to go over receiver's stream objects and - * destroy the appropriate Rivermax stream. - * - * @return: Status of the operation. - */ - ReturnStatus destroy_streams(); - /** - * @brief: Sets CPU related resources. - * - * This method is responsible to set sender's priority and CPU core affinity. - */ - void set_cpu_resources(); -}; - -} // namespace io_node -} // namespace ral - -#define RMAX_APPS_LIB_IO_NODE_RECEIVERS_IPO_RECEIVER_IO_NODE_H_ -#endif /* RMAX_APPS_LIB_IO_NODE_RECEIVERS_IPO_RECEIVER_IO_NODE_H_ */ diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/rmax_base_service.cpp b/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/rmax_base_service.cpp deleted file mode 100644 index 9ba9d0709f..0000000000 --- a/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/rmax_base_service.cpp +++ /dev/null @@ -1,218 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2023 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 "rt_threads.h" - -#include "rmax_base_service.h" -#include "api/rmax_apps_lib_api.h" - -using namespace ral::services; -using namespace ral::lib::services; - -namespace { -static const std::map UI_ALLOCATOR_TYPE_MAP{ - {AllocatorTypeUI::Auto, AllocatorType::HugePageDefault}, - {AllocatorTypeUI::HugePageDefault, AllocatorType::HugePageDefault}, - {AllocatorTypeUI::Malloc, AllocatorType::Malloc}, - {AllocatorTypeUI::HugePage2MB, AllocatorType::HugePage2MB}, - {AllocatorTypeUI::HugePage512MB, AllocatorType::HugePage512MB}, - {AllocatorTypeUI::HugePage1GB, AllocatorType::HugePage1GB}}; -} - -class MemoryAllocatorFactory { - public: - static std::shared_ptr getAllocator(AllocatorType type, - std::shared_ptr app_settings); - - private: - static std::map> s_allocators; - static std::mutex s_mutex; -}; - -std::map> MemoryAllocatorFactory::s_allocators; -std::mutex MemoryAllocatorFactory::s_mutex; - -std::shared_ptr MemoryAllocatorFactory::getAllocator( - AllocatorType type, std::shared_ptr app_settings) { - std::lock_guard lock(s_mutex); - - auto it = s_allocators.find(type); - if (it != s_allocators.end()) { return it->second; } - - std::shared_ptr allocator; - allocator = MemoryAllocator::get_memory_allocator(type, app_settings); - s_allocators[type] = allocator; - return allocator; -} - -RmaxBaseService::RmaxBaseService(const std::string& service_description) - : m_obj_init_status(ReturnStatus::obj_init_failure), - m_service_settings(nullptr), - m_rmax_apps_lib(nullptr), - m_signal_handler(nullptr), - m_stats_reader(nullptr), - m_service_description(service_description) { - memset(&m_local_address, 0, sizeof(m_local_address)); -} - -RmaxBaseService::~RmaxBaseService() { - if (m_obj_init_status != ReturnStatus::obj_init_success) { return; } - - for (auto& thread : m_threads) { - if (thread.joinable()) { thread.join(); } - } - - cleanup_rivermax_resources(); -} - -void RmaxBaseService::initialize_common_default_service_settings() { - m_service_settings->destination_ip = DESTINATION_IP_DEFAULT; - m_service_settings->destination_port = DESTINATION_PORT_DEFAULT; - m_service_settings->num_of_threads = NUM_OF_THREADS_DEFAULT; - m_service_settings->num_of_total_streams = NUM_OF_TOTAL_STREAMS_DEFAULT; - m_service_settings->num_of_total_flows = NUM_OF_TOTAL_FLOWS_DEFAULT; - m_service_settings->internal_thread_core = CPU_NONE; - m_service_settings->app_threads_cores = - std::vector(m_service_settings->num_of_threads, CPU_NONE); - m_service_settings->rate = {0, 0}; - m_service_settings->num_of_chunks = NUM_OF_CHUNKS_DEFAULT; - m_service_settings->num_of_packets_in_chunk = NUM_OF_PACKETS_IN_CHUNK_DEFAULT; - m_service_settings->packet_payload_size = PACKET_PAYLOAD_SIZE_DEFAULT; - m_service_settings->packet_app_header_size = PACKET_APP_HEADER_SIZE_DEFAULT; - m_service_settings->sleep_between_operations_us = SLEEP_BETWEEN_OPERATIONS_US_DEFAULT; - m_service_settings->sleep_between_operations = false; - m_service_settings->print_parameters = false; - m_service_settings->use_checksum_header = false; - m_service_settings->hw_queue_full_sleep_us = 0; - m_service_settings->gpu_id = INVALID_GPU_ID; - m_service_settings->allocator_type = AllocatorTypeUI::Auto; - m_service_settings->statistics_reader_core = INVALID_CORE_NUMBER; - m_service_settings->session_id_stats = UINT_MAX; -} - -ReturnStatus RmaxBaseService::initialize_memory_allocators() { - const auto alloc_type_iter = UI_ALLOCATOR_TYPE_MAP.find(m_service_settings->allocator_type); - if (alloc_type_iter == UI_ALLOCATOR_TYPE_MAP.end()) { - std::cerr << "Unknown UI allocator type " - << static_cast(m_service_settings->allocator_type) << std::endl; - return ReturnStatus::failure; - } - AllocatorType allocator_type = alloc_type_iter->second; - AllocatorType header_allocator_type; - AllocatorType payload_allocator_type; - if (m_service_settings->gpu_id != INVALID_GPU_ID) { - header_allocator_type = allocator_type; - payload_allocator_type = AllocatorType::Gpu; - } else { - header_allocator_type = allocator_type; - payload_allocator_type = allocator_type; - } - m_header_allocator = - MemoryAllocatorFactory::getAllocator(header_allocator_type, m_service_settings); - if (m_header_allocator == nullptr) { - std::cerr << "Failed to create header memory allocator" << std::endl; - return ReturnStatus::failure; - } - m_payload_allocator = - MemoryAllocatorFactory::getAllocator(payload_allocator_type, m_service_settings); - if (m_payload_allocator == nullptr) { - std::cerr << "Failed to create payload memory allocator" << std::endl; - return ReturnStatus::failure; - } - return ReturnStatus::success; -} - -ReturnStatus RmaxBaseService::initialize(const RmaxBaseServiceConfig& cfg) { - if (cfg.rmax_apps_lib == nullptr) { - m_rmax_apps_lib = std::make_shared(); - } else { - m_rmax_apps_lib = cfg.rmax_apps_lib; - } - - m_signal_handler = m_rmax_apps_lib->get_signal_handler(true); - - ReturnStatus rc = parse_configuration(cfg); - if (rc == ReturnStatus::failure) { return rc; } - - if (m_service_settings == nullptr) { - m_service_settings = std::make_shared(); - initialize_common_default_service_settings(); - } - - post_parse_config_initialization(); - - rc = initialize_memory_allocators(); - if (rc == ReturnStatus::failure) { - std::cerr << "Failed to initialize memory allocators" << std::endl; - m_obj_init_status = ReturnStatus::memory_allocation_failure; - return m_obj_init_status; - } - - rc = initialize_rivermax_resources(); - if (rc == ReturnStatus::failure) { - std::cerr << "Failed to initialize Rivermax resources" << std::endl; - return rc; - } - - rc = initialize_connection_parameters(); - if (rc == ReturnStatus::failure) { - std::cerr << "Failed to initialize application connection parameters" << std::endl; - return rc; - } - - rc = set_rivermax_clock(); - if (rc == ReturnStatus::failure) { - std::cerr << "Failed to set Rivermax clock" << std::endl; - return rc; - } - - return ReturnStatus::obj_init_success; -} - -ReturnStatus RmaxBaseService::set_rivermax_clock() { - return ReturnStatus::success; -} - -ReturnStatus RmaxBaseService::initialize_connection_parameters() { - memset(&m_local_address, 0, sizeof(sockaddr_in)); - m_local_address.sin_family = AF_INET; - int rc = inet_pton(AF_INET, m_service_settings->local_ip.c_str(), &m_local_address.sin_addr); - if (rc != 1) { - std::cerr << "Failed to parse local network address: " << m_service_settings->local_ip - << std::endl; - return ReturnStatus::failure; - } - - return ReturnStatus::success; -} - -void RmaxBaseService::run_stats_reader() { - if (!is_run_stats_reader()) { return; } - - m_stats_reader.reset(new StatisticsReader()); - m_stats_reader->set_cpu_core_affinity(m_service_settings->statistics_reader_core); - if (m_service_settings->session_id_stats != UINT_MAX) { - std::cout << "Set presen session id: " << m_service_settings->session_id_stats << std::endl; - m_stats_reader->set_session_id(m_service_settings->session_id_stats); - } - m_threads.push_back(std::thread(std::ref(*m_stats_reader))); -} diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/rmax_base_service.h b/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/rmax_base_service.h deleted file mode 100644 index 8585bb5617..0000000000 --- a/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/rmax_base_service.h +++ /dev/null @@ -1,222 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2023 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 RMAX_APPS_LIB_SERVICES_RMAX_BASE_SERVICE_H_ -#define RMAX_APPS_LIB_SERVICES_RMAX_BASE_SERVICE_H_ - -#include -#include - -#include "api/rmax_apps_lib_api.h" - -using namespace ral::lib::services; - -namespace ral { -namespace services { - -struct RmaxBaseServiceConfig { - std::shared_ptr app_settings; - std::shared_ptr rmax_apps_lib; -}; - -/** - * @class IRmaxServicesSynchronizer - * @brief Interface for synchronizing Rmax services. - * - * This interface provides a method for synchronizing the start of multiple Rmax services. - * Implementations of this interface should provide the necessary synchronization mechanisms - * to ensure that all services start together. - */ -class IRmaxServicesSynchronizer { - public: - virtual ~IRmaxServicesSynchronizer() = default; - /** - * @brief Wait for all services to reach the start point and then proceed. - * - * This method should be implemented to provide the necessary synchronization - * to ensure that all services reach the start point before any of them proceed. - */ - virtual void wait_for_start() = 0; -}; - -constexpr int INVALID_CORE_NUMBER = -1; - -/** - * @brief: Base calls for Rivermax service. - * - * This is a base class offers common operations for Rivermax service. - * The user of this interface must implement it's pure virtual methods and - * can override it's virtual methods. - */ -class RmaxBaseService { - protected: - /* Indicator on whether the object created correctly */ - ReturnStatus m_obj_init_status; - /* Service settings pointer */ - std::shared_ptr m_service_settings; - /* Rmax apps lib facade */ - std::shared_ptr m_rmax_apps_lib; - /* Local NIC address */ - sockaddr_in m_local_address; - /* Header memory allocator */ - std::shared_ptr m_header_allocator; - /* Payload memory allocator */ - std::shared_ptr m_payload_allocator; - /* Service signal handler */ - std::shared_ptr m_signal_handler; - /* Thread objects container */ - std::vector m_threads; - /* Statistics reader */ - std::unique_ptr m_stats_reader; - - std::string m_service_description; - - public: - /** - * @brief: RmaxBaseService constructor. - * - * @param [in] service_description: Service description string. - */ - explicit RmaxBaseService(const std::string& service_description); - virtual ~RmaxBaseService(); - /** - * @brief: Runs the service. - * - * This is the main entry point to the service. - * The initialization flow, using @ref ral::apps::RmaxBaseApp::initialize call, should be run - * before calling to this method. - * - * @return: Status of the operation. - */ - virtual ReturnStatus run(IRmaxServicesSynchronizer* sync_obj = nullptr) = 0; - - virtual ReturnStatus get_init_status() const { return m_obj_init_status; } - - protected: - /** - * @brief: Initializes service common default settings. - * - * The user of this interface can override function, in order to override - * service specific default setting. - */ - virtual void initialize_common_default_service_settings(); - /** - * @brief: Parse Configuration. - * - * It will be called as part of the @ref ral::apps::RmaxBaseService::initialize process. - */ - virtual ReturnStatus parse_configuration(const RmaxBaseServiceConfig& cfg) { - return ReturnStatus::success; - } - /** - * @brief: Does post config parsing initialization. - * - * Use this method to do any needed post config parsing service initialization. - * It will be called as part of the @ref ral::apps::RmaxBaseService::initialize process. - */ - virtual void post_parse_config_initialization() {} - /** - * @brief: Initializes memory allocators. - * - * @return: Status of the operation. - */ - virtual ReturnStatus initialize_memory_allocators(); - /** - * @brief: Runs service initialization flow. - * - * Use this method to run service initialization flow using the other methods in this - * interface. - * It will eventually initialize Rivermax library. - * - * The user can override the proposed initialization flow. - * - * @param [in] cfg: Service configuration - * - * @return: Status of the operation, on success: @ref - * ral::lib:services::ReturnStatus::obj_init_success. - */ - virtual ReturnStatus initialize(const RmaxBaseServiceConfig& cfg); - /** - * @brief: Initializes Rivermax library resources. - * - * Use this method to initialize Rivermax library configuration. It should - * use @ref ral::lib::RmaxAppsLibFacade::initialize_rivermax method do so. - * - * @return: Status of the operation. - */ - virtual ReturnStatus initialize_rivermax_resources() = 0; - /** - * @brief: Cleans up Rivermax library resources. - * - * Use this method to clean up any resources associated with Rivermax library. - * It will be called implicitly on class destruction. - * - * There is no need to call @ref rmax_cleanup, it will be done implicitly. - * - * @return: Status of the operation. - */ - virtual ReturnStatus cleanup_rivermax_resources() { return ReturnStatus::success; } - /** - * @brief: Sets Rivermax clock. - * - * Use this method to set Rivermax clock. - * - * @return: Status of the operation. - */ - virtual ReturnStatus set_rivermax_clock(); - /** - * @brief: Initializes the local NIC address. - * - * @return: Status of the operation. - */ - virtual ReturnStatus initialize_connection_parameters(); - /** - * @brief: Runs service threads. - * - * This method will run service IONodes as the context for @ref std::thread. - * The IONode should override the operator () as it's worker method. - * - * @param [in] io_nodes: A container of IONodes, it should follow STL containers interface. - */ - template - void run_threads(T& io_nodes); - /** - * @brief: Runs statistics reader thread. - */ - void run_stats_reader(); - - /** - * @brief: Check if need not run the statistics reader. - * - * @return: true if need to run the statistics reader. - */ - bool is_run_stats_reader() { - return (m_service_settings->statistics_reader_core != INVALID_CORE_NUMBER); - } -}; - -template -void RmaxBaseService::run_threads(T& io_nodes) { - for (auto& io_node : io_nodes) { m_threads.push_back(std::thread(std::ref(*io_node))); } - - for (auto& thread : m_threads) { thread.join(); } -} - -} // namespace services -} // namespace ral - -#endif /* RMAX_APPS_LIB_SERVICES_RMAX_SERVICE_BASE_APP_H_ */ diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/rmax_ipo_receiver_service.cpp b/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/rmax_ipo_receiver_service.cpp deleted file mode 100644 index becea26d81..0000000000 --- a/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/rmax_ipo_receiver_service.cpp +++ /dev/null @@ -1,383 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2023 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 "rt_threads.h" -#include "rmax_ipo_receiver_service.h" -#include "api/rmax_apps_lib_api.h" -#include "ipo_receiver_io_node.h" -#include "rmax_base_service.h" - -using namespace ral::lib::core; -using namespace ral::lib::services; -using namespace ral::io_node; -using namespace ral::services::rmax_ipo_receiver; - -bool RmaxIPOReceiverService::m_rivermax_lib_initialized = false; -std::mutex RmaxIPOReceiverService::m_rivermax_lib_mutex; - -RmaxIPOReceiverService::RmaxIPOReceiverService(const RmaxIPOReceiverConfig& cfg) - : RmaxBaseService(SERVICE_DESCRIPTION) { - m_obj_init_status = initialize(cfg); -} -ReturnStatus RmaxIPOReceiverService::parse_configuration(const RmaxBaseServiceConfig& cfg) { - const RmaxIPOReceiverConfig& ipo_service_cfg = static_cast(cfg); - m_service_settings = ipo_service_cfg.app_settings; - m_max_path_differential_us = ipo_service_cfg.max_path_differential_us; - m_is_extended_sequence_number = ipo_service_cfg.is_extended_sequence_number; - m_register_memory = ipo_service_cfg.register_memory; - m_max_chunk_size = ipo_service_cfg.max_chunk_size; - m_rx_stats_period_report_ms = ipo_service_cfg.rx_stats_period_report_ms; - - return ReturnStatus::success; -} - -ReturnStatus RmaxIPOReceiverService::initialize_connection_parameters() { - m_num_paths_per_stream = m_service_settings->source_ips.size(); - if (m_num_paths_per_stream == 0) { - std::cerr << "Must be at least one source IP" << std::endl; - return ReturnStatus::failure; - } - if (m_service_settings->destination_ips.size() != m_num_paths_per_stream) { - std::cerr << "Must be the same number of destination multicast IPs as number of source IPs" - << std::endl; - return ReturnStatus::failure; - } - if (m_service_settings->local_ips.size() != m_num_paths_per_stream) { - std::cerr << "Must be the same number of NIC addresses as number of source IPs" << std::endl; - return ReturnStatus::failure; - } - if (m_service_settings->destination_ports.size() != m_num_paths_per_stream) { - std::cerr << "Must be the same number of destination ports as number of source IPs" - << std::endl; - return ReturnStatus::failure; - } - - m_device_ifaces.resize(m_num_paths_per_stream); - for (size_t i = 0; i < m_num_paths_per_stream; ++i) { - in_addr device_address; - if (inet_pton(AF_INET, m_service_settings->local_ips[i].c_str(), &device_address) != 1) { - std::cerr << "Failed to parse address of device " << m_service_settings->local_ips[i] - << std::endl; - return ReturnStatus::failure; - } - rmx_status status = rmx_retrieve_device_iface_ipv4(&m_device_ifaces[i], &device_address); - if (status != RMX_OK) { - std::cerr << "Failed to get device: " << m_service_settings->local_ips[i] - << " with status: " << status << std::endl; - return ReturnStatus::failure; - } - } - - if (m_register_memory && m_service_settings->packet_app_header_size == 0) { - std::cerr << "Memory registration is supported only in header-data split mode!" << std::endl; - return ReturnStatus::failure; - } - - return ReturnStatus::success; -} - -ReturnStatus RmaxIPOReceiverService::run(IRmaxServicesSynchronizer* sync_obj) { - if (m_obj_init_status != ReturnStatus::obj_init_success) { return m_obj_init_status; } - - try { - distribute_work_for_threads(); - configure_network_flows(); - initialize_receive_io_nodes(); - - if (m_receivers.empty()) { - std::cerr << "No receivers initialized" << std::endl; - return ReturnStatus::failure; - } - - ReturnStatus rc = allocate_service_memory(); - if (rc == ReturnStatus::failure) { - std::cerr << "Failed to allocate the memory required for the service" << std::endl; - return rc; - } - distribute_memory_for_receivers(); - - m_service_running = true; - - if (sync_obj) { sync_obj->wait_for_start(); } - - run_threads(m_receivers); - - m_service_running = false; - - unregister_service_memory(); - } catch (const std::exception& error) { - std::cerr << error.what() << std::endl; - return ReturnStatus::failure; - } - - return ReturnStatus::success; -} - -ReturnStatus RmaxIPOReceiverService::initialize_rivermax_resources() { - std::lock_guard lock(m_rivermax_lib_mutex); - - if (RmaxIPOReceiverService::m_rivermax_lib_initialized) { return ReturnStatus::success; } - - rt_set_realtime_class(); - - ReturnStatus ret = m_rmax_apps_lib->initialize_rivermax(m_service_settings->internal_thread_core); - - if (ret == ReturnStatus::success) { RmaxIPOReceiverService::m_rivermax_lib_initialized = true; } - - return ret; -} - -void RmaxIPOReceiverService::configure_network_flows() { - std::vector ip_prefix_str; - std::vector ip_last_octet; - uint16_t src_port = 0; - - assert(m_num_paths_per_stream > 0); - ip_prefix_str.resize(m_num_paths_per_stream); - ip_last_octet.resize(m_num_paths_per_stream); - for (size_t i = 0; i < m_num_paths_per_stream; ++i) { - auto ip_vec = CLI::detail::split(m_service_settings->destination_ips[i], '.'); - if (ip_vec.size() != 4) { - std::cerr << "Invalid IP address format: " << m_service_settings->destination_ips[i] - << std::endl; - return; - } - ip_prefix_str[i] = std::string(ip_vec[0] + "." + ip_vec[1] + "." + ip_vec[2] + "."); - ip_last_octet[i] = std::stoi(ip_vec[3]); - } - - m_flows.reserve(m_service_settings->num_of_total_streams); - size_t id = 0; - for (size_t flow_index = 0; flow_index < m_service_settings->num_of_total_streams; ++flow_index) { - std::vector paths; - for (size_t i = 0; i < m_num_paths_per_stream; ++i) { - std::ostringstream ip; - ip << ip_prefix_str[i] - << (ip_last_octet[i] + flow_index * m_num_paths_per_stream) % IP_OCTET_LEN; - paths.emplace_back(id++, - m_service_settings->source_ips[i], - src_port, - ip.str(), - m_service_settings->destination_ports[i]); - } - m_flows.push_back(paths); - } -} - -void RmaxIPOReceiverService::distribute_work_for_threads() { - m_streams_per_thread.reserve(m_service_settings->num_of_threads); - for (int stream = 0; stream < m_service_settings->num_of_total_streams; stream++) { - m_streams_per_thread[stream % m_service_settings->num_of_threads]++; - } -} - -void RmaxIPOReceiverService::initialize_receive_io_nodes() { - size_t streams_offset = 0; - for (size_t rx_idx = 0; rx_idx < m_service_settings->num_of_threads; rx_idx++) { - int recv_cpu_core = - m_service_settings - ->app_threads_cores[rx_idx % m_service_settings->app_threads_cores.size()]; - - auto flows = std::vector>( - m_flows.begin() + streams_offset, - m_flows.begin() + streams_offset + m_streams_per_thread[rx_idx]); - m_receivers.push_back( - std::unique_ptr(new IPOReceiverIONode(*m_service_settings, - m_max_path_differential_us, - m_is_extended_sequence_number, - m_max_chunk_size, - m_service_settings->local_ips, - rx_idx, - recv_cpu_core, - m_chunk_consumer))); - - m_receivers[rx_idx]->initialize_streams(streams_offset, flows); - m_receivers[rx_idx]->print_statistics_settings((m_rx_stats_period_report_ms > 0 ? true : false), - m_rx_stats_period_report_ms); - streams_offset += m_streams_per_thread[rx_idx]; - } -} - -bool RmaxIPOReceiverService::allocate_and_align(size_t header_size, size_t payload_size, - byte_t*& header, byte_t*& payload) { - header = payload = nullptr; - if (header_size) { - header = static_cast( - m_header_allocator->allocate_aligned(header_size, m_header_allocator->get_page_size())); - } - payload = static_cast( - m_payload_allocator->allocate_aligned(payload_size, m_payload_allocator->get_page_size())); - return payload && (header_size == 0 || header); -} - -ReturnStatus RmaxIPOReceiverService::allocate_service_memory() { - size_t hdr_mem_size; - size_t pld_mem_size; - ReturnStatus rc = get_total_ipo_streams_memory_size(hdr_mem_size, pld_mem_size); - if (rc != ReturnStatus::success) { return rc; } - - bool alloc_successful = - allocate_and_align(hdr_mem_size, pld_mem_size, m_header_buffer, m_payload_buffer); - - if (alloc_successful) { - std::cout << "Allocated " << hdr_mem_size << " bytes for header" - << " at address " << static_cast(m_header_buffer) << " and " << pld_mem_size - << " bytes for payload" - << " at address " << static_cast(m_payload_buffer) << std::endl; - } else { - std::cerr << "Failed to allocate memory" << std::endl; - return ReturnStatus::failure; - } - - m_header_mem_regions.resize(m_num_paths_per_stream); - m_payload_mem_regions.resize(m_num_paths_per_stream); - - if (!m_register_memory) { return ReturnStatus::success; } - - for (size_t i = 0; i < m_num_paths_per_stream; ++i) { - m_header_mem_regions[i].addr = m_header_buffer; - m_header_mem_regions[i].length = hdr_mem_size; - m_header_mem_regions[i].mkey = 0; - if (hdr_mem_size) { - rmx_mem_reg_params mem_registry; - rmx_init_mem_registry(&mem_registry, &m_device_ifaces[i]); - rmx_status status = rmx_register_memory(&m_header_mem_regions[i], &mem_registry); - if (status != RMX_OK) { - std::cerr << "Failed to register header memory on device " - << m_service_settings->local_ips[i] << " with status: " << status << std::endl; - return ReturnStatus::failure; - } - } - } - for (size_t i = 0; i < m_num_paths_per_stream; ++i) { - rmx_mem_reg_params mem_registry; - rmx_init_mem_registry(&mem_registry, &m_device_ifaces[i]); - m_payload_mem_regions[i].addr = m_payload_buffer; - m_payload_mem_regions[i].length = pld_mem_size; - rmx_status status = rmx_register_memory(&m_payload_mem_regions[i], &mem_registry); - if (status != RMX_OK) { - std::cerr << "Failed to register payload memory on device " - << m_service_settings->local_ips[i] << " with status: " << status << std::endl; - return ReturnStatus::failure; - } - } - - return ReturnStatus::success; -} - -void RmaxIPOReceiverService::unregister_service_memory() { - if (!m_register_memory) { return; } - - if (m_header_buffer) { - for (size_t i = 0; i < m_header_mem_regions.size(); ++i) { - rmx_status status = rmx_deregister_memory(&m_header_mem_regions[i], &m_device_ifaces[i]); - if (status != RMX_OK) { - std::cerr << "Failed to deregister header memory on device " - << m_service_settings->local_ips[i] << " with status: " << status << std::endl; - } - } - } - for (size_t i = 0; i < m_payload_mem_regions.size(); ++i) { - rmx_status status = rmx_deregister_memory(&m_payload_mem_regions[i], &m_device_ifaces[i]); - if (status != RMX_OK) { - std::cerr << "Failed to deregister payload memory on device " - << m_service_settings->local_ips[i] << " with status: " << status << std::endl; - } - } -} - -ReturnStatus RmaxIPOReceiverService::get_total_ipo_streams_memory_size(size_t& hdr_mem_size, - size_t& pld_mem_size) { - hdr_mem_size = 0; - pld_mem_size = 0; - - if (!m_header_allocator || !m_payload_allocator) { - std::cerr << "Memory allocators are not initialized" << std::endl; - return ReturnStatus::failure; - } - - for (const auto& receiver : m_receivers) { - for (const auto& stream : receiver->get_streams()) { - size_t hdr_buf_size, pld_buf_size; - ReturnStatus rc = stream->query_buffer_size(hdr_buf_size, pld_buf_size); - if (rc != ReturnStatus::success) { - std::cerr << "Failed to query buffer size for stream " << stream->get_id() - << " of receiver " << receiver->get_index() << std::endl; - return rc; - } - hdr_mem_size += m_header_allocator->align_length(hdr_buf_size); - pld_mem_size += m_payload_allocator->align_length(pld_buf_size); - } - } - - std::cout << "Service requires " << hdr_mem_size << " bytes of header memory and " << pld_mem_size - << " bytes of payload memory" << std::endl; - - return ReturnStatus::success; -} - -void RmaxIPOReceiverService::distribute_memory_for_receivers() { - byte_t* hdr_ptr = m_header_buffer; - byte_t* pld_ptr = m_payload_buffer; - - for (auto& receiver : m_receivers) { - for (auto& stream : receiver->get_streams()) { - size_t hdr, pld; - - stream->set_buffers(hdr_ptr, pld_ptr); - if (m_register_memory) { - stream->set_memory_keys(m_header_mem_regions, m_payload_mem_regions); - } - stream->query_buffer_size(hdr, pld); - - if (hdr_ptr) { hdr_ptr += m_header_allocator->align_length(hdr); } - pld_ptr += m_payload_allocator->align_length(pld); - } - } -} - -bool RmaxIPOReceiverService::set_chunk_consumer(IIPOChunkConsumer* chunk_consumer) { - if (is_alive()) { - std::cerr << "Cannot set chunk consumer while service is running" << std::endl; - return false; - } - m_chunk_consumer = chunk_consumer; - return true; -} - -std::pair, std::vector>> -RmaxIPOReceiverService::get_streams_statistics() const { - std::vector streams_stats; - std::vector> streams_path_stats; - - // Populate streams_stats and streams_path_stats - for (const auto& receiver : m_receivers) { - auto [stream_stats, stream_path_stats] = receiver->get_streams_statistics(); - streams_stats.insert(streams_stats.end(), stream_stats.begin(), stream_stats.end()); - streams_path_stats.insert( - streams_path_stats.end(), stream_path_stats.begin(), stream_path_stats.end()); - } - - return {streams_stats, streams_path_stats}; -} diff --git a/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/rmax_ipo_receiver_service.h b/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/rmax_ipo_receiver_service.h deleted file mode 100644 index c80aa384fb..0000000000 --- a/operators/advanced_network/advanced_network/managers/rivermax/rmax_service/rmax_ipo_receiver_service.h +++ /dev/null @@ -1,213 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2023 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 RMAX_APPS_LIB_SERVICES_IPO_RECEIVER_SERVICE_H_ -#define RMAX_APPS_LIB_SERVICES_IPO_RECEIVER_SERVICE_H_ - -#include -#include -#include -#include -#include -#include - -#include - -#include "api/rmax_apps_lib_api.h" -#include "ipo_receiver_io_node.h" -#ifdef RMAX_APPS_LIB_FLAT_STRUCTURE -#include "ipo_chunk_consumer_base.h" -#else -#include "receivers/ipo_chunk_consumer_base.h" -#endif -#include "rmax_base_service.h" - -using namespace ral::lib::core; -using namespace ral::lib::services; -using namespace ral::io_node; -using namespace ral::services; - -namespace ral { -namespace services { -namespace rmax_ipo_receiver { -struct RmaxIPOReceiverConfig : RmaxBaseServiceConfig { - uint32_t max_path_differential_us; - bool is_extended_sequence_number; - bool register_memory; - size_t max_chunk_size; - uint32_t rx_stats_period_report_ms; -}; -/** - * Service constants. - */ -constexpr const char* SERVICE_DESCRIPTION = "NVIDIA Rivermax IPO Receiver Service "; -/** - * @brief: IPO Receiver service. - * - * This is an example of usage service for Rivermax Inline Packet Ordering RX API. - */ -class RmaxIPOReceiverService : public RmaxBaseService { - private: - static constexpr uint32_t DEFAULT_NUM_OF_PACKETS_IN_CHUNK = 262144; - static constexpr int USECS_IN_SECOND = 1000000; - - static bool m_rivermax_lib_initialized; - static std::mutex m_rivermax_lib_mutex; - - /* Sender objects container */ - std::vector> m_receivers; - /* Stream per thread distribution */ - std::unordered_map m_streams_per_thread; - /* Network recv flows */ - std::vector> m_flows; - /* NIC device interfaces */ - std::vector m_device_ifaces; - /* Memory regions for header memory allocated for each device interface */ - std::vector m_header_mem_regions; - /* Memory regions for payload memory allocated for each device interface */ - std::vector m_payload_mem_regions; - // Maximum Path Differential for "Class B: Moderate-Skew" receivers defined - // by SMPTE ST 2022-7:2019 "Seamless Protection Switching of RTP Datagrams". - uint64_t m_max_path_differential_us = 50000; - bool m_is_extended_sequence_number = false; - bool m_register_memory = false; - byte_t* m_header_buffer = nullptr; - byte_t* m_payload_buffer = nullptr; - size_t m_num_paths_per_stream = 0; - size_t m_max_chunk_size = 1024; - bool m_service_running = false; - uint32_t m_rx_stats_period_report_ms = 1000; - IIPOChunkConsumer* m_chunk_consumer = nullptr; - - public: - /** - * @brief: RmaxIPOReceiverService class constructor. - * - * @param [in] cfg: service configuration - */ - explicit RmaxIPOReceiverService(const RmaxIPOReceiverConfig& cfg); - virtual ~RmaxIPOReceiverService() = default; - ReturnStatus run(IRmaxServicesSynchronizer* sync_obj = nullptr) override; - bool is_alive() { return m_service_running; } - bool set_chunk_consumer(IIPOChunkConsumer* chunk_consumer); - - /** - * @brief: Returns streams statistics. - * - * @return: Pair of vectors of statistics. First vector contains stream - * statistics, second vector contains path statistics. - */ - std::pair, std::vector>> - get_streams_statistics() const; - - private: - /** - * @brief: Parse Configuration. - * - * It will be called as part of the @ref ral::apps::RmaxBaseService::initialize process. - */ - ReturnStatus parse_configuration(const RmaxBaseServiceConfig& cfg) final; - ReturnStatus initialize_connection_parameters() final; - ReturnStatus initialize_rivermax_resources() final; - /** - * @brief: Initializes network receive flows. - * - * This method initializes the receive flows that will be used - * in the service. These flows will be distributed - * in @ref ral::services::RmaxIPOReceiverService::distribute_work_for_threads - * between service threads. - * The service supports unicast and multicast UDPv4 receive flows. - */ - void configure_network_flows(); - /** - * @brief: Distributes work for threads. - * - * This method is responsible for distributing work to threads, by - * distributing number of streams per receiver thread uniformly. - * In future development, this can be extended to different - * streams per thread distribution policies. - */ - void distribute_work_for_threads(); - /** - * @brief: Initializes receiver I/O nodes. - * - * This method is responsible for initialization of - * @ref ral::io_node::IPOReceiverIONode objects to work. It will initiate - * objects with the relevant parameters. The objects initialized in this - * method, will be the contexts to the std::thread objects will run in - * @ref ral::services::RmaxBaseService::run_threads method. - */ - void initialize_receive_io_nodes(); - /** - * @brief: Allocates service memory and registers it if requested. - * - * This method is responsible for allocation of the required memory for - * the service using @ref ral::lib::services::MemoryAllocator interface. - * The allocation policy of the service is allocating one big memory - * block. This memory block will be distributed to the different - * components of the service. - * - * If @ref m_register_memory is set then this function also registers - * allocated memory using @ref rmax_register_memory on all devices. - * - * @return: Returns status of the operation. - */ - ReturnStatus allocate_service_memory(); - /** - * @brief Unregister previously registered memory. - * - * Unregister memory using @ref rmax_deregister_memory. - */ - void unregister_service_memory(); - /** - * @brief: Distributes memory for receivers. - * - * This method is responsible for distributing the memory allocated - * by @ref allocate_service_memory to the receivers of the service. - */ - void distribute_memory_for_receivers(); - /** - * @brief: Returns the memory size for all the receive streams. - * - * This method calculates the sum of memory sizes for all IONodes and their IPO streams. - * Inside an IPO stream memory size is not summed along redundant streams, - * they are only checked for equal requirements. - * - * @param [out] hdr_mem_size: Required header memory size. - * @param [out] pld_mem_size: Required payload memory size. - * - * @return: Return status of the operation. - */ - ReturnStatus get_total_ipo_streams_memory_size(size_t& hdr_mem_size, size_t& pld_mem_size); - /** - * @brief: Allocates memory and aligns it to page size. - * - * @param [in] header_size: Requested header memory size. - * @param [in] payload_size: Requested payload memory size. - * @param [out] header: Allocated header memory pointer. - * @param [out] payload: Allocated payload memory pointer. - * - * @return: True if successful. - */ - bool allocate_and_align(size_t header_size, size_t payload_size, byte_t*& header, - byte_t*& payload); -}; - -} // namespace rmax_ipo_receiver -} // namespace services -} // namespace ral -#endif // RMAX_APPS_LIB_SERVICES_IPO_RECEIVER_SERVICE_H_ diff --git a/operators/advanced_network/advanced_network/types.h b/operators/advanced_network/advanced_network/types.h index 3f514357e2..7070dc5a57 100644 --- a/operators/advanced_network/advanced_network/types.h +++ b/operators/advanced_network/advanced_network/types.h @@ -60,6 +60,7 @@ struct BurstHeaderParams { uint32_t max_pkt_size; uint32_t gpu_pkt0_idx; uintptr_t gpu_pkt0_addr; + uint32_t burst_flags; }; struct BurstHeader { @@ -83,6 +84,7 @@ struct BurstParams { std::array pkts; std::array pkt_lens; void** pkt_extra_info; + std::shared_ptr custom_pkt_data; cudaEvent_t event; }; diff --git a/operators/advanced_network/python/adv_network_common_pybind.cpp b/operators/advanced_network/python/adv_network_common_pybind.cpp index 6e4dd19efa..96db81338a 100644 --- a/operators/advanced_network/python/adv_network_common_pybind.cpp +++ b/operators/advanced_network/python/adv_network_common_pybind.cpp @@ -17,109 +17,335 @@ #include "advanced_network/common.h" #include +#include +#include +#include namespace py = pybind11; +using pybind11::literals::operator""_a; namespace holoscan::advanced_network { PYBIND11_MODULE(_advanced_network_common, m) { m.doc() = "Advanced Network utility functions"; + // Bind enums py::enum_(m, "Status") .value("SUCCESS", Status::SUCCESS) .value("NULL_PTR", Status::NULL_PTR) .value("NO_FREE_BURST_BUFFERS", Status::NO_FREE_BURST_BUFFERS) - .value("NO_FREE_PACKET_BUFFERS", Status::NO_FREE_PACKET_BUFFERS); + .value("NO_FREE_PACKET_BUFFERS", Status::NO_FREE_PACKET_BUFFERS) + .value("NOT_READY", Status::NOT_READY) + .value("INVALID_PARAMETER", Status::INVALID_PARAMETER) + .value("NO_SPACE_AVAILABLE", Status::NO_SPACE_AVAILABLE) + .value("NOT_SUPPORTED", Status::NOT_SUPPORTED) + .value("INTERNAL_ERROR", Status::INTERNAL_ERROR); + py::enum_(m, "ManagerType") + .value("UNKNOWN", ManagerType::UNKNOWN) + .value("DEFAULT", ManagerType::DEFAULT) + .value("DPDK", ManagerType::DPDK) + .value("DOCA", ManagerType::DOCA) + .value("RIVERMAX", ManagerType::RIVERMAX); + + py::enum_(m, "Direction") + .value("RX", Direction::RX) + .value("TX", Direction::TX) + .value("TX_RX", Direction::TX_RX); + + py::enum_(m, "BufferLocation") + .value("CPU", BufferLocation::CPU) + .value("GPU", BufferLocation::GPU) + .value("CPU_GPU_SPLIT", BufferLocation::CPU_GPU_SPLIT); + + py::enum_(m, "MemoryKind") + .value("HOST", MemoryKind::HOST) + .value("HOST_PINNED", MemoryKind::HOST_PINNED) + .value("HUGE", MemoryKind::HUGE) + .value("DEVICE", MemoryKind::DEVICE) + .value("INVALID", MemoryKind::INVALID); + + // Bind classes with their members + py::class_(m, "BurstHeaderParams") + .def(py::init<>()) + .def_readwrite("num_pkts", &BurstHeaderParams::num_pkts) + .def_readwrite("port_id", &BurstHeaderParams::port_id) + .def_readwrite("q_id", &BurstHeaderParams::q_id) + .def_readwrite("num_segs", &BurstHeaderParams::num_segs) + .def_readwrite("nbytes", &BurstHeaderParams::nbytes) + .def_readwrite("first_pkt_addr", &BurstHeaderParams::first_pkt_addr) + .def_readwrite("max_pkt", &BurstHeaderParams::max_pkt) + .def_readwrite("max_pkt_size", &BurstHeaderParams::max_pkt_size) + .def_readwrite("gpu_pkt0_idx", &BurstHeaderParams::gpu_pkt0_idx) + .def_readwrite("gpu_pkt0_addr", &BurstHeaderParams::gpu_pkt0_addr) + .def_readwrite("burst_flags", &BurstHeaderParams::burst_flags); + + py::class_(m, "BurstHeader") + .def(py::init<>()) + .def_readwrite("hdr", &BurstHeader::hdr); + + py::class_(m, "BurstParams") + .def(py::init<>()) + .def_readwrite("hdr", &BurstParams::hdr); + + // Network initialization and configuration + m.def( + "adv_net_init", + [](py::object config_obj) -> Status { + try { + // The config_obj is likely a Holoscan Arg or similar object + // We need to extract the underlying YAML data + + std::string yaml_str; + + // Check if it has a 'value' attribute (Holoscan Arg objects) + if (py::hasattr(config_obj, "value")) { + auto value = config_obj.attr("value"); + yaml_str = py::str(value); + } else if (py::hasattr(config_obj, "as_dict")) { + auto dict_obj = config_obj.attr("as_dict")(); + try { + py::object yaml_module = py::module_::import("yaml"); + py::object py_yaml_str = yaml_module.attr("dump")( + dict_obj, py::arg("default_flow_style") = false); + yaml_str = py::cast(py_yaml_str); + } catch (const py::error_already_set& e) { + HOLOSCAN_LOG_ERROR("Failed to import yaml module or convert dict to YAML: {}", + e.what()); + HOLOSCAN_LOG_ERROR("Please install PyYAML: pip install PyYAML"); + return Status::INTERNAL_ERROR; + } + } else { + yaml_str = py::str(config_obj); + } + + HOLOSCAN_LOG_DEBUG("Attempting to parse YAML config: {}", yaml_str); + + // Parse YAML string to YAML::Node + YAML::Node yaml_node = YAML::Load(yaml_str); + + // Use the existing YAML::convert specialization to convert to NetworkConfig + NetworkConfig config = yaml_node.as(); + + // Call the actual initialization function + auto status = adv_net_init(config); + if (status == Status::SUCCESS) { + HOLOSCAN_LOG_INFO("Successfully initialized Advanced Network from Python config"); + } + return status; + } catch (const YAML::Exception& e) { + HOLOSCAN_LOG_ERROR("YAML parsing error in Python config: {}", e.what()); + return Status::INVALID_PARAMETER; + } catch (const std::exception& e) { + HOLOSCAN_LOG_ERROR("Failed to initialize advanced network from Python config: {}", + e.what()); + return Status::INTERNAL_ERROR; + } + }, + "config"_a, + "Initialize the advanced network backend from Holoscan config object"); + + m.def("get_manager_type", + static_cast(&get_manager_type), + "Get the current manager type"); + + // Burst creation and management m.def("create_tx_burst_params", &create_tx_burst_params, py::return_value_policy::reference, "Create a shared pointer burst params structure"); + + // Packet information functions m.def("get_segment_packet_length", py::overload_cast(&get_segment_packet_length), - "Get length of one segments of the packet"); + "burst"_a, + "seg"_a, + "idx"_a, + "Get length of one segment of the packet"); m.def("get_packet_length", py::overload_cast(&get_packet_length), + "burst"_a, + "idx"_a, "Get length of the packet"); - m.def("free_all_segment_packets", - py::overload_cast(&free_all_segment_packets), - "Free all packets in a burst for one segment"); - m.def("free_all_packets_and_burst_rx", - py::overload_cast(&free_all_packets_and_burst_rx), - "Free all packets and burst structure for RX"); - m.def("free_all_packets_and_burst_tx", - py::overload_cast(&free_all_packets_and_burst_tx), - "Free all packets and burst structure for TX"); - m.def("free_segment_packets_and_burst", - py::overload_cast(&free_segment_packets_and_burst), - "Free all packets and burst structure for one packet segment"); - m.def("tx_burst_available", + m.def("get_packet_flow_id", + py::overload_cast(&get_packet_flow_id), + "burst"_a, + "idx"_a, + "Get flow ID of a packet"); + + // TX burst functions + m.def("is_tx_burst_available", py::overload_cast(&is_tx_burst_available), + "burst"_a, "Return true if a TX burst is available for use"); m.def("get_tx_packet_burst", py::overload_cast(&get_tx_packet_burst), + "burst"_a, "Get TX packet burst"); - m.def("shutdown", (&shutdown), "Shut down the advanced_network manager"); - m.def("print_stats", (&print_stats), "Print statistics from the advanced_network manager"); - // m.def("set_cpu_udp_payload", - // [](BurstParams *burst, int idx, long int data, int len) { - // return set_cpu_udp_payload(burst, idx, - // reinterpret_cast(data), len); }, - // "Set UDP header parameters and copy payload"); - // m.def("set_cpu_udp_payload", - // [](std::shared_ptr burst, int idx, long int data, int len) { - // return set_cpu_udp_payload(burst, idx, - // reinterpret_cast(data), len); }, - // "Set UDP header parameters and copy payload"); + m.def("send_tx_burst", + py::overload_cast(&send_tx_burst), + "burst"_a, + "Send a TX burst"); + + // RX burst functions - all overloads + m.def( + "get_rx_burst", + [](int port, int q) { + BurstParams* burst_ptr = nullptr; + Status status = get_rx_burst(&burst_ptr, port, q); + return py::make_tuple(status, py::cast(burst_ptr, py::return_value_policy::reference)); + }, + "port"_a, + "q"_a, + "Get RX burst for specific port and queue. " + "Must call free_rx_burst() when done to free resources."); + + m.def( + "get_rx_burst", + [](int port) { + BurstParams* burst_ptr = nullptr; + Status status = get_rx_burst(&burst_ptr, port); + return py::make_tuple(status, py::cast(burst_ptr, py::return_value_policy::reference)); + }, + "port"_a, + "Get RX burst from any queue on specific port. " + "Must call free_rx_burst() when done to free resources."); + m.def( + "get_rx_burst", + []() { + BurstParams* burst_ptr = nullptr; + Status status = get_rx_burst(&burst_ptr); + return py::make_tuple(status, py::cast(burst_ptr, py::return_value_policy::reference)); + }, + "Get RX burst from any queue on any port. " + "Must call free_rx_burst() when done to free resources."); + + // Header setting functions + m.def("set_eth_header", + [](BurstParams* burst, int idx, const std::string& dst_addr) -> Status { + char mac_bytes[6]; + format_eth_addr(mac_bytes, dst_addr); // Use existing utility function + return set_eth_header(burst, idx, mac_bytes); + }, + "burst"_a, + "idx"_a, + "dst_addr"_a, + "Set Ethernet header in packet. " + "dst_addr should be MAC address as string (e.g. 'aa:bb:cc:dd:ee:ff')"); + m.def("set_ipv4_header", + py::overload_cast( + &set_ipv4_header), + "burst"_a, + "idx"_a, + "ip_len"_a, + "proto"_a, + "src_host"_a, + "dst_host"_a, + "Set IPv4 header in packet"); + m.def("set_udp_header", + py::overload_cast(&set_udp_header), + "burst"_a, + "idx"_a, + "udp_len"_a, + "src_port"_a, + "dst_port"_a, + "Set UDP header in packet"); + m.def("set_udp_payload", + [](BurstParams* burst, int idx, py::buffer data) -> Status { + py::buffer_info buf_info = data.request(); + return set_udp_payload(burst, idx, buf_info.ptr, buf_info.size); + }, + "burst"_a, + "idx"_a, + "data"_a, + "Set UDP payload in packet. " + "Accepts bytes, bytearray, numpy arrays, or any buffer protocol object"); + + // Burst metadata functions m.def("get_num_packets", py::overload_cast(&get_num_packets), + "burst"_a, "Get number of packets in a burst"); - m.def("get_q_id", - py::overload_cast(&get_q_id), - "Get queue ID of a burst"); + m.def( + "get_q_id", py::overload_cast(&get_q_id), "burst"_a, "Get queue ID of a burst"); m.def("set_num_packets", py::overload_cast(&set_num_packets), + "burst"_a, + "num"_a, "Set number of packets in a burst"); m.def("set_header", py::overload_cast(&set_header), + "burst"_a, + "port"_a, + "q"_a, + "num"_a, + "segs"_a, "Set parameters of burst header"); - m.def("free_tx_burst", - py::overload_cast(&free_tx_burst), - "Free TX burst"); - m.def("free_rx_burst", - py::overload_cast(&free_rx_burst), - "Free RX burst"); - m.def("get_segment_packet_ptr", - py::overload_cast(&get_segment_packet_ptr), - "Get packet pointer for one segment"); - m.def("get_packet_ptr", - py::overload_cast(&get_packet_ptr), - "Get packet pointer"); - m.def("get_rx_burst", [](int port, int q) { - BurstParams* burst_ptr = nullptr; - Status status = get_rx_burst(&burst_ptr, port, q); - return py::make_tuple(status, py::cast(burst_ptr, - py::return_value_policy::take_ownership)); - }, py::arg("port"), py::arg("q")); - - // py::class_(m, "BurstHeaderParams").def(py::init<>()) - // .def_readwrite("num_pkts", &BurstHeaderParams::num_pkts) - // .def_readwrite("port_id", &BurstHeaderParams::port_id) - // .def_readwrite("q_id", &BurstHeaderParams::q_id); - - // py::class_(m, "BurstHeader").def(py::init<>()) - // .def_readwrite("hdr", &BurstHeader::hdr); - - // py::class_(m, "BurstParams").def(py::init<>()) - // .def_readwrite("hdr", &BurstParams::hdr) - // .def_readwrite("cpu_pkts", &BurstParams::cpu_pkts) - // .def_readwrite("gpu_pkts", &BurstParams::gpu_pkts); - - py::class_(m, "BurstHeaderParams").def(py::init<>()); - py::class_(m, "BurstHeader").def(py::init<>()); - py::class_(m, "BurstParams").def(py::init<>()); - // py::class_> - // (m, "BurstParams").def(py::init<>()); + + // Memory management functions + m.def("free_all_segment_packets", + py::overload_cast(&free_all_segment_packets), + "burst"_a, + "seg"_a, + "Free all packets in a burst for one segment"); + m.def("free_all_packets_and_burst_rx", + py::overload_cast(&free_all_packets_and_burst_rx), + "burst"_a, + "Free all packets and burst structure for RX"); + m.def("free_all_packets_and_burst_tx", + py::overload_cast(&free_all_packets_and_burst_tx), + "burst"_a, + "Free all packets and burst structure for TX"); + m.def("free_segment_packets_and_burst", + py::overload_cast(&free_segment_packets_and_burst), + "burst"_a, + "seg"_a, + "Free all packets and burst structure for one packet segment"); + m.def( + "free_tx_burst", py::overload_cast(&free_tx_burst), "burst"_a, "Free TX burst"); + m.def( + "free_rx_burst", py::overload_cast(&free_rx_burst), "burst"_a, "Free RX burst"); + + // Network interface functions + m.def("get_mac_addr", + [](int port) -> py::tuple { + char mac[6]; + Status status = get_mac_addr(port, mac); + if (status == Status::SUCCESS) { + char formatted[18]; + snprintf(formatted, sizeof(formatted), + "%02x:%02x:%02x:%02x:%02x:%02x", + static_cast(mac[0]), + static_cast(mac[1]), + static_cast(mac[2]), + static_cast(mac[3]), + static_cast(mac[4]), + static_cast(mac[5])); + return py::make_tuple(status, std::string(formatted)); + } + return py::make_tuple(status, std::string("")); + }, + "port"_a, + "Get MAC address of an interface as formatted string. " + "Returns (Status, mac_string)"); + m.def("get_port_id", + py::overload_cast(&get_port_id), + "key"_a, + "Get port number from interface name"); + + // Utility functions + m.def("shutdown", &shutdown, "Shut down the advanced_network manager"); + m.def("print_stats", &print_stats, "Print statistics from the advanced_network manager"); + + // Bind utility functions for string/enum conversion + m.def("manager_type_from_string", + &manager_type_from_string, + "str"_a, + "Convert string to manager type"); + m.def("manager_type_to_string", + &manager_type_to_string, + "type"_a, + "Convert manager type to string"); } }; // namespace holoscan::advanced_network diff --git a/operators/advanced_network_media/CMakeLists.txt b/operators/advanced_network_media/CMakeLists.txt new file mode 100644 index 0000000000..860786c99c --- /dev/null +++ b/operators/advanced_network_media/CMakeLists.txt @@ -0,0 +1,31 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 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) + +# Build only when Rivermax manager is enabled +if(NOT DEFINED ANO_MGR) + return() +elseif(NOT ANO_MGR STREQUAL "rivermax") + return() +endif() + +# 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) + diff --git a/operators/advanced_network_media/README.md b/operators/advanced_network_media/README.md new file mode 100644 index 0000000000..76b8acf3fc --- /dev/null +++ b/operators/advanced_network_media/README.md @@ -0,0 +1,843 @@ +### 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
(burst_processor)"] + P --> Q["Strategy Detection
(PacketsToFramesConverter)"] + + %% 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 (burst_processor) +├── Packets-to-Frames Conversion (packets_to_frames_converter) +│ ├── 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 RivermaxBurst { + +BurstHeader hdr + +PacketPointers pkts + +AnoBurstExtendedInfo custom_burst_data + +get_burst_info() AnoBurstExtendedInfo* + } + + 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 + } + + class BurstParams { + +BurstHeader hdr + +PacketPointers pkts + +void* custom_pkt_data + } + + class PacketsToFramesConverter { + +CopyStrategy current_strategy + +StrideInfo stride_info + +process_incoming_packet() + +get_current_strategy() + } + + class FrameBufferBase { + +void* buffer_ptr + +size_t buffer_size + +MemoryLocation memory_location + +get() void* + +get_size() size_t + } + + %% TX Data Structures + class MediaFrame { + +FrameBufferBase frame_buffer + +VideoFormat format + +uint32_t width + +uint32_t height + +size_t size + +get_data() void* + } + + class VideoBufferFrameBuffer { + +VideoBuffer gxf_buffer + +void* data_ptr + +size_t data_size + +wrap_in_entity() + } + + class TensorFrameBuffer { + +Tensor gxf_tensor + +void* data_ptr + +size_t data_size + +wrap_in_entity() + } + + class MediaFramePool { + +queue frames_pool + +size_t pool_size + +MemoryLocation memory_type + +get_frame() MediaFrame* + +return_frame() void + } + + %% Service Classes + class MediaSenderZeroCopyService { + +bool use_internal_memory_pool + +MediaSenderApp rdk_service + +send_frame() Status + } + + class MediaSenderService { + +bool use_internal_memory_pool + +MediaFramePool internal_pool + +MediaSenderApp rdk_service + +send_frame() Status + } + + %% Relationships + RivermaxBurst *-- AnoBurstExtendedInfo : contains + RivermaxBurst ..> PacketsToFramesConverter : processed by + PacketsToFramesConverter ..> FrameBufferBase : creates + + BurstParams ..> MediaFrame : references via custom_pkt_data + MediaFrame *-- VideoBufferFrameBuffer : wraps + MediaFrame *-- TensorFrameBuffer : wraps + + MediaSenderService *-- MediaFramePool : manages + MediaFramePool o-- MediaFrame : provides + + MediaSenderZeroCopyService ..> MediaFrame : uses directly + MediaSenderService ..> MediaFrame : copies to pool + + VideoBufferFrameBuffer --|> FrameBufferBase : inherits + TensorFrameBuffer --|> FrameBufferBase : inherits +``` + +## 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. + +#### Example Usage + +### MediaSenderZeroCopyService Usage (Pipeline Mode) + +**Scenario**: Operators that receive MediaFrame/VideoBuffer/Tensor from previous operators in the pipeline. The operator wraps the incoming data in a MediaFrame and passes it directly to RDK via `custom_pkt_data` with no data copying. + +**Configuration**: Set `use_internal_memory_pool: false` to enable zero-copy mode. + +### MediaSenderService Usage (Data Generation Mode) + +**Scenario**: Operators that generate their own data from sources like files, sensors, cameras, or synthetic data generators. The operator uses `get_tx_packet_burst()` to obtain pre-allocated buffers from the memory pool, performs a single memory copy into the pool buffer, and sends the burst. Pool buffers are reused after RDK processing. + +**Configuration**: Set `use_internal_memory_pool: true` to enable memory pool mode. + +### Application Examples + +For complete working examples, see: + +- **Pipeline Mode (Zero-Copy)**: [Advanced Networking Media Sender](../../applications/adv_networking_media_sender/README.md) - Demonstrates MediaFrame pipeline transmission +- **Data Generation (Memory Pool)**: [Advanced Networking Benchmark TX](../../applications/adv_networking_bench/README.md) - Demonstrates file-based data generation and transmission +- **Reception**: [Advanced Networking Media Player](../../applications/adv_networking_media_player/README.md) - Demonstrates receiving and displaying media streams + +### When to Use Each Mode + +**Use Zero-Copy Mode** (`use_internal_memory_pool: false`) when: +- Your operator receives MediaFrame/VideoBuffer/Tensor from previous pipeline operators +- You want absolute minimal latency and memory usage +- Data is already in the correct format and location + +**Use Memory Pool Mode** (`use_internal_memory_pool: true`) when: +- Your operator generates/reads data from files, sensors, or other sources +- You need consistent performance for sustained high-throughput streaming +- You require buffering capabilities for handling variable data rates + +Please refer to the [Advanced Network library documentation](../advanced_network/README.md) for detailed configuration instructions. + +#### 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..2d4459b398 --- /dev/null +++ b/operators/advanced_network_media/advanced_network_media_rx/CMakeLists.txt @@ -0,0 +1,59 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 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 + burst_processor.cpp + packets_to_frames_converter.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 +) + +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 +) + +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..c0843ca262 --- /dev/null +++ b/operators/advanced_network_media/advanced_network_media_rx/adv_network_media_rx.cpp @@ -0,0 +1,419 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023 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 "adv_network_media_rx.h" +#include "advanced_network/common.h" +#include "../common/adv_network_media_common.h" +#include "packets_to_frames_converter.h" +#include "burst_processor.h" +#include +#include "../common/frame_buffer.h" +#include "../common/video_parameters.h" +#include "advanced_network/managers/rivermax/rivermax_ano_data_types.h" + +using namespace holoscan::advanced_network; + +namespace holoscan::ops { + +constexpr size_t FRAMES_IN_POOL = 50; +constexpr size_t PACKETS_DISPLAY_INTERVAL = 1000000; // 1e6 packets + +// Enumeration for output format types +enum class OutputFormatType { VIDEO_BUFFER, TENSOR }; + +/** + * @class AdvNetworkMediaRxOpImpl + * @brief Implementation class for the AdvNetworkMediaRxOp operator. + * + * Handles high-level network management, frame pool management, and + * coordinates with BurstProcessor 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() { + HOLOSCAN_LOG_INFO("AdvNetworkMediaRxOp::initialize()"); + port_id_ = get_port_id(parent_.interface_name_.get()); + if (port_id_ == -1) { + HOLOSCAN_LOG_ERROR("Invalid RX port {} specified in the config", + parent_.interface_name_.get()); + exit(1); + } else { + HOLOSCAN_LOG_INFO("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; + HOLOSCAN_LOG_INFO("Using Tensor output format"); + } else { + output_format_ = OutputFormatType::VIDEO_BUFFER; + HOLOSCAN_LOG_INFO("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; + HOLOSCAN_LOG_INFO("Using Host memory location for output frames"); + } else { + storage_type_ = nvidia::gxf::MemoryStorageType::kDevice; + HOLOSCAN_LOG_INFO("Using Device memory location for output frames"); + } + + // Create pool of allocated frame buffers + create_frame_pool(); + + // Create converter and burst processor + auto converter = PacketsToFramesConverter::create(this); + burst_processor_ = std::make_unique(std::move(converter)); + } + + /** + * @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 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(); + } + + /** + * @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. + * + * @return Status indicating success or failure of the operation. + */ + Status compute(InputContext& op_input, OutputContext& op_output, ExecutionContext& context) { + BurstParams* burst; + auto status = get_rx_burst(&burst, port_id_, parent_.queue_id_.get(), parent_.stream_id_.get()); + if (status != Status::SUCCESS) { return status; } + + const auto& packets_received = burst->hdr.hdr.num_pkts; + total_packets_received_ += packets_received; + if (packets_received == 0) { return status; } + if (total_packets_received_ > PACKETS_DISPLAY_INTERVAL) { + HOLOSCAN_LOG_INFO("Got burst with {} pkts | total packets received {}", + packets_received, + total_packets_received_); + total_packets_received_ = 0; + } + + PACKET_TRACE_LOG("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 status; } + + size_t total_frames = ready_frames_.size(); + + if (total_frames > 1) { + HOLOSCAN_LOG_WARN( + "Multiple frames ready ({}), dropping {} earlier frames to prevent pipeline issues", + total_frames, + total_frames - 1); + } + + PACKET_TRACE_LOG("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); + PACKET_TRACE_LOG("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) { + PACKET_TRACE_LOG("Emitting latest frame: {} bytes", last_frame->get_size()); + PACKET_TRACE_LOG("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); + } + return status; + } + + /** + * @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) { + size_t ready_frames_before = ready_frames_.size(); + + PACKET_TRACE_LOG("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, parent_.hds_.get()); + + size_t ready_frames_after = ready_frames_.size(); + + PACKET_TRACE_LOG("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; + PACKET_TRACE_LOG("{} frame(s) completed, freeing {} accumulated bursts", + frames_completed, + bursts_awaiting_cleanup_.size()); + PACKET_TRACE_LOG("Freeing accumulated bursts: count={}", bursts_awaiting_cleanup_.size()); + while (!bursts_awaiting_cleanup_.empty()) { + auto burst_to_free = bursts_awaiting_cleanup_.front(); + PACKET_TRACE_LOG("Freeing burst: ptr={}, packets={}", + static_cast(burst_to_free), + burst_to_free->hdr.hdr.num_pkts); + 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); + + PACKET_TRACE_LOG("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)); + } + } + + std::shared_ptr get_allocated_frame() override { + 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) override { + ready_frames_.push_back(frame); + PACKET_TRACE_LOG("New frame ready: {}", frame->get_size()); + } + + private: + AdvNetworkMediaRxOp& parent_; + int port_id_; + std::unique_ptr burst_processor_; + std::deque> frames_pool_; + std::deque> ready_frames_; + std::deque bursts_awaiting_cleanup_; + size_t total_packets_received_ = 0; + nvidia::gxf::VideoFormat video_format_; + size_t frame_size_; + OutputFormatType output_format_{OutputFormatType::VIDEO_BUFFER}; + nvidia::gxf::MemoryStorageType storage_type_{nvidia::gxf::MemoryStorageType::kDevice}; +}; + +AdvNetworkMediaRxOp::AdvNetworkMediaRxOp() : pimpl_(nullptr) {} + +AdvNetworkMediaRxOp::~AdvNetworkMediaRxOp() { + if (pimpl_) { + pimpl_->cleanup(); // Clean up allocated resources + delete pimpl_; + pimpl_ = nullptr; + } +} + +void AdvNetworkMediaRxOp::setup(OperatorSpec& spec) { + 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(stream_id_, "stream_id", "Stream ID", "Stream ID for the media stream", 0); + 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")); +} + +void AdvNetworkMediaRxOp::initialize() { + HOLOSCAN_LOG_INFO("AdvNetworkMediaRxOp::initialize()"); + holoscan::Operator::initialize(); + + if (!pimpl_) { pimpl_ = new AdvNetworkMediaRxOpImpl(*this); } + + pimpl_->initialize(); +} + +void AdvNetworkMediaRxOp::compute(InputContext& op_input, OutputContext& op_output, + ExecutionContext& context) { + auto result = pimpl_->compute(op_input, op_output, context); + if (result == Status::INVALID_PARAMETER) { + stop_execution(); + } +} + +}; // 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..8742a3c999 --- /dev/null +++ b/operators/advanced_network_media/advanced_network_media_rx/adv_network_media_rx.h @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023 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 stream_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/burst_processor.cpp b/operators/advanced_network_media/advanced_network_media_rx/burst_processor.cpp new file mode 100644 index 0000000000..ad5b129f77 --- /dev/null +++ b/operators/advanced_network_media/advanced_network_media_rx/burst_processor.cpp @@ -0,0 +1,145 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023 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 "burst_processor.h" +#include "../common/adv_network_media_common.h" +#include "advanced_network/common.h" + +namespace holoscan::ops { + +BurstProcessor::BurstProcessor(std::shared_ptr converter) + : packets_to_frames_converter_(converter) {} + +void BurstProcessor::process_burst(BurstParams* burst, bool hds_enabled) { + if (!burst || burst->hdr.hdr.num_pkts == 0) { return; } + + ensure_converter_configuration(burst); + process_packets_in_burst(burst, hds_enabled); +} + +void BurstProcessor::ensure_converter_configuration(BurstParams* burst) { + static bool burst_info_initialized = false; + static size_t last_header_stride = 0; + static size_t last_payload_stride = 0; + static bool last_hds_on = false; + static bool last_payload_on_cpu = false; + + // Access burst extended info from custom_burst_data + const auto* burst_info = + reinterpret_cast(&(burst->hdr.custom_burst_data)); + + if (!burst_info_initialized) { + // First-time initialization + packets_to_frames_converter_->configure_burst_parameters( + burst_info->header_stride_size, burst_info->payload_stride_size, burst_info->hds_on); + + // Set source memory location based on burst info + packets_to_frames_converter_->set_source_memory_location(burst_info->payload_on_cpu); + + last_header_stride = burst_info->header_stride_size; + last_payload_stride = burst_info->payload_stride_size; + last_hds_on = burst_info->hds_on; + last_payload_on_cpu = burst_info->payload_on_cpu; + burst_info_initialized = true; + + HOLOSCAN_LOG_INFO( + "Burst configuration initialized: header_stride={}, payload_stride={}, hds_on={}, " + "payload_on_cpu={}", + burst_info->header_stride_size, + burst_info->payload_stride_size, + burst_info->hds_on, + burst_info->payload_on_cpu); + } else { + // Check for configuration changes after strategy confirmation + bool config_changed = + (last_header_stride != burst_info->header_stride_size || + last_payload_stride != burst_info->payload_stride_size || + last_hds_on != burst_info->hds_on || last_payload_on_cpu != burst_info->payload_on_cpu); + + if (config_changed) { + HOLOSCAN_LOG_WARN( + "Burst configuration changed after strategy confirmation - forcing re-detection"); + HOLOSCAN_LOG_INFO( + "Old config: header_stride={}, payload_stride={}, hds_on={}, payload_on_cpu={}", + last_header_stride, + last_payload_stride, + last_hds_on, + last_payload_on_cpu); + HOLOSCAN_LOG_INFO( + "New config: header_stride={}, payload_stride={}, hds_on={}, payload_on_cpu={}", + burst_info->header_stride_size, + burst_info->payload_stride_size, + burst_info->hds_on, + burst_info->payload_on_cpu); + + // Force strategy re-detection due to configuration change + packets_to_frames_converter_->force_strategy_redetection(); + + // Re-initialize with new configuration + packets_to_frames_converter_->configure_burst_parameters( + burst_info->header_stride_size, burst_info->payload_stride_size, burst_info->hds_on); + + packets_to_frames_converter_->set_source_memory_location(burst_info->payload_on_cpu); + + last_header_stride = burst_info->header_stride_size; + last_payload_stride = burst_info->payload_stride_size; + last_hds_on = burst_info->hds_on; + last_payload_on_cpu = burst_info->payload_on_cpu; + } + } + + // Log strategy detection progress (only during initial detection phase) + static bool strategy_logged = false; + if (!strategy_logged && + packets_to_frames_converter_->get_current_strategy() != CopyStrategy::UNKNOWN) { + HOLOSCAN_LOG_INFO( + "Packet copy strategy detected: {}", + packets_to_frames_converter_->get_current_strategy() == CopyStrategy::CONTIGUOUS + ? "CONTIGUOUS" + : "STRIDED"); + strategy_logged = true; + } +} + +void BurstProcessor::process_packets_in_burst(BurstParams* burst, bool hds_enabled) { + constexpr int GPU_PKTS = 1; + constexpr int CPU_PKTS = 0; + + for (size_t packet_index = 0; packet_index < burst->hdr.hdr.num_pkts; packet_index++) { + const uint8_t* rtp_header = + reinterpret_cast(burst->pkts[CPU_PKTS][packet_index]); + + uint8_t* payload = hds_enabled + ? reinterpret_cast(burst->pkts[GPU_PKTS][packet_index]) + : reinterpret_cast(burst->pkts[CPU_PKTS][packet_index]) + + RTP_SINGLE_SRD_HEADER_SIZE; + + RtpParams rtp_params; + if (!parse_rtp_header(rtp_header, rtp_params)) { + HOLOSCAN_LOG_ERROR("Failed to parse RTP header for packet {}, skipping", packet_index); + continue; + } + + packets_to_frames_converter_->process_incoming_packet(rtp_params, payload); + } +} + +bool BurstProcessor::has_pending_copy() const { + return packets_to_frames_converter_->has_pending_copy(); +} + +} // namespace holoscan::ops diff --git a/operators/advanced_network_media/advanced_network_media_rx/burst_processor.h b/operators/advanced_network_media/advanced_network_media_rx/burst_processor.h new file mode 100644 index 0000000000..f0229de6a8 --- /dev/null +++ b/operators/advanced_network_media/advanced_network_media_rx/burst_processor.h @@ -0,0 +1,83 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023 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_BURST_PROCESSOR_H_ +#define OPERATORS_ADVANCED_NETWORK_MEDIA_RX_BURST_PROCESSOR_H_ + +#include +#include "advanced_network/common.h" +#include "advanced_network/managers/rivermax/rivermax_ano_data_types.h" +#include "packets_to_frames_converter.h" + +namespace holoscan::ops { + +/** + * @brief Processor for handling burst-level operations and packet extraction + * + * This class encapsulates burst configuration parsing, packet extraction, + * and coordination with the PacketsToFramesConverter. It separates the + * low-level packet processing from the high-level network management. + * + * Responsibilities: + * - Parse burst configuration from AnoBurstExtendedInfo + * - Configure PacketsToFramesConverter with burst parameters + * - Extract RTP headers and payloads from burst packets + * - Handle Header Data Split (HDS) configuration + * - Detect and handle burst configuration changes + */ +class BurstProcessor { + public: + /** + * @brief Constructor + * @param converter Shared pointer to the packets-to-frames converter + */ + explicit BurstProcessor(std::shared_ptr converter); + + /** + * @brief Process an entire burst of packets + * @param burst The burst containing packets and configuration + * @param hds_enabled Whether header data splitting is enabled from operator config + */ + void process_burst(BurstParams* burst, bool hds_enabled); + + /** + * @brief Check if there is accumulated packet data waiting to be copied + * @return True if there is pending copy data, false otherwise + */ + bool has_pending_copy() const; + + private: + /** + * @brief Ensure converter has proper configuration (configure once, validate on subsequent calls) + * @param burst The burst containing configuration information + */ + void ensure_converter_configuration(BurstParams* burst); + + /** + * @brief Process all packets within a burst + * @param burst The burst containing packets to process + * @param hds_enabled Whether header data splitting is enabled + */ + void process_packets_in_burst(BurstParams* burst, bool hds_enabled); + + private: + std::shared_ptr packets_to_frames_converter_; +}; + +} // namespace holoscan::ops + +#endif // OPERATORS_ADVANCED_NETWORK_MEDIA_RX_BURST_PROCESSOR_H_ diff --git a/operators/advanced_network_media/advanced_network_media_rx/metadata.json b/operators/advanced_network_media/advanced_network_media_rx/metadata.json new file mode 100644 index 0000000000..ab57aed179 --- /dev/null +++ b/operators/advanced_network_media/advanced_network_media_rx/metadata.json @@ -0,0 +1,31 @@ +{ + "operator": { + "name": "advanced_network_media_rx", + "authors": [ + { + "name": "Rony Rado", + "affiliation": "NVIDIA" + } + ], + "version": "1.0", + "changelog": { + "1.0": "Initial Release" + }, + "platforms": ["x86_64", "aarch64"], + "tags": ["Network", "Networking", "DPDK", "UDP", "Ethernet", "IP", "GPUDirect", "Rivermax"], + "holoscan_sdk": { + "minimum_required_version": "2.6.0", + "tested_versions": [ + "2.6.0" + ] + }, + "ranking": 3, + "dependencies": { + "operators": [{ + "name": "advanced_network", + "version": "1.4" + } + ] + } + } +} \ No newline at end of file diff --git a/operators/advanced_network_media/advanced_network_media_rx/packets_to_frames_converter.cpp b/operators/advanced_network_media/advanced_network_media_rx/packets_to_frames_converter.cpp new file mode 100644 index 0000000000..7edffb5459 --- /dev/null +++ b/operators/advanced_network_media/advanced_network_media_rx/packets_to_frames_converter.cpp @@ -0,0 +1,847 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023 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 "advanced_network/common.h" +#include "packets_to_frames_converter.h" +#include "../common/adv_network_media_common.h" + +namespace holoscan::ops { + +cudaMemcpyKind MemoryCopyHelper::get_copy_kind(nvidia::gxf::MemoryStorageType src_storage_type, + nvidia::gxf::MemoryStorageType dst_storage_type) { + // Determine copy kind based on source and destination combinations + if (src_storage_type == nvidia::gxf::MemoryStorageType::kHost) { + if (dst_storage_type == nvidia::gxf::MemoryStorageType::kHost) { + return cudaMemcpyHostToHost; + } else { + return cudaMemcpyHostToDevice; + } + } else { // nvidia::gxf::MemoryStorageType::kDevice + if (dst_storage_type == nvidia::gxf::MemoryStorageType::kHost) { + return cudaMemcpyDeviceToHost; + } else { + return cudaMemcpyDeviceToDevice; + } + } +} + +nvidia::gxf::MemoryStorageType MemoryCopyHelper::to_storage_type(MemoryLocation location) { + switch (location) { + case MemoryLocation::Host: + return nvidia::gxf::MemoryStorageType::kHost; + case MemoryLocation::GPU: + return nvidia::gxf::MemoryStorageType::kDevice; + default: + return nvidia::gxf::MemoryStorageType::kDevice; + } +} + +void ContiguousStrategy::process_packet(PacketsToFramesConverter& converter, uint8_t* payload, + size_t payload_size) { + // Validate input parameters to prevent crashes + if (payload == nullptr) { + HOLOSCAN_LOG_ERROR("ContiguousStrategy: Received null payload pointer! Skipping packet."); + return; + } + if (payload_size == 0) { + HOLOSCAN_LOG_ERROR("ContiguousStrategy: Received zero payload size! Skipping packet."); + return; + } + + if (current_payload_start_ptr_ == nullptr) { + current_payload_start_ptr_ = payload; + PACKET_TRACE_LOG("ContiguousStrategy: Starting new accumulation at {} (size={})", + static_cast(payload), + payload_size); + } + + bool is_contiguous_memory = current_payload_start_ptr_ + accumulated_contiguous_size_ == payload; + if (!is_contiguous_memory) { + PACKET_TRACE_LOG("ContiguousStrategy: Stride break detected, copying {} bytes", + accumulated_contiguous_size_); + // Execute copy for previous accumulated batch (this will advance frame position) + execute_copy(converter); + + // Start new accumulation batch with current packet + current_payload_start_ptr_ = payload; + accumulated_contiguous_size_ = 0; + } + + // CRITICAL FIX: Check if adding this packet would exceed frame boundaries + // This prevents accumulation across multiple frames + auto frame = converter.get_destination_frame_buffer(); + size_t frame_remaining = frame->get_size() - converter.get_frame_position(); + size_t new_total_size = accumulated_contiguous_size_ + payload_size; + + if (new_total_size > frame_remaining && accumulated_contiguous_size_ > 0) { + PACKET_TRACE_LOG( + "ContiguousStrategy: Frame boundary detected, executing copy of {} bytes before adding new " + "packet", + accumulated_contiguous_size_); + // Execute copy for current accumulated data to avoid frame overflow + execute_copy(converter); + + // Start new accumulation with current packet + current_payload_start_ptr_ = payload; + accumulated_contiguous_size_ = 0; + } + + accumulated_contiguous_size_ += payload_size; +} + +void ContiguousStrategy::execute_copy(PacketsToFramesConverter& converter) { + if (accumulated_contiguous_size_ == 0 || current_payload_start_ptr_ == nullptr) return; + + auto frame = converter.get_destination_frame_buffer(); + uint8_t* dst_ptr = static_cast(frame->get()) + converter.get_frame_position(); + + PACKET_TRACE_LOG( + "ContiguousStrategy: Executing copy - frame_pos_before={}, copy_size={}, frame_total_size={}", + converter.get_frame_position(), + accumulated_contiguous_size_, + frame->get_size()); + + if (dst_ptr == nullptr) { + HOLOSCAN_LOG_ERROR("ERROR: dst_ptr is NULL! Skipping copy operation."); + return; + } + if (current_payload_start_ptr_ == nullptr) { + HOLOSCAN_LOG_ERROR("ERROR: current_payload_start_ptr_ is NULL! Skipping copy operation."); + return; + } + if (accumulated_contiguous_size_ == 0) { + HOLOSCAN_LOG_ERROR("WARNING: accumulated_contiguous_size_ is 0! Skipping copy operation."); + return; + } + + size_t dst_offset = converter.get_frame_position(); + size_t frame_size = frame->get_size(); + if (dst_offset + accumulated_contiguous_size_ > frame_size) { + HOLOSCAN_LOG_ERROR( + "ERROR: Copy would exceed frame bounds! dst_offset={}, copy_size={}, frame_size={}. " + "Skipping copy operation.", + dst_offset, + accumulated_contiguous_size_, + frame_size); + return; + } + + CUDA_TRY( + cudaMemcpy(dst_ptr, current_payload_start_ptr_, accumulated_contiguous_size_, copy_kind_)); + + // Advance frame position by the amount we just copied + converter.advance_frame_position(accumulated_contiguous_size_); + + PACKET_TRACE_LOG("ContiguousStrategy: Copy completed - frame_pos_after={}, copy_size={}", + converter.get_frame_position(), + accumulated_contiguous_size_); + + // Reset accumulation state for next batch + current_payload_start_ptr_ = nullptr; + accumulated_contiguous_size_ = 0; +} + +void ContiguousStrategy::reset_state() { + current_payload_start_ptr_ = nullptr; + accumulated_contiguous_size_ = 0; +} + +void StridedStrategy::process_packet(PacketsToFramesConverter& converter, uint8_t* payload, + size_t payload_size) { + if (payload == nullptr) { + HOLOSCAN_LOG_ERROR("StridedStrategy: Received null payload pointer! Skipping packet."); + return; + } + if (payload_size == 0) { + HOLOSCAN_LOG_ERROR("StridedStrategy: Received zero payload size! Skipping packet."); + return; + } + + if (first_packet_ptr_ == nullptr) { + reset_accumulation_state(payload, payload_size); + + HOLOSCAN_LOG_INFO("Strided strategy: First packet at {}, size: {}", + static_cast(payload), + payload_size); + return; + } + + if (!is_stride_maintained(payload, payload_size)) { + HOLOSCAN_LOG_INFO("Stride pattern broken, executing accumulated copy and falling back"); + + // Execute accumulated strided copy if we have multiple packets + if (packet_count_ > 1) { + execute_accumulated_strided_copy(converter); + } else { + // Single packet - use individual copy + execute_individual_copy(converter, first_packet_ptr_, total_data_size_); + } + + // Reset accumulation and start fresh with current packet + reset_accumulation_state(payload, payload_size); + return; + } + + // Stride is maintained - continue accumulating + last_packet_ptr_ = payload; + packet_count_++; + total_data_size_ += payload_size; +} + +void StridedStrategy::execute_copy(PacketsToFramesConverter& converter) { + if (packet_count_ == 0 || first_packet_ptr_ == nullptr) return; + + if (packet_count_ > 1 && stride_validated_) { + execute_accumulated_strided_copy(converter); + } else { + execute_individual_copy(converter, first_packet_ptr_, total_data_size_); + } +} + +void StridedStrategy::execute_accumulated_strided_copy(PacketsToFramesConverter& converter) { + if (packet_count_ <= 1 || first_packet_ptr_ == nullptr) return; + + auto frame = converter.get_destination_frame_buffer(); + uint8_t* dst_ptr = static_cast(frame->get()) + converter.get_frame_position(); + + // Use cudaMemcpy2D for strided copy + size_t width = stride_info_.payload_size; // Width of each row (payload size) + size_t height = packet_count_; // Number of rows (packets) + size_t src_pitch = actual_stride_; // Actual detected stride + size_t dst_pitch = width; // Destination pitch (contiguous) + + PACKET_TRACE_LOG("Executing strided copy: width={}, height={}, src_pitch={}, dst_pitch={}", + width, + height, + src_pitch, + dst_pitch); + + if (dst_ptr == nullptr) { + HOLOSCAN_LOG_ERROR("ERROR: dst_ptr is NULL! Skipping strided copy operation."); + return; + } + if (first_packet_ptr_ == nullptr) { + HOLOSCAN_LOG_ERROR("ERROR: first_packet_ptr_ is NULL! Skipping strided copy operation."); + return; + } + + // Check bounds to prevent frame overflow + size_t total_copy_size = width * height; + size_t dst_offset = converter.get_frame_position(); + size_t frame_size = frame->get_size(); + if (dst_offset + total_copy_size > frame_size) { + HOLOSCAN_LOG_ERROR( + "ERROR: Strided copy would exceed frame bounds! dst_offset={}, copy_size={}, " + "frame_size={}. Skipping copy operation.", + dst_offset, + total_copy_size, + frame_size); + return; + } + + CUDA_TRY( + cudaMemcpy2D(dst_ptr, dst_pitch, first_packet_ptr_, src_pitch, width, height, copy_kind_)); + + converter.advance_frame_position(total_data_size_); +} + +void StridedStrategy::execute_individual_copy(PacketsToFramesConverter& converter, + uint8_t* payload_ptr, size_t payload_size) { + auto frame = converter.get_destination_frame_buffer(); + uint8_t* dst_ptr = static_cast(frame->get()) + converter.get_frame_position(); + + PACKET_TRACE_LOG("Executing individual copy: size={}", payload_size); + + if (dst_ptr == nullptr) { + HOLOSCAN_LOG_ERROR("ERROR: dst_ptr is NULL! Skipping individual copy operation."); + return; + } + if (payload_ptr == nullptr) { + HOLOSCAN_LOG_ERROR("ERROR: payload_ptr is NULL! Skipping individual copy operation."); + return; + } + if (payload_size == 0) { + HOLOSCAN_LOG_ERROR("WARNING: payload_size is 0! Skipping individual copy operation."); + return; + } + + // Check bounds to prevent frame overflow + size_t dst_offset = converter.get_frame_position(); + size_t frame_size = frame->get_size(); + if (dst_offset + payload_size > frame_size) { + HOLOSCAN_LOG_ERROR( + "ERROR: Individual copy would exceed frame bounds! dst_offset={}, copy_size={}, " + "frame_size={}. Skipping copy operation.", + dst_offset, + payload_size, + frame_size); + return; + } + + CUDA_TRY(cudaMemcpy(dst_ptr, payload_ptr, payload_size, copy_kind_)); + + converter.advance_frame_position(payload_size); +} + +bool StridedStrategy::is_stride_maintained(uint8_t* payload, size_t payload_size) { + if (!last_packet_ptr_) { + // First packet in accumulation + return true; + } + + // Check if stride is maintained + size_t actual_diff = payload - last_packet_ptr_; + + if (!stride_validated_) { + // First stride validation + actual_stride_ = actual_diff; + stride_validated_ = true; + + // Verify it matches expected stride (with some tolerance) + if (actual_stride_ != stride_info_.stride_size) { + HOLOSCAN_LOG_INFO("Stride differs from expected: actual={}, expected={}", + actual_stride_, + stride_info_.stride_size); + // Still continue with actual stride, but log the difference + } + + HOLOSCAN_LOG_INFO("Stride validated: actual_stride={}", actual_stride_); + return true; + } else { + // Subsequent stride validation + if (actual_diff != actual_stride_) { + HOLOSCAN_LOG_INFO( + "Stride consistency broken: expected={}, actual={}", actual_stride_, actual_diff); + return false; + } + return true; + } +} + +bool StridedStrategy::detect_buffer_wraparound(uint8_t* current_ptr, uint8_t* expected_ptr) const { + // If current pointer is significantly lower than expected, likely a wrap-around + if (current_ptr < expected_ptr) { + ptrdiff_t backward_diff = expected_ptr - current_ptr; + if (backward_diff > WRAPAROUND_THRESHOLD) { return true; } + } + + // Additional check: if current pointer is way ahead of expected, might also indicate wrap + if (current_ptr > expected_ptr) { + ptrdiff_t forward_diff = current_ptr - expected_ptr; + if (forward_diff > WRAPAROUND_THRESHOLD) { return true; } + } + + return false; +} + +void StridedStrategy::reset_state() { + first_packet_ptr_ = nullptr; + last_packet_ptr_ = nullptr; + packet_count_ = 0; + total_data_size_ = 0; + stride_validated_ = false; + actual_stride_ = 0; +} + +void StridedStrategy::reset_accumulation_state(uint8_t* payload, size_t payload_size) { + first_packet_ptr_ = payload; + last_packet_ptr_ = payload; + packet_count_ = 1; + total_data_size_ = payload_size; + stride_validated_ = false; + actual_stride_ = 0; +} + +void PacketCopyStrategyDetector::configure_burst_parameters(size_t header_stride_size, + size_t payload_stride_size, + bool hds_on) { + if (!strategy_confirmed_) { + if (packets_analyzed_ > 0) { + bool config_changed = + (expected_header_stride_ != header_stride_size || + expected_payload_stride_ != payload_stride_size || expected_hds_on_ != hds_on); + + if (config_changed) { + HOLOSCAN_LOG_WARN("Burst configuration changed during detection, restarting analysis"); + reset_detection(); + } + } + + expected_header_stride_ = header_stride_size; + expected_payload_stride_ = payload_stride_size; + expected_hds_on_ = hds_on; + + HOLOSCAN_LOG_INFO( + "Burst info set for detection - header_stride: {}, payload_stride: {}, hds_on: {}", + header_stride_size, + payload_stride_size, + hds_on); + } else { + HOLOSCAN_LOG_INFO("Strategy already confirmed, ignoring burst info update"); + } +} + +bool PacketCopyStrategyDetector::collect_packet_info(const RtpParams& rtp_params, uint8_t* payload, + size_t payload_size) { + if (strategy_confirmed_) { return true; } + + // Use provided RTP parameters directly (no parsing needed) + detection_payloads_.push_back(payload); + detection_payload_sizes_.push_back(payload_size); + detection_rtp_sequences_.push_back(rtp_params.sequence_number); + packets_analyzed_++; + + HOLOSCAN_LOG_INFO("Collected packet {} for detection: payload={}, size={}, seq={}", + packets_analyzed_, + static_cast(payload), + payload_size, + rtp_params.sequence_number); + + return packets_analyzed_ >= STRATEGY_DETECTION_PACKETS; +} + +std::unique_ptr PacketCopyStrategyDetector::detect_strategy() { + if (detection_payloads_.size() < 2) { + strategy_confirmed_ = true; + HOLOSCAN_LOG_INFO("Insufficient packets for analysis, defaulting to CONTIGUOUS strategy"); + return std::make_unique(); + } + + if (!validate_rtp_sequence_continuity()) { + HOLOSCAN_LOG_WARN("RTP sequence drops detected, restarting detection"); + reset_detection(); + return nullptr; + } + + if (detect_cyclic_buffer_wraparound()) { + HOLOSCAN_LOG_WARN("Cyclic buffer wrap-around detected, restarting detection"); + reset_detection(); + return nullptr; + } + + auto analysis_result = analyze_packet_pattern(); + if (!analysis_result) { + HOLOSCAN_LOG_WARN("Pattern analysis failed, restarting detection"); + reset_detection(); + return nullptr; + } + + strategy_confirmed_ = true; + + auto [strategy_type, stride_info] = *analysis_result; + + HOLOSCAN_LOG_INFO("Strategy detection completed: {} (stride: {}, payload: {})", + strategy_type == CopyStrategy::CONTIGUOUS ? "CONTIGUOUS" : "STRIDED", + stride_info.stride_size, + stride_info.payload_size); + + if (strategy_type == CopyStrategy::CONTIGUOUS) { + return std::make_unique(); + } else { + return std::make_unique(stride_info); + } +} + +void PacketCopyStrategyDetector::reset_detection() { + strategy_confirmed_ = false; + detection_payloads_.clear(); + detection_payload_sizes_.clear(); + detection_rtp_sequences_.clear(); + packets_analyzed_ = 0; + + HOLOSCAN_LOG_INFO("Strategy detection reset"); +} + +std::optional> +PacketCopyStrategyDetector::analyze_packet_pattern() { + if (detection_payloads_.size() < 2) return std::nullopt; + + size_t expected_stride = expected_hds_on_ ? expected_payload_stride_ + : (expected_header_stride_ + expected_payload_stride_); + + // Analyze pointer differences for exact contiguity and stride consistency + bool is_exactly_contiguous = true; + bool is_stride_consistent = true; + size_t actual_stride = 0; + size_t detected_payload_size = detection_payload_sizes_[0]; // Use first packet's payload size + + // Verify all packets have exactly the same payload size + for (size_t i = 1; i < detection_payload_sizes_.size(); ++i) { + if (detection_payload_sizes_[i] != detected_payload_size) { + HOLOSCAN_LOG_WARN("Inconsistent payload sizes detected: first={}, packet_{}={}", + detected_payload_size, + i, + detection_payload_sizes_[i]); + return std::nullopt; // Cannot handle varying payload sizes + } + } + + for (size_t i = 1; i < detection_payloads_.size(); ++i) { + uint8_t* prev_ptr = detection_payloads_[i - 1]; + uint8_t* curr_ptr = detection_payloads_[i]; + + size_t pointer_diff = curr_ptr - prev_ptr; + + if (i == 1) { actual_stride = pointer_diff; } + + // Check for exact contiguity: next pointer should be exactly at previous_pointer + + // previous_payload_size + uint8_t* expected_next_ptr = prev_ptr + detected_payload_size; + + if (curr_ptr != expected_next_ptr) { + is_exactly_contiguous = false; + HOLOSCAN_LOG_INFO("Non-contiguous detected: packet {}, expected ptr {}, actual ptr {}", + i, + static_cast(expected_next_ptr), + static_cast(curr_ptr)); + } + + // Check if stride is consistent (independent of contiguity) + if (pointer_diff != actual_stride) { + is_stride_consistent = false; + HOLOSCAN_LOG_INFO("Inconsistent stride detected: packet {}, expected stride {}, actual {}", + i, + actual_stride, + pointer_diff); + break; // No point checking further if stride is inconsistent + } + } + + StrideInfo stride_info; + stride_info.stride_size = actual_stride; + stride_info.payload_size = detected_payload_size; // Use exact payload size, not average + + // Three-way decision logic: + CopyStrategy strategy; + if (is_exactly_contiguous) { + // Case 1: Exactly contiguous - use efficient CONTIGUOUS strategy + strategy = CopyStrategy::CONTIGUOUS; + HOLOSCAN_LOG_INFO("Packets are exactly contiguous, using CONTIGUOUS strategy"); + } else if (is_stride_consistent) { + // Case 2: Gaps but consistent stride - use STRIDED strategy + strategy = CopyStrategy::STRIDED; + HOLOSCAN_LOG_INFO( + "Packets have gaps but consistent stride, using STRIDED strategy (stride: {}, payload: {})", + actual_stride, + detected_payload_size); + } else { + // Case 3: Gaps with inconsistent stride - fallback to CONTIGUOUS (individual copies) + strategy = CopyStrategy::CONTIGUOUS; + HOLOSCAN_LOG_INFO( + "Packets have inconsistent stride, falling back to CONTIGUOUS strategy for individual " + "copies"); + } + + HOLOSCAN_LOG_INFO( + "Analysis results - actual_stride: {}, expected_stride: {}, payload_size: {}, " + "exactly_contiguous: {}, stride_consistent: {}", + actual_stride, + expected_stride, + detected_payload_size, + is_exactly_contiguous, + is_stride_consistent); + + return std::make_pair(strategy, stride_info); +} + +bool PacketCopyStrategyDetector::validate_rtp_sequence_continuity() const { + if (detection_rtp_sequences_.size() < 2) { return true; } + + for (size_t i = 1; i < detection_rtp_sequences_.size(); ++i) { + uint64_t prev_seq = detection_rtp_sequences_[i - 1]; + uint64_t curr_seq = detection_rtp_sequences_[i]; + + // Handle 16-bit sequence number wrap-around + uint64_t expected_seq = (prev_seq + 1) & 0xFFFFFFFFFFFFFFFF; + + if (curr_seq != expected_seq) { + HOLOSCAN_LOG_INFO("RTP sequence discontinuity: expected {}, got {} (prev was {})", + expected_seq, + curr_seq, + prev_seq); + return false; + } + } + + return true; +} + +bool PacketCopyStrategyDetector::detect_cyclic_buffer_wraparound() const { + if (detection_payloads_.size() < 2) { return false; } + + for (size_t i = 1; i < detection_payloads_.size(); ++i) { + uint8_t* prev_ptr = detection_payloads_[i - 1]; + uint8_t* curr_ptr = detection_payloads_[i]; + + // If current pointer is significantly lower than previous, likely a wrap-around + if (curr_ptr < prev_ptr) { + ptrdiff_t backward_diff = prev_ptr - curr_ptr; + // Consider it a wrap-around if the backward difference is large (> 1MB) + if (backward_diff > 1024 * 1024) { + HOLOSCAN_LOG_INFO("Potential cyclic buffer wrap-around detected: {} -> {}", + static_cast(prev_ptr), + static_cast(curr_ptr)); + return true; + } + } + } + + return false; +} + +PacketsToFramesConverter::PacketsToFramesConverter(std::shared_ptr frame_provider) + : frame_provider_(frame_provider), + frame_(frame_provider->get_allocated_frame()), + waiting_for_end_of_frame_(false), + current_byte_in_frame_(0) { + detector_.reset_detection(); +} + +std::unique_ptr PacketsToFramesConverter::create( + IFrameProvider* provider) { + // Create a non-owning shared_ptr wrapper + // This is safe as long as the provider outlives the converter (which it does in our ownership + // model) + auto shared_provider = std::shared_ptr(provider, [](IFrameProvider*) { + // Custom deleter that does nothing - we don't own the pointer + }); + + return std::make_unique(shared_provider); +} + +void PacketsToFramesConverter::configure_burst_parameters(size_t header_stride_size, + size_t payload_stride_size, bool hds_on) { + detector_.configure_burst_parameters(header_stride_size, payload_stride_size, hds_on); +} + +void PacketsToFramesConverter::set_source_memory_location(bool payload_on_cpu) { + source_memory_location_ = payload_on_cpu ? nvidia::gxf::MemoryStorageType::kHost + : nvidia::gxf::MemoryStorageType::kDevice; + + // Update current strategy if it exists + if (current_strategy_) { + auto dst_location = MemoryCopyHelper::to_storage_type(frame_->get_memory_location()); + current_strategy_->set_memory_locations(source_memory_location_, dst_location); + } + + HOLOSCAN_LOG_INFO( + "Source memory location set to: {} (payload_on_cpu={})", + source_memory_location_ == nvidia::gxf::MemoryStorageType::kHost ? "HOST" : "DEVICE", + payload_on_cpu); +} + +void PacketsToFramesConverter::set_source_memory_location( + nvidia::gxf::MemoryStorageType src_storage_type) { + source_memory_location_ = src_storage_type; + + // Update current strategy if it exists + if (current_strategy_) { + auto dst_location = MemoryCopyHelper::to_storage_type(frame_->get_memory_location()); + current_strategy_->set_memory_locations(source_memory_location_, dst_location); + } + + HOLOSCAN_LOG_INFO( + "Source memory location set to: {}", + source_memory_location_ == nvidia::gxf::MemoryStorageType::kHost ? "HOST" : "DEVICE"); +} + +void PacketsToFramesConverter::process_incoming_packet(const RtpParams& rtp_params, + uint8_t* payload) { + if (waiting_for_end_of_frame_) { + if (rtp_params.m_bit) { + HOLOSCAN_LOG_INFO("End of frame received, restarting"); + waiting_for_end_of_frame_ = false; + reset_frame_state(); + } + return; + } + + // Strategy detection phase (only during initialization) + if (!current_strategy_) { + if (!handle_strategy_detection(rtp_params, payload, rtp_params.payload_size)) { + return; // Still detecting, skip processing + } + ensure_strategy_available(); + } + + // Check if this is the end-of-frame marker packet BEFORE processing + if (rtp_params.m_bit) { + PACKET_TRACE_LOG( + "End-of-frame marker detected (M-bit=1) for seq={}, payload_size={}, frame_pos={}", + rtp_params.sequence_number, + rtp_params.payload_size, + current_byte_in_frame_); + + // CRITICAL: Execute any pending copy operations from previous packets FIRST + // This ensures we don't mix data from multiple frames in the same copy operation + if (current_strategy_ && current_strategy_->has_pending_copy()) { + PACKET_TRACE_LOG( + "Executing pending copy operations before processing M-bit packet (frame_pos={})", + current_byte_in_frame_); + current_strategy_->execute_copy(*this); + PACKET_TRACE_LOG("Pending copy completed, frame_pos now={}", current_byte_in_frame_); + } + + // Now process the final packet data for this frame + current_strategy_->process_packet(*this, payload, rtp_params.payload_size); + // Then handle end of frame + handle_end_of_frame(); + return; + } + + // Main data path - process packet (only for non-marker packets) + current_strategy_->process_packet(*this, payload, rtp_params.payload_size); + + // Validate packet integrity (only for non-marker packets) + const auto& [is_corrupted, error] = validate_packet_integrity(rtp_params); + if (is_corrupted) { + HOLOSCAN_LOG_ERROR("Frame is corrupted: {}", error); + waiting_for_end_of_frame_ = true; // Wait for next frame start + } +} + +void PacketsToFramesConverter::reset_frame_state() { + current_byte_in_frame_ = 0; + if (current_strategy_) { current_strategy_->reset_state(); } +} + +CopyStrategy PacketsToFramesConverter::get_current_strategy() const { + if (current_strategy_) { return current_strategy_->get_strategy_type(); } + return CopyStrategy::UNKNOWN; +} + +void PacketsToFramesConverter::force_strategy_redetection() { + detector_.reset_detection(); + current_strategy_.reset(); + HOLOSCAN_LOG_INFO("Forced strategy re-detection"); +} + +bool PacketsToFramesConverter::has_pending_copy() const { + if (!current_strategy_) { + HOLOSCAN_LOG_DEBUG("has_pending_copy: no strategy, returning false"); + return false; + } + bool result = current_strategy_->has_pending_copy(); + return result; +} + +std::pair PacketsToFramesConverter::validate_packet_integrity( + const RtpParams& rtp_params) { + int64_t bytes_left = frame_->get_size() - current_byte_in_frame_; + + if (bytes_left < 0) { + return {true, "Frame received is not aligned to the frame size and will be dropped"}; + } + + bool frame_full = bytes_left <= 0; + if (frame_full && !rtp_params.m_bit) { + return {true, "Frame is full but marker was not not appear"}; + } + + if (!frame_full && rtp_params.m_bit) { + HOLOSCAN_LOG_ERROR( + "Marker appeared but frame is not full - Frame details: " + "current_position={} bytes, total_frame_size={} bytes, " + "bytes_remaining={} bytes, payload_size={} bytes, " + "sequence_number={}", + current_byte_in_frame_, + frame_->get_size(), + bytes_left, + rtp_params.payload_size, + rtp_params.sequence_number); + return {true, "Marker appeared but frame is not full"}; + } + + return {false, ""}; +} + +bool PacketsToFramesConverter::handle_strategy_detection(const RtpParams& rtp_params, + uint8_t* payload, size_t payload_size) { + if (detector_.is_strategy_confirmed()) { + return true; // Strategy already confirmed + } + + HOLOSCAN_LOG_TRACE("Processing packet for detection: seq={}, payload_size={}, m_bit={}", + rtp_params.sequence_number, + payload_size, + rtp_params.m_bit); + + bool ready_for_detection = detector_.collect_packet_info(rtp_params, payload, payload_size); + if (ready_for_detection) { + auto strategy = detector_.detect_strategy(); + if (strategy) { + current_strategy_ = std::move(strategy); + auto dst_location = MemoryCopyHelper::to_storage_type(frame_->get_memory_location()); + current_strategy_->set_memory_locations(source_memory_location_, dst_location); + HOLOSCAN_LOG_INFO("Strategy detection successful: {}", + current_strategy_->get_strategy_type() == CopyStrategy::CONTIGUOUS + ? "CONTIGUOUS" + : "STRIDED"); + return true; // Strategy ready + } else { + HOLOSCAN_LOG_INFO("Strategy detection failed, will retry with next packets"); + } + } else { + HOLOSCAN_LOG_INFO( + "Still collecting packets for strategy detection, skipping packet processing"); + } + + return false; // Still detecting +} + +void PacketsToFramesConverter::ensure_strategy_available() { + if (!current_strategy_) { + current_strategy_ = std::make_unique(); + auto dst_location = MemoryCopyHelper::to_storage_type(frame_->get_memory_location()); + current_strategy_->set_memory_locations(source_memory_location_, dst_location); + HOLOSCAN_LOG_INFO("Using fallback CONTIGUOUS strategy"); + } +} + +void PacketsToFramesConverter::handle_end_of_frame() { + PACKET_TRACE_LOG( + "End of frame marker received, executing final copy. Frame position: {}/{} bytes", + current_byte_in_frame_, + frame_->get_size()); + + // Execute any remaining accumulated data + current_strategy_->execute_copy(*this); + + // Validate frame completion + int64_t bytes_left = frame_->get_size() - current_byte_in_frame_; + if (bytes_left > 0) { + HOLOSCAN_LOG_WARN("Frame incomplete after marker: {} bytes missing (expected: {}, actual: {})", + bytes_left, + frame_->get_size(), + current_byte_in_frame_); + } else if (bytes_left < 0) { + HOLOSCAN_LOG_ERROR("Frame overflow after marker: {} bytes too many (expected: {}, actual: {})", + -bytes_left, + frame_->get_size(), + current_byte_in_frame_); + } else { + PACKET_TRACE_LOG("Frame completed successfully: {} bytes", current_byte_in_frame_); + } + + // Complete frame and get new one + frame_provider_->on_new_frame(frame_); + frame_ = frame_provider_->get_allocated_frame(); + reset_frame_state(); +} + +} // namespace holoscan::ops diff --git a/operators/advanced_network_media/advanced_network_media_rx/packets_to_frames_converter.h b/operators/advanced_network_media/advanced_network_media_rx/packets_to_frames_converter.h new file mode 100644 index 0000000000..fcde6be735 --- /dev/null +++ b/operators/advanced_network_media/advanced_network_media_rx/packets_to_frames_converter.h @@ -0,0 +1,464 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023 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_PACKETS_TO_FRAMES_CONVERTER_H_ +#define OPERATORS_ADVANCED_NETWORK_MEDIA_RX_PACKETS_TO_FRAMES_CONVERTER_H_ + +#include +#include +#include +#include "advanced_network/common.h" +#include "../common/adv_network_media_common.h" +#include "../common/frame_buffer.h" + +namespace holoscan::ops { + +/** + * @brief Strategy for copying packet data to frames + */ +enum class CopyStrategy { + UNKNOWN, // Strategy not yet determined + CONTIGUOUS, // Packets are exactly contiguous in memory - use cudaMemcpy + STRIDED // Packets have any gaps - use cudaMemcpy2D +}; + +/** + * @brief Memory copy configuration helper + */ +class MemoryCopyHelper { + public: + /** + * @brief Determine appropriate cudaMemcpyKind based on source and destination memory locations + * @param src_storage_type Source memory storage type + * @param dst_storage_type Destination memory storage type + * @return Appropriate cudaMemcpyKind for the operation + */ + static cudaMemcpyKind get_copy_kind(nvidia::gxf::MemoryStorageType src_storage_type, + nvidia::gxf::MemoryStorageType dst_storage_type); + + /** + * @brief Convert MemoryLocation to MemoryStorageType + * @param location Memory location to convert + * @return Corresponding MemoryStorageType + */ + static nvidia::gxf::MemoryStorageType to_storage_type(MemoryLocation location); +}; + +/** + * @brief Information about detected stride pattern + */ +struct StrideInfo { + size_t stride_size = 0; // Detected stride size between packets + size_t payload_size = 0; // Expected payload size per packet +}; + +/** + * @brief Forward declarations + */ +class PacketsToFramesConverter; + +/** + * @brief Interface for providing and managing frame buffers + */ +class IFrameProvider { + public: + virtual ~IFrameProvider() = default; + + /** + * @brief Gets an allocated frame buffer + * @return Shared pointer to allocated frame buffer + */ + virtual std::shared_ptr get_allocated_frame() = 0; + + /** + * @brief Called when a new frame is completed + * @param frame The completed frame buffer + */ + virtual void on_new_frame(std::shared_ptr frame) = 0; +}; + +/** + * @brief Interface for packet copy strategies + */ +class IPacketCopyStrategy { + public: + virtual ~IPacketCopyStrategy() = default; + + /** + * @brief Process a packet using this strategy + * @param converter Reference to converter for accessing frame data + * @param payload Payload data pointer + * @param payload_size Size of payload data + */ + virtual void process_packet(PacketsToFramesConverter& converter, uint8_t* payload, + size_t payload_size) = 0; + + /** + * @brief Execute the copy operation + * @param converter Reference to converter for accessing frame data + */ + virtual void execute_copy(PacketsToFramesConverter& converter) = 0; + + /** + * @brief Reset strategy state for new frame + */ + virtual void reset_state() = 0; + + /** + * @brief Get strategy type + * @return Copy strategy type + */ + virtual CopyStrategy get_strategy_type() const = 0; + + /** + * @brief Check if strategy has accumulated data waiting to be copied + * @return True if there's pending copy data, false otherwise + */ + virtual bool has_pending_copy() const = 0; + + /** + * @brief Set source memory location for packet data and cache copy kind + * @param src_location Source memory storage type + * @param dst_location Destination memory storage type + */ + virtual void set_memory_locations(nvidia::gxf::MemoryStorageType src_location, + nvidia::gxf::MemoryStorageType dst_location) = 0; +}; + +/** + * @brief Contiguous packet copy strategy + */ +class ContiguousStrategy : public IPacketCopyStrategy { + public: + void process_packet(PacketsToFramesConverter& converter, uint8_t* payload, + size_t payload_size) override; + + void execute_copy(PacketsToFramesConverter& converter) override; + void reset_state() override; + CopyStrategy get_strategy_type() const override { return CopyStrategy::CONTIGUOUS; } + bool has_pending_copy() const override { return accumulated_contiguous_size_ > 0; } + void set_memory_locations(nvidia::gxf::MemoryStorageType src_location, + nvidia::gxf::MemoryStorageType dst_location) override { + src_memory_location_ = src_location; + copy_kind_ = MemoryCopyHelper::get_copy_kind(src_location, dst_location); + } + + private: + size_t accumulated_contiguous_size_ = 0; + uint8_t* current_payload_start_ptr_ = nullptr; + nvidia::gxf::MemoryStorageType src_memory_location_ = nvidia::gxf::MemoryStorageType::kDevice; + cudaMemcpyKind copy_kind_ = cudaMemcpyDeviceToDevice; +}; + +/** + * @brief Strided packet copy strategy + */ +class StridedStrategy : public IPacketCopyStrategy { + public: + explicit StridedStrategy(const StrideInfo& stride_info) : stride_info_(stride_info) {} + + void process_packet(PacketsToFramesConverter& converter, uint8_t* payload, + size_t payload_size) override; + + void execute_copy(PacketsToFramesConverter& converter) override; + void reset_state() override; + CopyStrategy get_strategy_type() const override { return CopyStrategy::STRIDED; } + bool has_pending_copy() const override { return packet_count_ > 0; } + void set_memory_locations(nvidia::gxf::MemoryStorageType src_location, + nvidia::gxf::MemoryStorageType dst_location) override { + src_memory_location_ = src_location; + copy_kind_ = MemoryCopyHelper::get_copy_kind(src_location, dst_location); + } + + private: + /** + * @brief Execute accumulated strided copy and reset accumulation state + * @param converter Reference to converter for accessing frame data + */ + void execute_accumulated_strided_copy(PacketsToFramesConverter& converter); + + /** + * @brief Execute individual packet copy (fallback when stride breaks) + * @param converter Reference to converter for accessing frame data + * @param payload_ptr Pointer to payload data + * @param payload_size Size of payload data + */ + void execute_individual_copy(PacketsToFramesConverter& converter, uint8_t* payload_ptr, + size_t payload_size); + + /** + * @brief Check if current packet maintains expected stride pattern + * @param payload Current packet payload pointer + * @param payload_size Current packet payload size + * @return True if stride is maintained, false if broken + */ + bool is_stride_maintained(uint8_t* payload, size_t payload_size); + + /** + * @brief Detect if buffer wrap-around occurred + * @param current_ptr Current packet pointer + * @param expected_ptr Expected pointer based on stride + * @return True if wrap-around detected + */ + bool detect_buffer_wraparound(uint8_t* current_ptr, uint8_t* expected_ptr) const; + + /** + * @brief Reset accumulation state and start fresh with new packet + * @param payload New packet payload pointer + * @param payload_size New packet payload size + */ + void reset_accumulation_state(uint8_t* payload, size_t payload_size); + + private: + StrideInfo stride_info_; + + // Strided copy accumulation state + uint8_t* first_packet_ptr_ = nullptr; + uint8_t* last_packet_ptr_ = nullptr; + size_t packet_count_ = 0; + size_t total_data_size_ = 0; + + // Stride validation state + bool stride_validated_ = false; + size_t actual_stride_ = 0; + + nvidia::gxf::MemoryStorageType src_memory_location_ = nvidia::gxf::MemoryStorageType::kDevice; + cudaMemcpyKind copy_kind_ = cudaMemcpyDeviceToDevice; + + // Buffer wrap-around detection threshold (1MB) + static constexpr size_t WRAPAROUND_THRESHOLD = 1024 * 1024; +}; + +/** + * @brief Strategy detector for analyzing packet patterns + */ +class PacketCopyStrategyDetector { + public: + static constexpr size_t STRATEGY_DETECTION_PACKETS = 4; // Number of packets to analyze + + /** + * @brief Configure detector with burst configuration parameters + * @param header_stride_size Header stride size from burst info + * @param payload_stride_size Payload stride size from burst info + * @param hds_on Whether header data splitting is enabled + */ + void configure_burst_parameters(size_t header_stride_size, size_t payload_stride_size, + bool hds_on); + + /** + * @brief Collect packet information for detection + * @param rtp_params Parsed RTP parameters from header + * @param payload Payload pointer for current packet + * @param payload_size Size of payload data + * @return True if enough packets collected for detection + */ + bool collect_packet_info(const RtpParams& rtp_params, uint8_t* payload, size_t payload_size); + + /** + * @brief Detect optimal copy strategy + * @return Unique pointer to detected strategy, nullptr if detection failed + */ + std::unique_ptr detect_strategy(); + + /** + * @brief Check if strategy has been confirmed + * @return True if strategy detection is complete + */ + bool is_strategy_confirmed() const { return strategy_confirmed_; } + + /** + * @brief Reset detector for new detection cycle + */ + void reset_detection(); + + /** + * @brief Get number of packets analyzed + * @return Number of packets analyzed + */ + size_t get_packets_analyzed() const { return packets_analyzed_; } + + private: + /** + * @brief Analyze collected packet information to determine strategy + * @return Detected strategy info, nullopt if detection failed + */ + std::optional> analyze_packet_pattern(); + + /** + * @brief Check if RTP sequences are consecutive (no drops) + * @return True if no drops detected, false otherwise + */ + bool validate_rtp_sequence_continuity() const; + + /** + * @brief Detect cyclic buffer wrap-around in pointer sequence + * @return True if wrap-around detected, false otherwise + */ + bool detect_cyclic_buffer_wraparound() const; + + private: + // Detection state + std::vector detection_payloads_; + std::vector detection_payload_sizes_; + std::vector detection_rtp_sequences_; + size_t packets_analyzed_ = 0; + bool strategy_confirmed_ = false; + + // Burst configuration + size_t expected_header_stride_ = 0; + size_t expected_payload_stride_ = 0; + bool expected_hds_on_ = false; +}; + +/** + * @brief Converter for transforming packets to frames with optimized copy strategies + */ +class PacketsToFramesConverter { + public: + /** + * @brief Constructor + * @param frame_provider Shared pointer to frame provider interface + */ + explicit PacketsToFramesConverter(std::shared_ptr frame_provider); + + /** + * @brief Factory method to create converter with raw @ref IFrameProvider pointer + * @param provider Raw pointer to frame provider (must outlive the converter) + * @return Unique pointer to the created converter + * @note This creates a non-owning shared_ptr wrapper for compatibility + */ + static std::unique_ptr create(IFrameProvider* provider); + + /** + * @brief Process an incoming packet + * @param rtp_params Parsed RTP parameters from header + * @param payload Payload data pointer + */ + void process_incoming_packet(const RtpParams& rtp_params, uint8_t* payload); + + /** + * @brief Configure with burst stride information (only affects detection if not already + * confirmed) + * @param header_stride_size Header stride size from burst info + * @param payload_stride_size Payload stride size from burst info + * @param hds_on Whether header data splitting is enabled + */ + void configure_burst_parameters(size_t header_stride_size, size_t payload_stride_size, + bool hds_on); + + /** + * @brief Set source memory location for packet data + * @param payload_on_cpu Whether payload data is on CPU (true) or GPU (false) + */ + void set_source_memory_location(bool payload_on_cpu); + + /** + * @brief Set source memory location for packet data using storage type + * @param src_storage_type Source memory storage type + */ + void set_source_memory_location(nvidia::gxf::MemoryStorageType src_storage_type); + + /** + * @brief Reset converter state for new frame sequence + */ + void reset_frame_state(); + + /** + * @brief Get current copy strategy (for debugging/monitoring) + * @return Current copy strategy + */ + CopyStrategy get_current_strategy() const; + + /** + * @brief Force strategy re-detection (for testing or configuration changes) + */ + void force_strategy_redetection(); + + /** + * @brief Check if there is accumulated packet data waiting to be copied + * @return True if there is pending copy data, false otherwise + */ + bool has_pending_copy() const; + + // Friend classes for accessing internal state + friend class ContiguousStrategy; + friend class StridedStrategy; + + /** + * @brief Get destination frame buffer for copy operations + * @return Shared pointer to current destination frame buffer + */ + std::shared_ptr get_destination_frame_buffer() const { return frame_; } + + /** + * @brief Get current byte position in frame + * @return Current byte position + */ + size_t get_frame_position() const { return current_byte_in_frame_; } + + /** + * @brief Advance current byte position in frame + * @param bytes Bytes to advance + */ + void advance_frame_position(size_t bytes) { current_byte_in_frame_ += bytes; } + + private: + /** + * @brief Validate packet integrity and frame completion status + * @param rtp_params Parsed RTP parameters from header + * @return Pair of corruption status and error message + */ + std::pair validate_packet_integrity(const RtpParams& rtp_params); + + /** + * @brief Handle strategy detection phase for incoming packet + * @param rtp_params Parsed RTP parameters from header + * @param payload Payload data pointer + * @param payload_size Size of payload data + * @return True if packet should be processed, false if still detecting + */ + bool handle_strategy_detection(const RtpParams& rtp_params, uint8_t* payload, + size_t payload_size); + + /** + * @brief Ensure strategy is available (create fallback if needed) + */ + void ensure_strategy_available(); + + /** + * @brief Handle end of frame processing + */ + void handle_end_of_frame(); + + private: + std::shared_ptr frame_provider_; + std::shared_ptr frame_; + + // Current frame state + bool waiting_for_end_of_frame_; + size_t current_byte_in_frame_; + + // Strategy components + PacketCopyStrategyDetector detector_; + std::unique_ptr current_strategy_; + + nvidia::gxf::MemoryStorageType source_memory_location_ = nvidia::gxf::MemoryStorageType::kDevice; +}; + +} // namespace holoscan::ops + +#endif // OPERATORS_ADVANCED_NETWORK_MEDIA_RX_PACKETS_TO_FRAMES_CONVERTER_H_ diff --git a/operators/advanced_network_media/advanced_network_media_rx/python/CMakeLists.txt b/operators/advanced_network_media/advanced_network_media_rx/python/CMakeLists.txt new file mode 100644 index 0000000000..805417511f --- /dev/null +++ b/operators/advanced_network_media/advanced_network_media_rx/python/CMakeLists.txt @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: Copyright (c) 2022-2023 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(pybind11_add_holohub_module) +find_package(holoscan 2.6 REQUIRED CONFIG + PATHS "/opt/nvidia/holoscan" "/workspace/holoscan-sdk/install") + +set(MODULE_NAME advanced_network_media_rx) +set(MODULE_CLASS_NAME "*") + +pybind11_add_holohub_module( + CPP_CMAKE_TARGET advanced_network_media_rx + CLASS_NAME "AdvNetworkMediaRxOp" + SOURCES adv_network_media_rx_pybind.cpp +) + +target_link_libraries(${MODULE_NAME}_python +PRIVATE + holoscan::core + ${MODULE_NAME} +) + +set(CMAKE_PYBIND11_HOLOHUB_MODULE_OUT_DIR ${CMAKE_BINARY_DIR}/python/${CMAKE_INSTALL_LIBDIR}/holohub) +set(CMAKE_SUBMODULE_OUT_DIR ${CMAKE_PYBIND11_HOLOHUB_MODULE_OUT_DIR}/${MODULE_NAME}) + +set_target_properties(${MODULE_NAME}_python PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SUBMODULE_OUT_DIR} + OUTPUT_NAME _${MODULE_NAME} +) + +configure_file( + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/pybind11/__init__.py + ${CMAKE_PYBIND11_HOLOHUB_MODULE_OUT_DIR}/advanced_network_media_rx/__init__.py +) diff --git a/operators/advanced_network_media/advanced_network_media_rx/python/adv_network_media_rx_pybind.cpp b/operators/advanced_network_media/advanced_network_media_rx/python/adv_network_media_rx_pybind.cpp new file mode 100644 index 0000000000..50bf9c1dbb --- /dev/null +++ b/operators/advanced_network_media/advanced_network_media_rx/python/adv_network_media_rx_pybind.cpp @@ -0,0 +1,139 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2022-2024 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 "../adv_network_media_rx.h" +#include "./adv_network_media_rx_pydoc.hpp" + +#include +#include // for unordered_map -> dict, etc. + +#include +#include +#include + +#include "../../../operator_util.hpp" +#include +#include +#include +#include + +// Add advanced network headers +#include "advanced_network/common.h" +#include "advanced_network/types.h" + +using std::string_literals::operator""s; +using pybind11::literals::operator""_a; + +#define STRINGIFY(x) #x +#define MACRO_STRINGIFY(x) STRINGIFY(x) + +namespace py = pybind11; + +namespace holoscan::ops { + +/* Trampoline classes for handling Python kwargs + * + * These add a constructor that takes a Fragment for which to initialize the operator. + * The explicit parameter list and default arguments take care of providing a Pythonic + * kwarg-based interface with appropriate default values matching the operator's + * default parameters in the C++ API `setup` method. + * + * The sequence of events in this constructor is based on Fragment::make_operator + */ + +class PyAdvNetworkMediaRxOp : public AdvNetworkMediaRxOp { + public: + /* Inherit the constructors */ + using AdvNetworkMediaRxOp::AdvNetworkMediaRxOp; + + // Define a constructor that fully initializes the object. + PyAdvNetworkMediaRxOp(Fragment* fragment, const py::args& args, + const std::string& interface_name = "", + uint16_t queue_id = default_queue_id, uint32_t frame_width = 1920, + uint32_t frame_height = 1080, uint32_t bit_depth = 8, + const std::string& video_format = "RGB888", bool hds = true, + const std::string& output_format = "video_buffer", + const std::string& memory_location = "device", + const std::string& name = "advanced_network_media_rx") { + add_positional_condition_and_resource_args(this, args); + name_ = name; + fragment_ = fragment; + spec_ = std::make_shared(fragment); + setup(*spec_.get()); + + // Set parameters if provided + if (!interface_name.empty()) { this->add_arg(Arg("interface_name", interface_name)); } + this->add_arg(Arg("queue_id", queue_id)); + this->add_arg(Arg("frame_width", frame_width)); + this->add_arg(Arg("frame_height", frame_height)); + this->add_arg(Arg("bit_depth", bit_depth)); + this->add_arg(Arg("video_format", video_format)); + this->add_arg(Arg("hds", hds)); + this->add_arg(Arg("output_format", output_format)); + this->add_arg(Arg("memory_location", memory_location)); + } +}; + +PYBIND11_MODULE(_advanced_network_media_rx, m) { + m.doc() = R"pbdoc( + Holoscan SDK Advanced Networking Media RX Operator Python Bindings + ------------------------------------------------------------------ + .. currentmodule:: _advanced_network_media_rx + + This module provides Python bindings for the Advanced Networking Media RX operator, + which receives video frames over Rivermax-enabled network infrastructure. + )pbdoc"; + +#ifdef VERSION_INFO + m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO); +#else + m.attr("__version__") = "dev"; +#endif + + py::class_>( + m, "AdvNetworkMediaRxOp", doc::AdvNetworkMediaRxOp::doc_AdvNetworkMediaRxOp) + .def(py::init(), + "fragment"_a, + "interface_name"_a = ""s, + "queue_id"_a = AdvNetworkMediaRxOp::default_queue_id, + "frame_width"_a = 1920, + "frame_height"_a = 1080, + "bit_depth"_a = 8, + "video_format"_a = "RGB888"s, + "hds"_a = true, + "output_format"_a = "video_buffer"s, + "memory_location"_a = "device"s, + "name"_a = "advanced_network_media_rx"s, + doc::AdvNetworkMediaRxOp::doc_AdvNetworkMediaRxOp_python) + .def("initialize", &AdvNetworkMediaRxOp::initialize, doc::AdvNetworkMediaRxOp::doc_initialize) + .def("setup", &AdvNetworkMediaRxOp::setup, "spec"_a, doc::AdvNetworkMediaRxOp::doc_setup); +} // PYBIND11_MODULE NOLINT +} // namespace holoscan::ops diff --git a/operators/advanced_network_media/advanced_network_media_rx/python/adv_network_media_rx_pydoc.hpp b/operators/advanced_network_media/advanced_network_media_rx/python/adv_network_media_rx_pydoc.hpp new file mode 100644 index 0000000000..95d2603d12 --- /dev/null +++ b/operators/advanced_network_media/advanced_network_media_rx/python/adv_network_media_rx_pydoc.hpp @@ -0,0 +1,101 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2022-2023 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 PYHOLOHUB_OPERATORS_ADV_NET_MEDIA_RX_PYDOC_HPP +#define PYHOLOHUB_OPERATORS_ADV_NET_MEDIA_RX_PYDOC_HPP + +#include + +#include "macros.hpp" + +namespace holoscan::doc { + +namespace AdvNetworkMediaRxOp { + +PYDOC(AdvNetworkMediaRxOp, R"doc( +Advanced Networking Media Receiver operator. + +This operator receives video frames over Rivermax-enabled network infrastructure +and outputs them as GXF VideoBuffer entities. +)doc") + +// PyAdvNetworkMediaRxOp Constructor +PYDOC(AdvNetworkMediaRxOp_python, R"doc( +Advanced Networking Media Receiver operator. + +This operator receives video frames over Rivermax-enabled network infrastructure +and outputs them as GXF VideoBuffer entities. + +Note: Advanced network initialization must be done in the application before creating +this operator using: adv_network_common.adv_net_init(config) + +Parameters +---------- +fragment : Fragment + The fragment that the operator belongs to. +interface_name : str, optional + Name of the network interface to use for reception. +queue_id : int, optional + Queue ID for the network interface (default: 0). +frame_width : int, optional + Width of the video frame in pixels (default: 1920). +frame_height : int, optional + Height of the video frame in pixels (default: 1080). +bit_depth : int, optional + Bit depth of the video data (default: 8). +video_format : str, optional + Video format for reception (default: "RGB888"). +hds : bool, optional + Header Data Split setting (default: True). +output_format : str, optional + Output format for the frames (default: "video_buffer"). +memory_location : str, optional + Memory location for frame storage (default: "device"). +name : str, optional + The name of the operator (default: "advanced_network_media_rx"). +)doc") + +PYDOC(gxf_typename, R"doc( +The GXF type name of the resource. + +Returns +------- +str + The GXF type name of the resource +)doc") + +PYDOC(initialize, R"doc( +Initialize the operator. + +This method is called only once when the operator is created for the first time, +and uses a light-weight initialization. +)doc") + +PYDOC(setup, R"doc( +Define the operator specification. + +Parameters +---------- +spec : ``holoscan.core.OperatorSpec`` + The operator specification. +)doc") + +} // namespace AdvNetworkMediaRxOp + +} // namespace holoscan::doc + +#endif // PYHOLOHUB_OPERATORS_ADV_NET_MEDIA_RX_PYDOC_HPP diff --git a/operators/advanced_network_media/advanced_network_media_tx/CMakeLists.txt b/operators/advanced_network_media/advanced_network_media_tx/CMakeLists.txt new file mode 100644 index 0000000000..713b92c947 --- /dev/null +++ b/operators/advanced_network_media/advanced_network_media_tx/CMakeLists.txt @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: Copyright (c) 2023 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_tx) + +find_package(holoscan 2.6 REQUIRED CONFIG + PATHS "/opt/nvidia/holoscan" "/workspace/holoscan-sdk/install") + +add_library(${PROJECT_NAME} SHARED + adv_network_media_tx.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 +) + +set_target_properties(${PROJECT_NAME} PROPERTIES + OUTPUT_NAME "holoscan_op_advanced_network_media_tx" + EXPORT_NAME ops::advanced_network_media_tx +) + +# Installation +install( + TARGETS + ${PROJECT_NAME} + EXPORT holoscan-networking-targets + COMPONENT advanced_network-cpp +) + +if(HOLOHUB_BUILD_PYTHON) + add_subdirectory(python) +endif() diff --git a/operators/advanced_network_media/advanced_network_media_tx/adv_network_media_tx.cpp b/operators/advanced_network_media/advanced_network_media_tx/adv_network_media_tx.cpp new file mode 100644 index 0000000000..3d2f3e7cb4 --- /dev/null +++ b/operators/advanced_network_media/advanced_network_media_tx/adv_network_media_tx.cpp @@ -0,0 +1,274 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024 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 "advanced_network/common.h" + +#include "../common/frame_buffer.h" +#include "../common/video_parameters.h" +#include "adv_network_media_tx.h" + +namespace holoscan::ops { + +/** + * @class AdvNetworkMediaTxOpImpl + * @brief Implementation class for the AdvNetworkMediaTxOp operator. + * + * Handles the actual processing of media frames and communication with + * the network infrastructure. + */ +class AdvNetworkMediaTxOpImpl { + public: + static constexpr int DISPLAY_WARNING_AFTER_BURST_NOT_AVAILABLE = 1000; + static constexpr int SLEEP_WHEN_BURST_NOT_AVAILABLE_US = 100; + + /** + * @brief Constructs an implementation for the given operator. + * + * @param parent Reference to the parent operator. + */ + explicit AdvNetworkMediaTxOpImpl(AdvNetworkMediaTxOp& parent) : parent_(parent) {} + + /** + * @brief Initializes the implementation. + * + * Sets up the network port, calculates frame size, and prepares + * for media transmission. + */ + void initialize() { + HOLOSCAN_LOG_INFO("AdvNetworkMediaTxOp::initialize()"); + try { + port_id_ = get_port_id(parent_.interface_name_.get()); + if (port_id_ == -1) { + HOLOSCAN_LOG_ERROR("Invalid TX port {} specified in the config", + parent_.interface_name_.get()); + exit(1); + } else { + HOLOSCAN_LOG_INFO("TX port {} found", port_id_); + } + + video_sampling_ = get_video_sampling_format(parent_.video_format_.get()); + color_bit_depth_ = get_color_bit_depth(parent_.bit_depth_.get()); + frame_size_ = calculate_frame_size(parent_.frame_width_.get(), parent_.frame_height_.get(), + video_sampling_, color_bit_depth_); + HOLOSCAN_LOG_INFO("Expected frame size: {} bytes", frame_size_); + + expected_video_format_ = get_expected_gxf_video_format(video_sampling_, color_bit_depth_); + + HOLOSCAN_LOG_INFO("AdvNetworkMediaTxOp::initialize() complete"); + } catch (const std::exception& e) { + HOLOSCAN_LOG_ERROR("Error in AdvNetworkMediaTxOp initialization: {}", e.what()); + throw; + } + } + + /** + * @brief Creates a MediaFrame from a GXF entity containing a VideoBuffer. + * + * @param entity The GXF entity containing the video buffer. + * @return A shared pointer to the created MediaFrame, or nullptr if validation fails. + */ + std::shared_ptr create_media_frame_from_video_buffer(nvidia::gxf::Entity entity) { + try { + auto frame = std::make_unique(std::move(entity)); + auto result = frame->validate_frame_parameters(parent_.frame_width_.get(), + parent_.frame_height_.get(), frame_size_, expected_video_format_); + + if (result != Status::SUCCESS) { + HOLOSCAN_LOG_ERROR("Video buffer validation failed"); + return nullptr; + } + + return std::make_shared(std::move(frame)); + } catch (const std::exception& e) { + HOLOSCAN_LOG_ERROR("Video buffer error: {}", e.what()); + return nullptr; + } + } + + /** + * @brief Creates a MediaFrame from a GXF entity containing a Tensor. + * + * @param entity The GXF entity containing the tensor. + * @return A shared pointer to the created MediaFrame, or nullptr if validation fails. + */ + std::shared_ptr create_media_frame_from_tensor(nvidia::gxf::Entity entity) { + try { + auto frame = std::make_unique(std::move(entity), expected_video_format_); + auto result = frame->validate_frame_parameters(parent_.frame_width_.get(), + parent_.frame_height_.get(), frame_size_, expected_video_format_); + + if (result != Status::SUCCESS) { + HOLOSCAN_LOG_ERROR("Tensor validation failed"); + return nullptr; + } + + return std::make_shared(std::move(frame)); + } catch (const std::exception& e) { + HOLOSCAN_LOG_ERROR("Tensor error: {}", e.what()); + return nullptr; + } + } + + /** + * @brief Processes input data from the operator context. + * + * Extracts the GXF entity from the input and creates a MediaFrame for transmission. + * + * @param op_input The operator input context. + */ + void process_input(InputContext& op_input) { + auto maybe_entity = op_input.receive("input"); + if (!maybe_entity) return; + + auto& entity = static_cast(maybe_entity.value()); + + auto maybe_video_buffer = entity.get(); + if (maybe_video_buffer) { + pending_tx_frame_ = create_media_frame_from_video_buffer(std::move(entity)); + } else { + auto maybe_tensor = entity.get(); + if (!maybe_tensor) { + HOLOSCAN_LOG_ERROR("Neither VideoBuffer nor Tensor found in message"); + return; + } + pending_tx_frame_ = create_media_frame_from_tensor(std::move(entity)); + } + + if (!pending_tx_frame_) { + HOLOSCAN_LOG_ERROR("Failed to create media frame"); + return; + } + } + + /** + * @brief Processes output data for the operator context. + * + * Transmits the pending media frame over the network if available. + * + * @param op_output The operator output context. + */ + void process_output(OutputContext& op_output) { + static int not_available_count = 0; + static int sent = 0; + static int err = 0; + + if (!pending_tx_frame_) { + HOLOSCAN_LOG_ERROR("No pending TX frame"); + return; + } + + if (!cur_msg_) { + cur_msg_ = create_tx_burst_params(); + set_header(cur_msg_, port_id_, parent_.queue_id_.get(), 1, 1); + } + + if (!is_tx_burst_available(cur_msg_)) { + std::this_thread::sleep_for(std::chrono::microseconds(SLEEP_WHEN_BURST_NOT_AVAILABLE_US)); + if (++not_available_count == DISPLAY_WARNING_AFTER_BURST_NOT_AVAILABLE) { + HOLOSCAN_LOG_ERROR( + "TX port {}, queue {}, burst not available too many times consecutively. " + "Make sure memory region has enough buffers. Sent {} and error {}", + port_id_, + parent_.queue_id_.get(), + sent, + err); + not_available_count = 0; + err++; + } + return; + } + not_available_count = 0; + Status ret; + if ((ret = get_tx_packet_burst(cur_msg_)) != Status::SUCCESS) { + HOLOSCAN_LOG_ERROR("Error returned from get_tx_packet_burst: {}", static_cast(ret)); + return; + } + + cur_msg_->custom_pkt_data = std::move(pending_tx_frame_); + pending_tx_frame_ = nullptr; + + ret = send_tx_burst(cur_msg_); + if (ret != Status::SUCCESS) { + HOLOSCAN_LOG_ERROR("Error returned from send_tx_burst: {}", static_cast(ret)); + free_tx_burst(cur_msg_); + err++; + } else { + sent++; + } + cur_msg_ = nullptr; + HOLOSCAN_LOG_TRACE("AdvNetworkMediaTxOp::process_output() {}:{} done. Emitted{}/Error{}", + port_id_, + parent_.queue_id_.get(), + sent, + err); + } + + BurstParams* cur_msg_ = nullptr; + std::shared_ptr pending_tx_frame_ = nullptr; + size_t frame_size_; + nvidia::gxf::VideoFormat expected_video_format_; + int port_id_; + VideoFormatSampling video_sampling_; + VideoColorBitDepth color_bit_depth_; + + private: + AdvNetworkMediaTxOp& parent_; +}; + +AdvNetworkMediaTxOp::AdvNetworkMediaTxOp() : pimpl_(nullptr) { +} + +AdvNetworkMediaTxOp::~AdvNetworkMediaTxOp() { + if (pimpl_) { + delete pimpl_; + pimpl_ = nullptr; + } +} + +void AdvNetworkMediaTxOp::initialize() { + HOLOSCAN_LOG_INFO("AdvNetworkMediaTxOp::initialize()"); + holoscan::Operator::initialize(); + + if (!pimpl_) { + pimpl_ = new AdvNetworkMediaTxOpImpl(*this); + } + + pimpl_->initialize(); +} + +void AdvNetworkMediaTxOp::setup(OperatorSpec& spec) { + spec.input("input"); + 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")); +} + +void AdvNetworkMediaTxOp::compute(InputContext& op_input, OutputContext& op_output, + ExecutionContext& context) { + pimpl_->process_input(op_input); + pimpl_->process_output(op_output); +} + +} // namespace holoscan::ops diff --git a/operators/advanced_network_media/advanced_network_media_tx/adv_network_media_tx.h b/operators/advanced_network_media/advanced_network_media_tx/adv_network_media_tx.h new file mode 100644 index 0000000000..e36a0fba94 --- /dev/null +++ b/operators/advanced_network_media/advanced_network_media_tx/adv_network_media_tx.h @@ -0,0 +1,72 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023 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_TX_ADV_NETWORK_MEDIA_TX_H_ +#define OPERATORS_ADVANCED_NETWORK_MEDIA_TX_ADV_NETWORK_MEDIA_TX_H_ + +#include +#include + +namespace holoscan::ops { + +// Forward declare the implementation class +class AdvNetworkMediaTxOpImpl; + +/** + * @class AdvNetworkMediaTxOp + * @brief 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. + */ +class AdvNetworkMediaTxOp : public Operator { + public: + static constexpr uint16_t default_queue_id = 0; + + HOLOSCAN_OPERATOR_FORWARD_ARGS(AdvNetworkMediaTxOp) + + /** + * @brief Constructs an AdvNetworkMediaTxOp operator. + */ + AdvNetworkMediaTxOp(); + + /** + * @brief Destroys the AdvNetworkMediaTxOp operator and its implementation. + */ + ~AdvNetworkMediaTxOp(); + + void initialize() override; + void setup(OperatorSpec& spec) override; + void compute(InputContext& op_input, OutputContext& op_output, ExecutionContext&) override; + + private: + friend class AdvNetworkMediaTxOpImpl; + + // Parameters + Parameter interface_name_; + Parameter queue_id_; + Parameter video_format_; + Parameter bit_depth_; + Parameter frame_width_; + Parameter frame_height_; + + AdvNetworkMediaTxOpImpl* pimpl_; +}; + +} // namespace holoscan::ops + +#endif // OPERATORS_ADVANCED_NETWORK_MEDIA_TX_ADV_NETWORK_MEDIA_TX_H_ diff --git a/operators/advanced_network_media/advanced_network_media_tx/metadata.json b/operators/advanced_network_media/advanced_network_media_tx/metadata.json new file mode 100644 index 0000000000..0d4c82c2a2 --- /dev/null +++ b/operators/advanced_network_media/advanced_network_media_tx/metadata.json @@ -0,0 +1,31 @@ +{ + "operator": { + "name": "advanced_network_media_tx", + "authors": [ + { + "name": "Rony Rado", + "affiliation": "NVIDIA" + } + ], + "version": "1.0", + "changelog": { + "1.0": "Initial Release" + }, + "platforms": ["x86_64", "aarch64"], + "tags": ["Network", "Networking", "DPDK", "UDP", "Ethernet", "IP", "GPUDirect", "Rivermax"], + "holoscan_sdk": { + "minimum_required_version": "2.6.0", + "tested_versions": [ + "2.6.0" + ] + }, + "ranking": 3, + "dependencies": { + "operators": [{ + "name": "advanced_network", + "version": "1.4" + } + ] + } + } +} \ No newline at end of file diff --git a/operators/advanced_network_media/advanced_network_media_tx/python/CMakeLists.txt b/operators/advanced_network_media/advanced_network_media_tx/python/CMakeLists.txt new file mode 100644 index 0000000000..4f617bffc0 --- /dev/null +++ b/operators/advanced_network_media/advanced_network_media_tx/python/CMakeLists.txt @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: Copyright (c) 2022-2024 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(pybind11_add_holohub_module) +find_package(holoscan 2.6 REQUIRED CONFIG + PATHS "/opt/nvidia/holoscan" "/workspace/holoscan-sdk/install") + +set(MODULE_NAME advanced_network_media_tx) +set(MODULE_CLASS_NAME "*") + +pybind11_add_holohub_module( + CPP_CMAKE_TARGET advanced_network_media_tx + CLASS_NAME "AdvNetworkMediaTxOp" + SOURCES adv_network_media_tx_pybind.cpp +) + +target_link_libraries(${MODULE_NAME}_python +PRIVATE + holoscan::core + ${MODULE_NAME} +) + +set(CMAKE_PYBIND11_HOLOHUB_MODULE_OUT_DIR ${CMAKE_BINARY_DIR}/python/${CMAKE_INSTALL_LIBDIR}/holohub) +set(CMAKE_SUBMODULE_OUT_DIR ${CMAKE_PYBIND11_HOLOHUB_MODULE_OUT_DIR}/${MODULE_NAME}) + +set_target_properties(${MODULE_NAME}_python PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SUBMODULE_OUT_DIR} + OUTPUT_NAME _${MODULE_NAME} +) + +configure_file( + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/pybind11/__init__.py + ${CMAKE_PYBIND11_HOLOHUB_MODULE_OUT_DIR}/advanced_network_media_tx/__init__.py +) diff --git a/operators/advanced_network_media/advanced_network_media_tx/python/adv_network_media_tx_pybind.cpp b/operators/advanced_network_media/advanced_network_media_tx/python/adv_network_media_tx_pybind.cpp new file mode 100644 index 0000000000..24d3c1e47f --- /dev/null +++ b/operators/advanced_network_media/advanced_network_media_tx/python/adv_network_media_tx_pybind.cpp @@ -0,0 +1,124 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2022-2024 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 "../adv_network_media_tx.h" +#include "./adv_network_media_tx_pydoc.hpp" + +#include +#include // for unordered_map -> dict, etc. + +#include +#include +#include + +#include "../../../operator_util.hpp" +#include +#include +#include +#include + +using std::string_literals::operator""s; +using pybind11::literals::operator""_a; + +#define STRINGIFY(x) #x +#define MACRO_STRINGIFY(x) STRINGIFY(x) + +namespace py = pybind11; + +namespace holoscan::ops { + +/* Trampoline classes for handling Python kwargs + * + * These add a constructor that takes a Fragment for which to initialize the operator. + * The explicit parameter list and default arguments take care of providing a Pythonic + * kwarg-based interface with appropriate default values matching the operator's + * default parameters in the C++ API `setup` method. + * + * The sequence of events in this constructor is based on Fragment::make_operator + */ + +class PyAdvNetworkMediaTxOp : public AdvNetworkMediaTxOp { + public: + /* Inherit the constructors */ + using AdvNetworkMediaTxOp::AdvNetworkMediaTxOp; + + // Define a constructor that fully initializes the object. + PyAdvNetworkMediaTxOp(Fragment* fragment, const py::args& args, + const std::string& interface_name = "", + uint16_t queue_id = default_queue_id, + const std::string& video_format = "RGB888", uint32_t bit_depth = 8, + uint32_t frame_width = 1920, uint32_t frame_height = 1080, + const std::string& name = "advanced_network_media_tx") { + add_positional_condition_and_resource_args(this, args); + name_ = name; + fragment_ = fragment; + spec_ = std::make_shared(fragment); + setup(*spec_.get()); + + // Set parameters if provided + if (!interface_name.empty()) { this->add_arg(Arg("interface_name", interface_name)); } + this->add_arg(Arg("queue_id", queue_id)); + this->add_arg(Arg("video_format", video_format)); + this->add_arg(Arg("bit_depth", bit_depth)); + this->add_arg(Arg("frame_width", frame_width)); + this->add_arg(Arg("frame_height", frame_height)); + } +}; + +PYBIND11_MODULE(_advanced_network_media_tx, m) { + m.doc() = R"pbdoc( + Holoscan SDK Advanced Networking Media TX Operator Python Bindings + ------------------------------------------------------------------ + .. currentmodule:: _advanced_network_media_tx + + This module provides Python bindings for the Advanced Networking Media TX operator, + which transmits video frames over Rivermax-enabled network infrastructure. + )pbdoc"; + +#ifdef VERSION_INFO + m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO); +#else + m.attr("__version__") = "dev"; +#endif + + py::class_>( + m, "AdvNetworkMediaTxOp", doc::AdvNetworkMediaTxOp::doc_AdvNetworkMediaTxOp) + .def(py::init(), + "fragment"_a, + "interface_name"_a = ""s, + "queue_id"_a = AdvNetworkMediaTxOp::default_queue_id, + "video_format"_a = "RGB888"s, + "bit_depth"_a = 8, + "frame_width"_a = 1920, + "frame_height"_a = 1080, + "name"_a = "advanced_network_media_tx"s, + doc::AdvNetworkMediaTxOp::doc_AdvNetworkMediaTxOp_python) + .def("initialize", &AdvNetworkMediaTxOp::initialize, doc::AdvNetworkMediaTxOp::doc_initialize) + .def("setup", &AdvNetworkMediaTxOp::setup, "spec"_a, doc::AdvNetworkMediaTxOp::doc_setup); +} // PYBIND11_MODULE NOLINT +} // namespace holoscan::ops diff --git a/operators/advanced_network_media/advanced_network_media_tx/python/adv_network_media_tx_pydoc.hpp b/operators/advanced_network_media/advanced_network_media_tx/python/adv_network_media_tx_pydoc.hpp new file mode 100644 index 0000000000..e1621ba472 --- /dev/null +++ b/operators/advanced_network_media/advanced_network_media_tx/python/adv_network_media_tx_pydoc.hpp @@ -0,0 +1,92 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2022-2023 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 PYHOLOHUB_OPERATORS_ADV_NET_MEDIA_TX_PYDOC_HPP +#define PYHOLOHUB_OPERATORS_ADV_NET_MEDIA_TX_PYDOC_HPP + +#include + +#include "macros.hpp" + +namespace holoscan::doc { + +namespace AdvNetworkMediaTxOp { + +PYDOC(AdvNetworkMediaTxOp, R"doc( +Advanced Networking Media Transmitter operator. + +This operator processes video frames from GXF entities (either VideoBuffer or Tensor) +and transmits them over Rivermax-enabled network infrastructure. +)doc") + +// PyAdvNetworkMediaTxOp Constructor +PYDOC(AdvNetworkMediaTxOp_python, R"doc( +Advanced Networking Media Transmitter operator. + +This operator processes video frames from GXF entities (either VideoBuffer or Tensor) +and transmits them over Rivermax-enabled network infrastructure. + +Parameters +---------- +fragment : Fragment + The fragment that the operator belongs to. +interface_name : str, optional + Name of the network interface to use for transmission. +queue_id : int, optional + Queue ID for the network interface (default: 0). +video_format : str, optional + Video format for transmission (default: "RGB888"). +bit_depth : int, optional + Bit depth of the video data (default: 8). +frame_width : int, optional + Width of the video frame in pixels (default: 1920). +frame_height : int, optional + Height of the video frame in pixels (default: 1080). +name : str, optional + The name of the operator (default: "advanced_network_media_tx"). +)doc") + +PYDOC(gxf_typename, R"doc( +The GXF type name of the resource. + +Returns +------- +str + The GXF type name of the resource +)doc") + +PYDOC(initialize, R"doc( +Initialize the operator. + +This method is called only once when the operator is created for the first time, +and uses a light-weight initialization. +)doc") + +PYDOC(setup, R"doc( +Define the operator specification. + +Parameters +---------- +spec : ``holoscan.core.OperatorSpec`` + The operator specification. +)doc") + +} // namespace AdvNetworkMediaTxOp + +} // namespace holoscan::doc + +#endif // PYHOLOHUB_OPERATORS_ADV_NET_MEDIA_TX_PYDOC_HPP diff --git a/operators/advanced_network_media/common/CMakeLists.txt b/operators/advanced_network_media/common/CMakeLists.txt new file mode 100644 index 0000000000..ac8f664505 --- /dev/null +++ b/operators/advanced_network_media/common/CMakeLists.txt @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 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_common) + +find_package(holoscan 2.6 REQUIRED CONFIG + PATHS "/opt/nvidia/holoscan" "/workspace/holoscan-sdk/install") + +add_library(${PROJECT_NAME} STATIC + video_parameters.cpp + frame_buffer.cpp +) + +# Add Position Independent Code flag for static library to be linked into shared libraries +set_property(TARGET ${PROJECT_NAME} PROPERTY POSITION_INDEPENDENT_CODE ON) + +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 +) diff --git a/operators/advanced_network_media/common/adv_network_media_common.h b/operators/advanced_network_media/common/adv_network_media_common.h new file mode 100644 index 0000000000..393767c0f6 --- /dev/null +++ b/operators/advanced_network_media/common/adv_network_media_common.h @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023 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_COMMON_ADV_NETWORK_MEDIA_COMMON_H_ +#define OPERATORS_ADVANCED_NETWORK_MEDIA_COMMON_ADV_NETWORK_MEDIA_COMMON_H_ + +#include +#include +#include +#include "rtp_params.h" + +#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"); \ + } \ + } + +// Packet tracing debug flag - uncomment to enable extensive packet debugging +// #define ENABLE_PACKET_TRACING + +#ifdef ENABLE_PACKET_TRACING +#define PACKET_TRACE_LOG(fmt, ...) HOLOSCAN_LOG_INFO("[PACKET_TRACE] " fmt, ##__VA_ARGS__) +#else +#define PACKET_TRACE_LOG(fmt, ...) \ + do { \ + } while (0) +#endif + +#endif // OPERATORS_ADVANCED_NETWORK_MEDIA_COMMON_ADV_NETWORK_MEDIA_COMMON_H_ diff --git a/operators/advanced_network_media/common/frame_buffer.cpp b/operators/advanced_network_media/common/frame_buffer.cpp new file mode 100644 index 0000000000..eec3d7c260 --- /dev/null +++ b/operators/advanced_network_media/common/frame_buffer.cpp @@ -0,0 +1,273 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024 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 "frame_buffer.h" + +namespace holoscan::ops { + +Status VideoFrameBufferBase::validate_frame_parameters( + uint32_t expected_width, uint32_t expected_height, size_t expected_frame_size, + nvidia::gxf::VideoFormat expected_format) const { + if (width_ != expected_width || height_ != expected_height) { + HOLOSCAN_LOG_ERROR( + "Resolution mismatch: {}}x{} vs {}}x{}", width_, height_, expected_width, expected_height); + return Status::INVALID_PARAMETER; + } + + if (frame_size_ != expected_frame_size) { + HOLOSCAN_LOG_ERROR("Frame size mismatch: {} vs {}", frame_size_, expected_frame_size); + return Status::INVALID_PARAMETER; + } + + return validate_format_compliance(expected_format); +} + +VideoBufferFrameBuffer::VideoBufferFrameBuffer(nvidia::gxf::Entity entity) { + entity_ = std::move(entity); + + auto maybe_video_buffer = entity_.get(); + if (!maybe_video_buffer) throw std::runtime_error("Entity doesn't contain a video buffer"); + + buffer_ = maybe_video_buffer.value(); + const auto& info = buffer_->video_frame_info(); + width_ = info.width; + height_ = info.height; + src_storage_type_ = buffer_->storage_type(); + memory_location_ = from_gxf_memory_type(src_storage_type_); + frame_size_ = buffer_->size(); + format_ = info.color_format; + planes_ = info.color_planes; +} + +Status VideoBufferFrameBuffer::validate_format_compliance( + nvidia::gxf::VideoFormat expected_format) const { + if (expected_format == nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_NV12_709) { + if (format_ != nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_NV12_709 && + format_ != nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_YUV420_709) { + HOLOSCAN_LOG_ERROR("Invalid NV12_709 format"); + return Status::INVALID_PARAMETER; + } + } else if (expected_format == nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_RGB) { + if (format_ != nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_RGB) { + HOLOSCAN_LOG_ERROR("Invalid RGB format"); + return Status::INVALID_PARAMETER; + } + } else if (format_ != nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_CUSTOM) { + HOLOSCAN_LOG_ERROR( + "Format mismatch: {} vs {}", static_cast(format_), static_cast(expected_format)); + return Status::INVALID_PARAMETER; + } + + if (expected_format == nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_NV12_709) { + if (width_ % SMPTE_420_ALIGNMENT != 0 || height_ % SMPTE_420_ALIGNMENT != 0) { + HOLOSCAN_LOG_ERROR("Resolution not 4:2:0 aligned"); + return Status::INVALID_PARAMETER; + } + } + + for (const auto& plane : planes_) { + if (plane.stride % SMPTE_STRIDE_ALIGNMENT != 0) { + HOLOSCAN_LOG_ERROR("Stride {} not {}-byte aligned", plane.stride, SMPTE_STRIDE_ALIGNMENT); + return Status::INVALID_PARAMETER; + } + } + + return Status::SUCCESS; +} + +TensorFrameBuffer::TensorFrameBuffer(nvidia::gxf::Entity entity, nvidia::gxf::VideoFormat format) { + entity_ = std::move(entity); + + auto maybe_tensor = entity_.get(); + if (!maybe_tensor) throw std::runtime_error("Entity doesn't contain a tensor"); + tensor_ = maybe_tensor.value(); + + const auto& shape = tensor_->shape(); + width_ = shape.dimension(1); + height_ = shape.dimension(0); + src_storage_type_ = tensor_->storage_type(); + memory_location_ = from_gxf_memory_type(src_storage_type_); + frame_size_ = tensor_->size(); + format_ = format; +} + +Status TensorFrameBuffer::validate_format_compliance( + nvidia::gxf::VideoFormat expected_format) const { + const auto& shape = tensor_->shape(); + switch (format_) { + case nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_NV12_709: + if (shape.rank() != 3 || shape.dimension(2) != 2 || + tensor_->element_type() != nvidia::gxf::PrimitiveType::kUnsigned8) { + HOLOSCAN_LOG_ERROR("Invalid NV12_709 tensor"); + return Status::INVALID_PARAMETER; + } + break; + + case nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_RGB: + if (shape.rank() != 3 || shape.dimension(2) != 3) { + HOLOSCAN_LOG_ERROR("Invalid RGB tensor"); + return Status::INVALID_PARAMETER; + } + break; + + default: + HOLOSCAN_LOG_ERROR("Unsupported tensor format: {}", static_cast(format_)); + return Status::INVALID_PARAMETER; + } + return Status::SUCCESS; +} + +AllocatedVideoBufferFrameBuffer::AllocatedVideoBufferFrameBuffer( + void* data, size_t size, uint32_t width, uint32_t height, nvidia::gxf::VideoFormat format, + nvidia::gxf::MemoryStorageType storage_type) { + data_ = data; + frame_size_ = size; + width_ = width; + height_ = height; + format_ = format; + src_storage_type_ = storage_type; + memory_location_ = from_gxf_memory_type(storage_type); +} + +Status AllocatedVideoBufferFrameBuffer::validate_format_compliance( + nvidia::gxf::VideoFormat expected_format) const { + if (format_ != expected_format) { + HOLOSCAN_LOG_ERROR( + "Format mismatch: {} vs {}", static_cast(format_), static_cast(expected_format)); + return Status::INVALID_PARAMETER; + } + return Status::SUCCESS; +} + +nvidia::gxf::Entity AllocatedVideoBufferFrameBuffer::wrap_in_entity( + void* context, std::function(void*)> release_func) { + auto result = nvidia::gxf::Entity::New(context); + if (!result) { throw std::runtime_error("Failed to allocate entity"); } + + auto buffer = result.value().add(); + if (!buffer) { throw std::runtime_error("Failed to allocate video buffer"); } + + // Set up video buffer based on format + nvidia::gxf::VideoBufferInfo info; + + switch (format_) { + case nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_RGB: { + nvidia::gxf::VideoTypeTraits video_type; + nvidia::gxf::VideoFormatSize color_format; + auto color_planes = color_format.getDefaultColorPlanes(width_, height_, false); + info = {width_, + height_, + video_type.value, + color_planes, + nvidia::gxf::SurfaceLayout::GXF_SURFACE_LAYOUT_PITCH_LINEAR}; + break; + } + case nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_NV12_709: { + nvidia::gxf::VideoTypeTraits video_type; + nvidia::gxf::VideoFormatSize + color_format; + auto color_planes = color_format.getDefaultColorPlanes(width_, height_, false); + info = {width_, + height_, + video_type.value, + color_planes, + nvidia::gxf::SurfaceLayout::GXF_SURFACE_LAYOUT_PITCH_LINEAR}; + break; + } + default: + throw std::runtime_error("Unsupported video format"); + } + + buffer.value()->wrapMemory(info, frame_size_, src_storage_type_, data_, release_func); + + return result.value(); +} + +AllocatedTensorFrameBuffer::AllocatedTensorFrameBuffer( + void* data, size_t size, uint32_t width, uint32_t height, uint32_t channels, + nvidia::gxf::VideoFormat format, nvidia::gxf::MemoryStorageType storage_type) { + data_ = data; + frame_size_ = size; + width_ = width; + height_ = height; + channels_ = channels; + format_ = format; + src_storage_type_ = storage_type; + memory_location_ = from_gxf_memory_type(storage_type); +} + +Status AllocatedTensorFrameBuffer::validate_format_compliance( + nvidia::gxf::VideoFormat expected_format) const { + if (format_ != expected_format) { + HOLOSCAN_LOG_ERROR( + "Format mismatch: {} vs {}", static_cast(format_), static_cast(expected_format)); + return Status::INVALID_PARAMETER; + } + + // Validate channel count based on format + if (expected_format == nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_RGB && channels_ != 3) { + HOLOSCAN_LOG_ERROR("Invalid channel count for RGB format: {}", channels_); + return Status::INVALID_PARAMETER; + } else if (expected_format == nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_NV12_709 && + channels_ != 2) { + HOLOSCAN_LOG_ERROR("Invalid channel count for NV12_709 format: {}", channels_); + return Status::INVALID_PARAMETER; + } + + return Status::SUCCESS; +} + +nvidia::gxf::Entity AllocatedTensorFrameBuffer::wrap_in_entity( + void* context, std::function(void*)> release_func) { + auto result = nvidia::gxf::Entity::New(context); + if (!result) { throw std::runtime_error("Failed to allocate entity"); } + + auto tensor = result.value().add(); + if (!tensor) { throw std::runtime_error("Failed to allocate tensor"); } + + // Set up tensor shape based on format + nvidia::gxf::Shape shape; + auto element_type = nvidia::gxf::PrimitiveType::kUnsigned8; + + switch (format_) { + case nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_RGB: + shape = {static_cast(height_), static_cast(width_), 3}; + break; + case nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_NV12_709: + // For NV12, we need 2 channels (Y and UV interleaved) + shape = {static_cast(height_), static_cast(width_), 2}; + break; + case nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_CUSTOM: + // For custom format, use the channels_ value provided + shape = {static_cast(height_), + static_cast(width_), + static_cast(channels_)}; + break; + default: + throw std::runtime_error("Unsupported tensor format"); + } + + auto element_size = nvidia::gxf::PrimitiveTypeSize(element_type); + auto strides = nvidia::gxf::ComputeTrivialStrides(shape, element_size); + + tensor.value()->wrapMemory( + shape, element_type, element_size, strides, src_storage_type_, data_, release_func); + + return result.value(); +} + +} // namespace holoscan::ops diff --git a/operators/advanced_network_media/common/frame_buffer.h b/operators/advanced_network_media/common/frame_buffer.h new file mode 100644 index 0000000000..c780bcf897 --- /dev/null +++ b/operators/advanced_network_media/common/frame_buffer.h @@ -0,0 +1,251 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024 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_COMMON_FRAME_BUFFER_H_ +#define OPERATORS_ADVANCED_NETWORK_MEDIA_COMMON_FRAME_BUFFER_H_ + +#include +#include + +#include "rdk/services/services.h" +#include "advanced_network/common.h" +#include "gxf/multimedia/video.hpp" +#include "gxf/core/entity.hpp" +#include "gxf/core/expected.hpp" +#include +#include "video_parameters.h" + +using namespace rivermax::dev_kit::services; +using namespace holoscan::advanced_network; + +namespace holoscan::ops { + +/** + * @class FrameBufferBase + * @brief Base class for frame buffers used in media transmission operations. + */ +class FrameBufferBase : public IFrameBuffer { + public: + virtual ~FrameBufferBase() = default; + + size_t get_size() const override { return frame_size_; } + size_t get_aligned_size() const override { return frame_size_; } + MemoryLocation get_memory_location() const override { return memory_location_; } + + protected: + /** + * @brief Converts GXF memory storage type to MemoryLocation enum. + * + * @param storage_type The GXF memory storage type to convert. + * @return The corresponding MemoryLocation. + */ + inline MemoryLocation from_gxf_memory_type(nvidia::gxf::MemoryStorageType storage_type) const { + switch (storage_type) { + case nvidia::gxf::MemoryStorageType::kHost: + case nvidia::gxf::MemoryStorageType::kSystem: + return MemoryLocation::Host; + case nvidia::gxf::MemoryStorageType::kDevice: + return MemoryLocation::GPU; + default: + return MemoryLocation::Host; + } + } + + protected: + MemoryLocation memory_location_; + nvidia::gxf::MemoryStorageType src_storage_type_; + size_t frame_size_; + nvidia::gxf::Entity entity_; +}; + +/** + * @class VideoFrameBufferBase + * @brief Base class for video frame buffers with common validation functionality. + */ +class VideoFrameBufferBase : public FrameBufferBase { + public: + virtual ~VideoFrameBufferBase() = default; + + /** + * @brief Validates the frame buffer against expected parameters. + * + * @param expected_width Expected frame width. + * @param expected_height Expected frame height. + * @param expected_frame_size Expected frame size in bytes. + * @param expected_format Expected video format. + * @return Status indicating validation result. + */ + Status validate_frame_parameters(uint32_t expected_width, uint32_t expected_height, + size_t expected_frame_size, + nvidia::gxf::VideoFormat expected_format) const; + + protected: + /** + * @brief Implementation-specific validation logic to be defined by derived classes. + * + * @param fmt The expected video format. + * @return Status indicating validation result. + */ + virtual Status validate_format_compliance(nvidia::gxf::VideoFormat fmt) const = 0; + + protected: + static constexpr uint32_t SMPTE_STRIDE_ALIGNMENT = 256; + static constexpr uint32_t SMPTE_420_ALIGNMENT = 2; + + uint32_t width_; + uint32_t height_; + nvidia::gxf::VideoFormat format_; +}; + +/** + * @class VideoBufferFrameBuffer + * @brief Frame buffer implementation for VideoBuffer type frames. + */ +class VideoBufferFrameBuffer : public VideoFrameBufferBase { + public: + /** + * @brief Constructs a VideoBufferFrameBuffer from a GXF entity. + * + * @param entity The GXF entity containing the video buffer. + */ + explicit VideoBufferFrameBuffer(nvidia::gxf::Entity entity); + byte_t* get() const override { + return (buffer_) ? static_cast(buffer_->pointer()) : nullptr; + } + + protected: + Status validate_format_compliance(nvidia::gxf::VideoFormat fmt) const override; + + private: + nvidia::gxf::Handle buffer_; + std::vector planes_; +}; + +/** + * @class TensorFrameBuffer + * @brief Frame buffer implementation for Tensor type frames. + */ +class TensorFrameBuffer : public VideoFrameBufferBase { + public: + /** + * @brief Constructs a TensorFrameBuffer from a GXF entity with a specific format. + * + * @param entity The GXF entity containing the tensor. + * @param format The video format to interpret the tensor as. + */ + TensorFrameBuffer(nvidia::gxf::Entity entity, nvidia::gxf::VideoFormat format); + virtual ~TensorFrameBuffer() = default; + byte_t* get() const override { + return (tensor_) ? static_cast(tensor_->pointer()) : nullptr; + } + + protected: + Status validate_format_compliance(nvidia::gxf::VideoFormat fmt) const override; + + private: + nvidia::gxf::Handle tensor_; +}; + +/** + * @class AllocatedVideoBufferFrameBuffer + * @brief Frame buffer implementation for pre-allocated memory buffers. + * + * Used primarily by the RX operator for receiving frames. + */ +class AllocatedVideoBufferFrameBuffer : public VideoFrameBufferBase { + public: + /** + * @brief Constructs an AllocatedViddeoBufferFrameBuffer from pre-allocated memory. + * + * @param data Pointer to the allocated memory + * @param size Size of the allocated memory in bytes + * @param width Frame width + * @param height Frame height + * @param format Video format + * @param storage_type Memory storage type (device or host) + */ + AllocatedVideoBufferFrameBuffer( + void* data, size_t size, uint32_t width, uint32_t height, nvidia::gxf::VideoFormat format, + nvidia::gxf::MemoryStorageType storage_type = nvidia::gxf::MemoryStorageType::kDevice); + virtual ~AllocatedVideoBufferFrameBuffer() = default; + + byte_t* get() const override { return static_cast(data_); } + + /** + * @brief Creates a GXF entity containing this frame's data. + * + * @param context GXF context for entity creation + * @param release_func Function to call when the entity is released + * @return Created GXF entity + */ + nvidia::gxf::Entity wrap_in_entity( + void* context, std::function(void*)> release_func); + + protected: + Status validate_format_compliance(nvidia::gxf::VideoFormat fmt) const override; + + private: + void* data_; +}; + +/** + * @class AllocatedTensorFrameBuffer + * @brief Frame buffer implementation for pre-allocated tensor memory buffers. + * + * Used primarily by the RX operator for receiving frames in tensor format. + */ +class AllocatedTensorFrameBuffer : public VideoFrameBufferBase { + public: + /** + * @brief Constructs an AllocatedTensorFrameBuffer from pre-allocated memory. + * + * @param data Pointer to the allocated memory + * @param size Size of the allocated memory in bytes + * @param width Frame width + * @param height Frame height + * @param channels Number of channels (typically 3 for RGB) + * @param format Video format + * @param storage_type Memory storage type (device or host) + */ + AllocatedTensorFrameBuffer( + void* data, size_t size, uint32_t width, uint32_t height, uint32_t channels, + nvidia::gxf::VideoFormat format, + nvidia::gxf::MemoryStorageType storage_type = nvidia::gxf::MemoryStorageType::kDevice); + virtual ~AllocatedTensorFrameBuffer() = default; + + byte_t* get() const override { return static_cast(data_); } + + /** + * @brief Creates a GXF entity containing this frame's data as a tensor. + * + * @param context GXF context for entity creation + * @param release_func Function to call when the entity is released + * @return Created GXF entity + */ + nvidia::gxf::Entity wrap_in_entity( + void* context, std::function(void*)> release_func); + + protected: + Status validate_format_compliance(nvidia::gxf::VideoFormat fmt) const override; + + private: + void* data_; + uint32_t channels_; +}; + +} // namespace holoscan::ops + +#endif // OPERATORS_ADVANCED_NETWORK_MEDIA_COMMON_FRAME_BUFFER_H_ diff --git a/operators/advanced_network_media/common/rtp_params.h b/operators/advanced_network_media/common/rtp_params.h new file mode 100644 index 0000000000..6269e87369 --- /dev/null +++ b/operators/advanced_network_media/common/rtp_params.h @@ -0,0 +1,79 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2023 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_COMMON_RTP_PARAMS_H_ +#define OPERATORS_ADVANCED_NETWORK_MEDIA_COMMON_RTP_PARAMS_H_ + +#include +#include +#include "rdk/services/services.h" + +// Structure to hold parsed RTP parameters +struct RtpParams { + uint32_t sequence_number; + uint32_t timestamp; + bool m_bit; // Marker bit - indicates end of frame + bool f_bit; // Field bit + uint16_t payload_size; // Payload size from SRD field + + RtpParams() : sequence_number(0), timestamp(0), m_bit(false), f_bit(false), payload_size(0) {} + + /** + * @brief Parse RTP header and populate this struct with extracted parameters + * @param rtp_hdr Pointer to RTP header data + * @return True if parsing successful, false if invalid RTP header + */ + bool parse(const uint8_t* rtp_hdr) { + // Validate RTP version (must be version 2) + if ((rtp_hdr[0] & 0xC0) != 0x80) { + return false; + } + + // Extract CSRC count and calculate offset + uint8_t cc = 0x0F & rtp_hdr[0]; + uint8_t offset = cc * RTP_HEADER_CSRC_GRANULARITY_BYTES; + + // Extract sequence number (16-bit + 16-bit extended) + sequence_number = rtp_hdr[3] | rtp_hdr[2] << 8; + sequence_number |= rtp_hdr[offset + 12] << 24 | rtp_hdr[offset + 13] << 16; + + // Extract field bit + f_bit = !!(rtp_hdr[offset + 16] & 0x80); + + // Extract timestamp (32-bit, network byte order) + timestamp = ntohl(*(uint32_t *)(rtp_hdr + 4)); + + // Extract marker bit + m_bit = !!(rtp_hdr[1] & 0x80); + + // Extract payload size from SRD Length field (2 bytes, network byte order) + payload_size = (rtp_hdr[offset + 14] << 8) | rtp_hdr[offset + 15]; + + return true; + } +}; + +/** + * @brief Parse RTP header and extract all parameters + * @param rtp_hdr Pointer to RTP header data + * @param params Reference to RtpParams struct to populate + * @return True if parsing successful, false if invalid RTP header + */ +inline bool parse_rtp_header(const uint8_t* rtp_hdr, RtpParams& params) { + return params.parse(rtp_hdr); +} +#endif // OPERATORS_ADVANCED_NETWORK_MEDIA_COMMON_RTP_PARAMS_H_ diff --git a/operators/advanced_network_media/common/video_parameters.cpp b/operators/advanced_network_media/common/video_parameters.cpp new file mode 100644 index 0000000000..5a0b3ee4db --- /dev/null +++ b/operators/advanced_network_media/common/video_parameters.cpp @@ -0,0 +1,142 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024 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 "video_parameters.h" + +namespace holoscan::ops { + +VideoFormatSampling get_video_sampling_format(const std::string& format) { + // Convert input format to lowercase for case-insensitive comparison + std::string format_lower = format; + std::transform( + format_lower.begin(), format_lower.end(), format_lower.begin(), [](unsigned char c) { + return std::tolower(c); + }); + + // Check against lowercase versions of format strings + if (format_lower == "rgb888" || format_lower == "rgb") return VideoFormatSampling::RGB; + + // YCbCr 4:2:2 / YUV 4:2:2 formats + if (format_lower == "ycbcr-4:2:2" || format_lower == "yuv422" || format_lower == "yuv-422" || + format_lower == "yuv-4:2:2" || format_lower == "ycbcr422") + return VideoFormatSampling::YCbCr_4_2_2; + + // YCbCr 4:2:0 / YUV 4:2:0 formats + if (format_lower == "ycbcr-4:2:0" || format_lower == "yuv420" || format_lower == "yuv-420" || + format_lower == "yuv-4:2:0" || format_lower == "ycbcr420") + return VideoFormatSampling::YCbCr_4_2_0; + + // YCbCr 4:4:4 / YUV 4:4:4 formats + if (format_lower == "ycbcr-4:4:4" || format_lower == "yuv444" || format_lower == "yuv-444" || + format_lower == "yuv-4:4:4" || format_lower == "ycbcr444") + return VideoFormatSampling::YCbCr_4_4_4; + + // Return CUSTOM for any unsupported format + HOLOSCAN_LOG_INFO("Unsupported video sampling format: {}. Using CUSTOM format.", format); + return VideoFormatSampling::CUSTOM; +} + +VideoColorBitDepth get_color_bit_depth(int bit_depth) { + switch (bit_depth) { + case 8: + return VideoColorBitDepth::_8; + case 10: + return VideoColorBitDepth::_10; + case 12: + return VideoColorBitDepth::_12; + default: + throw std::invalid_argument("Unsupported bit depth: " + std::to_string(bit_depth)); + } +} + +nvidia::gxf::VideoFormat get_expected_gxf_video_format(VideoFormatSampling sampling, + VideoColorBitDepth depth) { + if (sampling == VideoFormatSampling::RGB && depth == VideoColorBitDepth::_8) { + return nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_RGB; + } else if (sampling == VideoFormatSampling::YCbCr_4_2_0 && depth == VideoColorBitDepth::_8) { + return nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_NV12_709; + } else if (sampling == VideoFormatSampling::CUSTOM) { + return nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_CUSTOM; + } else { + // Return CUSTOM for any unsupported format + return nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_CUSTOM; + } +} + +size_t calculate_frame_size(uint32_t width, uint32_t height, VideoFormatSampling sampling_format, + VideoColorBitDepth bit_depth) { + using BytesPerPixelRatio = std::pair; + using ColorDepthPixelRatioMap = + std::unordered_map>; + + static const ColorDepthPixelRatioMap COLOR_DEPTH_TO_PIXEL_RATIO = { + {VideoFormatSampling::RGB, + {{VideoColorBitDepth::_8, {3, 1}}, + {VideoColorBitDepth::_10, {15, 4}}, + {VideoColorBitDepth::_12, {9, 2}}}}, + {VideoFormatSampling::YCbCr_4_4_4, + {{VideoColorBitDepth::_8, {3, 1}}, + {VideoColorBitDepth::_10, {15, 4}}, + {VideoColorBitDepth::_12, {9, 2}}}}, + {VideoFormatSampling::YCbCr_4_2_2, + {{VideoColorBitDepth::_8, {4, 2}}, + {VideoColorBitDepth::_10, {5, 2}}, + {VideoColorBitDepth::_12, {6, 2}}}}, + {VideoFormatSampling::YCbCr_4_2_0, + {{VideoColorBitDepth::_8, {6, 4}}, + {VideoColorBitDepth::_10, {15, 8}}, + {VideoColorBitDepth::_12, {9, 4}}}}}; + + auto format_it = COLOR_DEPTH_TO_PIXEL_RATIO.find(sampling_format); + if (format_it == COLOR_DEPTH_TO_PIXEL_RATIO.end()) { + throw std::invalid_argument("Unsupported sampling format"); + } + + auto depth_it = format_it->second.find(bit_depth); + if (depth_it == format_it->second.end()) { throw std::invalid_argument("Unsupported bit depth"); } + + float bytes_per_pixel = static_cast(depth_it->second.first) / depth_it->second.second; + return static_cast(width * height * bytes_per_pixel); +} + +uint32_t get_channel_count_for_format(nvidia::gxf::VideoFormat format) { + switch (format) { + // RGB formats + case nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_RGB: + return 3; + case nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_RGBA: + return 4; + // NV12 formats (semi-planar with interleaved UV) - all use 2 channels + case nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_NV12: + case nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_NV12_ER: + case nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_NV12_709: + case nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_NV12_709_ER: + return 2; + // YUV420 formats (multi-planar) - all use 3 planes (Y, U, V) + case nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_YUV420: + case nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_YUV420_ER: + case nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_YUV420_709: + case nvidia::gxf::VideoFormat::GXF_VIDEO_FORMAT_YUV420_709_ER: + return 3; + default: + HOLOSCAN_LOG_WARN("Unknown format {}, assuming 3 channels", static_cast(format)); + return 3; + } +} + +} // namespace holoscan::ops diff --git a/operators/advanced_network_media/common/video_parameters.h b/operators/advanced_network_media/common/video_parameters.h new file mode 100644 index 0000000000..b4cb2ea569 --- /dev/null +++ b/operators/advanced_network_media/common/video_parameters.h @@ -0,0 +1,104 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024 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_COMMON_VIDEO_PARAMETERS_H_ +#define OPERATORS_ADVANCED_NETWORK_MEDIA_COMMON_VIDEO_PARAMETERS_H_ + +#include +#include +#include "gxf/multimedia/video.hpp" +#include +#include "advanced_network/common.h" + +using namespace holoscan::advanced_network; + +namespace holoscan::ops { + +/** + * @enum VideoFormatSampling + * @brief Enumeration for video sampling formats. + */ +enum class VideoFormatSampling { + RGB, + YCbCr_4_4_4, + YCbCr_4_2_2, + YCbCr_4_2_0, + CUSTOM // Default for unsupported formats +}; + +/** + * @enum VideoColorBitDepth + * @brief Enumeration for video color bit depths. + */ +enum class VideoColorBitDepth { _8, _10, _12 }; + +/** + * @brief Converts a string format name to a VideoFormatSampling enum value. + * + * Supported formats include RGB888, YCbCr-4:2:2, YCbCr-4:2:0, YCbCr-4:4:4, + * and simplified notations like yuv422, yuv420, yuv444. The comparison is + * case-insensitive. + * + * @param format String representation of the video format. + * @return The corresponding VideoFormatSampling enum value. + * Returns VideoFormatSampling::CUSTOM for unsupported formats. + */ +VideoFormatSampling get_video_sampling_format(const std::string& format); + +/** + * @brief Converts a bit depth integer to a VideoColorBitDepth enum value. + * + * @param bit_depth Integer representation of the bit depth. + * @return The corresponding VideoColorBitDepth enum value. + * @throws std::invalid_argument If the bit depth is not supported. + */ +VideoColorBitDepth get_color_bit_depth(int bit_depth); + +/** + * @brief Maps internal video format representation to GXF video format. + * + * @param sampling The video sampling format. + * @param depth The color bit depth. + * @return The GXF video format corresponding to the given settings. + */ +nvidia::gxf::VideoFormat get_expected_gxf_video_format(VideoFormatSampling sampling, + VideoColorBitDepth depth); + +/** + * @brief Calculates the frame size based on resolution, sampling format, and bit depth. + * + * @param width Frame width in pixels. + * @param height Frame height in pixels. + * @param sampling_format The video sampling format. + * @param bit_depth The color bit depth. + * @return The calculated frame size in bytes. + * @throws std::invalid_argument If the sampling format or bit depth is unsupported. + */ +size_t calculate_frame_size(uint32_t width, uint32_t height, VideoFormatSampling sampling_format, + VideoColorBitDepth bit_depth); + +/** + * @brief Returns the number of channels required for a given video format + * + * @param format The GXF video format + * @return Number of channels required for this format + */ +uint32_t get_channel_count_for_format(nvidia::gxf::VideoFormat format); + +} // namespace holoscan::ops + +#endif // OPERATORS_ADVANCED_NETWORK_MEDIA_COMMON_VIDEO_PARAMETERS_H_ diff --git a/tutorials/high_performance_networking/README.md b/tutorials/high_performance_networking/README.md index 280f004239..82aad21be9 100644 --- a/tutorials/high_performance_networking/README.md +++ b/tutorials/high_performance_networking/README.md @@ -1508,7 +1508,7 @@ Identify the location of the `adv_networking_bench` executable, and of the confi adv_networking_bench_default_tx_rx_hds.yaml adv_networking_bench_default_tx_rx.yaml adv_networking_bench_gpunetio_tx_rx.yaml - adv_networking_bench_rmax_rx.yaml + adv_networking_bench_rivermax_rx.yaml CMakeLists.txt default_bench_op_rx.h default_bench_op_tx.h @@ -1531,7 +1531,7 @@ Identify the location of the `adv_networking_bench` executable, and of the confi adv_networking_bench_default_tx_rx.yaml adv_networking_bench_gpunetio_tx_rx.yaml adv_networking_bench.py - adv_networking_bench_rmax_rx.yaml + adv_networking_bench_rivermax_rx.yaml CMakeLists.txt default_bench_op_rx.h default_bench_op_tx.h