voice_audio_multi/tests/unittests/test_rknn_sidecar.cpp
Fabio c36510c21e
Some checks failed
Linux Build / Linux cpu (push) Has been cancelled
Linux Build / Linux cpu-clang (push) Has been cancelled
Linux Build / Linux vulkan (push) Has been cancelled
mac-build / macOS CPU (push) Has been cancelled
Nix Build / Nix (cpu) (push) Has been cancelled
Nix Build / Nix (vulkan) (push) Has been cancelled
windows-build / Windows CPU (push) Has been cancelled
Initial import: audio.cpp with Orange Pi documentation
2026-11-04 19:05:22 +01:00

163 lines
6.6 KiB
C++

// The board side of the RKNN path: everything in src/framework/rknn/engine.cpp that needs
// librknnrt and an engine compiled for the build host's SoC. The plan reader and the status
// formatter live in rknn_engine_test; this one is registered only in an RKNN-enabled build and
// self-skips (77) when no engine was supplied, so CI runs it as `Not Run` and a board runs it
// for real:
//
// cmake --build build --target rknn_sidecar_test
// ctest --test-dir build -R '^rknn_sidecar_test$' --output-on-failure \
// # with -DAUDIOCPP_RKNN_TEST_ENGINE=/path/to/this-board.engine.rknn at configure
//
// The engine is a configure-time path (the repo ships no compiled engines): point it at any
// engine built for this SoC, e.g. the sidecar artefacts a board deployment keeps beside the
// weights.
//
// The fake-engine section runs before that path is consulted: it needs only `librknnrt`, which
// every build that registers this test links, and it is the only board coverage the sidecars'
// own loader failure paths get — the validation chain behind `Engine::load` needs a real
// context of the right shape, so a fabricated image can only reach (and must be reported by)
// the driver refusal itself.
#include "engine/framework/rknn/engine.h"
#if defined(AUDIOCPP_TEST_HAS_MODEL_INFLECT_V2)
#include "engine/community_models/inflect_v2/rknn_decoder.h"
#endif
#if defined(AUDIOCPP_TEST_HAS_MODEL_PIPER_TTS)
#include "engine/community_models/piper_tts/rknn_decoder.h"
#endif
#if defined(AUDIOCPP_TEST_HAS_MODEL_COSYVOICE3)
#include "engine/models/cosyvoice3/rknn_flow.h"
#endif
#if defined(AUDIOCPP_TEST_HAS_MODEL_KOKORO_TTS)
#include "engine/models/kokoro_tts/rknn_generator.h"
#endif
#if defined(AUDIOCPP_TEST_HAS_MODEL_QWEN3_TTS)
#include "engine/models/qwen3_tts/rknn_speech_decoder.h"
#endif
#include "test_assert.h"
#include <filesystem>
#include <fstream>
#include <functional>
#include <string>
#include <vector>
namespace {
using engine::test::require;
// Every sidecar constructor must hand a refused image on with its own label: an operator
// reading `rknn_init failed with code -4` in a server log has to learn from the same line
// which stage's file is broken (the #989 fix this pins).
void require_driver_refusal(
const std::function<void ()> & construct, const std::string & label) {
std::string message;
bool threw = false;
try {
construct();
} catch (const std::runtime_error & e) {
threw = true;
message = e.what();
}
require(threw, label + ": a fabricated engine must throw");
require(
message.find(label + ": rknn_init failed with code -") == 0,
"the refusal carries the label: " + message);
}
void test_fake_engine(const std::filesystem::path & dir) {
const std::string fake = (dir / "fake.rknn").string();
// The flow ladder reads the length it covers from the file name, so its fake image
// carries the producer's name.
const std::string fake_flow = (dir / "flow_estimator_seq900.rknn").string();
for (const std::string & path : {fake, fake_flow}) {
std::ofstream out(path, std::ios::binary | std::ios::trunc);
out << "RKNN" << "not a compiled image";
}
#if defined(AUDIOCPP_TEST_HAS_MODEL_PIPER_TTS)
require_driver_refusal(
[&] {
engine::models::piper_tts::PiperTtsRknnDecoder(
fake, 192, 256);
},
"Piper TTS decoder RKNN");
#endif
#if defined(AUDIOCPP_TEST_HAS_MODEL_INFLECT_V2)
require_driver_refusal(
[&] {
engine::models::inflect_v2::InflectV2RknnDecoder(
fake, 32, 512);
},
"Inflect v2 decoder RKNN");
#endif
#if defined(AUDIOCPP_TEST_HAS_MODEL_KOKORO_TTS)
require_driver_refusal(
[&] {
engine::models::kokoro_tts::KokoroTtsRknnGenerator(fake, 76, 80, 100, 0);
},
"Kokoro generator RKNN");
#endif
#if defined(AUDIOCPP_TEST_HAS_MODEL_QWEN3_TTS)
require_driver_refusal(
[&] {
engine::models::qwen3_tts::RknnSpeechDecoder(fake, "", 16, 64, 1024, 10000.0F);
},
"Qwen3 speech decoder RKNN");
#endif
#if defined(AUDIOCPP_TEST_HAS_MODEL_COSYVOICE3)
// The flow ladder loads on demand, so its refusal surfaces on the first step, not on the
// constructor -- and the constructor has a refusal of its own before the driver is ever
// asked: a ladder named by nothing.
require_driver_refusal(
[&] {
engine::models::cosyvoice3::CosyVoice3RknnFlow flow(
{fake_flow}, 80);
std::vector<float> zeros(static_cast<size_t>(2 * 80), 0.0F);
(void)flow.run(
zeros.data(), zeros.data(), zeros.data(), zeros.data(), 0.5F, 1);
},
"CosyVoice3 flow estimator RKNN");
#endif
}
} // namespace
int main(int argc, char ** argv) {
// The fake-engine section needs only the driver this build links, so it runs before the
// real-engine guard decides the rest of the file applies to this host.
const auto dir = std::filesystem::temp_directory_path() / "audiocpp-rknn-sidecar-test";
std::filesystem::create_directories(dir);
test_fake_engine(dir);
std::filesystem::remove_all(dir);
const std::filesystem::path engine_path = argc > 1 ? argv[1] : std::string();
std::error_code ec;
if (engine_path.empty() || !std::filesystem::exists(engine_path, ec)) {
// No engine for this host: the driver path this test covers cannot run here.
return 77;
}
// Residency pricing: the model-info-only init and RKNN_QUERY_MEM_SIZE must say a real
// number for a real engine -- 0 is the "unknown" answer, and an engine the board's own
// driver reports as 0 MiB resident would make every ladder accounting meaningless.
const auto resident = engine::rknn::engine_memory_mib(engine_path.string(), "Sidecar test RKNN");
engine::test::require(
resident > 0,
"the driver prices its own engine, got " + std::to_string(resident) + " MiB");
// The same init caches the driver's self-report, which every later failure quotes.
const auto report = engine::rknn::driver_report();
engine::test::require(
report.find("api ") != std::string::npos && report.find("driver ") != std::string::npos,
"the driver report carries both versions: " + report);
// And the full init path builds a real context for the same image.
engine::rknn::Engine npu;
npu.load(engine_path.string(), "Sidecar test RKNN");
engine::test::require(npu.loaded(), "the driver built a context for its engine");
engine::test::require(
npu.input_count() > 0 && npu.output_count() > 0,
"the context reports its graph's inputs and outputs");
npu.release();
return 0;
}