性能优化4

This commit is contained in:
sladro 2026-01-12 20:51:27 +08:00
parent 00f254ae71
commit 4ae1ac54e1
15 changed files with 377 additions and 43 deletions

10
.clang-format Normal file
View File

@ -0,0 +1,10 @@
BasedOnStyle: LLVM
IndentWidth: 4
TabWidth: 4
UseTab: Never
ColumnLimit: 120
BreakBeforeBraces: Attach
AllowShortFunctionsOnASingleLine: InlineOnly
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
SortIncludes: false

View File

@ -6,22 +6,111 @@ on:
pull_request: pull_request:
branches: [ master, main ] branches: [ master, main ]
workflow_dispatch: workflow_dispatch:
inputs:
enable_cross_build:
description: "Enable RK3588 cross build job"
required: false
default: "false"
jobs: jobs:
host-build: host-build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Configure & build (host) with:
fetch-depth: 0
- name: Install dependencies
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y ninja-build sudo apt-get install -y ninja-build clang-format cppcheck
- name: Quality gates (clang-format + cppcheck on changed files)
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
import json, os, subprocess, sys
def sh(*args):
return subprocess.check_output(list(args), text=True).strip()
event = os.environ.get('GITHUB_EVENT_NAME','')
event_path = os.environ.get('GITHUB_EVENT_PATH','')
payload = {}
if event_path:
with open(event_path, 'r', encoding='utf-8') as f:
payload = json.load(f)
base = None
head = os.environ.get('GITHUB_SHA','HEAD')
if event == 'pull_request':
base = (((payload.get('pull_request') or {}).get('base') or {}).get('sha'))
else:
base = payload.get('before')
if base and set(base) == {'0'}:
base = None
if not base:
# Fallback to previous commit if possible.
try:
base = sh('git','rev-parse','HEAD~1')
except Exception:
base = None
if base:
diff = subprocess.check_output(['git','diff','--name-only',f'{base}...{head}'], text=True)
paths = [p.strip() for p in diff.splitlines() if p.strip()]
else:
paths = [p.strip() for p in sh('git','ls-files').splitlines() if p.strip()]
exts = ('.c','.cc','.cpp','.cxx','.h','.hh','.hpp','.hxx')
files = []
for p in paths:
if not p.endswith(exts):
continue
if p.startswith(('third_party/','build/','.git/')):
continue
files.append(p)
if not files:
print('No C/C++ files changed; skipping format/cppcheck.')
sys.exit(0)
# clang-format
subprocess.check_call(['clang-format','--version'])
subprocess.check_call(['clang-format','--dry-run','--Werror',*files])
# cppcheck
subprocess.check_call(['cppcheck','--version'])
subprocess.check_call([
'cppcheck',
'--error-exitcode=1',
'--enable=warning,style,performance,portability',
'--inline-suppr',
'--suppress=missingIncludeSystem',
'--std=c++17',
'-I','include',
'-I','third_party',
*files,
])
PY
- name: Configure & build (host)
env:
CMAKE_ARGS: -DBUILD_TESTS=ON
run: |
./scripts/build_host.sh ./scripts/build_host.sh
- name: Run unit tests
run: |
ctest --test-dir build/host --output-on-failure
rk3588-cross-build: rk3588-cross-build:
if: github.event_name == 'workflow_dispatch' && github.event.inputs.enable_cross_build == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install toolchain - name: Install toolchain
run: | run: |
sudo apt-get update sudo apt-get update

View File

@ -9,6 +9,7 @@ project(rk3588_media_server
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
option(BUILD_SAMPLES "Build demo binaries" ON) option(BUILD_SAMPLES "Build demo binaries" ON)
option(BUILD_TESTS "Build unit tests" ON)
option(ENABLE_RK_DEMOS "Use official Rockchip demos that link against vendor SDKs" OFF) option(ENABLE_RK_DEMOS "Use official Rockchip demos that link against vendor SDKs" OFF)
set(RK_MPP_ROOT "${CMAKE_SOURCE_DIR}/third_party/mpp" CACHE PATH "Path to Rockchip MPP SDK root") set(RK_MPP_ROOT "${CMAKE_SOURCE_DIR}/third_party/mpp" CACHE PATH "Path to Rockchip MPP SDK root")
@ -82,6 +83,11 @@ if(BUILD_SAMPLES)
add_subdirectory(samples) add_subdirectory(samples)
endif() endif()
if(BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()
add_subdirectory(plugins) add_subdirectory(plugins)
include(GNUInstallDirs) include(GNUInstallDirs)

View File

@ -136,7 +136,7 @@ private:
bool enabled = true; bool enabled = true;
SimpleJson config; SimpleJson config;
NodeContext context; NodeContext context;
std::unique_ptr<INode> node; NodePtr node;
std::vector<int> cpu_affinity; std::vector<int> cpu_affinity;
// Scheduling state for Executor. // Scheduling state for Executor.

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <atomic>
#include <thread>
#include <string> #include <string>
#include "graph_manager.h" #include "graph_manager.h"
@ -14,6 +16,9 @@ public:
private: private:
std::string config_path_; std::string config_path_;
GraphManager graph_manager_{}; GraphManager graph_manager_{};
std::atomic<bool> stop_signal_thread_{false};
std::thread signal_thread_;
}; };
} // namespace rk3588 } // namespace rk3588

View File

@ -52,17 +52,36 @@ public:
virtual void Drain() {} virtual void Drain() {}
}; };
constexpr int kNodeAbiVersion = 2; // ABI version for dynamic node plugins.
// Bump when plugin-required symbols or contracts change.
constexpr int kNodeAbiVersion = 3;
using CreateNodeFn = INode* (*)(); using CreateNodeFn = INode* (*)();
using DestroyNodeFn = void (*)(INode*); using DestroyNodeFn = void (*)(INode*);
using GetTypeFn = const char* (*)(); using GetTypeFn = const char* (*)();
using GetAbiFn = int (*)(); using GetAbiFn = int (*)();
struct NodeDeleter {
DestroyNodeFn destroy = nullptr;
void operator()(INode* p) const noexcept {
if (!p) return;
if (destroy) {
destroy(p);
} else {
delete p;
}
}
};
using NodePtr = std::unique_ptr<INode, NodeDeleter>;
#define REGISTER_NODE(NodeClass, NodeTypeStr) \ #define REGISTER_NODE(NodeClass, NodeTypeStr) \
extern "C" rk3588::INode* CreateNode() { \ extern "C" rk3588::INode* CreateNode() { \
return new NodeClass(); \ return new NodeClass(); \
} \ } \
extern "C" void DestroyNode(rk3588::INode* p) { \
delete p; \
} \
extern "C" const char* GetNodeType() { \ extern "C" const char* GetNodeType() { \
return NodeTypeStr; \ return NodeTypeStr; \
} \ } \

View File

@ -19,7 +19,7 @@ public:
PluginLoader(PluginLoader&& other) noexcept; PluginLoader(PluginLoader&& other) noexcept;
PluginLoader& operator=(PluginLoader&& other) noexcept; PluginLoader& operator=(PluginLoader&& other) noexcept;
std::unique_ptr<INode> Create(const std::string& type, std::string& err); NodePtr Create(const std::string& type, std::string& err);
// Switch plugin directory. This will unload any cached plugins. // Switch plugin directory. This will unload any cached plugins.
void SetPluginDir(std::string plugin_dir); void SetPluginDir(std::string plugin_dir);
@ -29,6 +29,7 @@ private:
struct PluginHandle { struct PluginHandle {
void* handle = nullptr; void* handle = nullptr;
CreateNodeFn create = nullptr; CreateNodeFn create = nullptr;
DestroyNodeFn destroy = nullptr;
GetTypeFn get_type = nullptr; GetTypeFn get_type = nullptr;
GetAbiFn get_abi = nullptr; GetAbiFn get_abi = nullptr;
}; };

View File

@ -7,6 +7,9 @@ INSTALL_PREFIX=${INSTALL_PREFIX:-${BUILD_DIR}/install}
BUILD_TYPE=${BUILD_TYPE:-Release} BUILD_TYPE=${BUILD_TYPE:-Release}
TOOLCHAIN_FILE=${TOOLCHAIN_FILE:-"$ROOT_DIR/cmake/toolchain/aarch64-rk3588.cmake"} TOOLCHAIN_FILE=${TOOLCHAIN_FILE:-"$ROOT_DIR/cmake/toolchain/aarch64-rk3588.cmake"}
# Extra args for customization.
CMAKE_ARGS=${CMAKE_ARGS:-}
if [[ -z "${RK3588_SYSROOT:-}" ]]; then if [[ -z "${RK3588_SYSROOT:-}" ]]; then
echo "RK3588_SYSROOT environment variable must be set" >&2 echo "RK3588_SYSROOT environment variable must be set" >&2
exit 1 exit 1
@ -15,7 +18,9 @@ fi
cmake -S "$ROOT_DIR" -B "$BUILD_DIR" \ cmake -S "$ROOT_DIR" -B "$BUILD_DIR" \
-G Ninja \ -G Ninja \
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
-DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN_FILE" -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN_FILE" \
-DBUILD_TESTS=OFF \
$CMAKE_ARGS
cmake --build "$BUILD_DIR" cmake --build "$BUILD_DIR"
cmake --install "$BUILD_DIR" --prefix "$INSTALL_PREFIX" cmake --install "$BUILD_DIR" --prefix "$INSTALL_PREFIX"

View File

@ -4,8 +4,12 @@ set -euo pipefail
BUILD_DIR=${BUILD_DIR:-build/host} BUILD_DIR=${BUILD_DIR:-build/host}
BUILD_TYPE=${BUILD_TYPE:-RelWithDebInfo} BUILD_TYPE=${BUILD_TYPE:-RelWithDebInfo}
# Extra args for CI/local customization, e.g. -DBUILD_TESTS=ON
CMAKE_ARGS=${CMAKE_ARGS:-}
cmake -S "$(dirname "$0")/.." -B "$BUILD_DIR" \ cmake -S "$(dirname "$0")/.." -B "$BUILD_DIR" \
-G Ninja \ -G Ninja \
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
$CMAKE_ARGS
cmake --build "$BUILD_DIR" cmake --build "$BUILD_DIR"

View File

@ -815,7 +815,7 @@ bool Graph::Start() {
if (!entry.enabled || !entry.node) continue; if (!entry.enabled || !entry.node) continue;
if (is_source_like(entry)) continue; if (is_source_like(entry)) continue;
if (!entry.node->Start()) { if (!entry.node->Start()) {
std::cerr << "[Graph] failed to start node: " << entry.id << "\n"; LogError("[Graph] failed to start node: " + entry.id);
Stop(); Stop();
return false; return false;
} }
@ -824,7 +824,7 @@ bool Graph::Start() {
// 2) Start executor + attach callbacks before any source can push frames. // 2) Start executor + attach callbacks before any source can push frames.
executor_ = std::make_unique<Executor>(*this); executor_ = std::make_unique<Executor>(*this);
if (!executor_->Start()) { if (!executor_->Start()) {
std::cerr << "[Graph] failed to start executor\n"; LogError("[Graph] failed to start executor");
Stop(); Stop();
return false; return false;
} }
@ -835,13 +835,13 @@ bool Graph::Start() {
if (!entry.enabled || !entry.node) continue; if (!entry.enabled || !entry.node) continue;
if (!is_source_like(entry)) continue; if (!is_source_like(entry)) continue;
if (!entry.node->Start()) { if (!entry.node->Start()) {
std::cerr << "[Graph] failed to start node: " << entry.id << "\n"; LogError("[Graph] failed to start node: " + entry.id);
Stop(); Stop();
return false; return false;
} }
} }
std::cout << "[Graph] started graph " << name_ << " with " << nodes_.size() << " nodes\n"; LogInfo("[Graph] started graph " + name_ + " with " + std::to_string(nodes_.size()) + " nodes");
return true; return true;
} }

View File

@ -1,12 +1,11 @@
#include "media_server_app.h" #include "media_server_app.h"
#include <iostream>
#include <signal.h>
#include <string>
#include <cstdlib>
#include <atomic> #include <atomic>
#include <thread>
#include <chrono> #include <chrono>
#include <csignal>
#include <cstdlib>
#include <string>
#include <thread>
#include <vector> #include <vector>
#if __has_include(<filesystem>) #if __has_include(<filesystem>)
@ -15,7 +14,9 @@ namespace fs = std::filesystem;
#endif #endif
#if defined(__linux__) #if defined(__linux__)
#include <signal.h>
#include <limits.h> #include <limits.h>
#include <pthread.h>
#include <unistd.h> #include <unistd.h>
#include <sys/inotify.h> #include <sys/inotify.h>
#include <fcntl.h> #include <fcntl.h>
@ -30,7 +31,13 @@ namespace rk3588 {
namespace { namespace {
GraphManager* g_manager = nullptr; #if !defined(__linux__)
volatile std::sig_atomic_t g_stop_signal = 0;
void HandleSignal(int) {
g_stop_signal = 1;
}
#endif
std::string ResolvePluginDir() { std::string ResolvePluginDir() {
if (const char* env = std::getenv("RK_PLUGIN_DIR")) { if (const char* env = std::getenv("RK_PLUGIN_DIR")) {
@ -52,13 +59,6 @@ std::string ResolvePluginDir() {
return std::string("plugins"); return std::string("plugins");
} }
void HandleSignal(int) {
if (g_manager) {
LogWarn("[MediaServerApp] Caught signal, stopping...");
g_manager->RequestStop();
}
}
bool HotReloadEnabled(const SimpleJson& root_cfg) { bool HotReloadEnabled(const SimpleJson& root_cfg) {
if (const SimpleJson* g = root_cfg.Find("global")) { if (const SimpleJson* g = root_cfg.Find("global")) {
if (const SimpleJson* hr = g->Find("hot_reload")) { if (const SimpleJson* hr = g->Find("hot_reload")) {
@ -195,12 +195,57 @@ int MediaServerApp::Start() {
return 1; return 1;
} }
g_manager = &graph_manager_; // Signal handling:
signal(SIGINT, HandleSignal); // - Linux: block SIGINT/SIGTERM and handle via a dedicated thread (async-signal-safe).
signal(SIGTERM, HandleSignal); // - Other: minimal signal handler sets a flag; a thread observes the flag and stops graphs.
#if defined(__linux__)
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGTERM);
pthread_sigmask(SIG_BLOCK, &set, nullptr);
stop_signal_thread_.store(false);
signal_thread_ = std::thread([this, set]() {
while (!stop_signal_thread_.load(std::memory_order_acquire)) {
timespec ts{};
ts.tv_sec = 0;
ts.tv_nsec = 200 * 1000 * 1000; // 200ms
const int sig = sigtimedwait(&set, nullptr, &ts);
if (sig == SIGINT || sig == SIGTERM) {
LogWarn("[MediaServerApp] Caught signal, stopping...");
graph_manager_.RequestStop();
return;
}
}
});
#else
g_stop_signal = 0;
std::signal(SIGINT, HandleSignal);
std::signal(SIGTERM, HandleSignal);
stop_signal_thread_.store(false);
signal_thread_ = std::thread([this]() {
while (!stop_signal_thread_.load(std::memory_order_acquire)) {
if (g_stop_signal) {
LogWarn("[MediaServerApp] Caught signal, stopping...");
graph_manager_.RequestStop();
return;
}
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
});
#endif
auto stop_signal = [&]() {
stop_signal_thread_.store(true, std::memory_order_release);
if (signal_thread_.joinable()) signal_thread_.join();
};
if (!graph_manager_.StartAll()) { if (!graph_manager_.StartAll()) {
LogError("[MediaServerApp] Failed to start graphs"); LogError("[MediaServerApp] Failed to start graphs");
stop_signal();
return 1; return 1;
} }
@ -228,6 +273,8 @@ int MediaServerApp::Start() {
http.Stop(); http.Stop();
stop_signal();
stop_watch.store(true); stop_watch.store(true);
if (watcher.joinable()) watcher.join(); if (watcher.joinable()) watcher.join();
LogInfo("[MediaServerApp] Shutdown complete."); LogInfo("[MediaServerApp] Shutdown complete.");

View File

@ -1,8 +1,9 @@
#include "plugin_loader.h" #include "plugin_loader.h"
#include <iostream>
#include <sstream> #include <sstream>
#include "utils/logger.h"
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
#else #else
@ -110,9 +111,10 @@ bool PluginLoader::LoadPlugin(const std::string& type, PluginHandle& out, std::s
if (!handle) return false; if (!handle) return false;
auto create = reinterpret_cast<CreateNodeFn>(LoadSymbol(handle, "CreateNode")); auto create = reinterpret_cast<CreateNodeFn>(LoadSymbol(handle, "CreateNode"));
auto destroy = reinterpret_cast<DestroyNodeFn>(LoadSymbol(handle, "DestroyNode"));
auto get_type = reinterpret_cast<GetTypeFn>(LoadSymbol(handle, "GetNodeType")); auto get_type = reinterpret_cast<GetTypeFn>(LoadSymbol(handle, "GetNodeType"));
auto get_abi = reinterpret_cast<GetAbiFn>(LoadSymbol(handle, "GetAbiVersion")); auto get_abi = reinterpret_cast<GetAbiFn>(LoadSymbol(handle, "GetAbiVersion"));
if (!create || !get_type || !get_abi) { if (!create || !destroy || !get_type || !get_abi) {
err = "Missing required symbols in plugin: " + path; err = "Missing required symbols in plugin: " + path;
CloseLibraryHandle(handle); CloseLibraryHandle(handle);
return false; return false;
@ -125,30 +127,40 @@ bool PluginLoader::LoadPlugin(const std::string& type, PluginHandle& out, std::s
CloseLibraryHandle(handle); CloseLibraryHandle(handle);
return false; return false;
} }
if (const char* declared = get_type()) {
if (type != declared) {
err = "Plugin type mismatch: requested '" + type + "' but plugin reports '" +
std::string(declared) + "'";
CloseLibraryHandle(handle);
return false;
}
}
out.handle = handle; out.handle = handle;
out.create = create; out.create = create;
out.destroy = destroy;
out.get_type = get_type; out.get_type = get_type;
out.get_abi = get_abi; out.get_abi = get_abi;
return true; return true;
} }
std::unique_ptr<INode> PluginLoader::Create(const std::string& type, std::string& err) { NodePtr PluginLoader::Create(const std::string& type, std::string& err) {
auto it = cache_.find(type); auto it = cache_.find(type);
if (it == cache_.end()) { if (it == cache_.end()) {
PluginHandle handle; PluginHandle handle;
if (!LoadPlugin(type, handle, err)) { if (!LoadPlugin(type, handle, err)) {
std::cerr << "[PluginLoader] Failed to load plugin '" << type << "': " << err LogError("[PluginLoader] Failed to load plugin '" + type + "': " + err);
<< "\n"; return NodePtr(nullptr, NodeDeleter{});
return nullptr;
} }
it = cache_.emplace(type, std::move(handle)).first; it = cache_.emplace(type, std::move(handle)).first;
} }
PluginHandle& handle = it->second; PluginHandle& handle = it->second;
std::unique_ptr<INode> node(handle.create()); NodePtr node(handle.create(), NodeDeleter{handle.destroy});
if (!node) { if (!node) {
err = "CreateNode returned null for type " + type; err = "CreateNode returned null for type " + type;
return nullptr; return NodePtr(nullptr, NodeDeleter{handle.destroy});
} }
return node; return node;
} }

View File

@ -3,11 +3,12 @@
#include <cstdlib> #include <cstdlib>
#include <cerrno> #include <cerrno>
#include <cstring> #include <cstring>
#include <iostream>
#include <mutex> #include <mutex>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include "utils/logger.h"
#if defined(__linux__) #if defined(__linux__)
#include <fcntl.h> #include <fcntl.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
@ -218,23 +219,24 @@ DmaBufferPtr DmaAlloc(size_t alloc_size) {
} }
if (dma_fd < 0) { if (dma_fd < 0) {
std::cerr << "[DmaAlloc] DMA_HEAP_IOCTL_ALLOC failed on all heaps: " << strerror(last_errno) << "\n"; LogError(std::string("[DmaAlloc] DMA_HEAP_IOCTL_ALLOC failed on all heaps: ") + strerror(last_errno));
std::cerr << "[DmaAlloc] tried: "; std::string tried = "[DmaAlloc] tried:";
for (int i = 0; heap_paths[i] != nullptr; ++i) { for (int i = 0; heap_paths[i] != nullptr; ++i) {
std::cerr << heap_paths[i] << " "; tried += " ";
tried += heap_paths[i];
} }
std::cerr << "\n"; LogError(tried);
return nullptr; return nullptr;
} }
if (first_success) { if (first_success) {
std::cout << "[DmaAlloc] using heap: " << used_heap << "\n"; LogInfo(std::string("[DmaAlloc] using heap: ") + (used_heap ? used_heap : "(unknown)"));
first_success = false; first_success = false;
} }
void* ptr = mmap(nullptr, alloc_size, PROT_READ | PROT_WRITE, MAP_SHARED, dma_fd, 0); void* ptr = mmap(nullptr, alloc_size, PROT_READ | PROT_WRITE, MAP_SHARED, dma_fd, 0);
if (ptr == MAP_FAILED) { if (ptr == MAP_FAILED) {
std::cerr << "[DmaAlloc] mmap failed: " << strerror(errno) << "\n"; LogError(std::string("[DmaAlloc] mmap failed: ") + strerror(errno));
close(dma_fd); close(dma_fd);
return nullptr; return nullptr;
} }
@ -246,7 +248,7 @@ DmaBufferPtr DmaAlloc(size_t alloc_size) {
return DmaBufferPtr(buf, [](DmaBuffer* b) { Pool().Put(b); }); return DmaBufferPtr(buf, [](DmaBuffer* b) { Pool().Put(b); });
#else #else
(void)alloc_size; (void)alloc_size;
std::cerr << "[DmaAlloc] not supported on this platform\n"; LogWarn("[DmaAlloc] not supported on this platform");
return nullptr; return nullptr;
#endif #endif
} }

13
tests/CMakeLists.txt Normal file
View File

@ -0,0 +1,13 @@
add_executable(rk3588_tests
test_main.cpp
${CMAKE_SOURCE_DIR}/src/utils/config_expand.cpp
)
target_include_directories(rk3588_tests PRIVATE
${CMAKE_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/third_party
)
target_link_libraries(rk3588_tests PRIVATE project_options Threads::Threads)
add_test(NAME rk3588_tests COMMAND rk3588_tests)

121
tests/test_main.cpp Normal file
View File

@ -0,0 +1,121 @@
#include <cstddef>
#include <iostream>
#include <string>
#include <vector>
#include "utils/config_expand.h"
#include "utils/config_schema.h"
#include "utils/simple_json.h"
#include "utils/spsc_queue.h"
namespace {
int Fail(const char* expr, const char* file, int line) {
std::cerr << "TEST FAIL: " << expr << " at " << file << ":" << line << "\n";
return 1;
}
#define CHECK(x) do { if (!(x)) return Fail(#x, __FILE__, __LINE__); } while (0)
using rk3588::ParseSimpleJson;
using rk3588::SimpleJson;
int TestSimpleJsonBasic() {
SimpleJson v;
std::string err;
CHECK(ParseSimpleJson("{\"a\":1,\"b\":true,\"c\":[\"x\",null]}", v, err));
CHECK(v.IsObject());
CHECK(v.ValueOr<int>("a", 0) == 1);
CHECK(v.ValueOr<bool>("b", false) == true);
const SimpleJson* c = v.Find("c");
CHECK(c && c->IsArray());
CHECK(c->AsArray().size() == 2);
CHECK(c->AsArray()[0].AsString("") == "x");
CHECK(c->AsArray()[1].IsNull());
SimpleJson bad;
std::string err2;
CHECK(!ParseSimpleJson("{\"a\":1} trailing", bad, err2));
CHECK(!err2.empty());
return 0;
}
int TestConfigExpandAndValidate() {
// Root with templates/instances -> graphs
const std::string src = R"JSON(
{
"queue": {"size": 8, "strategy": "drop_oldest"},
"templates": {
"t": {
"nodes": [
{"id": "n1", "type": "input_file", "role": "source", "path": "${path}"},
{"id": "n2", "type": "det_post", "role": "filter"}
],
"edges": [
["n1", "n2", {"queue": {"size": 4}}]
]
}
},
"instances": [
{
"name": "cam1",
"template": "t",
"params": {"path": "/tmp/a.mp4"},
"override": {"nodes": {"n2": {"enable": true}}}
}
]
}
)JSON";
SimpleJson root;
std::string perr;
CHECK(ParseSimpleJson(src, root, perr));
SimpleJson expanded;
std::string eerr;
CHECK(rk3588::ExpandRootConfig(root, expanded, eerr));
std::string verr;
CHECK(rk3588::ValidateExpandedRootConfig(expanded, verr));
const SimpleJson* graphs = expanded.Find("graphs");
CHECK(graphs && graphs->IsArray());
CHECK(graphs->AsArray().size() == 1);
const SimpleJson& g0 = graphs->AsArray()[0];
CHECK(g0.ValueOr<std::string>("name", "") == "cam1");
const SimpleJson* nodes = g0.Find("nodes");
CHECK(nodes && nodes->IsArray());
CHECK(nodes->AsArray().size() == 2);
// ids should be prefixed.
CHECK(nodes->AsArray()[0].ValueOr<std::string>("id", "") == "cam1_n1");
CHECK(nodes->AsArray()[1].ValueOr<std::string>("id", "") == "cam1_n2");
// placeholder replaced.
CHECK(nodes->AsArray()[0].ValueOr<std::string>("path", "") == "/tmp/a.mp4");
return 0;
}
int TestSpscQueueBasic() {
using rk3588::QueueDropStrategy;
rk3588::SpscQueue<int> q(2, QueueDropStrategy::DropOldest);
CHECK(q.Push(1));
CHECK(q.Push(2));
// Full: DropOldest should drop 1 and accept 3.
CHECK(q.Push(3));
int v = 0;
CHECK(q.TryPop(v));
CHECK(v == 2);
CHECK(q.TryPop(v));
CHECK(v == 3);
return 0;
}
} // namespace
int main() {
if (int rc = TestSimpleJsonBasic(); rc != 0) return rc;
if (int rc = TestConfigExpandAndValidate(); rc != 0) return rc;
if (int rc = TestSpscQueueBasic(); rc != 0) return rc;
std::cout << "All tests passed\n";
return 0;
}