NVIDIA Holoscan SDK v2.9.0

GXF 示例

警告

本节内容已过时 (0.2),我们建议使用 C++ 或 Python API 开发扩展和应用程序。有关最新的建议,请参阅开发者指南。

让我们看一个 GXF 实体的示例,以尝试了解其一般结构。例如,让我们从名为 format_converter_entity 的图像格式转换器实体的实体定义开始,如下所示。

列表 27 GXF 应用程序 YAML 代码片段示例

复制
已复制!
            

%YAML 1.2 --- # other entities declared --- name: format_converter_entity components: - name: in_tensor type: nvidia::gxf::DoubleBufferReceiver - type: nvidia::gxf::MessageAvailableSchedulingTerm parameters: receiver: in_tensor min_size: 1 - name: out_tensor type: nvidia::gxf::DoubleBufferTransmitter - type: nvidia::gxf::DownstreamReceptiveSchedulingTerm parameters: transmitter: out_tensor min_size: 1 - name: pool type: nvidia::gxf::BlockMemoryPool parameters: storage_type: 1 block_size: 4919040 # 854 * 480 * 3 (channel) * 4 (bytes per pixel) num_blocks: 2 - name: format_converter_component type: nvidia::holoscan::formatconverter::FormatConverter parameters: in: in_tensor out: out_tensor out_tensor_name: source_video out_dtype: "float32" scale_min: 0.0 scale_max: 255.0 pool: pool --- # other entities declared --- components: - name: input_connection type: nvidia::gxf::Connection parameters: source: upstream_entity/output target: format_converter/in_tensor --- components: - name: output_connection type: nvidia::gxf::Connection parameters: source: format_converter/out_tensor target: downstream_entity/input --- name: scheduler components: - type: nvidia::gxf::GreedyScheduler


以上

  1. 实体 format_converter_entity 从上游实体 upstream_entityin_tensor 消息中接收消息,如 input_connection 中声明的那样。

  2. 接收到的消息被传递到 format_converter_component 组件,以将张量元素精度从 uint8 转换为 float32,并缩放 [0, 255] 强度范围内的任何输入。

  3. format_converter_component 组件最终将结果放入 out_tensor 消息中,以便下游实体(downstream_ent,如 output_connection 中声明的那样)可以使用其结果。

  4. Connection 组件将各种组件的输入和输出绑定在一起,在上述情况下为 upstream_entity/output -> format_converter_entity/in_tensorformat_converter_entity/out_tensor -> downstream_entity/input

  5. scheduler 实体声明了一个 GreedyScheduler “系统组件”,该组件协调图中声明的实体的执行。在 GreedyScheduler 的特定情况下,实体被安排为独占运行,即在任何给定时间最多只能运行一个实体。

上面的 YAML 代码片段可以用可视化方式表示如下。

format_converter_entity_diagram.png

图 21 Holoscan 应用程序中组件和实体的排列

在图像中,与 YAML 中一样,您将注意到使用了 MessageAvailableSchedulingTermDownstreamReceptiveSchedulingTermBlockMemoryPool。这些组件在 in_tensorout_tensorformat_converter_component 组件中扮演“支持”角色。具体来说

  • MessageAvailableSchedulingTerm 是一个组件,它接受一个 Receiver(在本例中是名为 in_tensorDoubleBufferReceiver),并向图 Executor 发出消息可用的警报。此警报触发 format_converter_component

  • DownstreamReceptiveSchedulingTerm 是一个组件,它接受一个 Transmitter(在本例中是名为 out_tensorDoubleBufferTransmitter),并向图 Executor 发出已将消息放置在输出端的警报。

  • BlockMemoryPool 提供两个几乎 5MB 的块,这些块在 GPU 设备上分配,并由 format_converted_ent 使用,以在格式转换组件中分配将放置转换数据的输出张量。

这些组件共同使实体能够执行特定功能,并通过声明的调度程序协调与图中其他实体的通信。

更一般而言,实体可以被认为是组件的集合,其中组件可以相互传递以执行特定的子任务(例如,事件触发或消息通知、格式转换、内存分配),而应用程序是实体的图。

调度程序是 nvidia::gxf::System 类型的组件,它在应用程序运行时根据触发规则协调每个实体中执行组件。

实体通过消息相互通信,消息可能包含一个或多个有效负载。消息通过 nvidia::gxf::Queue 类型的组件传递和接收,nvidia::gxf::Receivernvidia::gxf::Transmitter 都从中派生而来。每个接收和传输消息的实体都至少有一个接收器和一个发射器队列。

Holoscan 使用 nvidia::gxf::SchedulingTerm 组件来协调 Scheduler 的数据访问和组件编排,Scheduler 通过每个 Codelet 中的 tick() 函数调用执行。

提示

SchedulingTerm 定义了一个特定条件,实体使用该条件来告知调度程序它已准备好执行。

在上面的示例中,我们使用了 MessageAvailableSchedulingTerm 来触发等待来自 in_tensor 接收器队列的数据的组件的执行,即 format_converter_component

列表 28 MessageAvailableSchedulingTerm

复制
已复制!
            

- type: nvidia::gxf::MessageAvailableSchedulingTerm parameters: receiver: in_tensor min_size: 1


类似地,DownStreamReceptiveSchedulingTerm 检查 out_tensor 发射器队列中是否至少有一个传出消息。如果有一个或多个传出消息,DownStreamReceptiveSchedulingTerm 将通知调度程序,调度程序反过来尝试将消息放置在下游实体的接收器队列中。但是,如果下游实体具有完整的接收器队列,则消息将保留在 out_tensor 队列中,作为处理反压的一种手段。

列表 29 DownstreamReceptiveSchedulingTerm

复制
已复制!
            

- type: nvidia::gxf::DownstreamReceptiveSchedulingTerm parameters: transmitter: out_tensor min_size: 1


如果我们要更详细地绘制 图 21 中的实体,它将如下所示。

format_converter_entity_diagram-detail-1.png

图 22 实体中的接收和发射 Queues 以及 SchedulingTerm

到目前为止,我们从高层次上介绍了“实体组件系统”,并展示了实体的功能部件;即消息队列和支持实体中组件执行的调度项。为了完成这幅图景,下一节将介绍组件的结构和生命周期,以及如何在其中处理事件。

Holoscan 中的 GXF 组件可以执行从数据转换到内存管理再到实体调度的多种子任务。在本节中,我们将探讨 nvidia::gxf::Codelet 组件,该组件在 Holoscan 中被称为“GXF 扩展”。Holoscan (GXF) 扩展 通常与特定于应用程序的子任务有关,例如数据转换、AI 模型推理等。

扩展生命周期

Codelet 的生命周期由以下五个阶段组成。

  1. initialize - 仅在首次创建代码小部件时调用一次,并用于轻量级初始化。

  2. deinitialize - 仅在销毁代码小部件之前调用一次,并用于轻量级反初始化。

  3. start - 在代码小部件的生命周期内根据生命周期中定义的顺序多次调用,并用于繁重的初始化任务,例如分配内存资源。

  4. stop - 在代码小部件的生命周期内根据生命周期中定义的顺序多次调用,并用于繁重的反初始化任务,例如释放先前在 start 中分配的所有资源。

  5. tick - 在触发代码小部件时调用,并在代码小部件生命周期内多次调用;甚至在 startstop 之间多次调用。

这些阶段之间的流程在 图 23 中详细说明。

codelet_lifecycle_diagram.png

图 23 Holoscan 扩展生命周期中方法调用的顺序

实现扩展

在本节中,我们将实现一个简单的记录器,它将突出显示我们在生命周期方法中执行的操作。记录器接收输入队列中的数据,并将数据记录到磁盘上的配置位置。记录器文件的输出格式是 GXF 格式的索引/二进制重放器文件(该格式也用于示例应用程序中的数据),其中 gxf_index 文件包含引用 gxf_entities 文件中保存的二进制/张量数据的计时和序列元数据。

声明将实现扩展功能的类

开发者可以通过扩展 Codelet 类、通过覆盖生命周期方法来实现扩展功能,并通过 registerInterface 方法定义扩展在应用程序级别公开的参数来创建其 Holoscan 扩展。要定义我们的记录器组件,我们需要实现 Codelet 中的一些方法。

首先,从此处克隆 Holoscan 项目,并创建一个文件夹来开发我们的扩展,例如在 gxf_extensions/my_recorder 下。

提示

使用 Bash,我们创建一个 Holoscan 扩展文件夹,如下所示。

复制
已复制!
            

git clone https://github.com/nvidia-holoscan/holoscan-sdk.git cd clara-holoscan-embedded-sdk mkdir -p gxf_extensions/my_recorder

在我们的扩展文件夹中,我们创建一个头文件 my_recorder.hpp,其中包含我们的 Holoscan 组件的声明。

列表 30 gxf_extensions/my_recorder/my_recorder.hpp

复制
已复制!
            

#include <string> #include "gxf/core/handle.hpp" #include "gxf/std/codelet.hpp" #include "gxf/std/receiver.hpp" #include "gxf/std/transmitter.hpp" #include "gxf/serialization/file_stream.hpp" #include "gxf/serialization/entity_serializer.hpp" class MyRecorder : public nvidia::gxf::Codelet { public: gxf_result_t registerInterface(nvidia::gxf::Registrar* registrar) override; gxf_result_t initialize() override; gxf_result_t deinitialize() override; gxf_result_t start() override; gxf_result_t tick() override; gxf_result_t stop() override; private: nvidia::gxf::Parameter<nvidia::gxf::Handle<nvidia::gxf::Receiver>> receiver_; nvidia::gxf::Parameter<nvidia::gxf::Handle<nvidia::gxf::EntitySerializer>> my_serializer_; nvidia::gxf::Parameter<std::string> directory_; nvidia::gxf::Parameter<std::string> basename_; nvidia::gxf::Parameter<bool> flush_on_tick_; // File stream for data index nvidia::gxf::FileStream index_file_stream_; // File stream for binary data nvidia::gxf::FileStream binary_file_stream_; // Offset into binary file size_t binary_file_offset_; };


声明要在应用程序级别公开的参数

接下来,我们可以开始在 my_recorder.cpp 文件中实现我们的生命周期方法,我们也在 gxf_extensions/my_recorder 路径中创建该文件。

我们的记录器将需要向应用程序公开 nvidia::gxf::Parameter 变量,以便可以通过配置修改参数。

列表 31 gxf_extensions/my_recorder/my_recorder.cpp 中的 registerInterface

复制
已复制!
            

#include "my_recorder.hpp" gxf_result_t MyRecorder::registerInterface(nvidia::gxf::Registrar* registrar) { nvidia::gxf::Expected<void> result; result &= registrar->parameter( receiver_, "receiver", "Entity receiver", "Receiver channel to log"); result &= registrar->parameter( my_serializer_, "serializer", "Entity serializer", "Serializer for serializing input data"); result &= registrar->parameter( directory_, "out_directory", "Output directory path", "Directory path to store received output"); result &= registrar->parameter( basename_, "basename", "File base name", "User specified file name without extension", nvidia::gxf::Registrar::NoDefaultParameter(), GXF_PARAMETER_FLAGS_OPTIONAL); result &= registrar->parameter( flush_on_tick_, "flush_on_tick", "Boolean to flush on tick", "Flushes output buffer on every `tick` when true", false); // default value `false` return nvidia::gxf::ToResultCode(result); }


对于纯 GXF 应用程序,我们组件的参数可以在 YAML 文件中以以下格式指定

列表 32 MyRecorder 组件的示例参数

复制
已复制!
            

name: my_recorder_entity components: - name: my_recorder_component type: MyRecorder parameters: receiver: receiver serializer: my_serializer out_directory: /home/user/out_path basename: my_output_file # optional # flush_on_tick: false # optional


请注意,在应用程序级别公开的所有参数都是强制性的,但 flush_on_tick 除外,它默认为 falsebasename 除外,其默认值在下面的 initialize() 中处理。

实现生命周期方法

此扩展不需要执行任何繁重的初始化任务,因此我们将重点关注 initialize()tick()deinitialize() 方法,这些方法定义了我们组件的核心功能。在初始化时,我们将创建一个文件流,并通过 binary_file_offset 跟踪我们在 tick() 上写入的字节。

列表 33 gxf_extensions/my_recorder/my_recorder.cpp 中的 initialize

复制
已复制!
            

gxf_result_t MyRecorder::initialize() { // Create path by appending receiver name to directory path if basename is not provided std::string path = directory_.get() + '/'; if (const auto& basename = basename_.try_get()) { path += basename.value(); } else { path += receiver_->name(); } // Initialize index file stream as write-only index_file_stream_ = nvidia::gxf::FileStream("", path + nvidia::gxf::FileStream::kIndexFileExtension); // Initialize binary file stream as write-only binary_file_stream_ = nvidia::gxf::FileStream("", path + nvidia::gxf::FileStream::kBinaryFileExtension); // Open index file stream nvidia::gxf::Expected<void> result = index_file_stream_.open(); if (!result) { return nvidia::gxf::ToResultCode(result); } // Open binary file stream result = binary_file_stream_.open(); if (!result) { return nvidia::gxf::ToResultCode(result); } binary_file_offset_ = 0; return GXF_SUCCESS; }


在反初始化时,我们的组件将负责关闭在初始化时创建的文件流。

列表 34 gxf_extensions/my_recorder/my_recorder.cpp 中的 deinitialize

复制
已复制!
            

gxf_result_t MyRecorder::deinitialize() { // Close binary file stream nvidia::gxf::Expected<void> result = binary_file_stream_.close(); if (!result) { return nvidia::gxf::ToResultCode(result); } // Close index file stream result = index_file_stream_.close(); if (!result) { return nvidia::gxf::ToResultCode(result); } return GXF_SUCCESS; }


在我们的记录器中,不需要繁重的初始化任务,因此我们实现以下内容;但是,我们将使用 start()stop() 方法来执行繁重的任务,例如内存分配和释放。

列表 35 gxf_extensions/my_recorder/my_recorder.cpp 中的 start/stop

复制
已复制!
            

gxf_result_t MyRecorder::start() { return GXF_SUCCESS; } gxf_result_t MyRecorder::stop() { return GXF_SUCCESS; }


提示

有关 start()stop() 的详细实现以及如何在其中处理内存管理,请参阅 AJA 视频源扩展的实现。

最后,我们通过实现 tick() 来编写我们扩展的组件特定功能。

列表 36 gxf_extensions/my_recorder/my_recorder.cpp 中的 tick

复制
已复制!
            

gxf_result_t MyRecorder::tick() { // Receive entity nvidia::gxf::Expected<nvidia::gxf::Entity> entity = receiver_->receive(); if (!entity) { return nvidia::gxf::ToResultCode(entity); } // Write entity to binary file nvidia::gxf::Expected<size_t> size = my_serializer_->serializeEntity(entity.value(), &binary_file_stream_); if (!size) { return nvidia::gxf::ToResultCode(size); } // Create entity index nvidia::gxf::EntityIndex index; index.log_time = std::chrono::system_clock::now().time_since_epoch().count(); index.data_size = size.value(); index.data_offset = binary_file_offset_; // Write entity index to index file nvidia::gxf::Expected<size_t> result = index_file_stream_.writeTrivialType(&index); if (!result) { return nvidia::gxf::ToResultCode(result); } binary_file_offset_ += size.value(); if (flush_on_tick_) { // Flush binary file output stream nvidia::gxf::Expected<void> result = binary_file_stream_.flush(); if (!result) { return nvidia::gxf::ToResultCode(result); } // Flush index file output stream result = index_file_stream_.flush(); if (!result) { return nvidia::gxf::ToResultCode(result); } } return GXF_SUCCESS; }


将扩展注册为 Holoscan 组件

作为最后一步,我们必须注册我们的扩展,以便它被识别为组件并由应用程序执行器加载。为此,我们在 my_recorder_ext.cpp 中创建一个简单的声明,如下所示。

列表 37 gxf_extensions/my_recorder/my_recorder_ext.cpp

复制
已复制!
            

#include "gxf/std/extension_factory_helper.hpp" #include "my_recorder.hpp" GXF_EXT_FACTORY_BEGIN() GXF_EXT_FACTORY_SET_INFO(0xb891cef3ce754825, 0x9dd3dcac9bbd8483, "MyRecorderExtension", "My example recorder extension", "NVIDIA", "0.1.0", "LICENSE"); GXF_EXT_FACTORY_ADD(0x2464fabf91b34ccf, 0xb554977fa22096bd, MyRecorder, nvidia::gxf::Codelet, "My example recorder codelet."); GXF_EXT_FACTORY_END()


GXF_EXT_FACTORY_SET_INFO 按顺序使用以下信息配置扩展

  • 可以使用 scripts/generate_extension_uuids.py 生成的 UUID,它定义了扩展 ID

  • 扩展名称

  • 扩展描述

  • 作者

  • 扩展版本

  • 许可文本

GXF_EXT_FACTORY_ADD 将新构建的扩展注册为有效的 Codelet 组件,并按顺序提供以下信息

  • 可以使用 scripts/generate_extension_uuids.py 生成的 UUID,它定义了组件 ID(这必须与扩展 ID 不同),

  • 完全限定的扩展类,

  • 完全限定的基类,

  • 组件描述

要为我们的新扩展构建一个共享库,该库可以在运行时由 Holoscan 应用程序加载,我们可以在 gxf_extensions/my_recorder/CMakeLists.txt 下使用 CMake 文件,内容如下。

列表 38 gxf_extensions/my_recorder/CMakeLists.txt

复制
已复制!
            

# Create library add_library(my_recorder_lib SHARED my_recorder.cpp my_recorder.hpp ) target_link_libraries(my_recorder_lib PUBLIC GXF::std GXF::serialization yaml-cpp ) # Create extension add_library(my_recorder SHARED my_recorder_ext.cpp ) target_link_libraries(my_recorder PUBLIC my_recorder_lib ) # Install GXF extension as a component 'holoscan-gxf_extensions' install_gxf_extension(my_recorder) # this will also install my_recorder_lib # install_gxf_extension(my_recorder_lib) # this statement is not necessary because this library follows `<extension library name>_lib` convention.


在这里,我们创建一个库 my_recorder_lib,其中包含生命周期方法的实现,以及扩展 my_recorder,它公开了应用程序运行时与我们的组件交互所需的 C API。

为了使我们的扩展可以从项目根目录发现,我们将以下行添加到

复制
已复制!
            

add_subdirectory(my_recorder)

CMake 文件 gxf_extensions/CMakeLists.txt

提示

要构建我们的扩展,我们可以按照 README 中的步骤操作。

此时,我们有了一个完整的扩展,它可以将进入其接收器队列的数据使用 GXF 格式的二进制/索引文件记录到磁盘上的指定位置。

对于我们的应用程序,我们创建目录 apps/my_recorder_app_gxf,其中包含应用程序定义文件 my_recorder_gxf.yamlmy_recorder_gxf.yaml 应用程序如下所示

列表 39 apps/my_recorder_app_gxf/my_recorder_gxf.yaml

复制
已复制!
            

%YAML 1.2 --- name: replayer components: - name: output type: nvidia::gxf::DoubleBufferTransmitter - name: allocator type: nvidia::gxf::UnboundedAllocator - name: component_serializer type: nvidia::gxf::StdComponentSerializer parameters: allocator: allocator - name: entity_serializer type: nvidia::holoscan::stream_playback::VideoStreamSerializer # inheriting from nvidia::gxf::EntitySerializer parameters: component_serializers: [component_serializer] - type: nvidia::holoscan::stream_playback::VideoStreamReplayer parameters: transmitter: output entity_serializer: entity_serializer boolean_scheduling_term: boolean_scheduling directory: "/workspace/data/racerx" basename: "racerx" frame_rate: 0 # as specified in timestamps repeat: false # default: false realtime: true # default: true count: 0 # default: 0 (no frame count restriction) - name: boolean_scheduling type: nvidia::gxf::BooleanSchedulingTerm - type: nvidia::gxf::DownstreamReceptiveSchedulingTerm parameters: transmitter: output min_size: 1 --- name: recorder components: - name: input type: nvidia::gxf::DoubleBufferReceiver - name: allocator type: nvidia::gxf::UnboundedAllocator - name: component_serializer type: nvidia::gxf::StdComponentSerializer parameters: allocator: allocator - name: entity_serializer type: nvidia::holoscan::stream_playback::VideoStreamSerializer # inheriting from nvidia::gxf::EntitySerializer parameters: component_serializers: [component_serializer] - type: MyRecorder parameters: receiver: input serializer: entity_serializer out_directory: "/tmp" basename: "tensor_out" - type: nvidia::gxf::MessageAvailableSchedulingTerm parameters: receiver: input min_size: 1 --- components: - name: input_connection type: nvidia::gxf::Connection parameters: source: replayer/output target: recorder/input --- name: scheduler components: - name: clock type: nvidia::gxf::RealtimeClock - name: greedy_scheduler type: nvidia::gxf::GreedyScheduler parameters: clock: clock


以上

  • 重放器从 /workspace/data/racerx/racerx.gxf_[index|entities] 文件读取数据,使用 VideoStreamSerializer 将二进制数据反序列化为 nvidia::gxf::Tensor,并将数据放在 replayer/output 发射器队列中的输出消息中。

  • input_connection 组件将 replayer/output 发射器队列连接到 recorder/input 接收器队列。

  • 记录器读取 input 接收器队列中的数据,使用 StdEntitySerializer 将接收到的 nvidia::gxf::Tensor 转换为二进制流,并输出到参数中指定的 /tmp/tensor_out.gxf_[index|entities] 位置。

  • scheduler 组件虽然没有显式连接到特定于应用程序的实体,但执行 数据流和触发规则中讨论的组件的编排。

请注意在我们新构建的记录器中使用了 component_serializer。此组件在以下实体中单独声明

复制
已复制!
            

- name: entity_serializer type: nvidia::holoscan::stream_playback::VideoStreamSerializer # inheriting from nvidia::gxf::EntitySerializer parameters: component_serializers: [component_serializer]

然后通过 serializer 参数将其传递到 MyRecorder 中,我们在扩展开发部分(声明要在应用程序级别公开的参数)中公开了该参数。

复制
已复制!
            

- type: MyRecorder parameters: receiver: input serializer: entity_serializer directory: "/tmp" basename: "tensor_out"

为了使我们的应用程序能够加载(并在必要时编译)运行时所需的扩展,我们需要声明一个 CMake 文件 apps/my_recorder_app_gxf/CMakeLists.txt,如下所示。

列表 40 apps/my_recorder_app_gxf/CMakeLists.txt

复制
已复制!
            

create_gxe_application( NAME my_recorder_gxf YAML my_recorder_gxf.yaml EXTENSIONS GXF::std GXF::cuda GXF::multimedia GXF::serialization my_recorder stream_playback ) # Download the associated dataset if needed if(HOLOSCAN_DOWNLOAD_DATASETS) add_dependencies(my_recorder_gxf racerx_data) endif()


create_gxe_application 的声明中,我们列出

  • 扩展开发部分的 CMake 文件中声明的 my_recorder 组件,位于 EXTENSIONS 参数下

  • 现有的 stream_playback Holoscan 扩展,它从磁盘读取数据

为了使我们新构建的应用程序可以被构建发现,在存储库的根目录中,我们将以下行添加到 apps/CMakeLists.txt

复制
已复制!
            

add_subdirectory(my_recorder_app_gxf)

我们现在有一个最小的工作应用程序来测试我们新构建的 MyRecorder 扩展的集成。

要在本地开发容器中运行我们的应用程序

  1. 请按照使用开发容器部分步骤 1-5 中的说明进行操作(尝试通过删除 build 文件夹来清除 CMake 缓存,然后再进行编译)。

    您可以执行以下命令来构建

    复制
    已复制!
                

    ./run build # ./run clear_cache # if you want to clear build/install/cache folders

  2. 我们的测试应用程序现在可以使用命令在开发容器中运行

    复制
    已复制!
                

    ./apps/my_recorder_app_gxf/my_recorder_gxf

    从开发容器内部。

    (您可以执行 ./run launch 来运行开发容器。)

    复制
    已复制!
                

    @LINUX:/workspace/holoscan-sdk/build$ ./apps/my_recorder_app_gxf/my_recorder_gxf 2022-08-24 04:46:47.333 INFO gxf/gxe/gxe.cpp@230: Creating context 2022-08-24 04:46:47.339 INFO gxf/gxe/gxe.cpp@107: Loading app: 'apps/my_recorder_app_gxf/my_recorder_gxf.yaml' 2022-08-24 04:46:47.339 INFO gxf/std/yaml_file_loader.cpp@117: Loading GXF entities from YAML file 'apps/my_recorder_app_gxf/my_recorder_gxf.yaml'... 2022-08-24 04:46:47.340 INFO gxf/gxe/gxe.cpp@291: Initializing... 2022-08-24 04:46:47.437 INFO gxf/gxe/gxe.cpp@298: Running... 2022-08-24 04:46:47.437 INFO gxf/std/greedy_scheduler.cpp@170: Scheduling 2 entities 2022-08-24 04:47:14.829 INFO /workspace/holoscan-sdk/gxf_extensions/stream_playback/video_stream_replayer.cpp@144: Reach end of file or playback count reaches to the limit. Stop ticking. 2022-08-24 04:47:14.829 INFO gxf/std/greedy_scheduler.cpp@329: Scheduler stopped: Some entities are waiting for execution, but there are no periodic or async entities to get out of the deadlock. 2022-08-24 04:47:14.829 INFO gxf/std/greedy_scheduler.cpp@353: Scheduler finished. 2022-08-24 04:47:14.829 INFO gxf/gxe/gxe.cpp@320: Deinitializing... 2022-08-24 04:47:14.863 INFO gxf/gxe/gxe.cpp@327: Destroying context 2022-08-24 04:47:14.863 INFO gxf/gxe/gxe.cpp@333: Context destroyed.

成功运行(大约需要 30 秒)将生成与原始输入文件(data/racerx 下的 racerx.gxf_indexracerx.gxf_entities)完全匹配的输出文件(/tmp 中的 tensor_out.gxf_indextensor_out.gxf_entities)。

复制
已复制!
            

@LINUX:/workspace/holoscan-sdk/build$ ls -al /tmp/ total 821384 drwxrwxrwt 1 root root 4096 Aug 24 04:37 . drwxr-xr-x 1 root root 4096 Aug 24 04:36 .. drwxrwxrwt 2 root root 4096 Aug 11 21:42 .X11-unix -rw-r--r-- 1 1000 1000 729309 Aug 24 04:47 gxf_log -rw-r--r-- 1 1000 1000 840054484 Aug 24 04:47 tensor_out.gxf_entities -rw-r--r-- 1 1000 1000 16392 Aug 24 04:47 tensor_out.gxf_index @LINUX:/workspace/holoscan-sdk/build$ ls -al ../data/racerx total 839116 drwxr-xr-x 2 1000 1000 4096 Aug 24 02:08 . drwxr-xr-x 4 1000 1000 4096 Aug 24 02:07 .. -rw-r--r-- 1 1000 1000 19164125 Jun 17 16:31 racerx-medium.mp4 -rw-r--r-- 1 1000 1000 840054484 Jun 17 16:31 racerx.gxf_entities -rw-r--r-- 1 1000 1000 16392 Jun 17 16:31 racerx.gxf_index

上一篇 Holoscan 和 GXF
下一篇 在 GXF 应用程序中使用 Holoscan 运算符、资源和类型
© 版权所有 2022-2024,NVIDIA。 最后更新于 2025 年 1 月 27 日。