From cabca69c4de5feb19c9aa3f1b09c13e8d33a0852 Mon Sep 17 00:00:00 2001 From: AngePierre Date: Thu, 10 Sep 2026 17:26:06 +0200 Subject: [PATCH] =?UTF-8?q?wrapper:=20build=20libk4arecord=5Fv2.so=20?= =?UTF-8?q?=E2=80=94=20the=20library=20the=20recorder=20actually=20dlopens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The recorder does not load the stock libk4a/libk4arecord. It dlopens a single custom libk4arecord_v2.so: the wrapper's MKV-writing sources linked to a pure-data shim (k4a_shim.cpp) instead of libk4a, so recording has no OrbbecSDK dependency. That target is FTF's own (wrapper/recordv2/), absent from the stock wrapper the Orin carried — so ship it and inject+build it, the same pattern as the recorder in the SDK examples tree. Idempotent; only added when missing. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/build_k4a_wrapper.sh | 77 +++++-- wrapper/recordv2/CMakeLists.txt | 43 ++++ wrapper/recordv2/k4a_shim.cpp | 385 ++++++++++++++++++++++++++++++++ 3 files changed, 482 insertions(+), 23 deletions(-) create mode 100644 wrapper/recordv2/CMakeLists.txt create mode 100644 wrapper/recordv2/k4a_shim.cpp diff --git a/scripts/build_k4a_wrapper.sh b/scripts/build_k4a_wrapper.sh index ad2aaaa..a551574 100755 --- a/scripts/build_k4a_wrapper.sh +++ b/scripts/build_k4a_wrapper.sh @@ -1,24 +1,29 @@ #!/usr/bin/env bash -# Makes sure the Orbbec K4A wrapper is built for THIS platform, and prints where -# it is as K4A_WRAPPER_DIR=... for build_sdk.sh (the recorder example needs its -# headers) and for the recorder's runtime rpath (its libk4a.so/libk4arecord.so). +# Makes sure the Orbbec K4A wrapper is built for THIS platform, INCLUDING FTF's +# libk4arecord_v2.so, and prints K4A_WRAPPER_DIR=... for build_sdk.sh (the recorder +# example needs the headers) and for the recorder's runtime rpath. # -# This is the libk4a provider — Orbbec's own, NOT Microsoft's Azure Kinect SDK. -# It bundles its own OrbbecSDK internally, so it builds standalone. The recorder -# dlopens the libk4a it produces (RTLD_DEEPBIND) rather than link-time linking it, -# which is why only its build/lib path matters at runtime, not link time. +# This is the libk4a provider — Orbbec's own, NOT Microsoft's Azure Kinect SDK. It +# bundles its own OrbbecSDK, so it builds standalone. # -# Reuses an existing build (leaving a working node untouched) or clones + builds. +# The recorder does not dlopen the stock libk4a/libk4arecord. It dlopens a single +# custom library, libk4arecord_v2.so: the wrapper's own MKV-writing sources linked +# to a pure-data shim (k4a_shim.cpp) instead of libk4a — K4A-compatible recording +# with no OrbbecSDK dependency. That target is FTF's (wrapper/recordv2/ in this +# repo), not part of the stock wrapper, so it is injected and built here. +# +# Reuses an existing build; only adds the v2 target if it is missing. set -euo pipefail here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +repo=$(cd "$here/.." && pwd) eval "$("$here/detect_platform.sh")" REPO_URL="${OB_K4A_REPO:-https://github.com/orbbec/OrbbecSDK-K4A-Wrapper}" -# The tag the Nano runs. A caller can override to one known to build on a newer -# JetPack. REF="${OB_K4A_REF:-v1.10.4}" +# A usable wrapper base: headers and the stock libk4a. The v2 target is ensured +# separately, so a wrapper built before this repo existed still qualifies. find_wrapper() { local d="$1" [[ -f "$d/include/k4a/k4a.h" ]] || return 1 @@ -26,42 +31,68 @@ find_wrapper() { echo "$d" } +# Injects FTF's recordv2 sources and builds libk4arecord_v2.so if it is not there. +# Idempotent: the add_subdirectory line and the copy are guarded. +ensure_recordv2() { + local d="$1" + [[ -f "$d/build/lib/libk4arecord_v2.so" ]] && return 0 + + echo ">> libk4arecord_v2.so absent — injection de recordv2 et compilation…" >&2 + local dst="$d/src/orbbec/recordv2" + mkdir -p "$dst" + cp "$repo/wrapper/recordv2/CMakeLists.txt" "$dst/" + cp "$repo/wrapper/recordv2/k4a_shim.cpp" "$dst/" + + local ob_cmake="$d/src/orbbec/CMakeLists.txt" + if [[ -f "$ob_cmake" ]] && ! grep -q "add_subdirectory(recordv2)" "$ob_cmake"; then + echo "add_subdirectory(recordv2)" >> "$ob_cmake" + fi + + ( cd "$d/build" + cmake .. -DCMAKE_BUILD_TYPE=Release >&2 + make -j"$(nproc)" k4arecord_v2 >&2 ) + + [[ -f "$d/build/lib/libk4arecord_v2.so" ]] +} + +# 1 & 2: reuse a wrapper base, then make sure the v2 target is present. for candidate in "${K4A_WRAPPER_DIR:-}" \ "$HOME/OrbbecSDK-K4A-Wrapper" \ "$HOME/Downloads/OrbbecSDK-K4A-Wrapper"; do [[ -z "$candidate" ]] && continue if find_wrapper "$candidate" >/dev/null 2>&1; then - echo ">> Wrapper K4A deja bati, reutilise: $candidate" >&2 - echo "K4A_WRAPPER_DIR=$candidate" - exit 0 + echo ">> Wrapper K4A trouve: $candidate" >&2 + if ensure_recordv2 "$candidate"; then + echo "K4A_WRAPPER_DIR=$candidate" + exit 0 + fi + echo "!! libk4arecord_v2.so n'a pas pu etre bati dans $candidate" >&2 + exit 1 fi done +# 3: clone + build the wrapper base, then the v2 target. dir="${K4A_WRAPPER_DIR:-$HOME/OrbbecSDK-K4A-Wrapper}" echo ">> Wrapper K4A absent — clone et compilation (JetPack ${JETPACK}, ${SOC})" >&2 echo ">> ${REPO_URL} @ ${REF} -> ${dir}" >&2 if [[ ! -d "$dir/.git" ]]; then - # --recursive: the wrapper carries its OrbbecSDK and depth engine as submodules. git clone --recursive --depth 1 --branch "$REF" "$REPO_URL" "$dir" >&2 fi mkdir -p "$dir/build" -( - cd "$dir/build" - cmake .. -DCMAKE_BUILD_TYPE=Release >&2 - make -j"$(nproc)" k4a k4arecord >&2 -) +( cd "$dir/build" + cmake .. -DCMAKE_BUILD_TYPE=Release >&2 + make -j"$(nproc)" k4a k4arecord >&2 ) -if ! find_wrapper "$dir" >/dev/null 2>&1; then +if ! find_wrapper "$dir" >/dev/null 2>&1 || ! ensure_recordv2 "$dir"; then cat >&2 <> Wrapper K4A bati: $dir" >&2 +echo ">> Wrapper K4A + libk4arecord_v2.so batis: $dir" >&2 echo "K4A_WRAPPER_DIR=$dir" diff --git a/wrapper/recordv2/CMakeLists.txt b/wrapper/recordv2/CMakeLists.txt new file mode 100644 index 0000000..8794233 --- /dev/null +++ b/wrapper/recordv2/CMakeLists.txt @@ -0,0 +1,43 @@ +# Copyright (c) French Touch Factory. All Rights Reserved. +# +# libk4arecord_v2 : ecriture MKV compatible K4A sans aucune dependance OrbbecSDK. +# +# Reprend telles quelles les sources d'ecriture du wrapper (record.cpp + +# matroska_write.cpp + iocallback.cpp) mais les lie a k4a_shim.cpp au lieu de +# libk4a.so, qui implemente k4a_image_t / k4a_capture_t au-dessus d'OrbbecSDK +# v1.10.18. playback.cpp est volontairement exclu : c'est lui qui appelle +# ob_create_context(), et le recorder n'a pas besoin de relecture. + +set(RECORD_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../record) + +add_library(k4arecord_v2 SHARED + ${RECORD_SRC_DIR}/sdk/record.cpp + ${RECORD_SRC_DIR}/sdk/dll_main.c + ${RECORD_SRC_DIR}/internal/iocallback.cpp + ${RECORD_SRC_DIR}/internal/matroska_write.cpp + k4a_shim.cpp + ) + +# k4arecord_EXPORTS : exporte les k4a_record_* (macro de k4arecord_export.h). +# k4a_EXPORTS : idem pour les k4a_image_* / k4a_capture_* du shim, dont la +# visibilite est portee par K4A_EXPORT dans k4a.h. +target_compile_definitions(k4arecord_v2 PRIVATE k4arecord_EXPORTS k4a_EXPORTS) + +target_include_directories(k4arecord_v2 PRIVATE + ${K4A_INCLUDE_DIR} + ${K4A_PRIV_INCLUDE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../include + # k4arecord/k4arecord_export.h, genere par la cible k4arecord + ${CMAKE_BINARY_DIR}/src/orbbec/record/sdk/include + ) + +# Aucun k4a::k4a, aucun OrbbecSDK : c'est tout l'objet de cette cible. +target_link_libraries(k4arecord_v2 PRIVATE + k4ainternal::logging + ebml::ebml + matroska::matroska + ) + +set_target_properties(k4arecord_v2 PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib + ) diff --git a/wrapper/recordv2/k4a_shim.cpp b/wrapper/recordv2/k4a_shim.cpp new file mode 100644 index 0000000..05bb47b --- /dev/null +++ b/wrapper/recordv2/k4a_shim.cpp @@ -0,0 +1,385 @@ +// Copyright (c) French Touch Factory. All Rights Reserved. +// +// k4a_shim — implementation des objets k4a_image_t / k4a_capture_t SANS OrbbecSDK. +// +// Pourquoi : libk4a.so implemente ces deux types au-dessus d'OrbbecSDK v1.10.18 +// (k4a_capture_create -> ob_create_frameset, k4a_image_create_from_buffer -> +// ob_create_frame_from_buffer). Le recorder capture deja via OrbbecSDK v2, et le +// chemin d'ecriture MKV ne fait que relire buffer/size/format/timestamp de ces +// objets : l'aller-retour par la v1 n'apporte rien et impose de charger un second +// SDK dans le process (d'ou le hack RTLD_DEEPBIND, cf. le recorder). +// +// Ici les deux types sont de simples conteneurs de donnees comptes par reference. +// Semantique reproduite a l'identique de l'API Azure Kinect : +// - create* -> refcount = 1, appartient a l'appelant +// - capture_set_* -> la capture prend SA propre reference +// - capture_get_* -> rend une NOUVELLE reference (l'appelant doit release) +// - release -> decremente ; a 0, appelle le buffer_release_cb fourni +// +// Les quatre fonctions k4a_device_* / k4a_calibration_get_from_raw ne sont +// presentes que pour satisfaire l'editeur de liens : record.cpp ne les appelle +// que si un k4a_device_t est passe a k4a_record_create(), et le recorder passe +// nullptr (il attache lui-meme le JSON de calibration lu via la v2). + +#include + +#include +#include +#include +#include + +namespace { + +struct ShimImage +{ + std::atomic refcount; + k4a_image_format_t format; + int width_pixels; + int height_pixels; + int stride_bytes; + uint8_t *buffer; + size_t size; + k4a_memory_destroy_cb_t *release_cb; + void *release_cb_context; + uint64_t device_timestamp_usec; + uint64_t system_timestamp_nsec; +}; + +struct ShimCapture +{ + std::atomic refcount; + ShimImage *color; + ShimImage *depth; + ShimImage *ir; + float temperature_c; +}; + +inline ShimImage *as_image(k4a_image_t h) +{ + return reinterpret_cast(h); +} + +inline ShimCapture *as_capture(k4a_capture_t h) +{ + return reinterpret_cast(h); +} + +inline void image_add_ref(ShimImage *i) +{ + if (i != nullptr) + { + i->refcount.fetch_add(1, std::memory_order_relaxed); + } +} + +// Rend la reference. A zero : rend le buffer a son proprietaire via le callback +// fourni a la creation, puis detruit le conteneur. +void image_dec_ref(ShimImage *i) +{ + if (i == nullptr) + { + return; + } + if (i->refcount.fetch_sub(1, std::memory_order_acq_rel) == 1) + { + if (i->release_cb != nullptr) + { + i->release_cb(i->buffer, i->release_cb_context); + } + delete i; + } +} + +// Remplace un des trois emplacements d'une capture en respectant les references : +// on prend la nouvelle avant de rendre l'ancienne, au cas ou ce soit la meme. +void capture_set_slot(ShimImage **slot, k4a_image_t image_handle) +{ + ShimImage *incoming = as_image(image_handle); + image_add_ref(incoming); + ShimImage *previous = *slot; + *slot = incoming; + image_dec_ref(previous); +} + +k4a_image_t capture_get_slot(ShimImage *slot) +{ + image_add_ref(slot); + return reinterpret_cast(slot); +} + +} // namespace + +extern "C" { + +// ---- images ---------------------------------------------------------------- + +k4a_result_t k4a_image_create_from_buffer(k4a_image_format_t format, + int width_pixels, + int height_pixels, + int stride_bytes, + uint8_t *buffer, + size_t buffer_size, + k4a_memory_destroy_cb_t *buffer_release_cb, + void *buffer_release_cb_context, + k4a_image_t *image_handle) +{ + if (image_handle == nullptr || buffer == nullptr) + { + return K4A_RESULT_FAILED; + } + + ShimImage *i = new (std::nothrow) ShimImage(); + if (i == nullptr) + { + return K4A_RESULT_FAILED; + } + + i->refcount.store(1, std::memory_order_relaxed); + i->format = format; + i->width_pixels = width_pixels; + i->height_pixels = height_pixels; + i->stride_bytes = stride_bytes; + i->buffer = buffer; + i->size = buffer_size; + i->release_cb = buffer_release_cb; + i->release_cb_context = buffer_release_cb_context; + i->device_timestamp_usec = 0; + i->system_timestamp_nsec = 0; + + *image_handle = reinterpret_cast(i); + return K4A_RESULT_SUCCEEDED; +} + +uint8_t *k4a_image_get_buffer(k4a_image_t image_handle) +{ + ShimImage *i = as_image(image_handle); + return (i != nullptr) ? i->buffer : nullptr; +} + +size_t k4a_image_get_size(k4a_image_t image_handle) +{ + ShimImage *i = as_image(image_handle); + return (i != nullptr) ? i->size : 0; +} + +k4a_image_format_t k4a_image_get_format(k4a_image_t image_handle) +{ + ShimImage *i = as_image(image_handle); + return (i != nullptr) ? i->format : K4A_IMAGE_FORMAT_CUSTOM; +} + +int k4a_image_get_width_pixels(k4a_image_t image_handle) +{ + ShimImage *i = as_image(image_handle); + return (i != nullptr) ? i->width_pixels : 0; +} + +int k4a_image_get_height_pixels(k4a_image_t image_handle) +{ + ShimImage *i = as_image(image_handle); + return (i != nullptr) ? i->height_pixels : 0; +} + +int k4a_image_get_stride_bytes(k4a_image_t image_handle) +{ + ShimImage *i = as_image(image_handle); + return (i != nullptr) ? i->stride_bytes : 0; +} + +uint64_t k4a_image_get_device_timestamp_usec(k4a_image_t image_handle) +{ + ShimImage *i = as_image(image_handle); + return (i != nullptr) ? i->device_timestamp_usec : 0; +} + +void k4a_image_set_device_timestamp_usec(k4a_image_t image_handle, uint64_t timestamp_usec) +{ + ShimImage *i = as_image(image_handle); + if (i != nullptr) + { + i->device_timestamp_usec = timestamp_usec; + } +} + +uint64_t k4a_image_get_system_timestamp_nsec(k4a_image_t image_handle) +{ + ShimImage *i = as_image(image_handle); + return (i != nullptr) ? i->system_timestamp_nsec : 0; +} + +void k4a_image_set_system_timestamp_nsec(k4a_image_t image_handle, uint64_t timestamp_nsec) +{ + ShimImage *i = as_image(image_handle); + if (i != nullptr) + { + i->system_timestamp_nsec = timestamp_nsec; + } +} + +void k4a_image_reference(k4a_image_t image_handle) +{ + image_add_ref(as_image(image_handle)); +} + +void k4a_image_release(k4a_image_t image_handle) +{ + image_dec_ref(as_image(image_handle)); +} + +// ---- captures -------------------------------------------------------------- + +k4a_result_t k4a_capture_create(k4a_capture_t *capture_handle) +{ + if (capture_handle == nullptr) + { + return K4A_RESULT_FAILED; + } + + ShimCapture *c = new (std::nothrow) ShimCapture(); + if (c == nullptr) + { + return K4A_RESULT_FAILED; + } + + c->refcount.store(1, std::memory_order_relaxed); + c->color = nullptr; + c->depth = nullptr; + c->ir = nullptr; + c->temperature_c = 0.0f; + + *capture_handle = reinterpret_cast(c); + return K4A_RESULT_SUCCEEDED; +} + +void k4a_capture_reference(k4a_capture_t capture_handle) +{ + ShimCapture *c = as_capture(capture_handle); + if (c != nullptr) + { + c->refcount.fetch_add(1, std::memory_order_relaxed); + } +} + +void k4a_capture_release(k4a_capture_t capture_handle) +{ + ShimCapture *c = as_capture(capture_handle); + if (c == nullptr) + { + return; + } + if (c->refcount.fetch_sub(1, std::memory_order_acq_rel) == 1) + { + image_dec_ref(c->color); + image_dec_ref(c->depth); + image_dec_ref(c->ir); + delete c; + } +} + +void k4a_capture_set_color_image(k4a_capture_t capture_handle, k4a_image_t image_handle) +{ + ShimCapture *c = as_capture(capture_handle); + if (c != nullptr) + { + capture_set_slot(&c->color, image_handle); + } +} + +void k4a_capture_set_depth_image(k4a_capture_t capture_handle, k4a_image_t image_handle) +{ + ShimCapture *c = as_capture(capture_handle); + if (c != nullptr) + { + capture_set_slot(&c->depth, image_handle); + } +} + +void k4a_capture_set_ir_image(k4a_capture_t capture_handle, k4a_image_t image_handle) +{ + ShimCapture *c = as_capture(capture_handle); + if (c != nullptr) + { + capture_set_slot(&c->ir, image_handle); + } +} + +k4a_image_t k4a_capture_get_color_image(k4a_capture_t capture_handle) +{ + ShimCapture *c = as_capture(capture_handle); + return (c != nullptr) ? capture_get_slot(c->color) : nullptr; +} + +k4a_image_t k4a_capture_get_depth_image(k4a_capture_t capture_handle) +{ + ShimCapture *c = as_capture(capture_handle); + return (c != nullptr) ? capture_get_slot(c->depth) : nullptr; +} + +k4a_image_t k4a_capture_get_ir_image(k4a_capture_t capture_handle) +{ + ShimCapture *c = as_capture(capture_handle); + return (c != nullptr) ? capture_get_slot(c->ir) : nullptr; +} + +void k4a_capture_set_temperature_c(k4a_capture_t capture_handle, float temperature_c) +{ + ShimCapture *c = as_capture(capture_handle); + if (c != nullptr) + { + c->temperature_c = temperature_c; + } +} + +float k4a_capture_get_temperature_c(k4a_capture_t capture_handle) +{ + ShimCapture *c = as_capture(capture_handle); + return (c != nullptr) ? c->temperature_c : 0.0f; +} + +// ---- stubs de lien --------------------------------------------------------- +// record.cpp ne touche a un k4a_device_t que si k4a_record_create() en recoit un. +// Le recorder passe nullptr, donc ces chemins sont morts. On echoue proprement +// plutot que de mentir sur des donnees qu'on ne peut pas fournir. + +k4a_buffer_result_t k4a_device_get_raw_calibration(k4a_device_t device_handle, + uint8_t *data, + size_t *data_size) +{ + (void)device_handle; + (void)data; + (void)data_size; + return K4A_BUFFER_RESULT_FAILED; +} + +k4a_buffer_result_t k4a_device_get_serialnum(k4a_device_t device_handle, + char *serial_number, + size_t *serial_number_size) +{ + (void)device_handle; + (void)serial_number; + (void)serial_number_size; + return K4A_BUFFER_RESULT_FAILED; +} + +k4a_result_t k4a_device_get_version(k4a_device_t device_handle, k4a_hardware_version_t *version) +{ + (void)device_handle; + (void)version; + return K4A_RESULT_FAILED; +} + +k4a_result_t k4a_calibration_get_from_raw(char *raw_calibration, + size_t raw_calibration_size, + const k4a_depth_mode_t depth_mode, + const k4a_color_resolution_t color_resolution, + k4a_calibration_t *calibration) +{ + (void)raw_calibration; + (void)raw_calibration_size; + (void)depth_mode; + (void)color_resolution; + (void)calibration; + return K4A_RESULT_FAILED; +} + +} // extern "C"