diff --git a/build-t23.sh b/build-t23.sh new file mode 100755 index 0000000..f825b81 --- /dev/null +++ b/build-t23.sh @@ -0,0 +1,162 @@ +#!/bin/sh +set -eu + +project_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +output_dir=${T23_OUTPUT_DIR:-"$project_dir/build/t23"} +firmware_dir=${THINGINO_DIR:-} + +if [ -z "$firmware_dir" ]; then + for candidate in \ + "$project_dir/../thingino-firmware-opensensor" \ + "$project_dir/../thingino-firmware" \ + "$project_dir/../../thingino-firmware-opensensor" \ + "$project_dir/../../thingino-firmware" + do + if [ -d "$candidate/output/master" ]; then + firmware_dir=$(CDPATH= cd -- "$candidate" && pwd) + break + fi + done +fi + +: "${firmware_dir:?set THINGINO_DIR to a Thingino firmware checkout}" +target_name=${T23_TARGET:-cinnado_d1_t23n_sc2336_atbm6012bx-3.10.14-uclibc} +target_dir="$firmware_dir/output/master/$target_name" +toolchain_prefix=${TOOLCHAIN_PREFIX:-"$target_dir/host/bin/mipsel-linux"} +compiler="${toolchain_prefix}-gcc" +stripper="${toolchain_prefix}-strip" + +test -x "$compiler" +mkdir -p "$output_dir" + +base_flags="-std=gnu99 -O2 -mabi=32 -march=mips32r2 -mabicalls" +base_flags="$base_flags -fPIC -G0 -fno-stack-protector -DPLATFORM_T23" +repo_includes="-I$project_dir/include -I$project_dir/src" + +compile() +{ + object=$1 + source=$2 + shift 2 + "$compiler" $base_flags $repo_includes -Wall -Wextra "$@" \ + -c "$project_dir/$source" -o "$output_dir/$object.o" +} + +# The T23 public ABI feeds the recovered AVPU backend through the T-series +# stock-driver seam. Audio remains in the OEM libimp used by RAD. +compile openimp_p0 src/t40/openimp_p0.c -Werror +compile openimp_profile src/openimp_profile.c -Werror +compile openimp_tuning src/openimp_tuning.c -Werror +compile openimp_p2_encoder src/t40/openimp_p2_encoder.c -Werror +compile openimp_avc src/t40/openimp_avc.c -Werror +compile t40_ep1 src/t40/t40_ep1.c -Werror +compile enc_hw_scaling src/alcodec/EncHwScalingList.c +compile codec src/t40/codec-t40.c -Wno-stringop-overflow +compile al_avpu src/al_avpu.c -Wno-stringop-overflow +compile device_pool src/device_pool.c -Wno-stringop-overflow +compile fifo src/fifo.c -Wno-stringop-overflow +compile hw_encoder src/hw_encoder.c -Wno-stringop-overflow + +compile time64_shim src/time64_shim.c +compile kernel_interface src/kernel_interface.c +compile dma_alloc src/dma_alloc.c +compile core_device src/core/device.c +compile core_group src/core/group.c +compile core_module src/core/module.c +compile framesource src/framesource/framesource_tseries.c +compile isp src/isp/isp_tseries.c +compile t23_compat src/t31/openimp_t31_compat.c +compile t23_state src/t31/openimp_t31_state.c -Werror +compile t23_services src/t31/openimp_t31_services.c -Werror +compile t23_platform_services src/t23/openimp_t23_services.c -Werror +compile t23_helix_bridge src/t23/openimp_t23_helix_bridge.c -Werror +compile t23_persist src/t23/openimp_t23_persist.c -Werror + +"$compiler" -shared -nostartfiles \ + -Wl,-soname,libimp.so \ + -Wl,--version-script="$project_dir/src/t40/libimp.map" \ + -o "$output_dir/libimp.so" \ + "$output_dir/openimp_p0.o" \ + "$output_dir/openimp_profile.o" \ + "$output_dir/openimp_tuning.o" \ + "$output_dir/openimp_p2_encoder.o" \ + "$output_dir/openimp_avc.o" \ + "$output_dir/t40_ep1.o" \ + "$output_dir/enc_hw_scaling.o" \ + "$output_dir/codec.o" \ + "$output_dir/al_avpu.o" \ + "$output_dir/device_pool.o" \ + "$output_dir/fifo.o" \ + "$output_dir/hw_encoder.o" \ + "$output_dir/time64_shim.o" \ + "$output_dir/kernel_interface.o" \ + "$output_dir/dma_alloc.o" \ + "$output_dir/core_device.o" \ + "$output_dir/core_group.o" \ + "$output_dir/core_module.o" \ + "$output_dir/framesource.o" \ + "$output_dir/isp.o" \ + "$output_dir/t23_compat.o" \ + "$output_dir/t23_state.o" \ + "$output_dir/t23_services.o" \ + "$output_dir/t23_platform_services.o" \ + "$output_dir/t23_helix_bridge.o" \ + "$output_dir/t23_persist.o" \ + -ldl -lpthread -lrt + +"$stripper" --strip-unneeded "$output_dir/libimp.so" + +if readelf -d "$output_dir/libimp.so" | + grep -q 'Shared library: \[libimp.so' +then + echo "T23 build has an OEM libimp dependency" >&2 + exit 1 +fi + +if readelf --dyn-syms --wide "$output_dir/libimp.so" | + awk '$7 != "UND" && $8 ~ /^IMP_(AI|AO|AENC|ADEC|DMIC)_/ {found=1} END {exit !found}' +then + echo "T23 build exports audio APIs" >&2 + exit 1 +fi + +rvd=${T23_RVD:-"$target_dir/target/usr/bin/rvd"} +if [ -f "$rvd" ]; then + readelf --dyn-syms --wide "$rvd" | + awk '$7 == "UND" && $8 ~ /^IMP_/ { + sub(/@.*/, "", $8) + print $8 + }' | sort -u >"$output_dir/rvd-imp-imports.txt" + readelf --dyn-syms --wide "$output_dir/libimp.so" | + awk '$7 != "UND" && $5 == "GLOBAL" && $8 ~ /^IMP_/ {print $8}' | + sort -u >"$output_dir/libimp-exports.txt" + comm -23 "$output_dir/rvd-imp-imports.txt" \ + "$output_dir/libimp-exports.txt" >"$output_dir/rvd-imp-missing.txt" + echo "RVD IMP coverage: $(comm -12 "$output_dir/rvd-imp-imports.txt" "$output_dir/libimp-exports.txt" | wc -l)/$(wc -l <"$output_dir/rvd-imp-imports.txt")" + if [ -s "$output_dir/rvd-imp-missing.txt" ]; then + echo "T23 build is missing RVD IMP imports:" >&2 + cat "$output_dir/rvd-imp-missing.txt" >&2 + exit 1 + fi +fi + +sha256sum "$output_dir/libimp.so" +readelf -d "$output_dir/libimp.so" | grep -E 'SONAME|NEEDED' + +# Keep the proprietary T23 Helix implementation in a clean process. RVD loads +# OpenIMP, while this small worker resolves libimp from /usr/lib and exchanges +# raw frames through tmpfs-backed shared memory. +"$compiler" $base_flags $repo_includes -Wall -Wextra -Werror \ + "$project_dir/src/t23/openimp_t23_helix_worker.c" \ + -L"$target_dir/target/usr/lib" \ + -Wl,-rpath,/usr/lib -Wl,-rpath-link,"$target_dir/target/usr/lib" \ + -limp -lalog -lpthread -ldl \ + -o "$output_dir/openimp-t23-helixd" +"$stripper" --strip-unneeded "$output_dir/openimp-t23-helixd" +if ! readelf -d "$output_dir/openimp-t23-helixd" | + grep -q 'Shared library: \[libimp.so\]' +then + echo "T23 Helix worker is not linked to the OEM libimp ABI" >&2 + exit 1 +fi +ls -l "$output_dir/openimp-t23-helixd" diff --git a/include/imp/imp_common.h b/include/imp/imp_common.h index d829ad6..bb99c3c 100644 --- a/include/imp/imp_common.h +++ b/include/imp/imp_common.h @@ -12,6 +12,7 @@ extern "C" { #endif #include +#include /** * Return codes @@ -84,11 +85,48 @@ typedef struct { char aVersion[64]; /**< Version string */ } IMPVersion; -/** Frame info structure (width/height) */ +/** Frame info structure. T23 1.3.0 exposes the complete DMA descriptor. */ +#if defined(PLATFORM_T23) +typedef struct { + int index; + int pool_idx; + uint32_t width; + uint32_t height; + uint32_t pixfmt; + uint32_t size; + uint32_t phyAddr; + uint32_t virAddr; + uint32_t direct_phyAddr; + int64_t timeStamp; + int64_t timeStamp_ivdc; + uint32_t priv[0]; +} IMPFrameInfo; + +typedef struct { + uint64_t ts; + uint64_t minus; + uint64_t plus; +} IMPFrameTimestamp; + +_Static_assert(offsetof(IMPFrameInfo, direct_phyAddr) == 0x20, + "T23 IMPFrameInfo.direct_phyAddr ABI mismatch"); +_Static_assert(offsetof(IMPFrameInfo, timeStamp) == 0x28, + "T23 IMPFrameInfo.timeStamp ABI mismatch"); +_Static_assert(sizeof(IMPFrameInfo) == 0x38, + "T23 IMPFrameInfo ABI mismatch"); +#else typedef struct { int width; int height; } IMPFrameInfo; +#endif + +/** T23 encoder payload identifiers (SDK 1.3.0). */ +typedef enum { + PT_JPEG = 0, + PT_H264 = 1, + PT_H265 = 2, +} IMPPayloadType; /** * Rectangle structure @@ -184,4 +222,3 @@ typedef int IMPRgnHandle; #endif #endif /* __IMP_COMMON_H__ */ - diff --git a/include/imp/imp_encoder.h b/include/imp/imp_encoder.h index f1d7ea5..d94b656 100644 --- a/include/imp/imp_encoder.h +++ b/include/imp/imp_encoder.h @@ -50,10 +50,23 @@ typedef enum { IMP_ENC_RC_MODE_FIXQP = 0, /**< Fixed QP */ IMP_ENC_RC_MODE_CBR = 1, /**< Constant bitrate */ IMP_ENC_RC_MODE_VBR = 2, /**< Variable bitrate */ +#if defined(PLATFORM_T23) + IMP_ENC_RC_MODE_SMART = 3, /**< T23 Smart rate control */ + IMP_ENC_RC_MODE_INV = 4, /**< T23 invalid mode sentinel */ +#else IMP_ENC_RC_MODE_CAPPED_VBR = 4, /**< Capped VBR */ IMP_ENC_RC_MODE_CAPPED_QUALITY = 8 /**< Capped quality */ +#endif } IMPEncoderRcMode; +#if defined(PLATFORM_T23) +#define ENC_RC_MODE_FIXQP IMP_ENC_RC_MODE_FIXQP +#define ENC_RC_MODE_CBR IMP_ENC_RC_MODE_CBR +#define ENC_RC_MODE_VBR IMP_ENC_RC_MODE_VBR +#define ENC_RC_MODE_SMART IMP_ENC_RC_MODE_SMART +#define ENC_RC_MODE_INV IMP_ENC_RC_MODE_INV +#endif + /** * GOP mode */ @@ -253,6 +266,198 @@ _Static_assert(offsetof(IMPEncoderChnAttr, rcAttr) == 0x34, "T41 IMPEncoderChnAt _Static_assert(offsetof(IMPEncoderChnAttr, gopAttr) == 0x60, "T41 IMPEncoderChnAttr.gopAttr ABI mismatch"); _Static_assert(offsetof(IMPEncoderChnAttr, bEnableIvdc) == 0x78, "T41 IMPEncoderChnAttr.bEnableIvdc ABI mismatch"); _Static_assert(sizeof(IMPEncoderChnAttr) == 0x7c, "T41 IMPEncoderChnAttr ABI mismatch"); +#elif defined(PLATFORM_T23) +typedef struct { + uint32_t qp; +} IMPEncoderAttrH264FixQP; + +typedef struct { + uint32_t maxQp; + uint32_t minQp; + uint32_t outBitRate; + int iBiasLvl; + uint32_t frmQPStep; + uint32_t gopQPStep; + bool adaptiveMode; + bool gopRelation; +} IMPEncoderAttrH264CBR; + +typedef struct { + uint32_t maxQp; + uint32_t minQp; + uint32_t staticTime; + uint32_t maxBitRate; + int32_t iBiasLvl; + uint32_t changePos; + uint32_t qualityLvl; + uint32_t frmQPStep; + uint32_t gopQPStep; + bool gopRelation; +} IMPEncoderAttrH264VBR; + +typedef IMPEncoderAttrH264VBR IMPEncoderAttrH264Smart; + +typedef struct { + uint32_t qp; +} IMPEncoderAttrH265FixQP; + +typedef struct { + uint32_t maxQp; + uint32_t minQp; + uint32_t staticTime; + uint32_t outBitRate; + int iBiasLvl; + uint32_t frmQPStep; + uint32_t gopQPStep; + uint32_t flucLvl; +} IMPEncoderAttrH265CBR; + +typedef struct { + uint32_t maxQp; + uint32_t minQp; + uint32_t staticTime; + uint32_t maxBitRate; + int32_t iBiasLvl; + uint32_t changePos; + uint32_t qualityLvl; + uint32_t frmQPStep; + uint32_t gopQPStep; + uint32_t flucLvl; +} IMPEncoderAttrH265VBR; + +typedef IMPEncoderAttrH265VBR IMPEncoderAttrH265Smart; + +typedef struct { + bool enable; + int dnType; + int dnIQp; + int dnPQp; +} IMPEncoderAttrDenoise; + +typedef enum { + ENC_FRM_BYPASS = 0, + ENC_FRM_REUSED = 1, + ENC_FRM_SKIP = 2, +} EncFrmUsedMode; + +typedef struct { + bool enable; + EncFrmUsedMode frmUsedMode; + uint32_t frmUsedTimes; +} IMPEncoderAttrFrmUsed; + +typedef enum { + IMP_Encoder_STYPE_N1X = 0, + IMP_Encoder_STYPE_N2X = 1, + IMP_Encoder_STYPE_N4X = 2, + IMP_Encoder_STYPE_HN1_FALSE = 3, + IMP_Encoder_STYPE_HN1_TRUE = 4, + IMP_Encoder_STYPE_H1M_FALSE = 5, + IMP_Encoder_STYPE_H1M_TRUE = 6, +} IMPSkipType; + +typedef enum { + IMP_Encoder_FSTYPE_IDR = 0, + IMP_Encoder_FSTYPE_LBASE = 1, + IMP_Encoder_FSTYPE_SBASE = 2, + IMP_Encoder_FSTYPE_ENHANCE = 3, +} IMPRefType; + +typedef struct { + IMPSkipType skipType; + int m; + int n; + int maxSameSceneCnt; + int bEnableScenecut; + int bBlackEnhance; +} IMPEncoderAttrHSkip; + +typedef struct { + IMPEncoderAttrHSkip hSkipAttr; + IMPSkipType maxHSkipType; +} IMPEncoderAttrInitHSkip; + +typedef struct { + IMPEncoderRcMode rcMode; + union { + IMPEncoderAttrH264FixQP attrH264FixQp; + IMPEncoderAttrH264CBR attrH264Cbr; + IMPEncoderAttrH264VBR attrH264Vbr; + IMPEncoderAttrH264Smart attrH264Smart; + IMPEncoderAttrH265FixQP attrH265FixQp; + IMPEncoderAttrH265CBR attrH265Cbr; + IMPEncoderAttrH265VBR attrH265Vbr; + IMPEncoderAttrH265Smart attrH265Smart; + }; +} IMPEncoderAttrRcMode; + +typedef struct { + IMPEncoderFrmRate outFrmRate; + uint32_t maxGop; + IMPEncoderAttrRcMode attrRcMode; + IMPEncoderAttrFrmUsed attrFrmUsed; + IMPEncoderAttrDenoise attrDenoise; + IMPEncoderAttrInitHSkip attrHSkip; +} IMPEncoderRcAttr; + +typedef struct { + bool enable; + uint32_t x; + uint32_t y; + uint32_t w; + uint32_t h; +} IMPEncoderCropCfg; + +typedef struct { + uint32_t maxUserDataCnt; + uint32_t maxUserDataSize; +} IMPEncoderUserDataCfg; + +typedef struct { + IMPPayloadType enType; + uint32_t bufSize; + uint32_t profile; + uint32_t picWidth; + uint32_t picHeight; + IMPEncoderCropCfg crop; + IMPEncoderUserDataCfg userData; +} IMPEncoderAttr; + +typedef IMPEncoderAttr IMPEncoderAttrH264; +typedef IMPEncoderAttr IMPEncoderAttrH265; +typedef IMPEncoderAttr IMPEncoderAttrJpeg; + +typedef struct { + IMPEncoderAttr encAttr; + IMPEncoderRcAttr rcAttr; + bool bEnableIvdc; +} IMPEncoderCHNAttr; + +typedef IMPEncoderCHNAttr IMPEncoderChnAttr; + +/* Compatibility control type used internally by the shared AVPU backend. */ +typedef struct { + uint32_t gopLength; + uint32_t ipQpDelta; + IMPEncoderGopMode gopMode; +} IMPEncoderGopAttr; + +typedef struct { + int gopsize; +} IMPEncoderGOPSizeCfg; + +_Static_assert(sizeof(IMPEncoderAttrRcMode) == 0x2c, + "T23 IMPEncoderAttrRcMode ABI mismatch"); +_Static_assert(sizeof(IMPEncoderRcAttr) == 0x70, + "T23 IMPEncoderRcAttr ABI mismatch"); +_Static_assert(sizeof(IMPEncoderAttr) == 0x30, + "T23 IMPEncoderAttr ABI mismatch"); +_Static_assert(offsetof(IMPEncoderCHNAttr, rcAttr) == 0x30, + "T23 IMPEncoderCHNAttr.rcAttr ABI mismatch"); +_Static_assert(offsetof(IMPEncoderCHNAttr, bEnableIvdc) == 0xa0, + "T23 IMPEncoderCHNAttr.bEnableIvdc ABI mismatch"); +_Static_assert(sizeof(IMPEncoderCHNAttr) == 0xa4, + "T23 IMPEncoderCHNAttr ABI mismatch"); #elif defined(PLATFORM_T31) || defined(PLATFORM_T40) typedef uint32_t IMPEncoderRcOptions; @@ -583,7 +788,38 @@ typedef enum { IMP_ENC_SLICE_MAX_ENUM, } IMPEncoderSliceType; -/* Stream pack (match vendor layout: bool frameEnd; union nalType; enum sliceType) */ +#if defined(PLATFORM_T23) +typedef union { + IMPEncoderH264NaluType h264Type; + IMPEncoderH265NaluType h265Type; +} IMPEncoderDataType; + +typedef struct { + uint32_t phyAddr; + uint32_t virAddr; + uint32_t length; + int64_t timestamp; + bool frameEnd; + IMPEncoderDataType dataType; +} IMPEncoderPack; + +typedef struct { + IMPEncoderPack *pack; + uint32_t packCount; + uint32_t seq; + IMPRefType refType; +} IMPEncoderStream; + +_Static_assert(offsetof(IMPEncoderPack, timestamp) == 0x10, + "T23 IMPEncoderPack.timestamp ABI mismatch"); +_Static_assert(offsetof(IMPEncoderPack, dataType) == 0x1c, + "T23 IMPEncoderPack.dataType ABI mismatch"); +_Static_assert(sizeof(IMPEncoderPack) == 0x20, + "T23 IMPEncoderPack ABI mismatch"); +_Static_assert(sizeof(IMPEncoderStream) == 0x10, + "T23 IMPEncoderStream ABI mismatch"); +#else +/* Stream pack (match newer vendor layout). */ typedef struct { uint32_t offset; uint32_t length; @@ -625,10 +861,24 @@ typedef struct { IMPEncoderJpegInfo jpegInfo; }; } IMPEncoderStream; +#endif /** * Encoder channel statistics (T20/T21/T23) */ +#if defined(PLATFORM_T23) +typedef struct { + bool registered; + uint32_t leftPics; + uint32_t leftStreamBytes; + uint32_t leftStreamFrames; + uint32_t curPacks; + uint32_t work_done; +} IMPEncoderCHNStat; + +_Static_assert(sizeof(IMPEncoderCHNStat) == 0x18, + "T23 IMPEncoderCHNStat ABI mismatch"); +#else typedef struct { uint32_t leftPics; /**< Left pictures */ uint32_t leftBytes; /**< Left bytes */ @@ -636,6 +886,7 @@ typedef struct { uint32_t curPacks; /**< Current packs */ uint32_t work_done; /**< Work done flag */ } IMPEncoderCHNStat; +#endif /** * Encoder channel statistics (T31/C100/T40/T41) @@ -651,12 +902,19 @@ typedef struct { /** * JPEG quality level */ +#if defined(PLATFORM_T23) +typedef struct { + bool user_ql_en; + uint8_t qmem_table[128]; +} IMPEncoderJpegeQl; +#else typedef struct { uint32_t qmaxI; /**< Maximum I frame quality */ uint32_t qminI; /**< Minimum I frame quality */ uint32_t qmaxP; /**< Maximum P frame quality */ uint32_t qminP; /**< Minimum P frame quality */ } IMPEncoderJpegeQl; +#endif /** * Quantization Parameter (QP) structure @@ -824,6 +1082,7 @@ int IMP_Encoder_GetChnAttr(int encChn, IMPEncoderChnAttr *attr); * @return 0 on success, negative on error */ int IMP_Encoder_SetJpegeQl(int encChn, IMPEncoderJpegeQl *attr); +int IMP_Encoder_GetJpegeQl(int encChn, IMPEncoderJpegeQl *attr); /** * Set buffer sharing between channels (T31/C100/T40/T41) @@ -903,8 +1162,14 @@ int IMP_Encoder_GetChnEvalInfo(int encChn, void *info); /* Additional encoder functions (raptor-hal parity) */ int IMP_Encoder_GetStreamBufSize(int encChn, int *size); int IMP_Encoder_GetMaxStreamCnt(int encChn, int *cnt); +#if defined(PLATFORM_T23) +int IMP_Encoder_SetChnFrmRate(int encChn, const IMPEncoderFrmRate *rate); +int IMP_Encoder_GetChnFrmRate(int encChn, IMPEncoderFrmRate *rate); +int IMP_Encoder_SetGOPSize(int encChn, const IMPEncoderGOPSizeCfg *gop); +#else int IMP_Encoder_SetChnFrmRate(int encChn, IMPEncoderFrmRate *in, IMPEncoderFrmRate *out); int IMP_Encoder_GetChnFrmRate(int encChn, IMPEncoderFrmRate *in, IMPEncoderFrmRate *out); +#endif int IMP_Encoder_SetChnRcAttr(int encChn, void *rcAttr); int IMP_Encoder_GetChnRcAttr(int encChn, void *rcAttr); int IMP_Encoder_SetChnAttrRcMode(int encChn, IMPEncoderAttrRcMode *rcMode); @@ -921,7 +1186,11 @@ int IMP_Encoder_SetChnQpBoundsPerFrame(int encChn, int minQpI, int maxQpI, int IMP_Encoder_SetChnMaxPictureSize(int encChn, uint32_t maxPictureSizeI, uint32_t maxPictureSizeP); int IMP_Encoder_SetChnQpIPDelta(int encChn, int delta); +#if defined(PLATFORM_T23) +int IMP_Encoder_GetChnEncType(int encChn, IMPPayloadType *encType); +#else int IMP_Encoder_GetChnEncType(int encChn, IMPEncoderEncType *encType); +#endif #if defined(PLATFORM_T31) int IMP_Encoder_GetChnAveBitrate(int encChn, IMPEncoderStream *stream, int frames, double *bitrate); diff --git a/include/imp/imp_framesource.h b/include/imp/imp_framesource.h index bc39f30..c16729a 100644 --- a/include/imp/imp_framesource.h +++ b/include/imp/imp_framesource.h @@ -54,11 +54,23 @@ typedef struct { int outFrmRateDen; /**< Output frame rate denominator */ int nrVBs; /**< Number of video buffers */ IMPFSChnType type; /**< Channel type */ -#if defined(PLATFORM_T31) || defined(PLATFORM_C100) || defined(PLATFORM_T40) || defined(PLATFORM_T41) +#if defined(PLATFORM_T23) + int mirr_enable; /**< T23 1.3.0 mirror flag */ + IMPFSChnCrop fcrop; /**< T23 frame crop */ +#elif defined(PLATFORM_T31) || defined(PLATFORM_C100) || defined(PLATFORM_T40) || defined(PLATFORM_T41) IMPFSChnCrop fcrop; /**< Frame crop (newer platforms) */ #endif } IMPFSChnAttr; +#if defined(PLATFORM_T23) +_Static_assert(offsetof(IMPFSChnAttr, mirr_enable) == 0x3c, + "T23 IMPFSChnAttr.mirr_enable ABI mismatch"); +_Static_assert(offsetof(IMPFSChnAttr, fcrop) == 0x40, + "T23 IMPFSChnAttr.fcrop ABI mismatch"); +_Static_assert(sizeof(IMPFSChnAttr) == 0x54, + "T23 IMPFSChnAttr ABI mismatch"); +#endif + /** * FIFO attributes */ diff --git a/src/framesource/framesource_tseries.c b/src/framesource/framesource_tseries.c index 3a48005..c400a3f 100644 --- a/src/framesource/framesource_tseries.c +++ b/src/framesource/framesource_tseries.c @@ -47,6 +47,9 @@ #include "core/globals.h" #include "core/imp_alloc.h" #include "kernel_interface.h" +#if defined(PLATFORM_T23) +#include "t23/openimp_t23_persist.h" +#endif /* --------------------------------------------------------------------- * Compatibility forward declarations (functions ported in other tasks). @@ -87,18 +90,47 @@ static void *fs_retaddr(void) return __builtin_return_address(0); } +static int fs_trace_enabled(void) +{ + static int enabled = -1; + + if (enabled < 0) { + const char *value = getenv("OPENIMP_TRACE_KMSG"); + + enabled = value && value[0] && value[0] != '0'; + } + return enabled; +} + static void fs_thread_trace(const char *fmt, ...) { - int fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC); - if (fd < 0) return; + int trace_kmsg = fs_trace_enabled(); +#if defined(PLATFORM_T23) + int trace_persist = openimp_t23_persist_enabled(); +#else + int trace_persist = 0; +#endif + if (!trace_kmsg && !trace_persist) + return; char buf[256]; va_list ap; va_start(ap, fmt); int n = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); - if (n > 0) write(fd, buf, (size_t)n); - close(fd); + if (n > 0) { + if (trace_kmsg) { + int fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC); + if (fd >= 0) { + write(fd, buf, (size_t)n); + close(fd); + } + } +#if defined(PLATFORM_T23) + if (trace_persist) + openimp_t23_persist_write(buf, (size_t)n); +#endif + } } static void fs_user_trace(const char *fmt, ...) @@ -106,13 +138,20 @@ static void fs_user_trace(const char *fmt, ...) char buf[256]; va_list ap; + if (!fs_trace_enabled() +#if defined(PLATFORM_T23) + && !openimp_t23_persist_enabled() +#endif + ) + return; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); - imp_log_fun(4, IMP_Log_Get_Option(), 2, "Framesource", - "/home/user/git/proj/sdk-lv3/src/imp/framesource/framesource_tseries.c", - 0x3f0, "frame_pooling_thread", "%s\n", buf); + if (fs_trace_enabled()) + imp_log_fun(4, IMP_Log_Get_Option(), 2, "Framesource", + "/home/user/git/proj/sdk-lv3/src/imp/framesource/framesource_tseries.c", + 0x3f0, "frame_pooling_thread", "%s\n", buf); } /* Bind callbacks are defined later in the file but are needed during @@ -733,8 +772,14 @@ static volatile int g_fs_thread_enabled_seen[FS_MAX_CHANNELS]; static void fs_bind_trace(const char *fmt, ...) { - int fd = open("/dev/kmsg", O_WRONLY); - if (fd < 0) return; + int trace_kmsg = fs_trace_enabled(); +#if defined(PLATFORM_T23) + int trace_persist = openimp_t23_persist_enabled(); +#else + int trace_persist = 0; +#endif + if (!trace_kmsg && !trace_persist) + return; char buf[512]; va_list ap; @@ -742,8 +787,25 @@ static void fs_bind_trace(const char *fmt, ...) int n = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); - if (n > 0) write(fd, buf, (size_t)n); - close(fd); + if (n > 0) { + if (trace_kmsg) { + int fd = open("/dev/kmsg", O_WRONLY); + if (fd >= 0) { + write(fd, buf, (size_t)n); + close(fd); + } + } +#if defined(PLATFORM_T23) + if (trace_persist && + strstr(buf, "select-") == NULL && + strstr(buf, "dequeue") == NULL && + strstr(buf, "release_frame") == NULL && + strstr(buf, "notify-empty") == NULL && + strstr(buf, " notify ") == NULL && + strstr(buf, "direct-enc-dispatch") == NULL) + openimp_t23_persist_write(buf, (size_t)n); +#endif + } } __attribute__((constructor)) diff --git a/src/isp/isp_tseries.c b/src/isp/isp_tseries.c index 8b6ead5..73167c5 100644 --- a/src/isp/isp_tseries.c +++ b/src/isp/isp_tseries.c @@ -14,6 +14,17 @@ #include "core/globals.h" #include "imp/imp_isp.h" #include "isp_ioctl_compat.h" +#if defined(PLATFORM_T23) +#include "t23/openimp_t23_persist.h" +#endif + +#if defined(PLATFORM_T23) +#define TISP_TUNING_IOCTL 0xc01056c6U +#define TISP_TUNING_SENSOR_FIELD int32_t sensor; +#else +#define TISP_TUNING_IOCTL 0xc00c56c6U +#define TISP_TUNING_SENSOR_FIELD +#endif int IMP_Log_Get_Option(void); void imp_log_fun(int level, int option, int type, ...); @@ -22,17 +33,40 @@ typedef struct ISPDevice { char dev_name[0x20]; int32_t fd; uint32_t opened; +#if defined(PLATFORM_T23) + uint8_t sensor_info[0x54]; +#else uint8_t unk_28[0x50]; +#endif char tuning_path[0x20]; int32_t tuning_fd; void *tuning; int32_t mem_fd; void *isp_base; int32_t tuning_state; +#if defined(PLATFORM_T23) + void *sensor_alloc[2]; + int32_t wdr_mode; + void *wdr_alloc; +#else uint8_t unk_ac[8]; int32_t wdr_mode; +#endif } ISPDevice; +#if defined(PLATFORM_T23) +_Static_assert(offsetof(ISPDevice, tuning_path) == 0x7c, + "T23 ISP tuning path ABI mismatch"); +_Static_assert(offsetof(ISPDevice, tuning_fd) == 0x9c, + "T23 ISP tuning fd ABI mismatch"); +_Static_assert(offsetof(ISPDevice, tuning) == 0xa0, + "T23 ISP tuning object ABI mismatch"); +_Static_assert(offsetof(ISPDevice, tuning_state) == 0xac, + "T23 ISP tuning state ABI mismatch"); +_Static_assert(offsetof(ISPDevice, sensor_alloc) == 0xb0, + "T23 ISP sensor allocation ABI mismatch"); +#endif + static char *bpath; static uint8_t custom_contrast; static uint8_t custom_sharpness; @@ -54,6 +88,8 @@ int IMP_ISP_Open(void) { int32_t result = 0; + kmsg_trace("libimp/ISP: Open entry gISP=%p\n", (void *)gISP); + /* Early kmsg trace — confirm this port's IMP_ISP_Open is the one invoked. */ { int kfd = open("/dev/kmsg", O_WRONLY); @@ -67,7 +103,11 @@ int IMP_ISP_Open(void) } if (gISP == NULL) { +#if defined(PLATFORM_T23) + void *v0_2 = calloc(0xf0, 1); +#else void *v0_2 = calloc(0xe0, 1); +#endif gISP = v0_2; { int kfd = open("/dev/kmsg", O_WRONLY); @@ -89,6 +129,9 @@ int IMP_ISP_Open(void) __builtin_strcpy((char *)v0_2, "/dev/tx-isp"); ((ISPDevice *)v0_2)->fd = open((char *)v0_2, 0x80002, 0); + kmsg_trace("libimp/ISP: Open /dev/tx-isp fd=%d errno=%d\n", + ((ISPDevice *)v0_2)->fd, + ((ISPDevice *)v0_2)->fd < 0 ? errno : 0); if (((ISPDevice *)gISP)->fd < 0) { result = -1; imp_log_fun(6, IMP_Log_Get_Option(), 2, "IMP-ISP", @@ -103,6 +146,9 @@ int IMP_ISP_Open(void) "IMP_ISP_Open", "~~~~~~ %s[%d] ~~~~~~~\n", "IMP_ISP_Open", 0x14b); } + kmsg_trace("libimp/ISP: Open return=%d gISP=%p\n", result, + (void *)gISP); + return result; } @@ -477,8 +523,9 @@ int IMP_ISP_Tuning_GetTotalGain(uint32_t *arg1) int32_t cmd; int32_t subcmd; int32_t value; + TISP_TUNING_SENSOR_FIELD } var_18 = { 1, 0x8000027, 0 }; - int32_t result = ioctl(gISP_1->tuning_fd, 0xc00c56c6, &var_18); + int32_t result = ioctl(gISP_1->tuning_fd, TISP_TUNING_IOCTL, &var_18); if (result == 0) { *arg1 = (uint32_t)var_18.value; @@ -884,8 +931,9 @@ int IMP_ISP_Tuning_SetAeComp(int arg1) int32_t cmd; int32_t subcmd; int32_t value; + TISP_TUNING_SENSOR_FIELD } var_18 = { 0, 0x8000023, arg1 }; - int32_t result = ioctl(gISP_1->tuning_fd, 0xc00c56c6, &var_18); + int32_t result = ioctl(gISP_1->tuning_fd, TISP_TUNING_IOCTL, &var_18); if (result == 0) { return 0; @@ -927,8 +975,9 @@ int IMP_ISP_Tuning_GetAeComp(int *arg1) int32_t cmd; int32_t subcmd; int32_t value; + TISP_TUNING_SENSOR_FIELD } var_20 = { 1, 0x8000023, 0 }; - int32_t result = ioctl(gISP_1->tuning_fd, 0xc00c56c6, &var_20); + int32_t result = ioctl(gISP_1->tuning_fd, TISP_TUNING_IOCTL, &var_20); if (result != 0) { imp_log_fun(6, IMP_Log_Get_Option(), 2, "IMP-ISP", @@ -965,7 +1014,7 @@ enum { TISP_VIDIOC_GET_FRAME_DROP = 0xc00456e7, TISP_VIDIOC_GPIO_INIT_OR_FREE = 0xc00456e8, TISP_VIDIOC_GPIO_STA = 0xc00456e9, - TISP_VIDIOC_TUNING = 0xc00c56c6, + TISP_VIDIOC_TUNING = TISP_TUNING_IOCTL, TISP_VIDIOC_G_CTRL = 0xc008561b, TISP_VIDIOC_S_CTRL = 0xc008561c, TISP_CID_WB_ATTR = 0x8000004, @@ -1027,12 +1076,14 @@ typedef struct TSeriesTuningValReq { int32_t cmd; int32_t subcmd; int32_t value; + TISP_TUNING_SENSOR_FIELD } TSeriesTuningValReq; typedef struct TSeriesTuningPtrReq { int32_t cmd; int32_t subcmd; void *ptr; + TISP_TUNING_SENSOR_FIELD } TSeriesTuningPtrReq; typedef struct TSeriesV4L2Ctrl { @@ -1365,6 +1416,14 @@ int IMP_ISP_Tuning_SetISPBypass(IMPISPTuningOpsMode enable) return -1; } +#if defined(PLATFORM_T23) + if (getenv("OPENIMP_T23_SKIP_BYPASS")) { + kmsg_trace("libimp/ISP: SetISPBypass skipped by T23 cold-start guard enable=%d\n", + enable); + return 0; + } +#endif + kmsg_trace("libimp/ISP: SetISPBypass enter enable=%d isp=%p fd=%d tuning_fd=%d\n", enable, (void *)isp, isp->fd, isp->tuning_fd); @@ -2529,13 +2588,7 @@ int IMP_ISP_SET_GPIO_STA(int *gpio) return ioctl(isp->fd, TISP_VIDIOC_GPIO_STA, gpio); } -/* ----- Missing symbols required by rvd hal_init() ----- - * Ported from libimp.so decomps at 0x9bd84 / 0x9c69c / 0x9e0f8 / 0x9cd40. - * The struct layout uses raw byte offsets because the ISPDevice struct - * in this file doesn't fully match the binary's internal layout - * (fields +0xac=ncu_buf pointer and +0xb4=wdr_buf pointer are unsplit - * in the struct). - */ +/* ----- Missing symbols required by rvd hal_init() ----- */ /* T68 forward decls */ int32_t IMP_Alloc(void *info, int32_t size, const char *name); @@ -2606,8 +2659,12 @@ int IMP_ISP_AddSensor(IMPSensorInfo *pinfo) return -1; } - /* Copy 0x50 bytes of sensor-info into gISP +0x28 via halfword pack */ + /* T23's OEM ISPDevice retains the complete 0x54-byte sensor record. */ +#if defined(PLATFORM_T23) + memcpy(isp_b + 0x28, pinfo, 0x54); +#else memcpy(isp_b + 0x28, pinfo, 0x50); +#endif /* Read required ISP buffer size using the platform-specific ABI. */ tx_isp_buf_t buf_info; @@ -2635,7 +2692,16 @@ int IMP_ISP_AddSensor(IMPSensorInfo *pinfo) printf("error(%s,%d): IMP_Alloc\n", "IMP_ISP_AddSensor", 0x1e8); return -1; } +#if defined(PLATFORM_T23) + if ((unsigned int)sensor_idx >= 2u) { + IMP_Free(ncu_alloc, *(int32_t *)((char *)ncu_alloc + 0x80)); + free(ncu_alloc); + return -1; + } + isp->sensor_alloc[sensor_idx] = ncu_alloc; +#else *(void **)(isp_b + 0xac) = ncu_alloc; +#endif int32_t ncu_phys = *(int32_t *)((char *)ncu_alloc + 0x84); tx_isp_buf_t set_buf; TXISP_BUF_INIT(set_buf); @@ -2650,8 +2716,7 @@ int IMP_ISP_AddSensor(IMPSensorInfo *pinfo) return -1; } - /* WDR path only if WDR mode flag (+0xb0) is set */ - if (*(int32_t *)(isp_b + 0xb0) != 1) return 0; + if (isp->wdr_mode != 1) return 0; struct { uint32_t addr; uint32_t size; } wdr_info = {0, 0}; if (ioctl(isp->fd, 0x800856d7, &wdr_info) != 0) { @@ -2673,7 +2738,11 @@ int IMP_ISP_AddSensor(IMPSensorInfo *pinfo) printf("error(%s,%d): IMP_Alloc\n", "IMP_ISP_AddSensor", 0x202); return -1; } +#if defined(PLATFORM_T23) + isp->wdr_alloc = wdr_alloc; +#else *(void **)(isp_b + 0xb4) = wdr_alloc; +#endif int32_t wdr_phys = *(int32_t *)((char *)wdr_alloc + 0x84); struct { uint32_t addr; uint32_t size; } set_wdr; set_wdr.addr = (uint32_t)wdr_phys; @@ -2722,18 +2791,40 @@ int IMP_ISP_DelSensor(IMPSensorInfo *pinfo) return r; } - memset(isp_b + 0x28, 0, 0x50); + memset(isp_b + 0x28, 0, +#if defined(PLATFORM_T23) + 0x54 +#else + 0x50 +#endif + ); +#if defined(PLATFORM_T23) + void *ncu = isp->sensor_alloc[0]; +#else void *ncu = *(void **)(isp_b + 0xac); +#endif if (ncu != NULL) { IMP_Free(ncu, *(int32_t *)((char *)ncu + 0x80)); free(ncu); +#if defined(PLATFORM_T23) + isp->sensor_alloc[0] = NULL; +#else *(void **)(isp_b + 0xac) = NULL; +#endif } +#if defined(PLATFORM_T23) + void *wdr = isp->wdr_alloc; +#else void *wdr = *(void **)(isp_b + 0xb4); +#endif if (wdr != NULL) { IMP_Free(wdr, *(int32_t *)((char *)wdr + 0x80)); free(wdr); +#if defined(PLATFORM_T23) + isp->wdr_alloc = NULL; +#else *(void **)(isp_b + 0xb4) = NULL; +#endif } return 0; } @@ -2744,31 +2835,40 @@ int IMP_ISP_DelSensor(IMPSensorInfo *pinfo) static void kmsg_trace(const char *fmt, ...) { static int kfd = -2; - if (kfd == -2) kfd = open("/dev/kmsg", O_WRONLY); - if (kfd < 0) return; char buf[256]; va_list ap; + + if (kfd == -2) kfd = open("/dev/kmsg", O_WRONLY); va_start(ap, fmt); int n = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); - if (n > 0) write(kfd, buf, (size_t)n); + if (n <= 0) + return; + if (kfd >= 0) + write(kfd, buf, (size_t)n); +#if defined(PLATFORM_T23) + openimp_t23_persist_write(buf, (size_t)n); +#endif + if (getenv("OPENIMP_STARTUP_TRACE")) { + write(STDERR_FILENO, buf, (size_t)n); + fsync(STDERR_FILENO); + } } int IMP_ISP_EnableTuning(void) { ISPDevice *isp = (ISPDevice *)gISP; - uint8_t *isp_b = (uint8_t *)isp; kmsg_trace("libimp/ISP: EnableTuning entry gISP=%p\n", (void *)isp); if (isp != NULL) kmsg_trace("libimp/ISP: opened=%u tuning=%p\n", - isp->opened, *(void **)(isp_b + 0x9c)); + isp->opened, isp->tuning); if (isp == NULL) { kmsg_trace("libimp/ISP: EnableTuning FAILED — gISP NULL (Open not called)\n"); return -1; } - if (*(void **)(isp_b + 0x9c) != NULL) return 0; /* already enabled */ + if (isp->tuning != NULL) return 0; /* already enabled */ /* Build "/dev/isp-m0" path at +0x78. * Stock binary uses open flags 0x80002 (O_RDWR | O_CLOEXEC), but the @@ -2776,50 +2876,56 @@ int IMP_ISP_EnableTuning(void) * this particular module build — the legacy openimp impl used plain * O_RDWR and got the stream on. Prefer the flag value that works on * real hardware over binary-exact. */ - strcpy((char *)(isp_b + 0x78), "/dev/isp-m0"); - int32_t tfd = open((char *)(isp_b + 0x78), O_RDWR); + strcpy(isp->tuning_path, "/dev/isp-m0"); + int32_t tfd = open(isp->tuning_path, O_RDWR); int32_t err1 = (tfd < 0) ? errno : 0; kmsg_trace("libimp/ISP: open(/dev/isp-m0, O_RDWR) = %d (errno=%d %s)\n", tfd, err1, err1 ? strerror(err1) : "ok"); if (tfd < 0) { - strcpy((char *)(isp_b + 0x78), "/dev/isp-w02"); - tfd = open((char *)(isp_b + 0x78), O_RDWR); + strcpy(isp->tuning_path, "/dev/isp-w02"); + tfd = open(isp->tuning_path, O_RDWR); int32_t err2 = (tfd < 0) ? errno : 0; kmsg_trace("libimp/ISP: open(/dev/isp-w02, O_RDWR) = %d (errno=%d %s)\n", tfd, err2, err2 ? strerror(err2) : "ok"); } - *(int32_t *)(isp_b + 0x98) = tfd; + isp->tuning_fd = tfd; if (tfd < 0) { kmsg_trace("libimp/ISP: EnableTuning FAILED — could not open any tuning node\n"); return -1; } kmsg_trace("libimp/ISP: EnableTuning opened %s as fd=%d\n", - (char *)(isp_b + 0x78), tfd); + isp->tuning_path, tfd); void *tune = calloc(0x1c, 1); if (tune == NULL) { close(tfd); return -1; } - *(void **)(isp_b + 0x9c) = tune; - *(int32_t *)(isp_b + 0xa8) = 2; + isp->tuning = tune; + isp->tuning_state = 2; int32_t mem_fd = open("/dev/mem", O_RDWR | O_SYNC); - *(int32_t *)(isp_b + 0xa0) = mem_fd; + isp->mem_fd = mem_fd; if (mem_fd <= 0) { imp_log_fun(6, IMP_Log_Get_Option(), 2, "IMP-ISP", "/home/user/git/proj/sdk-lv3/src/imp/isp/isp_tseries.c", 0x485, "IMP_ISP_EnableTuning", "Failed to open %s\n", "/dev/mem"); } - void *base = mmap(0, 0x10000, PROT_READ | PROT_WRITE, MAP_SHARED, +#if defined(PLATFORM_T23) + const size_t isp_map_size = 0x1b000u; +#else + const size_t isp_map_size = 0x10000u; +#endif + void *base = mmap(0, isp_map_size, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, 0x13380000); - *(void **)(isp_b + 0xa4) = base; + isp->isp_base = base; if (base == NULL || base == MAP_FAILED) { imp_log_fun(6, IMP_Log_Get_Option(), 2, "IMP-ISP", "/home/user/git/proj/sdk-lv3/src/imp/isp/isp_tseries.c", 0x489, "IMP_ISP_EnableTuning", "Failed to mmap isp base addr\n"); } - int32_t fps_pack[3] = { 1, 0, 0x80000e0 }; - if (ioctl(*(int32_t *)(isp_b + 0x98), 0xc00c56c6, fps_pack) == 0) { - *(int32_t *)((char *)tune + 0xc) = (uint32_t)fps_pack[2] >> 16; - *(int32_t *)((char *)tune + 0x10) = fps_pack[2] & 0xffff; + TSeriesTuningValReq fps_req = { 1, TISP_CID_SENSOR_FPS, 0 }; + if (ioctl(isp->tuning_fd, TISP_VIDIOC_TUNING, + &fps_req) == 0) { + *(int32_t *)((char *)tune + 0xc) = (uint32_t)fps_req.value >> 16; + *(int32_t *)((char *)tune + 0x10) = fps_req.value & 0xffff; } *(uint8_t *)((char *)tune + 9) = custom_contrast; if (tseries_start_tuning_worker() != 0) @@ -2830,23 +2936,27 @@ int IMP_ISP_EnableTuning(void) int IMP_ISP_DisableTuning(void) { ISPDevice *isp = (ISPDevice *)gISP; - uint8_t *isp_b = (uint8_t *)isp; if (isp == NULL) return 0; tseries_stop_tuning_worker(); - void *tune = *(void **)(isp_b + 0x9c); + void *tune = isp->tuning; if (tune != NULL) free(tune); - int32_t tfd = *(int32_t *)(isp_b + 0x98); - *(void **)(isp_b + 0x9c) = NULL; + int32_t tfd = isp->tuning_fd; + isp->tuning = NULL; if (tfd > 0) close(tfd); - void *base = *(void **)(isp_b + 0xa4); - if (base != NULL) munmap(base, 0x10000); - int32_t mem_fd = *(int32_t *)(isp_b + 0xa0); + void *base = isp->isp_base; +#if defined(PLATFORM_T23) + const size_t isp_map_size = 0x1b000u; +#else + const size_t isp_map_size = 0x10000u; +#endif + if (base != NULL && base != MAP_FAILED) munmap(base, isp_map_size); + int32_t mem_fd = isp->mem_fd; if (mem_fd > 0) close(mem_fd); - *(void **)(isp_b + 0xa4) = NULL; - *(int32_t *)(isp_b + 0xa0) = 0; - *(int32_t *)(isp_b + 0xa8) = 0; + isp->isp_base = NULL; + isp->mem_fd = 0; + isp->tuning_state = 0; return 0; } diff --git a/src/kernel_interface.c b/src/kernel_interface.c index a405000..b2b4578 100644 --- a/src/kernel_interface.c +++ b/src/kernel_interface.c @@ -21,8 +21,22 @@ extern int64_t IMP_System_GetTimeStamp(void); extern int64_t OpenIMP_P0_NormalizeMonotonicTimeStamp(uint64_t timestamp); +static int ki_trace_enabled(void) +{ + static int enabled = -1; + + if (enabled < 0) { + const char *value = getenv("OPENIMP_TRACE_KMSG"); + + enabled = value && value[0] && value[0] != '0'; + } + return enabled; +} + static void ki_trace(const char *fmt, ...) { + if (!ki_trace_enabled()) + return; int fd = open("/dev/kmsg", O_WRONLY); if (fd < 0) return; @@ -736,11 +750,26 @@ typedef struct { int size; /* 0x14: Frame size */ uint32_t phys_addr; /* 0x18: Physical address */ uint32_t virt_addr; /* 0x1c: Virtual address */ +#if defined(PLATFORM_T23) + uint32_t direct_phys_addr; /* 0x20: T23 direct physical address */ + uint32_t _timestamp_pad; /* 0x24: o32 alignment */ + int64_t time_stamp; /* 0x28: Public capture timestamp */ + int64_t time_stamp_ivdc; /* 0x30: IVDC dequeue timestamp */ + uint8_t data[0x3f0]; /* 0x38-0x427: Private frame data */ +#else uint8_t data[0x408]; /* 0x20-0x427: Frame data */ +#endif } VBMFrame; +#if defined(PLATFORM_T23) +_Static_assert(offsetof(VBMFrame, time_stamp) == 0x28, + "T23 frame timestamp offset must remain 0x28"); +#else _Static_assert(offsetof(VBMFrame, data) == 0x20, "T31 frame timestamp offset must remain 0x20"); +#endif +_Static_assert(sizeof(VBMFrame) == VBM_FRAME_SIZE, + "VBM public/private frame record size mismatch"); /* VBM Pool structure */ typedef struct { @@ -1025,12 +1054,20 @@ int VBMCreatePool(int chn, void *fmt, void *ops, void *priv) { uint32_t virt = pool->virt_base + (i * pool->frame_size); memcpy(frame_bytes + 0x1c, &virt, sizeof(uint32_t)); +#if defined(PLATFORM_T23) + /* The 1.3.0 public descriptor exposes a direct address followed by + * two aligned timestamps. OpenTX uses the same physical DMA window + * for direct and normal capture on T23. */ + memcpy(frame_bytes + 0x20, &phys, sizeof(uint32_t)); +#else + /* OEM encoder callback reads framePriv->i_fps_num/den from * words 0xb/0xc (byte offsets 0x2c/0x30). Seed those fields * directly in the public frame record so on_encoder_group_data_update * can enter sub_8eea0 instead of rejecting the frame. */ memcpy(frame_bytes + 0x2c, &fps_num, sizeof(int)); memcpy(frame_bytes + 0x30, &fps_den, sizeof(int)); +#endif fprintf(stderr, "[VBM] Frame %d: phys=0x%x virt=0x%x fourcc=0x%x fps=%d/%d\n", i, phys, virt, frame_fourcc, fps_num, fps_den); @@ -1245,8 +1282,13 @@ int VBMKernelDequeue(int chn, int fd, void **frame_out) { OpenIMP_P0_NormalizeMonotonicTimeStamp(absolute_timestamp); if (frame_timestamp < 0) frame_timestamp = IMP_System_GetTimeStamp(); +#if defined(PLATFORM_T23) + pool->frames[idx].time_stamp = frame_timestamp; + pool->frames[idx].time_stamp_ivdc = frame_timestamp; +#else memcpy(pool->frames[idx].data, &frame_timestamp, sizeof(frame_timestamp)); +#endif /* Mark buffer as in userspace — VBMReleaseFrame will only QBUF it back * if this flag is set, preventing double-QBUF. */ if (pool->buf_in_userspace) diff --git a/src/t23/openimp_t23_helix_bridge.c b/src/t23/openimp_t23_helix_bridge.c new file mode 100644 index 0000000..25922ed --- /dev/null +++ b/src/t23/openimp_t23_helix_bridge.c @@ -0,0 +1,532 @@ +#include "openimp_t23_helix_bridge.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "openimp_t23_persist.h" + +#define T23_HELIX_HELPER_DEFAULT "/opt/openimp-t23/openimp-t23-helixd" +#define T23_HELIX_PAGE_SIZE 4096u +#define T23_HELIX_MAX_RECOVERIES 8u +#define T23_HELIX_INIT_TIMEOUT_MS 5000 +#define T23_HELIX_ENCODE_TIMEOUT_MS 3000 +#define T23_HELIX_EXIT_TIMEOUT_MS 250 + +static pthread_mutex_t t23_worker_start_lock = PTHREAD_MUTEX_INITIALIZER; + +_Static_assert(sizeof(T23EncoderYuvIn) == 0x3c, + "T23 YUV encoder input ABI mismatch"); + +static void t23_log(int priority, const char *format, ...) +{ + char message[512]; + va_list ap; + int length; + size_t size; + + va_start(ap, format); + length = vsnprintf(message, sizeof(message), format, ap); + va_end(ap); + if (length <= 0) + return; + size = (size_t)length; + if (size >= sizeof(message)) + size = sizeof(message) - 1u; + syslog(priority, "%s", message); + (void)write(STDERR_FILENO, message, size); + fputc('\n', stderr); + openimp_t23_persist_write(message, size); +} + +static void t23_trace_annexb(unsigned int frame_number, + const unsigned char *data, uint32_t length) +{ + char types[96]; + size_t used = 0u; + uint32_t offset = 0u; + unsigned int count = 0u; + + if (!data || !getenv("OPENIMP_T23_ENCODE_TRACE") || + frame_number >= 128u) + return; + while (offset + 3u < length && count < 12u) { + uint32_t prefix = 0u; + + if (data[offset] == 0u && data[offset + 1u] == 0u) { + if (data[offset + 2u] == 1u) + prefix = 3u; + else if (offset + 4u < length && data[offset + 2u] == 0u && + data[offset + 3u] == 1u) + prefix = 4u; + } + if (!prefix) { + offset++; + continue; + } + if (offset + prefix >= length) + break; + { + int written = snprintf(types + used, sizeof(types) - used, + "%s%u", count ? "," : "", + data[offset + prefix] & 0x1fu); + + if (written < 0 || (size_t)written >= sizeof(types) - used) + break; + used += (size_t)written; + } + count++; + offset += prefix + 1u; + } + if (!count) + snprintf(types, sizeof(types), "none"); + t23_log(LOG_NOTICE, + "openimp/T23: Helix AU frame=%u len=%u NALs=%s", + frame_number, length, types); +} + +static int t23_send_all(int fd, const void *buffer, size_t size) +{ + const unsigned char *bytes = buffer; + size_t written = 0u; + + while (written < size) { + ssize_t count = send(fd, bytes + written, size - written, + MSG_NOSIGNAL); + + if (count < 0) { + if (errno == EINTR) + continue; + return -1; + } + if (!count) + return -1; + written += (size_t)count; + } + return 0; +} + +static int t23_receive_all(int fd, void *buffer, size_t size, int timeout_ms) +{ + unsigned char *bytes = buffer; + size_t consumed = 0u; + + while (consumed < size) { + struct pollfd descriptor; + ssize_t count; + int ready; + + descriptor.fd = fd; + descriptor.events = POLLIN; + descriptor.revents = 0; + do { + ready = poll(&descriptor, 1, timeout_ms); + } while (ready < 0 && errno == EINTR); + if (ready <= 0 || + (descriptor.revents & (POLLERR | POLLHUP | POLLNVAL))) + return -1; + count = recv(fd, bytes + consumed, size - consumed, 0); + if (count < 0) { + if (errno == EINTR) + continue; + return -1; + } + if (!count) + return -1; + consumed += (size_t)count; + } + return 0; +} + +static int t23_exchange(T23HelixBridge *bridge, + const T23HelixIpcRequest *request, + T23HelixIpcResponse *response, int timeout_ms) +{ + if (!bridge || bridge->socket_fd < 0 || !request || !response || + t23_send_all(bridge->socket_fd, request, sizeof(*request)) != 0 || + t23_receive_all(bridge->socket_fd, response, sizeof(*response), + timeout_ms) != 0 || + response->magic != T23_HELIX_IPC_MAGIC || + response->version != T23_HELIX_IPC_VERSION || + response->command != request->command) + return -1; + return response->status; +} + +static void t23_make_request(const T23HelixBridge *bridge, + T23HelixIpcRequest *request, uint32_t command) +{ + memset(request, 0, sizeof(*request)); + request->magic = T23_HELIX_IPC_MAGIC; + request->version = T23_HELIX_IPC_VERSION; + request->command = command; + request->width = bridge->width; + request->height = bridge->height; + request->input_size = bridge->input_size; + request->input_capacity = bridge->input_capacity; + request->output_capacity = bridge->output_capacity; + request->encoder_input = bridge->input; +} + +static void t23_stop_worker(T23HelixBridge *bridge) +{ + pid_t pid; + unsigned int attempt; + + if (!bridge) + return; + pid = bridge->worker_pid; + if (bridge->socket_fd >= 0) { + T23HelixIpcRequest request; + T23HelixIpcResponse response; + + t23_make_request(bridge, &request, T23_HELIX_COMMAND_EXIT); + (void)t23_exchange(bridge, &request, &response, + T23_HELIX_EXIT_TIMEOUT_MS); + close(bridge->socket_fd); + bridge->socket_fd = -1; + } + if (pid > 0) { + for (attempt = 0; attempt < 20u; attempt++) { + pid_t result = waitpid(pid, NULL, WNOHANG); + + if (result == pid || (result < 0 && errno == ECHILD)) { + pid = -1; + break; + } + usleep(10000); + } + if (pid > 0) { + kill(pid, SIGKILL); + (void)waitpid(pid, NULL, WNOHANG); + } + } + bridge->worker_pid = -1; + if (bridge->shared_buffer && bridge->shared_size) + munmap(bridge->shared_buffer, bridge->shared_size); + bridge->shared_buffer = NULL; + if (bridge->shared_fd >= 0) + close(bridge->shared_fd); + bridge->shared_fd = -1; +} + +static int t23_start_worker(T23HelixBridge *bridge) +{ + static char *const helper_environment[] = { + "PATH=/usr/bin:/bin", + "LD_LIBRARY_PATH=/usr/lib", + NULL, + }; + T23HelixIpcRequest request; + T23HelixIpcResponse response; + const char *helper; + char socket_text[16]; + char shared_fd_text[16]; + char shared_size_text[24]; + char shared_path[] = "/tmp/openimp-t23-helix-XXXXXX"; + char *helper_argv[5]; + int sockets[2] = {-1, -1}; + pid_t pid; + int result = -1; + + if (!bridge || bridge->socket_fd >= 0 || bridge->shared_fd >= 0 || + bridge->shared_buffer) + return -1; + helper = getenv("OPENIMP_T23_HELIX_HELPER"); + if (!helper || !*helper) + helper = T23_HELIX_HELPER_DEFAULT; + if (access(helper, X_OK) != 0) { + t23_log(LOG_ERR, "openimp/T23: Helix helper unavailable: %s", + helper); + return -1; + } + bridge->shared_fd = mkstemp(shared_path); + if (bridge->shared_fd < 0 || + ftruncate(bridge->shared_fd, bridge->shared_size) != 0 || + socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) != 0) + goto out; + bridge->shared_buffer = + mmap(NULL, bridge->shared_size, PROT_READ | PROT_WRITE, MAP_SHARED, + bridge->shared_fd, 0); + if (bridge->shared_buffer == MAP_FAILED) { + bridge->shared_buffer = NULL; + goto out; + } + unlink(shared_path); + snprintf(socket_text, sizeof(socket_text), "%d", sockets[1]); + snprintf(shared_fd_text, sizeof(shared_fd_text), "%d", + bridge->shared_fd); + snprintf(shared_size_text, sizeof(shared_size_text), "%u", + bridge->shared_size); + helper_argv[0] = (char *)helper; + helper_argv[1] = socket_text; + helper_argv[2] = shared_fd_text; + helper_argv[3] = shared_size_text; + helper_argv[4] = NULL; + + pthread_mutex_lock(&t23_worker_start_lock); + pid = fork(); + if (pid == 0) { + close(sockets[0]); + execve(helper, helper_argv, helper_environment); + _exit(127); + } + pthread_mutex_unlock(&t23_worker_start_lock); + if (pid < 0) + goto out; + bridge->worker_pid = pid; + close(sockets[1]); + sockets[1] = -1; + bridge->socket_fd = sockets[0]; + sockets[0] = -1; + + t23_make_request(bridge, &request, T23_HELIX_COMMAND_INIT); + if (t23_exchange(bridge, &request, &response, + T23_HELIX_INIT_TIMEOUT_MS) != 0) { + t23_log(LOG_ERR, "openimp/T23: Helix helper initialization failed"); + goto out; + } + t23_log(LOG_NOTICE, + "openimp/T23: isolated Helix helper pid=%ld ready %ux%u " + "%u/%u fps gop=%u", + (long)bridge->worker_pid, bridge->width, bridge->height, + bridge->input.outFrmRate.frmRateNum, + bridge->input.outFrmRate.frmRateDen, bridge->input.maxGop); + result = 0; + +out: + unlink(shared_path); + if (sockets[0] >= 0) + close(sockets[0]); + if (sockets[1] >= 0) + close(sockets[1]); + if (result != 0) + t23_stop_worker(bridge); + return result; +} + +static void t23_fill_yuv_input(T23EncoderYuvIn *input, + const HWEncoderParams *params) +{ + uint32_t bitrate_kbps; + + memset(input, 0, sizeof(*input)); + input->type = PT_H264; + input->outFrmRate.frmRateNum = params->fps_num ? params->fps_num : 25u; + input->outFrmRate.frmRateDen = params->fps_den ? params->fps_den : 1u; + input->maxGop = params->gop_length ? params->gop_length : 25u; + bitrate_kbps = params->bitrate / 1000u; + if (!bitrate_kbps) + bitrate_kbps = 2000u; + + switch (params->rc_mode) { + case HW_RC_MODE_FIXQP: + input->mode.rcMode = IMP_ENC_RC_MODE_FIXQP; + input->mode.attrH264FixQp.qp = params->qp ? params->qp : 26u; + break; + case HW_RC_MODE_VBR: + input->mode.rcMode = IMP_ENC_RC_MODE_VBR; + input->mode.attrH264Vbr.maxQp = params->max_qp; + input->mode.attrH264Vbr.minQp = params->min_qp; + input->mode.attrH264Vbr.staticTime = 1u; + input->mode.attrH264Vbr.maxBitRate = bitrate_kbps; + input->mode.attrH264Vbr.changePos = 80u; + input->mode.attrH264Vbr.qualityLvl = 2u; + input->mode.attrH264Vbr.frmQPStep = 3u; + input->mode.attrH264Vbr.gopQPStep = 3u; + break; + case HW_RC_MODE_CBR: + default: + input->mode.rcMode = IMP_ENC_RC_MODE_CBR; + input->mode.attrH264Cbr.maxQp = params->max_qp; + input->mode.attrH264Cbr.minQp = params->min_qp; + input->mode.attrH264Cbr.outBitRate = bitrate_kbps; + input->mode.attrH264Cbr.frmQPStep = 3u; + input->mode.attrH264Cbr.gopQPStep = 3u; + break; + } +} + +int OpenIMP_T23_HelixInit(T23HelixBridge *bridge, + const HWEncoderParams *params) +{ + uint64_t input_size; + uint64_t input_capacity; + uint64_t output_capacity; + uint64_t shared_size; + + if (!bridge || !params || !params->width || !params->height) + return -1; + if (bridge->worker_pid > 0) + return 0; + if (bridge->failed) + return -1; + + memset(bridge, 0, sizeof(*bridge)); + bridge->socket_fd = -1; + bridge->shared_fd = -1; + bridge->worker_pid = -1; + bridge->width = params->width; + bridge->height = params->height; + t23_fill_yuv_input(&bridge->input, params); + + input_size = ((uint64_t)params->width + 15u) & ~15u; + input_size *= ((uint64_t)params->height + 15u) & ~15u; + input_size = input_size * 3u / 2u; + input_capacity = (input_size + T23_HELIX_PAGE_SIZE - 1u) & + ~((uint64_t)T23_HELIX_PAGE_SIZE - 1u); + output_capacity = (uint64_t)params->width * params->height * 2u + + 65536u; + output_capacity = + (output_capacity + T23_HELIX_PAGE_SIZE - 1u) & + ~((uint64_t)T23_HELIX_PAGE_SIZE - 1u); + shared_size = input_capacity + output_capacity; + if (!input_size || input_size > UINT32_MAX || + input_capacity > UINT32_MAX || output_capacity > UINT32_MAX || + shared_size > UINT32_MAX) + goto fail; + bridge->input_size = (uint32_t)input_size; + bridge->input_capacity = (uint32_t)input_capacity; + bridge->output_capacity = (uint32_t)output_capacity; + bridge->shared_size = (uint32_t)shared_size; + if (t23_start_worker(bridge) != 0) + goto fail; + return 0; + +fail: + t23_stop_worker(bridge); + bridge->failed = 1; + return -1; +} + +static int t23_encode_once(T23HelixBridge *bridge, + const IMPFrameInfo *frame, + T23HelixIpcResponse *response) +{ + T23HelixIpcRequest request; + + if (!bridge->shared_buffer || frame->size < bridge->input_size) + return -1; + memcpy(bridge->shared_buffer, (const void *)(uintptr_t)frame->virAddr, + bridge->input_size); + __sync_synchronize(); + t23_make_request(bridge, &request, T23_HELIX_COMMAND_ENCODE); + request.pixel_format = frame->pixfmt; + if (request.pixel_format == 0x3231564eu) /* V4L2_PIX_FMT_NV12 */ + request.pixel_format = PIX_FMT_NV12; + else if (request.pixel_format == 0x3132564eu) /* V4L2_PIX_FMT_NV21 */ + request.pixel_format = PIX_FMT_NV21; + request.timestamp = frame->timeStamp; + return t23_exchange(bridge, &request, response, + T23_HELIX_ENCODE_TIMEOUT_MS); +} + +static int t23_recover_worker(T23HelixBridge *bridge) +{ + if (!bridge || bridge->recoveries >= T23_HELIX_MAX_RECOVERIES) + return -1; + bridge->recoveries++; + t23_log(LOG_WARNING, + "openimp/T23: restarting Helix helper (%u/%u)", + bridge->recoveries, T23_HELIX_MAX_RECOVERIES); + t23_stop_worker(bridge); + return t23_start_worker(bridge); +} + +int OpenIMP_T23_HelixEncode(T23HelixBridge *bridge, + const IMPFrameInfo *frame, + HWStreamBuffer **stream) +{ + T23HelixIpcResponse response; + HWStreamBuffer *result; + unsigned char *encoded; + void *copy; + uint32_t frame_number; + + if (!bridge || bridge->worker_pid <= 0 || !frame || !stream || + !frame->virAddr || !bridge->input_size) + return -1; + *stream = NULL; + frame_number = bridge->frames; + if ((frame_number < 256u || frame_number % 100u == 0u) && + getenv("OPENIMP_T23_ENCODE_TRACE")) + t23_log(LOG_NOTICE, + "openimp/T23: enter helper encode frame=%u input=%d/%d " + "virt=0x%08x size=%u", + frame_number, frame->index, frame->pool_idx, frame->virAddr, + frame->size); + if (t23_encode_once(bridge, frame, &response) != 0) { + t23_log(LOG_ERR, + "openimp/T23: Helix helper failed or timed out at frame %u", + frame_number); + if (t23_recover_worker(bridge) != 0 || + t23_encode_once(bridge, frame, &response) != 0) + return -1; + } + if (response.output_offset != bridge->input_capacity || + response.output_length == 0u || + response.output_offset > bridge->shared_size || + response.output_length > bridge->shared_size - response.output_offset) { + t23_log(LOG_ERR, + "openimp/T23: invalid helper output offset=%u len=%u", + response.output_offset, response.output_length); + return -1; + } + __sync_synchronize(); + encoded = (unsigned char *)bridge->shared_buffer + + response.output_offset; + if ((frame_number < 256u || frame_number % 100u == 0u) && + getenv("OPENIMP_T23_ENCODE_TRACE")) + t23_log(LOG_NOTICE, + "openimp/T23: leave helper encode frame=%u len=%u", + frame_number, response.output_length); + t23_trace_annexb(frame_number, encoded, response.output_length); + + copy = malloc(response.output_length); + result = (HWStreamBuffer *)calloc(1, sizeof(*result)); + if (!copy || !result) { + free(copy); + free(result); + return -1; + } + memcpy(copy, encoded, response.output_length); + result->virt_addr = (uint32_t)(uintptr_t)copy; + result->length = response.output_length; + result->timestamp = (uint64_t)frame->timeStamp; + bridge->frames++; + *stream = result; + return 0; +} + +int OpenIMP_T23_HelixRequestIDR(T23HelixBridge *bridge) +{ + T23HelixIpcRequest request; + T23HelixIpcResponse response; + + if (!bridge || bridge->worker_pid <= 0) + return -1; + t23_make_request(bridge, &request, T23_HELIX_COMMAND_REQUEST_IDR); + return t23_exchange(bridge, &request, &response, + T23_HELIX_ENCODE_TIMEOUT_MS); +} + +void OpenIMP_T23_HelixExit(T23HelixBridge *bridge) +{ + if (!bridge) + return; + t23_stop_worker(bridge); + memset(bridge, 0, sizeof(*bridge)); +} diff --git a/src/t23/openimp_t23_helix_bridge.h b/src/t23/openimp_t23_helix_bridge.h new file mode 100644 index 0000000..ccc2fe2 --- /dev/null +++ b/src/t23/openimp_t23_helix_bridge.h @@ -0,0 +1,38 @@ +#ifndef OPENIMP_T23_HELIX_BRIDGE_H +#define OPENIMP_T23_HELIX_BRIDGE_H + +#include +#include + +#include +#include + +#include "hw_encoder.h" +#include "openimp_t23_helix_ipc.h" + +typedef struct { + T23EncoderYuvIn input; + void *shared_buffer; + uint32_t shared_size; + uint32_t input_capacity; + uint32_t input_size; + uint32_t output_capacity; + uint32_t width; + uint32_t height; + uint32_t frames; + uint32_t recoveries; + int socket_fd; + int shared_fd; + pid_t worker_pid; + int failed; +} T23HelixBridge; + +int OpenIMP_T23_HelixInit(T23HelixBridge *bridge, + const HWEncoderParams *params); +int OpenIMP_T23_HelixEncode(T23HelixBridge *bridge, + const IMPFrameInfo *frame, + HWStreamBuffer **stream); +int OpenIMP_T23_HelixRequestIDR(T23HelixBridge *bridge); +void OpenIMP_T23_HelixExit(T23HelixBridge *bridge); + +#endif diff --git a/src/t23/openimp_t23_helix_ipc.h b/src/t23/openimp_t23_helix_ipc.h new file mode 100644 index 0000000..a007761 --- /dev/null +++ b/src/t23/openimp_t23_helix_ipc.h @@ -0,0 +1,53 @@ +#ifndef OPENIMP_T23_HELIX_IPC_H +#define OPENIMP_T23_HELIX_IPC_H + +#include + +#include + +#define T23_HELIX_IPC_MAGIC 0x4f483233u /* "OH23" */ +#define T23_HELIX_IPC_VERSION 1u + +typedef struct { + IMPPayloadType type; + IMPEncoderAttrRcMode mode; + IMPEncoderFrmRate outFrmRate; + uint32_t maxGop; +} T23EncoderYuvIn; + +typedef struct { + void *outAddr; + uint32_t outLen; +} T23EncoderYuvOut; + +enum { + T23_HELIX_COMMAND_INIT = 1, + T23_HELIX_COMMAND_ENCODE = 2, + T23_HELIX_COMMAND_REQUEST_IDR = 3, + T23_HELIX_COMMAND_EXIT = 4, +}; + +typedef struct { + uint32_t magic; + uint32_t version; + uint32_t command; + uint32_t width; + uint32_t height; + uint32_t input_size; + uint32_t input_capacity; + uint32_t output_capacity; + uint32_t pixel_format; + int64_t timestamp; + T23EncoderYuvIn encoder_input; +} T23HelixIpcRequest; + +typedef struct { + uint32_t magic; + uint32_t version; + uint32_t command; + int32_t status; + uint32_t output_offset; + uint32_t output_length; +} T23HelixIpcResponse; + +#endif diff --git a/src/t23/openimp_t23_helix_worker.c b/src/t23/openimp_t23_helix_worker.c new file mode 100644 index 0000000..16f36dc --- /dev/null +++ b/src/t23/openimp_t23_helix_worker.c @@ -0,0 +1,320 @@ +#include "openimp_t23_helix_ipc.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Private entry points exported by Ingenic's T23 libimp. */ +extern int EncoderInit(void); +extern int EncoderExit(void); +extern int IMP_FlushCache(void *address, uint32_t size, int direction); +extern int IMP_Encoder_YuvInit(void **handle, int width, int height, + T23EncoderYuvIn *input); +extern int IMP_Encoder_YuvEncode(void *handle, IMPFrameInfo frame, + T23EncoderYuvOut *output); +extern int IMP_Encoder_YuvExit(void *handle); +extern int IMP_Encoder_YuvRequestIDR(void *handle); +extern void *IMP_Encoder_VbmAlloc(uint32_t size, uint32_t align); +extern void IMP_Encoder_VbmFree(void *address); +extern intptr_t IMP_Encoder_VbmV2P(intptr_t address); + +#define T23_HELIX_PAGE_SIZE 4096u +#define T23_CACHE_WBACK 1 + +typedef struct { + int socket_fd; + unsigned char *shared; + size_t shared_size; + void *encoder; + unsigned char *input_buffer; + unsigned char *output_buffer; + uint32_t input_physical; + uint32_t input_size; + uint32_t input_capacity; + uint32_t output_capacity; + uint32_t width; + uint32_t height; + int subsystem_ready; +} T23HelixWorker; + +_Static_assert(sizeof(T23EncoderYuvIn) == 0x3c, + "T23 YUV encoder input ABI mismatch"); +_Static_assert(sizeof(T23EncoderYuvOut) == 0x08, + "T23 YUV encoder output ABI mismatch"); + +static int read_all(int fd, void *buffer, size_t size) +{ + unsigned char *bytes = buffer; + size_t consumed = 0u; + + while (consumed < size) { + ssize_t count = read(fd, bytes + consumed, size - consumed); + + if (count < 0) { + if (errno == EINTR) + continue; + return -1; + } + if (!count) + return -1; + consumed += (size_t)count; + } + return 0; +} + +static int write_all(int fd, const void *buffer, size_t size) +{ + const unsigned char *bytes = buffer; + size_t written = 0u; + + while (written < size) { + ssize_t count = write(fd, bytes + written, size - written); + + if (count < 0) { + if (errno == EINTR) + continue; + return -1; + } + if (!count) + return -1; + written += (size_t)count; + } + return 0; +} + +static void worker_release(T23HelixWorker *worker) +{ + if (!worker) + return; + if (worker->input_buffer) + IMP_Encoder_VbmFree(worker->input_buffer); + if (worker->output_buffer) + IMP_Encoder_VbmFree(worker->output_buffer); + if (worker->encoder) + IMP_Encoder_YuvExit(worker->encoder); + if (worker->subsystem_ready) + EncoderExit(); + worker->input_buffer = NULL; + worker->output_buffer = NULL; + worker->encoder = NULL; + worker->subsystem_ready = 0; +} + +static int worker_init(T23HelixWorker *worker, + const T23HelixIpcRequest *request) +{ + intptr_t physical; + uint64_t required; + + if (!worker || !request || request->width == 0u || + request->height == 0u || request->input_size == 0u || + request->input_size > request->input_capacity || + request->input_capacity % T23_HELIX_PAGE_SIZE != 0u || + request->output_capacity == 0u || + request->output_capacity % T23_HELIX_PAGE_SIZE != 0u) + return -EINVAL; + required = (uint64_t)request->input_capacity + request->output_capacity; + if (required > worker->shared_size) + return -EINVAL; + if (EncoderInit() != 0) + return -EIO; + worker->subsystem_ready = 1; + if (IMP_Encoder_YuvInit(&worker->encoder, (int)request->width, + (int)request->height, + (T23EncoderYuvIn *)&request->encoder_input) != 0 || + !worker->encoder) + return -EIO; + + worker->input_buffer = + IMP_Encoder_VbmAlloc(request->input_capacity, T23_HELIX_PAGE_SIZE); + worker->output_buffer = + IMP_Encoder_VbmAlloc(request->output_capacity, T23_HELIX_PAGE_SIZE); + if (!worker->input_buffer || !worker->output_buffer) + return -ENOMEM; + physical = IMP_Encoder_VbmV2P((intptr_t)(uintptr_t)worker->input_buffer); + if (physical <= 0 || (uintptr_t)physical > UINT32_MAX) + return -EFAULT; + + memset(worker->input_buffer, 0, request->input_capacity); + memset(worker->output_buffer, 0, request->output_capacity); + if (IMP_FlushCache(worker->input_buffer, request->input_capacity, + T23_CACHE_WBACK) != 0 || + IMP_FlushCache(worker->output_buffer, request->output_capacity, + T23_CACHE_WBACK) != 0) + return -EIO; + + worker->input_physical = (uint32_t)(uintptr_t)physical; + worker->input_size = request->input_size; + worker->input_capacity = request->input_capacity; + worker->output_capacity = request->output_capacity; + worker->width = request->width; + worker->height = request->height; + syslog(LOG_NOTICE, + "openimp/T23 helper: Helix ready %ux%u input=%u output=%u", + worker->width, worker->height, worker->input_size, + worker->output_capacity); + return 0; +} + +static int worker_encode(T23HelixWorker *worker, + const T23HelixIpcRequest *request, + T23HelixIpcResponse *response) +{ + IMPFrameInfo frame; + T23EncoderYuvOut output; + uintptr_t output_begin; + uintptr_t buffer_begin; + uintptr_t buffer_end; + unsigned char *shared_output; + + if (!worker || !request || !response || !worker->encoder || + request->width != worker->width || + request->height != worker->height || + request->input_size != worker->input_size) + return -EINVAL; + + memcpy(worker->input_buffer, worker->shared, worker->input_size); + if (IMP_FlushCache(worker->input_buffer, worker->input_size, + T23_CACHE_WBACK) != 0) + return -EIO; + + memset(&frame, 0, sizeof(frame)); + frame.index = -1; + frame.pool_idx = -1; + frame.width = worker->width; + frame.height = worker->height; + frame.pixfmt = request->pixel_format; + frame.size = worker->input_size; + frame.phyAddr = worker->input_physical; + frame.virAddr = (uint32_t)(uintptr_t)worker->input_buffer; + frame.direct_phyAddr = worker->input_physical; + frame.timeStamp = request->timestamp; + + output.outAddr = worker->output_buffer; + output.outLen = worker->output_capacity; + if (IMP_Encoder_YuvEncode(worker->encoder, frame, &output) != 0 || + !output.outAddr || !output.outLen) + return -EIO; + + buffer_begin = (uintptr_t)worker->output_buffer; + buffer_end = buffer_begin + worker->output_capacity; + output_begin = (uintptr_t)output.outAddr; + if (output_begin < buffer_begin || output_begin > buffer_end || + output.outLen > buffer_end - output_begin) + return -EOVERFLOW; + + shared_output = worker->shared + worker->input_capacity; + memcpy(shared_output, output.outAddr, output.outLen); + __sync_synchronize(); + response->output_offset = worker->input_capacity; + response->output_length = output.outLen; + return 0; +} + +static int parse_fd(const char *text) +{ + char *end = NULL; + long value; + + errno = 0; + value = strtol(text, &end, 10); + if (errno || !end || *end || value < 0 || value > 0x7fffffffL) + return -1; + return (int)value; +} + +static int parse_size(const char *text, size_t *size) +{ + char *end = NULL; + unsigned long value; + + errno = 0; + value = strtoul(text, &end, 10); + if (errno || !end || *end || !value || value > UINT32_MAX) + return -1; + *size = (size_t)value; + return 0; +} + +int main(int argc, char **argv) +{ + T23HelixWorker worker; + int shared_fd; + int result = 1; + + if (argc != 4) + return 2; + memset(&worker, 0, sizeof(worker)); + worker.socket_fd = parse_fd(argv[1]); + shared_fd = parse_fd(argv[2]); + if (worker.socket_fd < 0 || shared_fd < 0 || + parse_size(argv[3], &worker.shared_size) != 0) + return 2; + worker.shared = mmap(NULL, worker.shared_size, PROT_READ | PROT_WRITE, + MAP_SHARED, shared_fd, 0); + close(shared_fd); + if (worker.shared == MAP_FAILED) + return 2; + + signal(SIGPIPE, SIG_IGN); + openlog("openimp-t23-helixd", LOG_PID, LOG_DAEMON); + for (;;) { + T23HelixIpcRequest request; + T23HelixIpcResponse response; + int status; + + if (read_all(worker.socket_fd, &request, sizeof(request)) != 0) + break; + memset(&response, 0, sizeof(response)); + response.magic = T23_HELIX_IPC_MAGIC; + response.version = T23_HELIX_IPC_VERSION; + response.command = request.command; + if (request.magic != T23_HELIX_IPC_MAGIC || + request.version != T23_HELIX_IPC_VERSION) { + status = -EPROTO; + } else { + switch (request.command) { + case T23_HELIX_COMMAND_INIT: + status = worker.encoder ? -EALREADY + : worker_init(&worker, &request); + break; + case T23_HELIX_COMMAND_ENCODE: + status = worker_encode(&worker, &request, &response); + break; + case T23_HELIX_COMMAND_REQUEST_IDR: + status = worker.encoder + ? IMP_Encoder_YuvRequestIDR(worker.encoder) + : -EINVAL; + break; + case T23_HELIX_COMMAND_EXIT: + status = 0; + break; + default: + status = -ENOSYS; + break; + } + } + response.status = status; + if (write_all(worker.socket_fd, &response, sizeof(response)) != 0) + break; + if (request.command == T23_HELIX_COMMAND_EXIT) { + result = status ? 1 : 0; + break; + } + } + + worker_release(&worker); + munmap(worker.shared, worker.shared_size); + close(worker.socket_fd); + closelog(); + return result; +} diff --git a/src/t23/openimp_t23_persist.c b/src/t23/openimp_t23_persist.c new file mode 100644 index 0000000..e367901 --- /dev/null +++ b/src/t23/openimp_t23_persist.c @@ -0,0 +1,309 @@ +/* + * Short-lived T23 diagnostic trace which survives a watchdog reset. + * + * The T23 bootloader reserves an rmem arena for media buffers. The OpenIMP + * allocator grows upward from its base, so diagnostic builds may use the + * final 64 KiB while running the deliberately small, encoder-disabled smoke + * profile. Every record and the ring header are written back through the + * OEM rmem cache-maintenance ioctl before returning. + * + * This is opt-in because the tail is not reserved from normal media-buffer + * allocation. Never enable OPENIMP_RMEM_TRACE in a production profile. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "openimp_t23_persist.h" + +#define T23_TRACE_REGION_SIZE 0x10000u +#define T23_TRACE_VERSION 1u +#define RMEM_IOCTL_FLUSH_CACHE 0xc00c7200u +#define T23_TRACE_FILE_LIMIT 0x20000u + +typedef struct { + char magic[8]; + uint32_t version; + uint32_t header_size; + uint32_t capacity; + uint32_t write_pos; + uint32_t valid; + uint32_t records; + uint32_t wraps; + uint32_t rmem_base; + uint32_t rmem_size; + uint32_t reserved[5]; + unsigned char data[]; +} T23PersistHeader; + +typedef struct { + uint32_t addr; + uint32_t size; + uint32_t dir; +} T23RmemFlush; + +_Static_assert(sizeof(T23PersistHeader) == 64u, + "T23 persistent trace header must remain stable"); + +static pthread_mutex_t t23_trace_lock = PTHREAD_MUTEX_INITIALIZER; +static int t23_trace_checked; +static int t23_trace_enabled_value; +static int t23_trace_fd = -1; +static int t23_trace_file_fd = -1; +static uint32_t t23_trace_file_bytes; +static uint32_t t23_trace_file_records; +static void *t23_trace_mapping; +static T23PersistHeader *t23_trace_header; + +static int t23_parse_rmem(uint32_t *base_out, uint32_t *size_out) +{ + char cmdline[1024]; + char *field; + char *endp; + unsigned long amount; + unsigned long base; + uint64_t bytes; + FILE *file; + + file = fopen("/proc/cmdline", "r"); + if (file == NULL) + return -1; + if (fgets(cmdline, sizeof(cmdline), file) == NULL) { + fclose(file); + return -1; + } + fclose(file); + + field = strstr(cmdline, "rmem="); + if (field == NULL) + return -1; + field += strlen("rmem="); + amount = strtoul(field, &endp, 0); + if (endp == field || amount == 0) + return -1; + + bytes = amount; + if (*endp == 'M' || *endp == 'm') { + bytes *= 1024u * 1024u; + endp++; + } else if (*endp == 'K' || *endp == 'k') { + bytes *= 1024u; + endp++; + } + if (*endp != '@' || bytes < T23_TRACE_REGION_SIZE || + bytes > UINT32_MAX) + return -1; + + field = endp + 1; + base = strtoul(field, &endp, 0); + if (endp == field || base > UINT32_MAX || + (uint64_t)base + bytes > UINT32_MAX) + return -1; + + *base_out = (uint32_t)base; + *size_out = (uint32_t)bytes; + return 0; +} + +static void t23_flush(void *address, uint32_t size, uint32_t direction) +{ + T23RmemFlush flush; + + if (t23_trace_fd < 0 || address == NULL || size == 0) + return; + flush.addr = (uint32_t)(uintptr_t)address; + flush.size = size; + flush.dir = direction; + (void)ioctl(t23_trace_fd, RMEM_IOCTL_FLUSH_CACHE, &flush); +} + +static int t23_trace_init(void) +{ + uint32_t rmem_base; + uint32_t rmem_size; + uint32_t trace_phys; + + if (t23_trace_header != NULL) + return 0; + if (t23_parse_rmem(&rmem_base, &rmem_size) != 0) + return -1; + + trace_phys = rmem_base + rmem_size - T23_TRACE_REGION_SIZE; + t23_trace_fd = open("/dev/rmem", O_RDWR | O_CLOEXEC); + if (t23_trace_fd < 0) + return -1; + t23_trace_mapping = mmap(NULL, T23_TRACE_REGION_SIZE, + PROT_READ | PROT_WRITE, MAP_SHARED, + t23_trace_fd, (off_t)trace_phys); + if (t23_trace_mapping == MAP_FAILED) { + t23_trace_mapping = NULL; + close(t23_trace_fd); + t23_trace_fd = -1; + return -1; + } + + t23_trace_header = (T23PersistHeader *)t23_trace_mapping; + memset(t23_trace_header, 0, T23_TRACE_REGION_SIZE); + memcpy(t23_trace_header->magic, "OIT23TRC", 8u); + t23_trace_header->version = T23_TRACE_VERSION; + t23_trace_header->header_size = sizeof(*t23_trace_header); + t23_trace_header->capacity = + T23_TRACE_REGION_SIZE - sizeof(*t23_trace_header); + t23_trace_header->rmem_base = rmem_base; + t23_trace_header->rmem_size = rmem_size; + t23_flush(t23_trace_header, T23_TRACE_REGION_SIZE, 1u); + return 0; +} + +int openimp_t23_persist_enabled(void) +{ + if (!t23_trace_checked) { + const char *value = getenv("OPENIMP_RMEM_TRACE"); + const char *file = getenv("OPENIMP_PERSIST_TRACE_FILE"); + + t23_trace_enabled_value = + (value != NULL && value[0] != '\0' && value[0] != '0') || + (file != NULL && file[0] == '/'); + t23_trace_checked = 1; + } + return t23_trace_enabled_value; +} + +static int t23_trace_file_init(void) +{ + const char *path; + struct stat status; + + if (t23_trace_file_fd >= 0) + return 0; + path = getenv("OPENIMP_PERSIST_TRACE_FILE"); + if (path == NULL || path[0] != '/') + return -1; + t23_trace_file_fd = open(path, O_WRONLY | O_APPEND | O_CREAT | + O_SYNC | O_CLOEXEC, 0600); + if (t23_trace_file_fd < 0) + return -1; + if (fstat(t23_trace_file_fd, &status) == 0 && status.st_size > 0) + t23_trace_file_bytes = (uint64_t)status.st_size > UINT32_MAX + ? UINT32_MAX : (uint32_t)status.st_size; + return 0; +} + +static void t23_ring_copy(T23PersistHeader *header, const char *data, + uint32_t size) +{ + uint32_t first; + + if (size >= header->capacity) { + data += size - header->capacity; + size = header->capacity; + header->write_pos = 0; + header->valid = 0; + header->wraps++; + } + + first = header->capacity - header->write_pos; + if (first > size) + first = size; + memcpy(header->data + header->write_pos, data, first); + t23_flush(header->data + header->write_pos, first, 1u); + if (size > first) { + memcpy(header->data, data + first, size - first); + t23_flush(header->data, size - first, 1u); + header->wraps++; + } + header->write_pos = (header->write_pos + size) % header->capacity; + if (header->valid < header->capacity) { + uint32_t available = header->capacity - header->valid; + + header->valid += size < available ? size : available; + } +} + +void openimp_t23_persist_write(const char *message, size_t size) +{ + char record[512]; + const char *file_path; + int prefix_size; + uint32_t record_size; + uint32_t record_number; + + if (!openimp_t23_persist_enabled() || message == NULL || size == 0) + return; + + pthread_mutex_lock(&t23_trace_lock); + file_path = getenv("OPENIMP_PERSIST_TRACE_FILE"); + if (file_path != NULL && file_path[0] == '/') { + if (t23_trace_file_init() != 0) { + pthread_mutex_unlock(&t23_trace_lock); + return; + } + record_number = ++t23_trace_file_records; + } else { + if (t23_trace_init() != 0) { + pthread_mutex_unlock(&t23_trace_lock); + return; + } + record_number = t23_trace_header->records + 1u; + } + + prefix_size = snprintf(record, sizeof(record), "%06u ", + record_number); + if (prefix_size < 0) { + pthread_mutex_unlock(&t23_trace_lock); + return; + } + if (size > sizeof(record) - (size_t)prefix_size - 1u) + size = sizeof(record) - (size_t)prefix_size - 1u; + memcpy(record + prefix_size, message, size); + record_size = (uint32_t)prefix_size + (uint32_t)size; + if (record_size == 0 || record[record_size - 1u] != '\n') + record[record_size++] = '\n'; + + if (file_path != NULL && file_path[0] == '/') { + if (t23_trace_file_bytes + record_size <= T23_TRACE_FILE_LIMIT) { + ssize_t written = write(t23_trace_file_fd, record, record_size); + + if (written > 0) + t23_trace_file_bytes += (uint32_t)written; + (void)fsync(t23_trace_file_fd); + } + pthread_mutex_unlock(&t23_trace_lock); + return; + } + + t23_ring_copy(t23_trace_header, record, record_size); + t23_trace_header->records++; + t23_flush(t23_trace_header, sizeof(*t23_trace_header), 1u); + pthread_mutex_unlock(&t23_trace_lock); +} + +void openimp_t23_persist_trace(const char *format, ...) +{ + char message[448]; + va_list arguments; + int length; + size_t size; + + if (!openimp_t23_persist_enabled()) + return; + va_start(arguments, format); + length = vsnprintf(message, sizeof(message), format, arguments); + va_end(arguments); + if (length <= 0) + return; + size = (size_t)length; + if (size >= sizeof(message)) + size = sizeof(message) - 1u; + openimp_t23_persist_write(message, size); +} diff --git a/src/t23/openimp_t23_persist.h b/src/t23/openimp_t23_persist.h new file mode 100644 index 0000000..64b1b58 --- /dev/null +++ b/src/t23/openimp_t23_persist.h @@ -0,0 +1,11 @@ +#ifndef OPENIMP_T23_PERSIST_H +#define OPENIMP_T23_PERSIST_H + +#include + +int openimp_t23_persist_enabled(void); +void openimp_t23_persist_write(const char *message, size_t size); +void openimp_t23_persist_trace(const char *format, ...) + __attribute__((format(printf, 1, 2))); + +#endif diff --git a/src/t23/openimp_t23_services.c b/src/t23/openimp_t23_services.c new file mode 100644 index 0000000..f619962 --- /dev/null +++ b/src/t23/openimp_t23_services.c @@ -0,0 +1,177 @@ +/* + * T23 1.3.0 compatibility services. + * + * The T23 SDK exposes single-camera tuning calls and parallel + * IMP_ISP_MultiCamera_* entry points. Raptor is built against the latter, + * even on one-sensor products. OpenIMP has one ISP instance today, so map + * VI_MAIN to the working single-camera implementation and reject VI_SEC. + * + * ISP-integrated OSD is separate from the ordinary IMP_OSD bind-chain used + * by Raptor. Keep its small handle API ABI-correct for configurations which + * probe it; the actual ISP overlay plane is not implemented yet. + */ + +#include +#include + +#include +#include + +#define T23_VI_MAIN 0 +#define T23_ISP_OSD_HANDLES 8 + +static uint8_t isp_osd_handles[T23_ISP_OSD_HANDLES]; + +extern int IMP_ISP_Tuning_GetTotalGain(uint32_t *gain); +extern int IMP_ISP_Tuning_SetAeFreeze(int enable); + +static int t23_check_vi(int vi) +{ + return vi == T23_VI_MAIN ? 0 : -ENODEV; +} + +int IMP_ISP_MultiCamera_SetSwitchgpio(void *info) +{ + /* A one-sensor T23 has no sensor-switch GPIO to program. */ + (void)info; + return 0; +} + +#define T23_TUNING_WRAP_U8(name) \ + int IMP_ISP_MultiCamera_Tuning_##name(int vi, unsigned char value) \ + { \ + int ret = t23_check_vi(vi); \ + return ret ? ret : IMP_ISP_Tuning_##name(value); \ + } + +#define T23_TUNING_WRAP_U32(name) \ + int IMP_ISP_MultiCamera_Tuning_##name(int vi, uint32_t value) \ + { \ + int ret = t23_check_vi(vi); \ + return ret ? ret : IMP_ISP_Tuning_##name(value); \ + } + +T23_TUNING_WRAP_U8(SetBrightness) +T23_TUNING_WRAP_U8(SetContrast) +T23_TUNING_WRAP_U8(SetSaturation) +T23_TUNING_WRAP_U8(SetSharpness) +T23_TUNING_WRAP_U8(SetBcshHue) + +T23_TUNING_WRAP_U32(SetSinterStrength) +T23_TUNING_WRAP_U32(SetTemperStrength) +T23_TUNING_WRAP_U32(SetMaxAgain) +T23_TUNING_WRAP_U32(SetMaxDgain) + +int IMP_ISP_MultiCamera_Tuning_SetAeComp(int vi, int value) +{ + int ret = t23_check_vi(vi); + return ret ? ret : IMP_ISP_Tuning_SetAeComp(value); +} + +int IMP_ISP_MultiCamera_Tuning_SetAeFreeze(int vi, + IMPISPTuningOpsMode mode) +{ + int ret = t23_check_vi(vi); + return ret ? ret : IMP_ISP_Tuning_SetAeFreeze((int)mode); +} + +int IMP_ISP_MultiCamera_Tuning_SetAntiFlickerAttr( + int vi, IMPISPAntiflickerAttr attr) +{ + int ret = t23_check_vi(vi); + return ret ? ret : IMP_ISP_Tuning_SetAntiFlickerAttr(attr); +} + +int IMP_ISP_MultiCamera_Tuning_SetHVFLIP(int vi, IMPISPHVFLIP mode) +{ + int ret = t23_check_vi(vi); + return ret ? ret : IMP_ISP_Tuning_SetHVFLIP(mode); +} + +int IMP_ISP_MultiCamera_Tuning_SetISPCustomMode( + int vi, IMPISPTuningOpsMode mode) +{ + int ret = t23_check_vi(vi); + return ret ? ret : IMP_ISP_Tuning_SetISPCustomMode(mode); +} + +int IMP_ISP_MultiCamera_Tuning_SetISPRunningMode( + int vi, IMPISPRunningMode mode) +{ + int ret = t23_check_vi(vi); + return ret ? ret : IMP_ISP_Tuning_SetISPRunningMode(mode); +} + +int IMP_ISP_MultiCamera_Tuning_SetSensorFPS(int vi, uint32_t numerator, + uint32_t denominator) +{ + int ret = t23_check_vi(vi); + return ret ? ret : IMP_ISP_Tuning_SetSensorFPS(numerator, denominator); +} + +int IMP_ISP_MultiCamera_Tuning_GetTotalGain(int vi, uint32_t *gain) +{ + int ret = t23_check_vi(vi); + + if (ret) + return ret; + if (!gain) + return -EINVAL; + return IMP_ISP_Tuning_GetTotalGain(gain); +} + +int IMP_ISP_Tuning_SetOsdPoolSize(int size) +{ + return size >= 0 ? 0 : -EINVAL; +} + +int IMP_ISP_Tuning_CreateOsdRgn(int channel, IMPIspOsdAttrAsm *attr) +{ + int handle; + + (void)attr; + if (channel != T23_VI_MAIN) + return -ENODEV; + for (handle = 0; handle < T23_ISP_OSD_HANDLES; handle++) { + if (!isp_osd_handles[handle]) { + isp_osd_handles[handle] = 1; + return handle; + } + } + return -ENOSPC; +} + +int IMP_ISP_Tuning_DestroyOsdRgn(int channel, int handle) +{ + if (channel != T23_VI_MAIN) + return -ENODEV; + if (handle < 0 || handle >= T23_ISP_OSD_HANDLES || + !isp_osd_handles[handle]) + return -EINVAL; + isp_osd_handles[handle] = 0; + return 0; +} + +int IMP_ISP_Tuning_SetOsdRgnAttr(int channel, int handle, + IMPIspOsdAttrAsm *attr) +{ + (void)attr; + if (channel != T23_VI_MAIN) + return -ENODEV; + return handle >= 0 && handle < T23_ISP_OSD_HANDLES && + isp_osd_handles[handle] ? 0 : -EINVAL; +} + +int IMP_ISP_Tuning_ShowOsdRgn(int channel, int handle, int show) +{ + (void)show; + return IMP_ISP_Tuning_SetOsdRgnAttr(channel, handle, 0); +} + +int IMP_OSD_MultiCamera_SetRgnAttr_ISP(int vi, IMPOSDRgnAttr *attr, + int reserved) +{ + (void)attr; + (void)reserved; + return t23_check_vi(vi); +} diff --git a/src/t31/openimp_t31_state.c b/src/t31/openimp_t31_state.c index 68cc842..07f390d 100644 --- a/src/t31/openimp_t31_state.c +++ b/src/t31/openimp_t31_state.c @@ -32,7 +32,11 @@ uint64_t system_gettime(int clock_type) int32_t get_cpu_id(void) { +#if defined(PLATFORM_T23) + return 0x0f; /* T23-N */ +#else return 0x15; /* T31X */ +#endif } int32_t is_has_simd128(void) diff --git a/src/t40/codec-t40.c b/src/t40/codec-t40.c index 7ea5997..e125c5a 100644 --- a/src/t40/codec-t40.c +++ b/src/t40/codec-t40.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -27,6 +28,10 @@ #if defined(PLATFORM_T41) #include "t41_stream_layout.h" #endif +#if defined(PLATFORM_T23) +#include "t23/openimp_t23_helix_bridge.h" +#include "t23/openimp_t23_persist.h" +#endif #include "t40_ep1.h" #if defined(PLATFORM_T41) #include "t41_command_builder.h" @@ -64,6 +69,48 @@ enum { ((ctx) && ((ctx)->frames_encoded < 3 || ((ctx)->frames_encoded % AVPU_LOG_INTERVAL) == 0)) #define LOG_CODEC_THROTTLE(ctx, ...) do { if (AVPU_SHOULD_LOG(ctx)) LOG_CODEC(__VA_ARGS__); } while(0) +static void codec_startup_marker(const char *marker, size_t size) +{ + if (getenv("OPENIMP_STARTUP_TRACE")) + (void)write(STDERR_FILENO, marker, size); +#if defined(PLATFORM_T23) + openimp_t23_persist_write(marker, size); +#endif +} + +#define CODEC_STARTUP_MARKER(value) \ + codec_startup_marker((value), sizeof(value) - 1u) + +static void codec_startup_trace(const char *format, ...) +{ + char message[256]; + va_list arguments; + int length; + + if (!getenv("OPENIMP_STARTUP_TRACE") +#if defined(PLATFORM_T23) + && !openimp_t23_persist_enabled() +#endif + ) + return; + va_start(arguments, format); + length = vsnprintf(message, sizeof(message), format, arguments); + va_end(arguments); + if (length > 0) { + size_t size = (size_t)length; + + if (size >= sizeof(message)) + size = sizeof(message) - 1u; + if (getenv("OPENIMP_STARTUP_TRACE")) { + (void)write(STDERR_FILENO, message, size); + (void)fsync(STDERR_FILENO); + } +#if defined(PLATFORM_T23) + openimp_t23_persist_write(message, size); +#endif + } +} + typedef struct AL_CodecEncode AL_CodecEncode; static int avpu_queue_completed_stream(ALAvpuContext *ctx, int buf_idx, void *user_data, @@ -5566,6 +5613,9 @@ struct AL_CodecEncode { * by mmap/DMABUF-style consumers. */ HWStreamBuffer avpu_stream_descriptors[16]; ALAvpuContext avpu; /* Vendor-like AL over /dev/avpu (scaffolding) */ +#if defined(PLATFORM_T23) + T23HelixBridge t23_helix; /* T23 Helix /dev/soc_vpu bootstrap */ +#endif }; #if !defined(PLATFORM_T40) @@ -5969,13 +6019,24 @@ static void codec_sync_rc_cache(AL_CodecEncode *enc) qp = enc->hw_params.qp; rc->outFrmRate = enc->fps_cache; -#if !(defined(PLATFORM_T31) || defined(PLATFORM_T40) || defined(PLATFORM_T41)) +#if defined(PLATFORM_T23) + rc->maxGop = enc->gop_cache.gopLength; +#elif !(defined(PLATFORM_T31) || defined(PLATFORM_T40) || defined(PLATFORM_T41)) rc->attrGop = enc->gop_cache; #endif switch (rc->attrRcMode.rcMode) { case IMP_ENC_RC_MODE_CBR: -#if defined(PLATFORM_T31) || defined(PLATFORM_T40) || defined(PLATFORM_T41) +#if defined(PLATFORM_T23) + rc->attrRcMode.attrH264Cbr.outBitRate = bitrate_kbps / 1000u; + rc->attrRcMode.attrH264Cbr.maxQp = enc->hw_params.max_qp; + rc->attrRcMode.attrH264Cbr.minQp = enc->hw_params.min_qp; + rc->attrRcMode.attrH264Cbr.iBiasLvl = 0; + rc->attrRcMode.attrH264Cbr.frmQPStep = 0; + rc->attrRcMode.attrH264Cbr.gopQPStep = 0; + rc->attrRcMode.attrH264Cbr.adaptiveMode = false; + rc->attrRcMode.attrH264Cbr.gopRelation = false; +#elif defined(PLATFORM_T31) || defined(PLATFORM_T40) || defined(PLATFORM_T41) rc->attrRcMode.attrH264Cbr.uTargetBitRate = bitrate_kbps; rc->attrRcMode.attrH264Cbr.iInitialQP = (int16_t)clamp_qp_u32(qp); rc->attrRcMode.attrH264Cbr.iMinQP = (int16_t)clamp_qp_u32(enc->hw_params.min_qp); @@ -5998,9 +6059,20 @@ static void codec_sync_rc_cache(AL_CodecEncode *enc) break; case IMP_ENC_RC_MODE_VBR: +#if defined(PLATFORM_T23) + case IMP_ENC_RC_MODE_SMART: +#else case IMP_ENC_RC_MODE_CAPPED_VBR: case IMP_ENC_RC_MODE_CAPPED_QUALITY: -#if defined(PLATFORM_T31) || defined(PLATFORM_T40) || defined(PLATFORM_T41) +#endif +#if defined(PLATFORM_T23) + rc->attrRcMode.attrH264Vbr.maxBitRate = bitrate_kbps / 1000u; + rc->attrRcMode.attrH264Vbr.maxQp = enc->hw_params.max_qp; + rc->attrRcMode.attrH264Vbr.minQp = enc->hw_params.min_qp; + rc->attrRcMode.attrH264Vbr.staticTime = 1; + rc->attrRcMode.attrH264Vbr.changePos = 80; + rc->attrRcMode.attrH264Vbr.qualityLvl = 2; +#elif defined(PLATFORM_T31) || defined(PLATFORM_T40) || defined(PLATFORM_T41) rc->attrRcMode.attrH264Vbr.uTargetBitRate = bitrate_kbps; rc->attrRcMode.attrH264Vbr.uMaxBitRate = bitrate_kbps; rc->attrRcMode.attrH264Vbr.iInitialQP = (int16_t)clamp_qp_u32(qp); @@ -6024,7 +6096,9 @@ static void codec_sync_rc_cache(AL_CodecEncode *enc) case IMP_ENC_RC_MODE_FIXQP: default: -#if defined(PLATFORM_T31) || defined(PLATFORM_T40) || defined(PLATFORM_T41) +#if defined(PLATFORM_T23) + rc->attrRcMode.attrH264FixQp.qp = clamp_qp_u32(qp); +#elif defined(PLATFORM_T31) || defined(PLATFORM_T40) || defined(PLATFORM_T41) #if defined(PLATFORM_T41) rc->attrRcMode.attrH264FixQp.iInitialQP = (int16_t)clamp_qp_u32(qp); @@ -6356,6 +6430,9 @@ int AL_Codec_Encode_SetStreamBufferCount(void *codec, int count) * Creates a codec encoder instance */ int AL_Codec_Encode_Create(void **codec, void *params) { + CODEC_STARTUP_MARKER("openimp/codec marker A0 Create entry\n"); + codec_startup_trace("openimp/codec startup: Create entry codec=%p params=%p\n", + codec, params); if (codec == NULL || params == NULL) { LOG_CODEC("Create: NULL parameters"); return -1; @@ -6363,12 +6440,17 @@ int AL_Codec_Encode_Create(void **codec, void *params) { /* Allocate codec structure using real size */ AL_CodecEncode *enc = (AL_CodecEncode*)malloc(sizeof(AL_CodecEncode)); + CODEC_STARTUP_MARKER("openimp/codec marker A1 malloc returned\n"); + codec_startup_trace("openimp/codec startup: encoder malloc size=%u result=%p\n", + (unsigned int)sizeof(AL_CodecEncode), enc); if (enc == NULL) { LOG_CODEC("Create: malloc failed"); return -1; } memset(enc, 0, sizeof(AL_CodecEncode)); + CODEC_STARTUP_MARKER("openimp/codec marker A2 memset returned\n"); + codec_startup_trace("openimp/codec startup: encoder memset done\n"); /* Sentinel fd values: memset zeroed everything, but fd=0 is stdin, * which causes every 'if (enc->avpu.fd >= 0)' check to be true @@ -6394,7 +6476,9 @@ int AL_Codec_Encode_Create(void **codec, void *params) { enc->callback = NULL; enc->callback_arg = enc; int efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); + CODEC_STARTUP_MARKER("openimp/codec marker A3 eventfd returned\n"); if (efd >= 0) enc->event = (void*)(uintptr_t)efd; + codec_startup_trace("openimp/codec startup: eventfd=%d\n", efd); /* Set default buffer counts and sizes */ enc->frame_buf_count = 4; /* Default frame buffer count */ @@ -6406,6 +6490,9 @@ int AL_Codec_Encode_Create(void **codec, void *params) { int fifo_size = Fifo_SizeOf(); enc->fifo_frames = malloc(fifo_size); enc->fifo_streams = malloc(fifo_size); + CODEC_STARTUP_MARKER("openimp/codec marker A4 fifo malloc returned\n"); + codec_startup_trace("openimp/codec startup: fifo size=%d frames=%p streams=%p\n", + fifo_size, enc->fifo_frames, enc->fifo_streams); if (enc->fifo_frames == NULL || enc->fifo_streams == NULL) { LOG_CODEC("Create: FIFO alloc failed"); if (enc->fifo_frames) free(enc->fifo_frames); @@ -6415,6 +6502,8 @@ int AL_Codec_Encode_Create(void **codec, void *params) { } Fifo_Init(enc->fifo_frames, enc->frame_buf_count); Fifo_Init(enc->fifo_streams, enc->stream_buf_count); + CODEC_STARTUP_MARKER("openimp/codec marker A5 fifo init returned\n"); + codec_startup_trace("openimp/codec startup: fifo init done\n"); /* Set source FourCC to NV12 */ enc->src_fourcc = 0x3231564e; /* 'NV12' */ @@ -6478,11 +6567,21 @@ int AL_Codec_Encode_Create(void **codec, void *params) { enc->loop_filter_tc_offset = 0; codec_sync_rc_cache(enc); + CODEC_STARTUP_MARKER("openimp/codec marker A6 params complete\n"); + codec_startup_trace("openimp/codec startup: params done size=%ux%u bitrate=%u qp=%u/%u/%u\n", + (unsigned int)enc->hw_params.width, + (unsigned int)enc->hw_params.height, + (unsigned int)enc->hw_params.bitrate, + (unsigned int)enc->hw_params.qp, + (unsigned int)enc->hw_params.min_qp, + (unsigned int)enc->hw_params.max_qp); LOG_CODEC("Create: hardware encoder will be attempted via /dev/avpu (lazy init)"); /* Register in global instances */ pthread_mutex_lock(&g_codec_mutex); + CODEC_STARTUP_MARKER("openimp/codec marker A7 global lock acquired\n"); + codec_startup_trace("openimp/codec startup: global codec lock acquired\n"); for (int i = 0; i < 6; i++) { if (g_codec_instances[i] == NULL) { g_codec_instances[i] = enc; @@ -6490,6 +6589,9 @@ int AL_Codec_Encode_Create(void **codec, void *params) { pthread_mutex_unlock(&g_codec_mutex); *codec = enc; + CODEC_STARTUP_MARKER("openimp/codec marker A8 Create complete\n"); + codec_startup_trace("openimp/codec startup: Create done slot=%d codec=%p\n", + i, enc); LOG_CODEC("Create: codec=%p, channel=%d", enc, i); return 0; } @@ -6519,6 +6621,10 @@ int AL_Codec_Encode_Destroy(void *codec) { LOG_CODEC("Destroy: codec=%p, channel=%d", codec, enc->channel_id - 1); +#if defined(PLATFORM_T23) + OpenIMP_T23_HelixExit(&enc->t23_helix); +#endif + /* Deinitialize hardware encoder(s) - OEM parity: no separate deinit function */ if (enc->use_hardware == 2 && enc->avpu.fd >= 0) { #if defined(PLATFORM_T40) @@ -6965,12 +7071,54 @@ static int al_codec_encode_process_impl(void *codec, void *frame, uint64_t timestamp = 0; #if defined(PLATFORM_T31) memcpy(×tamp, (const uint8_t *)frame + 0x20, sizeof(timestamp)); -#elif defined(PLATFORM_T41) +#elif defined(PLATFORM_T41) || defined(PLATFORM_T23) memcpy(×tamp, (const uint8_t *)frame + 0x28, sizeof(timestamp)); #endif if (!timestamp) timestamp = IMP_System_GetTimeStamp(); +#if defined(PLATFORM_T23) + /* T23 has the Helix encoder at /dev/soc_vpu, not the Allegro AVPU used by + * the newer T-series backend below. Use Ingenic's standalone YUV codec + * seam in a stock-linked helper process while the native open Helix + * descriptor builder is brought over. Capture, binding and stream + * ownership remain entirely in OpenIMP. */ + if (codec_param_read_codec_type(enc->codec_param) == IMP_ENC_TYPE_AVC) { + if (getenv("OPENIMP_T23_SKIP_HELIX")) { + codec_set_error(enc, -1); + return -1; + } + if (enc->t23_helix.worker_pid <= 0 && !enc->t23_helix.failed) { + enc->hw_params.width = width; + enc->hw_params.height = height; + if (!enc->hw_params.fps_num) + enc->hw_params.fps_num = 25u; + if (!enc->hw_params.fps_den) + enc->hw_params.fps_den = 1u; + if (!enc->hw_params.gop_length) + enc->hw_params.gop_length = 25u; + if (!enc->hw_params.bitrate) + enc->hw_params.bitrate = 2000000u; + if (OpenIMP_T23_HelixInit(&enc->t23_helix, + &enc->hw_params) != 0) { + enc->use_hardware = 0; + LOG_CODEC("Process: T23 Helix init failed; using software fallback"); + } else { + enc->use_hardware = 3; + } + } + if (enc->t23_helix.worker_pid > 0) { + if (__sync_lock_test_and_set(&enc->force_next_idr, 0)) + OpenIMP_T23_HelixRequestIDR(&enc->t23_helix); + if (OpenIMP_T23_HelixEncode(&enc->t23_helix, + (const IMPFrameInfo *)frame, + &hw_stream) != 0) + return -1; + goto queue_encoded_stream; + } + } +#endif + if (enc->use_hardware) { /* Lazy-init hardware encoder on first frame */ if (enc->hw_encoder_fd < 0) { @@ -8312,6 +8460,7 @@ static int al_codec_encode_process_impl(void *codec, void *frame, } } +queue_encoded_stream: /* Queue encoded stream to FIFO */ if (Fifo_Queue(enc->fifo_streams, hw_stream, -1) == 0) { LOG_CODEC("Process: failed to queue stream"); @@ -8386,11 +8535,6 @@ int AL_Codec_Encode_GetStream(void *codec, void **stream, void **user_data) { *user_data = NULL; - { static unsigned int gs_count = 0; unsigned int c = __sync_add_and_fetch(&gs_count, 1); - if (c <= 5 || (c % 50) == 0) - LOG_CODEC("GetStream: use_hw=%d avpu.fd=%d [#%u]", enc->use_hardware, enc->avpu.fd, c); - } - if (enc->use_hardware == 2 && enc->avpu.fd >= 0) { ALAvpuContext *ctx = &enc->avpu; @@ -8631,7 +8775,9 @@ int AL_Codec_Encode_SetRcParam(void *codec, void *rcAttr) memcpy(&enc->rc_attr_cache, src, sizeof(*src)); enc->fps_cache = src->outFrmRate; -#if !(defined(PLATFORM_T31) || defined(PLATFORM_T40) || defined(PLATFORM_T41)) +#if defined(PLATFORM_T23) + enc->gop_cache.gopLength = src->maxGop; +#elif !(defined(PLATFORM_T31) || defined(PLATFORM_T40) || defined(PLATFORM_T41)) enc->gop_cache = src->attrGop; #endif if (enc->fps_cache.frmRateNum == 0) @@ -8654,7 +8800,16 @@ int AL_Codec_Encode_SetRcParam(void *codec, void *rcAttr) case IMP_ENC_RC_MODE_CBR: *(uint32_t *)(enc->codec_param + 0x6c) = HW_RC_MODE_CBR; enc->hw_params.rc_mode = HW_RC_MODE_CBR; -#if defined(PLATFORM_T31) || defined(PLATFORM_T40) || defined(PLATFORM_T41) +#if defined(PLATFORM_T23) + enc->hw_params.bitrate = + src->attrRcMode.attrH264Cbr.outBitRate * 1000u; + enc->hw_params.min_qp = + clamp_qp_u32(src->attrRcMode.attrH264Cbr.minQp); + enc->hw_params.max_qp = + clamp_qp_u32(src->attrRcMode.attrH264Cbr.maxQp); + enc->hw_params.qp = + (enc->hw_params.min_qp + enc->hw_params.max_qp) / 2u; +#elif defined(PLATFORM_T31) || defined(PLATFORM_T40) || defined(PLATFORM_T41) enc->hw_params.bitrate = src->attrRcMode.attrH264Cbr.uTargetBitRate; enc->hw_params.qp = clamp_qp_u32(src->attrRcMode.attrH264Cbr.iInitialQP); enc->hw_params.min_qp = clamp_qp_u32(src->attrRcMode.attrH264Cbr.iMinQP); @@ -8666,11 +8821,24 @@ int AL_Codec_Encode_SetRcParam(void *codec, void *rcAttr) #endif break; case IMP_ENC_RC_MODE_VBR: +#if defined(PLATFORM_T23) + case IMP_ENC_RC_MODE_SMART: +#else case IMP_ENC_RC_MODE_CAPPED_VBR: case IMP_ENC_RC_MODE_CAPPED_QUALITY: +#endif *(uint32_t *)(enc->codec_param + 0x6c) = HW_RC_MODE_VBR; enc->hw_params.rc_mode = HW_RC_MODE_VBR; -#if defined(PLATFORM_T31) || defined(PLATFORM_T40) || defined(PLATFORM_T41) +#if defined(PLATFORM_T23) + enc->hw_params.bitrate = + src->attrRcMode.attrH264Vbr.maxBitRate * 1000u; + enc->hw_params.min_qp = + clamp_qp_u32(src->attrRcMode.attrH264Vbr.minQp); + enc->hw_params.max_qp = + clamp_qp_u32(src->attrRcMode.attrH264Vbr.maxQp); + enc->hw_params.qp = + (enc->hw_params.min_qp + enc->hw_params.max_qp) / 2u; +#elif defined(PLATFORM_T31) || defined(PLATFORM_T40) || defined(PLATFORM_T41) enc->hw_params.bitrate = src->attrRcMode.attrH264Vbr.uTargetBitRate; if (enc->hw_params.bitrate == 0) enc->hw_params.bitrate = src->attrRcMode.attrH264Vbr.uMaxBitRate; @@ -8770,6 +8938,8 @@ int AL_Codec_Encode_SetQpIPDelta(void *codec, int delta) default: break; } +#elif defined(PLATFORM_T23) + (void)delta; #else enc->gop_cache.ipQpDelta = (uint32_t)delta; enc->rc_attr_cache.attrGop.ipQpDelta = (uint32_t)delta; @@ -8809,7 +8979,9 @@ int AL_Codec_Encode_SetGopParam(void *codec, void *gopAttr) *(uint32_t *)(enc->codec_param + 0xb0) = enc->gop_cache.gopLength; enc->hw_params.gop_length = enc->gop_cache.gopLength; enc->avpu.gop_length = enc->gop_cache.gopLength; -#if !(defined(PLATFORM_T31) || defined(PLATFORM_T40) || defined(PLATFORM_T41)) +#if defined(PLATFORM_T23) + enc->rc_attr_cache.maxGop = enc->gop_cache.gopLength; +#elif !(defined(PLATFORM_T31) || defined(PLATFORM_T40) || defined(PLATFORM_T41)) enc->rc_attr_cache.attrGop = enc->gop_cache; #endif codec_set_error(enc, 0); @@ -8936,6 +9108,10 @@ int AL_Codec_Encode_RequestIDR(void *codec) { } AL_CodecEncode *enc = (AL_CodecEncode*)codec; +#if defined(PLATFORM_T23) + if (enc->t23_helix.worker_pid > 0) + return OpenIMP_T23_HelixRequestIDR(&enc->t23_helix); +#endif __sync_lock_test_and_set(&enc->force_next_idr, 1); { static unsigned int idr_req_count = 0; unsigned int c = __sync_add_and_fetch(&idr_req_count, 1); diff --git a/src/t40/openimp_p0.c b/src/t40/openimp_p0.c index 1b22aa4..713322b 100644 --- a/src/t40/openimp_p0.c +++ b/src/t40/openimp_p0.c @@ -2,8 +2,11 @@ #include #include +#include +#include #include #include +#include #define OPENIMP_P0_MAGIC 0x50305434U /* "P0T4" */ #define OPENIMP_MAX_FS_CHANNELS 16 @@ -300,6 +303,12 @@ int IMP_System_Init(void) int result; uint64_t now; + if (getenv("OPENIMP_STARTUP_TRACE")) { + static const char message[] = "libimp/System: Init entry\n"; + + write(STDERR_FILENO, message, sizeof(message) - 1u); + fsync(STDERR_FILENO); + } lock_state(); if (state.initialized) { unlock_state(); @@ -313,7 +322,28 @@ int IMP_System_Init(void) } state.timestamp_base_us = now; for (root = 0; root < OPENIMP_ROOT_COUNT; root++) { + if (getenv("OPENIMP_STARTUP_TRACE")) { + char message[64]; + int length = snprintf(message, sizeof(message), + "libimp/System: root %d begin\n", root); + + if (length > 0) { + write(STDERR_FILENO, message, (size_t)length); + fsync(STDERR_FILENO); + } + } result = root_init((enum openimp_root_id)root); + if (getenv("OPENIMP_STARTUP_TRACE")) { + char message[64]; + int length = snprintf(message, sizeof(message), + "libimp/System: root %d ret=%d\n", root, + result); + + if (length > 0) { + write(STDERR_FILENO, message, (size_t)length); + fsync(STDERR_FILENO); + } + } if (result < 0) { while (--root >= 0) root_exit((enum openimp_root_id)root); @@ -347,7 +377,9 @@ int IMP_System_Exit(void) int IMP_System_GetVersion(char *version) { -#if defined(PLATFORM_T31) +#if defined(PLATFORM_T23) + static const char text[] = "libimp.so T23 openimp"; +#elif defined(PLATFORM_T31) static const char text[] = "libimp.so T31 openimp"; #elif defined(PLATFORM_T41) static const char text[] = "libimp.so T41 openimp"; @@ -364,7 +396,9 @@ int IMP_System_GetVersion(char *version) const char *IMP_System_GetCPUInfo(void) { -#if defined(PLATFORM_T31) +#if defined(PLATFORM_T23) + return "T23-N"; +#elif defined(PLATFORM_T31) return "T31X"; #elif defined(PLATFORM_T41) return "T41NQ"; diff --git a/src/t40/openimp_p2_encoder.c b/src/t40/openimp_p2_encoder.c index 8f63d75..74ced72 100644 --- a/src/t40/openimp_p2_encoder.c +++ b/src/t40/openimp_p2_encoder.c @@ -12,6 +12,9 @@ #include #include "openimp_profile.h" +#if defined(PLATFORM_T23) +#include "t23/openimp_t23_persist.h" +#endif #include #include @@ -23,7 +26,7 @@ /* T40 1.3.1 ends IMPEncoderStream after isVI and pads it to 28 bytes. T31 * 1.1.6 includes the streamInfo/jpegInfo union, matching the compatibility * header. Write exactly the ABI selected for the target caller. */ -#if defined(PLATFORM_T31) +#if defined(PLATFORM_T31) || defined(PLATFORM_T23) #define P2_ENCODER_STREAM_ABI_SIZE sizeof(IMPEncoderStream) #else #define P2_ENCODER_STREAM_ABI_SIZE 28u @@ -36,7 +39,7 @@ typedef enum { static P2CaptureReleasePolicy p2_capture_release_policy(void) { -#if defined(PLATFORM_T31) +#if defined(PLATFORM_T31) || defined(PLATFORM_T23) return P2_CAPTURE_RELEASE_AFTER_COMPLETION; #elif defined(PLATFORM_T40) || defined(PLATFORM_T41) return P2_CAPTURE_RELEASE_AFTER_SUBMIT; @@ -112,6 +115,7 @@ typedef struct { P2SyntheticFrame synthetic_frame; IMPEncoderCHNAttr attr; IMPEncoderPack pack; + IMPEncoderJpegeQl jpeg_quality; uint32_t sequence; uint32_t stream_buf_size; int max_stream_count; @@ -130,6 +134,48 @@ static P2Bind p2_binds[P2_MAX_BINDS]; static pthread_mutex_t p2_state_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t p2_core_lock = PTHREAD_MUTEX_INITIALIZER; +static void p2_startup_marker(const char *marker, size_t size) +{ + if (getenv("OPENIMP_STARTUP_TRACE")) + (void)write(STDERR_FILENO, marker, size); +#if defined(PLATFORM_T23) + openimp_t23_persist_write(marker, size); +#endif +} + +#define P2_STARTUP_MARKER(value) \ + p2_startup_marker((value), sizeof(value) - 1u) + +static void p2_startup_trace(const char *format, ...) +{ + char message[256]; + va_list arguments; + int length; + + if (!getenv("OPENIMP_STARTUP_TRACE") +#if defined(PLATFORM_T23) + && !openimp_t23_persist_enabled() +#endif + ) + return; + va_start(arguments, format); + length = vsnprintf(message, sizeof(message), format, arguments); + va_end(arguments); + if (length > 0) { + size_t size = (size_t)length; + + if (size >= sizeof(message)) + size = sizeof(message) - 1u; + if (getenv("OPENIMP_STARTUP_TRACE")) { + (void)write(STDERR_FILENO, message, size); + (void)fsync(STDERR_FILENO); + } +#if defined(PLATFORM_T23) + openimp_t23_persist_write(message, size); +#endif + } +} + static void p2_trace(const char *format, ...) { char message[256]; @@ -137,9 +183,13 @@ static void p2_trace(const char *format, ...) int fd; int length; - fd = open("/dev/kmsg", O_WRONLY); - if (fd < 0) + if (!getenv("OPENIMP_P2_TRACE") +#if defined(PLATFORM_T23) + && !openimp_t23_persist_enabled() +#endif + ) return; + va_start(arguments, format); length = vsnprintf(message, sizeof(message), format, arguments); va_end(arguments); @@ -148,9 +198,17 @@ static void p2_trace(const char *format, ...) if (size > sizeof(message)) size = sizeof(message); - write(fd, message, size); + if (getenv("OPENIMP_P2_TRACE")) { + fd = open("/dev/kmsg", O_WRONLY); + if (fd >= 0) { + write(fd, message, size); + close(fd); + } + } +#if defined(PLATFORM_T23) + openimp_t23_persist_write(message, size); +#endif } - close(fd); } static uint64_t p2_monotonic_us(void) @@ -271,8 +329,91 @@ static int p2_find_source_channel(int encoder_group) return encoder_group; } +static uint32_t p2_attr_codec_type(const IMPEncoderCHNAttr *attr) +{ +#if defined(PLATFORM_T23) + switch (attr->encAttr.enType) { + case PT_JPEG: + return IMP_ENC_TYPE_JPEG; + case PT_H265: + return IMP_ENC_TYPE_HEVC; + case PT_H264: + default: + return IMP_ENC_TYPE_AVC; + } +#else + return (((uint32_t)attr->encAttr.profile) >> 24) & 0xffu; +#endif +} + +static uint32_t p2_attr_profile(const IMPEncoderCHNAttr *attr) +{ +#if defined(PLATFORM_T23) + uint32_t codec_type = p2_attr_codec_type(attr); + + if (codec_type == IMP_ENC_TYPE_JPEG) + return IMP_ENC_PROFILE_JPEG; + if (codec_type == IMP_ENC_TYPE_HEVC) + return IMP_ENC_PROFILE_HEVC_MAIN; + switch (attr->encAttr.profile) { + case 2: + case IMP_ENC_AVC_PROFILE_IDC_HIGH: + return IMP_ENC_PROFILE_AVC_HIGH; + case 1: + case IMP_ENC_AVC_PROFILE_IDC_MAIN: + return IMP_ENC_PROFILE_AVC_MAIN; + default: + return IMP_ENC_PROFILE_AVC_BASELINE; + } +#else + return (uint32_t)attr->encAttr.profile; +#endif +} + +static uint32_t p2_attr_width(const IMPEncoderCHNAttr *attr) +{ +#if defined(PLATFORM_T23) + return attr->encAttr.picWidth; +#else + return attr->encAttr.maxPicWidth; +#endif +} + +static uint32_t p2_attr_height(const IMPEncoderCHNAttr *attr) +{ +#if defined(PLATFORM_T23) + return attr->encAttr.picHeight; +#else + return attr->encAttr.maxPicHeight; +#endif +} + +static uint32_t p2_attr_gop_length(const IMPEncoderCHNAttr *attr) +{ +#if defined(PLATFORM_T23) + return attr->rcAttr.maxGop; +#else + return attr->gopAttr.uGopLength; +#endif +} + static uint32_t p2_attr_bitrate_kbps(const IMPEncoderCHNAttr *attr) { +#if defined(PLATFORM_T23) + switch (attr->rcAttr.attrRcMode.rcMode) { + case IMP_ENC_RC_MODE_CBR: + return p2_attr_codec_type(attr) == IMP_ENC_TYPE_HEVC + ? attr->rcAttr.attrRcMode.attrH265Cbr.outBitRate + : attr->rcAttr.attrRcMode.attrH264Cbr.outBitRate; + case IMP_ENC_RC_MODE_VBR: + case IMP_ENC_RC_MODE_SMART: + return p2_attr_codec_type(attr) == IMP_ENC_TYPE_HEVC + ? attr->rcAttr.attrRcMode.attrH265Vbr.maxBitRate + : attr->rcAttr.attrRcMode.attrH264Vbr.maxBitRate; + default: + return 0; + } +#else switch (attr->rcAttr.attrRcMode.rcMode) { case IMP_ENC_RC_MODE_CBR: return attr->rcAttr.attrRcMode.attrCbr.uTargetBitRate; @@ -285,12 +426,13 @@ static uint32_t p2_attr_bitrate_kbps(const IMPEncoderCHNAttr *attr) default: return 0; } +#endif } static void p2_codec_params(unsigned char *params, const IMPEncoderCHNAttr *attr) { - uint32_t profile = (uint32_t)attr->encAttr.profile; - uint32_t codec_type = (profile >> 24) & 0xffu; + uint32_t profile = p2_attr_profile(attr); + uint32_t codec_type = p2_attr_codec_type(attr); uint32_t fps_num = attr->rcAttr.outFrmRate.frmRateNum; uint32_t fps_den = attr->rcAttr.outFrmRate.frmRateDen; uint32_t bitrate = p2_attr_bitrate_kbps(attr) * 1000u; @@ -298,18 +440,45 @@ static void p2_codec_params(unsigned char *params, const IMPEncoderCHNAttr *attr int min_qp = 15; int max_qp = 45; + p2_startup_trace("openimp/P2 startup: codec params defaults begin\n"); AL_Codec_Encode_SetDefaultParam(params); - *(uint16_t *)(params + 0x08) = attr->encAttr.maxPicWidth; - *(uint16_t *)(params + 0x0a) = attr->encAttr.maxPicHeight; - *(uint16_t *)(params + 0x0c) = attr->encAttr.maxPicWidth; - *(uint16_t *)(params + 0x0e) = attr->encAttr.maxPicHeight; + p2_startup_trace("openimp/P2 startup: codec params defaults done\n"); + *(uint16_t *)(params + 0x08) = (uint16_t)p2_attr_width(attr); + *(uint16_t *)(params + 0x0a) = (uint16_t)p2_attr_height(attr); + *(uint16_t *)(params + 0x0c) = (uint16_t)p2_attr_width(attr); + *(uint16_t *)(params + 0x0e) = (uint16_t)p2_attr_height(attr); *(uint32_t *)(params + 0x20) = profile; *(uint32_t *)(params + 0x6c) = (uint32_t)attr->rcAttr.attrRcMode.rcMode; *(uint16_t *)(params + 0x78) = (uint16_t)(fps_num ? fps_num : 25u); *(uint16_t *)(params + 0x7a) = (uint16_t)((fps_den ? fps_den : 1u) * 1000u); *(uint32_t *)(params + 0x7c) = bitrate; - *(uint32_t *)(params + 0xb0) = attr->gopAttr.uGopLength; + *(uint32_t *)(params + 0xb0) = p2_attr_gop_length(attr); +#if defined(PLATFORM_T23) + if (attr->rcAttr.attrRcMode.rcMode == IMP_ENC_RC_MODE_FIXQP) { + qp = codec_type == IMP_ENC_TYPE_HEVC + ? (int)attr->rcAttr.attrRcMode.attrH265FixQp.qp + : (int)attr->rcAttr.attrRcMode.attrH264FixQp.qp; + } else if (attr->rcAttr.attrRcMode.rcMode == IMP_ENC_RC_MODE_CBR) { + if (codec_type == IMP_ENC_TYPE_HEVC) { + min_qp = (int)attr->rcAttr.attrRcMode.attrH265Cbr.minQp; + max_qp = (int)attr->rcAttr.attrRcMode.attrH265Cbr.maxQp; + } else { + min_qp = (int)attr->rcAttr.attrRcMode.attrH264Cbr.minQp; + max_qp = (int)attr->rcAttr.attrRcMode.attrH264Cbr.maxQp; + } + qp = (min_qp + max_qp) / 2; + } else { + if (codec_type == IMP_ENC_TYPE_HEVC) { + min_qp = (int)attr->rcAttr.attrRcMode.attrH265Vbr.minQp; + max_qp = (int)attr->rcAttr.attrRcMode.attrH265Vbr.maxQp; + } else { + min_qp = (int)attr->rcAttr.attrRcMode.attrH264Vbr.minQp; + max_qp = (int)attr->rcAttr.attrRcMode.attrH264Vbr.maxQp; + } + qp = (min_qp + max_qp) / 2; + } +#else if (attr->rcAttr.attrRcMode.rcMode == IMP_ENC_RC_MODE_FIXQP) { qp = attr->rcAttr.attrRcMode.attrFixQp.iInitialQP; } else if (attr->rcAttr.attrRcMode.rcMode == IMP_ENC_RC_MODE_CBR) { @@ -321,6 +490,7 @@ static void p2_codec_params(unsigned char *params, const IMPEncoderCHNAttr *attr min_qp = attr->rcAttr.attrRcMode.attrVbr.iMinQP; max_qp = attr->rcAttr.attrRcMode.attrVbr.iMaxQP; } +#endif if (codec_type == IMP_ENC_TYPE_JPEG && qp < 1) qp = 25; if (qp < 1 || qp > 51) @@ -336,13 +506,19 @@ static void p2_codec_params(unsigned char *params, const IMPEncoderCHNAttr *attr *(uint16_t *)(params + 0x84) = (uint16_t)qp; *(uint8_t *)(params + 0x86) = (uint8_t)min_qp; *(uint16_t *)(params + 0x88) = (uint16_t)max_qp; + p2_startup_trace("openimp/P2 startup: codec params done rc=%u bitrate=%u qp=%d/%d/%d\n", + (unsigned int)attr->rcAttr.attrRcMode.rcMode, + (unsigned int)bitrate, qp, min_qp, max_qp); } int EncoderInit(void) { int i; + p2_startup_trace("openimp/P2 startup: EncoderInit entry initialized=%d\n", + p2_initialized); pthread_mutex_lock(&p2_state_lock); + p2_startup_trace("openimp/P2 startup: EncoderInit state lock acquired\n"); if (!p2_initialized) { memset(p2_groups, 0, sizeof(p2_groups)); memset(p2_channels, 0, sizeof(p2_channels)); @@ -355,8 +531,10 @@ int EncoderInit(void) pthread_mutex_init(&p2_channels[i].lock, NULL); } p2_initialized = 1; + p2_startup_trace("openimp/P2 startup: EncoderInit channel locks initialized\n"); } pthread_mutex_unlock(&p2_state_lock); + p2_startup_trace("openimp/P2 startup: EncoderInit done\n"); return 0; } @@ -479,24 +657,39 @@ int IMP_Encoder_CreateChn(int channel, IMPEncoderCHNAttr *attr) P2EncoderChannel *ch; unsigned char params[P2_PARAM_SIZE]; + P2_STARTUP_MARKER("openimp/P2 marker C0 CreateChn entry\n"); if (!p2_valid_channel(channel) || !attr) return -1; + P2_STARTUP_MARKER("openimp/P2 marker C1 args valid\n"); EncoderInit(); + P2_STARTUP_MARKER("openimp/P2 marker C2 EncoderInit returned\n"); + p2_startup_trace("openimp/P2 startup: CreateChn after EncoderInit\n"); p2_trace("openimp/P2: CreateChn begin ch=%d profile=0x%x size=%ux%u\n", - channel, (unsigned int)attr->encAttr.profile, - (unsigned int)attr->encAttr.maxPicWidth, - (unsigned int)attr->encAttr.maxPicHeight); + channel, (unsigned int)p2_attr_profile(attr), + (unsigned int)p2_attr_width(attr), + (unsigned int)p2_attr_height(attr)); ch = &p2_channels[channel]; + P2_STARTUP_MARKER("openimp/P2 marker C3 before channel lock\n"); + p2_startup_trace("openimp/P2 startup: CreateChn locking channel\n"); pthread_mutex_lock(&ch->lock); + P2_STARTUP_MARKER("openimp/P2 marker C4 channel locked\n"); + p2_startup_trace("openimp/P2 startup: CreateChn channel lock acquired\n"); if (ch->created) { pthread_mutex_unlock(&ch->lock); return -1; } + P2_STARTUP_MARKER("openimp/P2 marker C5 before codec params\n"); p2_codec_params(params, attr); + P2_STARTUP_MARKER("openimp/P2 marker C6 codec params returned\n"); + p2_startup_trace("openimp/P2 startup: CreateChn calling codec create\n"); + P2_STARTUP_MARKER("openimp/P2 marker C7 before codec create\n"); if (AL_Codec_Encode_Create(&ch->codec, params) != 0) { pthread_mutex_unlock(&ch->lock); return -1; } + P2_STARTUP_MARKER("openimp/P2 marker C8 codec create returned\n"); + p2_startup_trace("openimp/P2 startup: CreateChn codec created %p\n", + ch->codec); if (AL_Codec_Encode_SetStreamBufferCount(ch->codec, ch->max_stream_count) != 0) { AL_Codec_Encode_Destroy(ch->codec); @@ -504,6 +697,8 @@ int IMP_Encoder_CreateChn(int channel, IMPEncoderCHNAttr *attr) pthread_mutex_unlock(&ch->lock); return -1; } + p2_startup_trace("openimp/P2 startup: CreateChn stream count set=%d\n", + ch->max_stream_count); if (ch->stream_buf_size != 0u && AL_Codec_Encode_SetStreamBufferSize(ch->codec, (int)ch->stream_buf_size) != 0) { @@ -513,7 +708,7 @@ int IMP_Encoder_CreateChn(int channel, IMPEncoderCHNAttr *attr) return -1; } ch->attr = *attr; - ch->codec_type = (((uint32_t)attr->encAttr.profile) >> 24) & 0xffu; + ch->codec_type = (int)p2_attr_codec_type(attr); ch->created = 1; ch->group = -1; ch->source_channel = -1; @@ -636,6 +831,7 @@ int IMP_Encoder_StopRecvPic(int channel) int IMP_Encoder_PollingStream(int channel, uint32_t timeout_ms) { static unsigned int trace_count; + static unsigned int t23_avc_trace_count; P2EncoderChannel *ch; void *frame = NULL; void *stream = NULL; @@ -670,15 +866,21 @@ int IMP_Encoder_PollingStream(int channel, uint32_t timeout_ms) return -1; } /* - * The physical FrameSource ioctl blocks at sensor cadence. Do not add a - * second clock to AVC: sleeping until the same edge and then issuing - * WAIT_FRAME races past that edge and waits an additional sensor period. - * The metadata-only JPEG fallback has no FrameSource wait, so it still - * needs explicit pacing. + * The physical FrameSource ioctl normally blocks at sensor cadence. T23 + * is the exception: its recovered frame-channel currently continues at + * sensor cadence even when Raptor requests a lower encoder frame rate. + * The standalone Helix API does not drop those excess input frames and + * eventually wedges the SoC under sustained 1080p load. Pace T23 AVC at + * the public channel rate before dequeuing; other AVC backends retain the + * one-clock behavior. */ interval_us = 0u; wait_us = 0u; - if (ch->codec_type == IMP_ENC_TYPE_JPEG) { + if (ch->codec_type == IMP_ENC_TYPE_JPEG +#if defined(PLATFORM_T23) + || ch->codec_type == IMP_ENC_TYPE_AVC +#endif + ) { fps_num = ch->attr.rcAttr.outFrmRate.frmRateNum; fps_den = ch->attr.rcAttr.outFrmRate.frmRateDen; if (!fps_num || !fps_den) { @@ -724,8 +926,8 @@ int IMP_Encoder_PollingStream(int channel, uint32_t timeout_ms) memset(&ch->synthetic_frame, 0, sizeof(ch->synthetic_frame)); ch->synthetic_frame.index = -1; ch->synthetic_frame.pool_index = -1; - ch->synthetic_frame.width = ch->attr.encAttr.maxPicWidth; - ch->synthetic_frame.height = ch->attr.encAttr.maxPicHeight; + ch->synthetic_frame.width = p2_attr_width(&ch->attr); + ch->synthetic_frame.height = p2_attr_height(&ch->attr); ch->synthetic_frame.pixel_format = 10u; /* PIX_FMT_NV12 */ frame = &ch->synthetic_frame; } else if (IMP_FrameSource_GetFrame(ch->source_channel, &frame) != 0) { @@ -736,6 +938,12 @@ int IMP_Encoder_PollingStream(int channel, uint32_t timeout_ms) channel, ch->source_channel, frame); if (AL_Codec_Encode_Process(ch->codec, frame, frame) != 0) goto done; +#if defined(PLATFORM_T23) + if (ch->codec_type == IMP_ENC_TYPE_AVC && + __sync_add_and_fetch(&t23_avc_trace_count, 1u) <= 16u) + p2_trace("openimp/P2: encode returned frame=%u ptr=%p\n", + t23_avc_trace_count - 1u, frame); +#endif /* * Capture ownership differs at the stock-driver seam. T31 1080p can * outlast one sensor interval, so it must retain the source until AVPU @@ -761,12 +969,22 @@ int IMP_Encoder_PollingStream(int channel, uint32_t timeout_ms) } if (!stream) goto done; +#if defined(PLATFORM_T23) + if (ch->codec_type == IMP_ENC_TYPE_AVC && t23_avc_trace_count <= 16u) + p2_trace("openimp/P2: stream dequeued frame=%u stream=%p user=%p\n", + t23_avc_trace_count - 1u, stream, user); +#endif openimp_profile_count(OPENIMP_PROFILE_GETSTREAM_RETRIES, (uint64_t)retry); if (p2_capture_release_policy() == P2_CAPTURE_RELEASE_AFTER_COMPLETION && frame != &ch->synthetic_frame && IMP_FrameSource_ReleaseFrame(ch->source_channel, frame) == 0) frame = NULL; +#if defined(PLATFORM_T23) + if (ch->codec_type == IMP_ENC_TYPE_AVC && t23_avc_trace_count <= 16u) + p2_trace("openimp/P2: capture released frame=%u remaining=%p\n", + t23_avc_trace_count - 1u, frame); +#endif pthread_mutex_lock(&ch->lock); ch->source_frame = frame ? frame : &ch->synthetic_frame; ch->raw_stream = stream; @@ -828,6 +1046,7 @@ int IMP_Encoder_PollingModuleStream(uint32_t *channel_bitmap, int IMP_Encoder_GetStream(int channel, IMPEncoderStream *stream, int block) { + static unsigned int t23_get_trace_count; P2EncoderChannel *ch; P2HWStream *raw; int is_idr; @@ -852,6 +1071,30 @@ int IMP_Encoder_GetStream(int channel, IMPEncoderStream *stream, int block) #endif memset(stream, 0, P2_ENCODER_STREAM_ABI_SIZE); memset(&ch->pack, 0, sizeof(ch->pack)); +#if defined(PLATFORM_T23) + ch->pack.phyAddr = raw->phys_addr; + ch->pack.virAddr = raw->virt_addr; + ch->pack.length = raw->length; + ch->pack.timestamp = raw->timestamp + ? (int64_t)raw->timestamp : (int64_t)p2_monotonic_us(); + ch->pack.frameEnd = true; + ch->pack.dataType.h264Type = ch->codec_type == IMP_ENC_TYPE_JPEG + ? IMP_H264_NAL_UNKNOWN + : (is_idr ? IMP_H264_NAL_SLICE_IDR : IMP_H264_NAL_SLICE); + stream->pack = &ch->pack; + stream->packCount = 1; + stream->seq = ch->sequence++; + stream->refType = is_idr ? IMP_Encoder_FSTYPE_IDR + : IMP_Encoder_FSTYPE_SBASE; +#if defined(PLATFORM_T23) + if (__sync_add_and_fetch(&t23_get_trace_count, 1u) <= 16u) + p2_trace("openimp/P2: get public frame=%u ch=%d seq=%u " + "raw=%p addr=0x%08x len=%u idr=%d nal=%d\n", + t23_get_trace_count - 1u, channel, stream->seq, + (void *)raw, ch->pack.virAddr, ch->pack.length, is_idr, + (int)ch->pack.dataType.h264Type); +#endif +#else ch->pack.offset = 0; ch->pack.length = raw->length; ch->pack.timestamp = raw->timestamp @@ -868,12 +1111,14 @@ int IMP_Encoder_GetStream(int channel, IMPEncoderStream *stream, int block) stream->packCount = 1; stream->seq = ch->sequence++; stream->isVI = false; +#endif pthread_mutex_unlock(&ch->lock); return 0; } int IMP_Encoder_ReleaseStream(int channel, IMPEncoderStream *stream) { + static unsigned int t23_release_trace_count; P2EncoderChannel *ch; void *raw; void *user; @@ -893,6 +1138,12 @@ int IMP_Encoder_ReleaseStream(int channel, IMPEncoderStream *stream) pthread_mutex_unlock(&ch->lock); if (!raw || !frame) return -1; +#if defined(PLATFORM_T23) + if (__sync_add_and_fetch(&t23_release_trace_count, 1u) <= 16u) + p2_trace("openimp/P2: release public frame=%u raw=%p user=%p " + "source=%p\n", t23_release_trace_count - 1u, raw, user, + frame); +#endif result = AL_Codec_Encode_ReleaseStream(ch->codec, raw, user); if (frame != &ch->synthetic_frame && IMP_FrameSource_ReleaseFrame(ch->source_channel, frame) != 0) @@ -902,11 +1153,28 @@ int IMP_Encoder_ReleaseStream(int channel, IMPEncoderStream *stream) int IMP_Encoder_RequestIDR(int channel) { +#if defined(PLATFORM_T23) + static unsigned int t23_idr_request_count; +#endif + if (!p2_valid_channel(channel) || !p2_channels[channel].codec) return -1; if (p2_channels[channel].codec_type == IMP_ENC_TYPE_JPEG) return 0; +#if defined(PLATFORM_T23) + /* IMP_Encoder_YuvRequestIDR can leave the standalone Helix encoder in a + * permanently asserted IRQ state when it is called after streaming has + * begun. Raptor requests an IDR whenever an RTSP client joins, and the + * next YuvEncode then wedges in the stock IRQ handler. The configured + * maxGop already emits regular SPS/PPS/IDR access units, so acknowledge + * the hint and let the natural GOP provide the next safe join point. */ + if (__sync_add_and_fetch(&t23_idr_request_count, 1u) <= 16u) + p2_trace("openimp/P2: T23 IDR request deferred to natural GOP " + "ch=%d request=%u\n", channel, t23_idr_request_count); + return 0; +#else return AL_Codec_Encode_RequestIDR(p2_channels[channel].codec); +#endif } int IMP_Encoder_Query(int channel, IMPEncoderCHNStat *stat) @@ -914,6 +1182,9 @@ int IMP_Encoder_Query(int channel, IMPEncoderCHNStat *stat) if (!p2_valid_channel(channel) || !stat || !p2_channels[channel].created) return -1; memset(stat, 0, sizeof(*stat)); +#if defined(PLATFORM_T23) + stat->registered = p2_channels[channel].registered != 0; +#endif stat->leftPics = p2_channels[channel].raw_stream ? 1u : 0u; stat->curPacks = p2_channels[channel].raw_stream ? 1u : 0u; return 0; @@ -927,6 +1198,7 @@ int IMP_Encoder_GetChnAttr(int channel, IMPEncoderChnAttr *attr) return 0; } +#if !defined(PLATFORM_T23) int IMP_Encoder_SetDefaultParam(IMPEncoderChnAttr *attr, IMPEncoderProfile profile, IMPEncoderRcMode rc_mode, int width, int height, int fps_num, int fps_den, int gop_length, @@ -965,6 +1237,7 @@ int IMP_Encoder_SetDefaultParam(IMPEncoderChnAttr *attr, IMPEncoderProfile profi } return 0; } +#endif int IMP_Encoder_FlushStream(int channel) { @@ -978,7 +1251,18 @@ int IMP_Encoder_SetbufshareChn(int channel, int share_channel) int IMP_Encoder_SetJpegeQl(int channel, IMPEncoderJpegeQl *quality) { - return p2_valid_channel(channel) && quality ? 0 : -1; + if (!p2_valid_channel(channel) || !quality) + return -1; + p2_channels[channel].jpeg_quality = *quality; + return 0; +} + +int IMP_Encoder_GetJpegeQl(int channel, IMPEncoderJpegeQl *quality) +{ + if (!p2_valid_channel(channel) || !quality) + return -1; + *quality = p2_channels[channel].jpeg_quality; + return 0; } int IMP_Encoder_SetMaxStreamCnt(int channel, int count) @@ -1035,12 +1319,18 @@ int IMP_Encoder_GetStreamBufSize(int channel, int *size) * T40 callers pass two. Only the second argument is read, so the function is * ABI-correct for the T40 two-argument call while retaining a compilable * prototype in this standalone unit. */ +#if defined(PLATFORM_T23) +int IMP_Encoder_SetChnFrmRate(int channel, const IMPEncoderFrmRate *rate) +#else int IMP_Encoder_SetChnFrmRate(int channel, IMPEncoderFrmRate *rate, IMPEncoderFrmRate *unused) +#endif { P2EncoderChannel *ch; +#if !defined(PLATFORM_T23) (void)unused; +#endif if (!p2_valid_channel(channel) || !rate || !rate->frmRateNum || !rate->frmRateDen) return -1; @@ -1052,7 +1342,7 @@ int IMP_Encoder_SetChnFrmRate(int channel, IMPEncoderFrmRate *rate, } ch->attr.rcAttr.outFrmRate = *rate; ch->next_frame_due_us = 0; - if (AL_Codec_Encode_SetFrameRate(ch->codec, rate) != 0) { + if (AL_Codec_Encode_SetFrameRate(ch->codec, (void *)rate) != 0) { pthread_mutex_unlock(&ch->lock); return -1; } @@ -1060,10 +1350,16 @@ int IMP_Encoder_SetChnFrmRate(int channel, IMPEncoderFrmRate *rate, return 0; } +#if defined(PLATFORM_T23) +int IMP_Encoder_GetChnFrmRate(int channel, IMPEncoderFrmRate *rate) +#else int IMP_Encoder_GetChnFrmRate(int channel, IMPEncoderFrmRate *rate, IMPEncoderFrmRate *unused) +#endif { +#if !defined(PLATFORM_T23) (void)unused; +#endif if (!p2_valid_channel(channel) || !rate || !p2_channels[channel].created) return -1; *rate = p2_channels[channel].attr.rcAttr.outFrmRate; @@ -1082,8 +1378,10 @@ int IMP_Encoder_SetChnAttrRcMode(int channel, IMPEncoderAttrRcMode *mode) { P2EncoderChannel *ch; IMPEncoderRcAttr codec_rc; +#if !defined(PLATFORM_T23) uint32_t target_kbps = 0; uint32_t maximum_kbps = 0; +#endif if (!p2_valid_channel(channel) || !mode) return -1; @@ -1096,6 +1394,13 @@ int IMP_Encoder_SetChnAttrRcMode(int channel, IMPEncoderAttrRcMode *mode) ch->attr.rcAttr.attrRcMode = *mode; codec_rc = ch->attr.rcAttr; +#if defined(PLATFORM_T23) + if (codec_rc.attrRcMode.rcMode < IMP_ENC_RC_MODE_FIXQP || + codec_rc.attrRcMode.rcMode >= IMP_ENC_RC_MODE_INV) { + pthread_mutex_unlock(&ch->lock); + return -1; + } +#else switch (codec_rc.attrRcMode.rcMode) { case IMP_ENC_RC_MODE_CBR: target_kbps = codec_rc.attrRcMode.attrCbr.uTargetBitRate; @@ -1139,6 +1444,7 @@ int IMP_Encoder_SetChnAttrRcMode(int channel, IMPEncoderAttrRcMode *mode) pthread_mutex_unlock(&ch->lock); return -1; } +#endif if (AL_Codec_Encode_SetRcParam(ch->codec, &codec_rc) != 0) { pthread_mutex_unlock(&ch->lock); @@ -1164,6 +1470,25 @@ int IMP_Encoder_SetChnBitRate(int channel, int bitrate, int max_bitrate) if (!maximum) maximum = target; mode = &p2_channels[channel].attr.rcAttr.attrRcMode; +#if defined(PLATFORM_T23) + switch (mode->rcMode) { + case IMP_ENC_RC_MODE_CBR: + if (p2_channels[channel].codec_type == IMP_ENC_TYPE_HEVC) + mode->attrH265Cbr.outBitRate = target; + else + mode->attrH264Cbr.outBitRate = target; + break; + case IMP_ENC_RC_MODE_VBR: + case IMP_ENC_RC_MODE_SMART: + if (p2_channels[channel].codec_type == IMP_ENC_TYPE_HEVC) + mode->attrH265Vbr.maxBitRate = maximum; + else + mode->attrH264Vbr.maxBitRate = maximum; + break; + default: + return -1; + } +#else switch (mode->rcMode) { case IMP_ENC_RC_MODE_CBR: mode->attrCbr.uTargetBitRate = target; @@ -1183,6 +1508,7 @@ int IMP_Encoder_SetChnBitRate(int channel, int bitrate, int max_bitrate) default: return -1; } +#endif if (AL_Codec_Encode_SetBitRate(p2_channels[channel].codec, (int)(target * 1000u), (int)(maximum * 1000u)) != 0) @@ -1194,7 +1520,12 @@ int IMP_Encoder_GetChnGopAttr(int channel, IMPEncoderGopAttr *gop) { if (!p2_valid_channel(channel) || !gop || !p2_channels[channel].created) return -1; +#if defined(PLATFORM_T23) + memset(gop, 0, sizeof(*gop)); + gop->gopLength = p2_channels[channel].attr.rcAttr.maxGop; +#else *gop = p2_channels[channel].attr.gopAttr; +#endif return 0; } @@ -1202,7 +1533,11 @@ int IMP_Encoder_SetChnGopAttr(int channel, IMPEncoderGopAttr *gop) { if (!p2_valid_channel(channel) || !gop || !p2_channels[channel].created) return -1; +#if defined(PLATFORM_T23) + p2_channels[channel].attr.rcAttr.maxGop = gop->gopLength; +#else p2_channels[channel].attr.gopAttr = *gop; +#endif return AL_Codec_Encode_SetGopParam(p2_channels[channel].codec, gop); } @@ -1211,13 +1546,36 @@ int IMP_Encoder_SetChnGopLength(int channel, int length) if (!p2_valid_channel(channel) || length <= 0 || !p2_channels[channel].created) return -1; +#if defined(PLATFORM_T23) + p2_channels[channel].attr.rcAttr.maxGop = (uint32_t)length; +#else p2_channels[channel].attr.gopAttr.uGopLength = (uint16_t)length; +#endif return AL_Codec_Encode_SetGopLength(p2_channels[channel].codec, length); } +#if defined(PLATFORM_T23) +int IMP_Encoder_SetGOPSize(int channel, const IMPEncoderGOPSizeCfg *gop) +{ + if (!gop || gop->gopsize <= 0) + return -1; + return IMP_Encoder_SetChnGopLength(channel, gop->gopsize); +} +#endif + static void p2_set_qp_bounds(IMPEncoderAttrRcMode *mode, int minimum, int maximum) { +#if defined(PLATFORM_T23) + if (mode->rcMode == IMP_ENC_RC_MODE_CBR) { + mode->attrH264Cbr.minQp = (uint32_t)minimum; + mode->attrH264Cbr.maxQp = (uint32_t)maximum; + } else if (mode->rcMode == IMP_ENC_RC_MODE_VBR || + mode->rcMode == IMP_ENC_RC_MODE_SMART) { + mode->attrH264Vbr.minQp = (uint32_t)minimum; + mode->attrH264Vbr.maxQp = (uint32_t)maximum; + } +#else if (mode->rcMode == IMP_ENC_RC_MODE_CBR) { mode->attrCbr.iMinQP = (int16_t)minimum; mode->attrCbr.iMaxQP = (int16_t)maximum; @@ -1231,6 +1589,7 @@ static void p2_set_qp_bounds(IMPEncoderAttrRcMode *mode, int minimum, mode->attrCappedQuality.iMinQP = (int16_t)minimum; mode->attrCappedQuality.iMaxQP = (int16_t)maximum; } +#endif } int IMP_Encoder_SetChnQpBounds(int channel, int minimum, int maximum) @@ -1274,6 +1633,10 @@ int IMP_Encoder_SetChnMaxPictureSize(int channel, uint32_t maximum_i, return -1; mode = &p2_channels[channel].attr.rcAttr.attrRcMode; maximum = maximum_i > maximum_p ? maximum_i : maximum_p; +#if defined(PLATFORM_T23) + (void)mode; + (void)maximum; +#else if (mode->rcMode == IMP_ENC_RC_MODE_CBR) mode->attrCbr.uMaxPictureSize = maximum; else if (mode->rcMode == IMP_ENC_RC_MODE_VBR) @@ -1282,6 +1645,7 @@ int IMP_Encoder_SetChnMaxPictureSize(int channel, uint32_t maximum_i, mode->attrCappedVbr.uMaxPictureSize = maximum; else if (mode->rcMode == IMP_ENC_RC_MODE_CAPPED_QUALITY) mode->attrCappedQuality.uMaxPictureSize = maximum; +#endif return 0; } @@ -1364,7 +1728,12 @@ int IMP_Encoder_GetChnAveBitrate(int channel, IMPEncoderStream *stream, fps_num = 25; fps_den = 1; } +#if defined(PLATFORM_T23) + bits_per_second = (uint64_t)(stream->packCount && stream->pack + ? stream->pack[0].length : 0u) * 8u * fps_num / fps_den; +#else bits_per_second = (uint64_t)stream->streamSize * 8u * fps_num / fps_den; +#endif #if defined(PLATFORM_T31) *bitrate = (double)bits_per_second; #else @@ -1374,11 +1743,29 @@ int IMP_Encoder_GetChnAveBitrate(int channel, IMPEncoderStream *stream, return 0; } +#if defined(PLATFORM_T23) +int IMP_Encoder_GetChnEncType(int channel, IMPPayloadType *type) +#else int IMP_Encoder_GetChnEncType(int channel, IMPEncoderEncType *type) +#endif { if (!p2_valid_channel(channel) || !type || !p2_channels[channel].created) return -1; +#if defined(PLATFORM_T23) + switch (p2_channels[channel].codec_type) { + case IMP_ENC_TYPE_JPEG: + *type = PT_JPEG; + break; + case IMP_ENC_TYPE_HEVC: + *type = PT_H265; + break; + default: + *type = PT_H264; + break; + } +#else *type = (IMPEncoderEncType)p2_channels[channel].codec_type; +#endif return 0; }