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
以上
实体
format_converter_entity
从上游实体upstream_entity
的in_tensor
消息中接收消息,如input_connection
中声明的那样。接收到的消息被传递到
format_converter_component
组件,以将张量元素精度从uint8
转换为float32
,并缩放[0, 255]
强度范围内的任何输入。format_converter_component
组件最终将结果放入out_tensor
消息中,以便下游实体(downstream_ent
,如output_connection
中声明的那样)可以使用其结果。Connection
组件将各种组件的输入和输出绑定在一起,在上述情况下为upstream_entity/output -> format_converter_entity/in_tensor
和format_converter_entity/out_tensor -> downstream_entity/input
。scheduler
实体声明了一个GreedyScheduler
“系统组件”,该组件协调图中声明的实体的执行。在GreedyScheduler
的特定情况下,实体被安排为独占运行,即在任何给定时间最多只能运行一个实体。
上面的 YAML 代码片段可以用可视化方式表示如下。

图 21 Holoscan 应用程序中组件和实体的排列
在图像中,与 YAML 中一样,您将注意到使用了 MessageAvailableSchedulingTerm
、DownstreamReceptiveSchedulingTerm
和 BlockMemoryPool
。这些组件在 in_tensor
、out_tensor
和 format_converter_component
组件中扮演“支持”角色。具体来说
MessageAvailableSchedulingTerm
是一个组件,它接受一个Receiver
(在本例中是名为in_tensor
的DoubleBufferReceiver
),并向图Executor
发出消息可用的警报。此警报触发format_converter_component
。DownstreamReceptiveSchedulingTerm
是一个组件,它接受一个Transmitter
(在本例中是名为out_tensor
的DoubleBufferTransmitter
),并向图Executor
发出已将消息放置在输出端的警报。BlockMemoryPool
提供两个几乎5MB
的块,这些块在 GPU 设备上分配,并由format_converted_ent
使用,以在格式转换组件中分配将放置转换数据的输出张量。
这些组件共同使实体能够执行特定功能,并通过声明的调度程序协调与图中其他实体的通信。
更一般而言,实体可以被认为是组件的集合,其中组件可以相互传递以执行特定的子任务(例如,事件触发或消息通知、格式转换、内存分配),而应用程序是实体的图。
调度程序是 nvidia::gxf::System
类型的组件,它在应用程序运行时根据触发规则协调每个实体中执行组件。
实体通过消息相互通信,消息可能包含一个或多个有效负载。消息通过 nvidia::gxf::Queue
类型的组件传递和接收,nvidia::gxf::Receiver
和 nvidia::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 中的实体,它将如下所示。

图 22 实体中的接收和发射 Queues
以及 SchedulingTerm
。
到目前为止,我们从高层次上介绍了“实体组件系统”,并展示了实体的功能部件;即消息队列和支持实体中组件执行的调度项。为了完成这幅图景,下一节将介绍组件的结构和生命周期,以及如何在其中处理事件。
Holoscan 中的 GXF 组件可以执行从数据转换到内存管理再到实体调度的多种子任务。在本节中,我们将探讨 nvidia::gxf::Codelet
组件,该组件在 Holoscan 中被称为“GXF 扩展”。Holoscan (GXF) 扩展 通常与特定于应用程序的子任务有关,例如数据转换、AI 模型推理等。
扩展生命周期
Codelet
的生命周期由以下五个阶段组成。
initialize
- 仅在首次创建代码小部件时调用一次,并用于轻量级初始化。deinitialize
- 仅在销毁代码小部件之前调用一次,并用于轻量级反初始化。start
- 在代码小部件的生命周期内根据生命周期中定义的顺序多次调用,并用于繁重的初始化任务,例如分配内存资源。stop
- 在代码小部件的生命周期内根据生命周期中定义的顺序多次调用,并用于繁重的反初始化任务,例如释放先前在start
中分配的所有资源。tick
- 在触发代码小部件时调用,并在代码小部件生命周期内多次调用;甚至在start
和stop
之间多次调用。
这些阶段之间的流程在 图 23 中详细说明。

图 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
除外,它默认为 false
,basename
除外,其默认值在下面的 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.yaml
。my_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-5 中的说明进行操作(尝试通过删除
build
文件夹来清除 CMake 缓存,然后再进行编译)。您可以执行以下命令来构建
./run build # ./run clear_cache # if you want to clear build/install/cache folders
我们的测试应用程序现在可以使用命令在开发容器中运行
./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_index
和 racerx.gxf_entities
)完全匹配的输出文件(/tmp
中的 tensor_out.gxf_index
和 tensor_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