From bdad3f8f56f74b41eb9a17f76922ea3463b1fa37 Mon Sep 17 00:00:00 2001 From: kasperiio <101901857+kasperiio@users.noreply.github.com> Date: Sat, 5 Sep 2026 19:51:56 +0300 Subject: [PATCH 1/2] hal: add Fullhan FH8852/FH8856 (V100) support A HAL for the Fullhan V100 generation (ARM1176, ARMv6), driving the vendor SDK's libdsp/libisp/libvmm through dlopen, with a GC4653 sensor driver. Identification is by chip_name in /proc/driver/chip, which is where the vendor kernel reports FH8852/FH8856; the platform is gated on __ARM_ARCH == 6 so no other ARM build changes. Notable points, all found on hardware and commented at the point of use: - Video frames are copied once out of the SDK's uncached DMA buffer. Every pass over it reads at bus speed, and a 300 KB keyframe cost about 400 ms of CPU. - The HEVC encoder bundles VPS, SPS and PPS into the IDR entry, so entries are split on start codes for the consumers that trust per-NALU boundaries. - H.264 High profile is rejected by the encoder (0x19d) and would leave the channel closed, so every profile maps to Main. - There is no FH_VENC_DestroyChn; a channel's pool lives until FH_SYS_Exit, so channels are created once with both codecs reserved and switched afterwards. - gpio: the exported line's sysfs directory is GPIO on these kernels rather than gpio; probed and cached. The same commit corrects three open() error checks that tested !fd rather than fd < 0. - quirks: the Fullhan ISP library exports its own isp_malloc(), which the trampoline would shadow under -rdynamic, so it is left out of ARMv6 builds. - An audio init failure is a warning rather than fatal on this platform: the SoC is watchdog-guarded and aborting the SDK start means a reboot loop. --- src/app_config.c | 16 +- src/app_config.h | 3 + src/gpio.c | 35 +- src/hal/fh/fh_aud.h | 77 +++ src/hal/fh/fh_common.h | 47 ++ src/hal/fh/fh_compat.c | 86 +++ src/hal/fh/fh_hal.c | 1154 +++++++++++++++++++++++++++++++ src/hal/fh/fh_hal.h | 57 ++ src/hal/fh/fh_isp.h | 226 ++++++ src/hal/fh/fh_snr.h | 25 + src/hal/fh/fh_snr_gc4653.c | 299 ++++++++ src/hal/fh/fh_snr_gc4653_regs.h | 132 ++++ src/hal/fh/fh_sys.h | 61 ++ src/hal/fh/fh_venc.h | 125 ++++ src/hal/fh/fh_vpss.h | 86 +++ src/hal/hisi/quirks.c | 4 + src/hal/support.c | 20 + src/hal/support.h | 3 + src/hal/types.h | 1 + src/jpeg.c | 11 + src/media.c | 72 +- src/night.c | 38 + src/region.c | 15 + 23 files changed, 2581 insertions(+), 12 deletions(-) create mode 100644 src/hal/fh/fh_aud.h create mode 100644 src/hal/fh/fh_common.h create mode 100644 src/hal/fh/fh_compat.c create mode 100644 src/hal/fh/fh_hal.c create mode 100644 src/hal/fh/fh_hal.h create mode 100644 src/hal/fh/fh_isp.h create mode 100644 src/hal/fh/fh_snr.h create mode 100644 src/hal/fh/fh_snr_gc4653.c create mode 100644 src/hal/fh/fh_snr_gc4653_regs.h create mode 100644 src/hal/fh/fh_sys.h create mode 100644 src/hal/fh/fh_venc.h create mode 100644 src/hal/fh/fh_vpss.h diff --git a/src/app_config.c b/src/app_config.c index 512c588d..d14ffdac 100644 --- a/src/app_config.c +++ b/src/app_config.c @@ -98,11 +98,16 @@ int app_config_save(void) { fprintf(file, " enable: %s\n", app_config.night_mode_enable ? "true" : "false"); fprintf(file, " ir_sensor_pin: %d\n", app_config.ir_sensor_pin); fprintf(file, " check_interval_s: %d\n", app_config.check_interval_s); + if (app_config.smartir_gain_night || app_config.smartir_gain_day) { + fprintf(file, " smartir_gain_night: %d\n", app_config.smartir_gain_night); + fprintf(file, " smartir_gain_day: %d\n", app_config.smartir_gain_day); + } fprintf(file, " ir_cut_pin1: %d\n", app_config.ir_cut_pin1); fprintf(file, " ir_cut_pin2: %d\n", app_config.ir_cut_pin2); fprintf(file, " ir_led_pin: %d\n", app_config.ir_led_pin); fprintf(file, " pin_switch_delay_us: %d\n", app_config.pin_switch_delay_us); fprintf(file, " adc_device: %s\n", app_config.adc_device); + fprintf(file, " lamp: %s\n", app_config.night_lamp); fprintf(file, " adc_threshold: %d\n", app_config.adc_threshold); fprintf(file, "isp:\n"); @@ -277,6 +282,7 @@ enum ConfigError app_config_parse(void) { app_config.pin_switch_delay_us = 250; app_config.check_interval_s = 10; app_config.adc_device[0] = 0; + strcpy(app_config.night_lamp, "ir"); app_config.adc_threshold = 128; struct IniConfig ini; @@ -344,11 +350,17 @@ enum ConfigError app_config_parse(void) { #define PIN_MAX 95 if (app_config.night_mode_enable) { parse_int( - &ini, "night_mode", "ir_sensor_pin", 0, PIN_MAX, + &ini, "night_mode", "ir_sensor_pin", 0, 999, /* 999 = no sensor (the default) */ &app_config.ir_sensor_pin); parse_int( &ini, "night_mode", "check_interval_s", 0, 600, &app_config.check_interval_s); + parse_int( + &ini, "night_mode", "smartir_gain_night", 0, 65535, + &app_config.smartir_gain_night); + parse_int( + &ini, "night_mode", "smartir_gain_day", 0, 65535, + &app_config.smartir_gain_day); parse_int( &ini, "night_mode", "ir_cut_pin1", 0, PIN_MAX, &app_config.ir_cut_pin1); @@ -363,6 +375,8 @@ enum ConfigError app_config_parse(void) { &app_config.pin_switch_delay_us); parse_param_value( &ini, "night_mode", "adc_device", app_config.adc_device); + parse_param_value( + &ini, "night_mode", "lamp", app_config.night_lamp); parse_int( &ini, "night_mode", "adc_threshold", INT_MIN, INT_MAX, &app_config.adc_threshold); diff --git a/src/app_config.h b/src/app_config.h index 1bf42c5f..ce68812c 100644 --- a/src/app_config.h +++ b/src/app_config.h @@ -35,7 +35,10 @@ struct AppConfig { unsigned int check_interval_s; unsigned int pin_switch_delay_us; char adc_device[128]; + char night_lamp[8]; /* ir (default), white (colour night vision), none */ int adc_threshold; + int smartir_gain_night; /* Fullhan SmartIR: image gain above which it is night (0 = library default) */ + int smartir_gain_day; /* ... and below which it is day again */ // [isp] bool mirror; diff --git a/src/gpio.c b/src/gpio.c index cb19919d..a2219bab 100644 --- a/src/gpio.c +++ b/src/gpio.c @@ -72,11 +72,26 @@ int gpio_init(void) { return EXIT_SUCCESS; } +/* The exported line's sysfs directory is "gpio" on most kernels but + * "GPIO" on Fullhan (fh) kernels; probe both and cache the format. */ +static const char *gpio_node(char pin) { + static char fmt[24]; + static char dir[40]; + if (!fmt[0]) { + char probe[40]; + sprintf(probe, "/sys/class/gpio/GPIO%d", pin); + strcpy(fmt, access(probe, F_OK) == 0 ? + "/sys/class/gpio/GPIO%d" : "/sys/class/gpio/gpio%d"); + } + sprintf(dir, fmt, pin); + return dir; +} + static inline int gpio_direction(char pin, char *mode) { - char path[40]; - sprintf(path, "/sys/class/gpio/gpio%d/direction", pin); + char path[48]; + sprintf(path, "%s/direction", gpio_node(pin)); int fd = open(path, O_WRONLY); - if (!fd) + if (fd < 0) HAL_ERROR("gpio", "Unable to control the direction of GPIO pin %d!\n", pin); if (write(fd, mode, strlen(mode)) < 0) { close(fd); @@ -91,7 +106,7 @@ static inline int gpio_export(char pin, bool create) { char path[40]; int fd = open(create ? "/sys/class/gpio/export" : "/sys/class/gpio/unexport", O_WRONLY); - if (!fd) + if (fd < 0) HAL_ERROR("gpio", "Unable to (un)export a GPIO pin!\n"); char val[4]; @@ -110,10 +125,10 @@ int gpio_read(char pin, bool *value) { gpio_export(pin, true); if (gpio_direction(pin, "in")) return EXIT_FAILURE; - char path[40]; - sprintf(path, "/sys/class/gpio/gpio%d/value", pin); + char path[48]; + sprintf(path, "%s/value", gpio_node(pin)); int fd = open(path, O_RDONLY); - if (!fd) + if (fd < 0) HAL_ERROR("gpio", "Unable to read from GPIO pin %d!\n", pin); char val = 0; @@ -136,10 +151,10 @@ int gpio_write(char pin, bool value) { gpio_export(pin, true); if (gpio_direction(pin, "out")) return EXIT_FAILURE; - char path[40]; - sprintf(path, "/sys/class/gpio/gpio%d/value", pin); + char path[48]; + sprintf(path, "%s/value", gpio_node(pin)); int fd = open(path, O_WRONLY); - if (!fd) + if (fd < 0) HAL_ERROR("gpio", "Unable to write to GPIO pin %d!\n", pin); char val = value ? '1' : '0'; diff --git a/src/hal/fh/fh_aud.h b/src/hal/fh/fh_aud.h new file mode 100644 index 00000000..5aa0e2b8 --- /dev/null +++ b/src/hal/fh/fh_aud.h @@ -0,0 +1,77 @@ +#pragma once + +#include "fh_common.h" + +/* FH_AC_Set_Config() payload (audio codec on the ARC coprocessor, /dev/fh_audio) */ +typedef struct { + unsigned int ioType; /* 0/1 = capture, 2/3 = playback */ + unsigned int sampleRate; + unsigned int bitWidth; /* 16 or 24 */ + unsigned int encFormat; /* 0 = PCM, 1..5 = other formats (forces 16 bit) */ + unsigned int channels; + unsigned int frameSamples; /* >= 80; vendor uses 320 at 8 kHz */ + unsigned int volume; /* 0..100 */ +} fh_aud_cnf; + +typedef struct { + unsigned int length; /* filled with the number of bytes read */ + void *data; +} fh_aud_frm; + +typedef struct { + void *handle; + + int (*fnDeinit)(void); + int (*fnDisable)(void); + int (*fnEnable)(void); + int (*fnGetFrame)(fh_aud_frm *frame, unsigned long long *timestamp); + int (*fnInit)(void); + int (*fnSetConfig)(fh_aud_cnf *config); + int (*fnSetMicVolume)(unsigned int volume); + int (*fnSetVolume)(unsigned int volume); +} fh_aud_impl; + +static int fh_aud_load(fh_aud_impl *aud_lib) { + if (!(aud_lib->handle = dlopen("libacw_mpi.so", RTLD_NOW | RTLD_GLOBAL))) + HAL_ERROR("fh_aud", "Failed to load library!\nError: %s\n", dlerror()); + + if (!(aud_lib->fnDeinit = (int(*)(void)) + hal_symbol_load("fh_aud", aud_lib->handle, "FH_AC_DeInit"))) + return EXIT_FAILURE; + + if (!(aud_lib->fnDisable = (int(*)(void)) + hal_symbol_load("fh_aud", aud_lib->handle, "FH_AC_AI_Disable"))) + return EXIT_FAILURE; + + if (!(aud_lib->fnEnable = (int(*)(void)) + hal_symbol_load("fh_aud", aud_lib->handle, "FH_AC_AI_Enable"))) + return EXIT_FAILURE; + + if (!(aud_lib->fnGetFrame = (int(*)(fh_aud_frm *frame, unsigned long long *timestamp)) + hal_symbol_load("fh_aud", aud_lib->handle, "FH_AC_AI_GetFrameWithPts"))) + return EXIT_FAILURE; + + if (!(aud_lib->fnInit = (int(*)(void)) + hal_symbol_load("fh_aud", aud_lib->handle, "FH_AC_Init"))) + return EXIT_FAILURE; + + if (!(aud_lib->fnSetConfig = (int(*)(fh_aud_cnf *config)) + hal_symbol_load("fh_aud", aud_lib->handle, "FH_AC_Set_Config"))) + return EXIT_FAILURE; + + if (!(aud_lib->fnSetMicVolume = (int(*)(unsigned int volume)) + hal_symbol_load("fh_aud", aud_lib->handle, "FH_AC_AI_MICIN_SetVol"))) + return EXIT_FAILURE; + + if (!(aud_lib->fnSetVolume = (int(*)(unsigned int volume)) + hal_symbol_load("fh_aud", aud_lib->handle, "FH_AC_AI_SetVol"))) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} + +static void fh_aud_unload(fh_aud_impl *aud_lib) { + if (aud_lib->handle) dlclose(aud_lib->handle); + aud_lib->handle = NULL; + memset(aud_lib, 0, sizeof(*aud_lib)); +} diff --git a/src/hal/fh/fh_common.h b/src/hal/fh/fh_common.h new file mode 100644 index 00000000..2c8530d2 --- /dev/null +++ b/src/hal/fh/fh_common.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +#include "../symbols.h" +#include "../types.h" + +/* + * Fullhan FH8852/FH8856 (V100 generation, SDK V1.2.0 "OSDRV" libraries). + * + * The SDK ships as binary-only shared objects without headers; the interface + * below was recovered from the libraries and from a vendor application that + * statically links the same SDK. Only the subset divinus needs is declared. + */ + +#define FH_VPSS_CHN_NUM 4 +#define FH_VENC_CHN_NUM 8 + +/* Error codes returned by the MPI (negative) */ +#define FH_ERR_NODEV (-0x3e9) +#define FH_ERR_PARAM (-0x3ed) +#define FH_ERR_CHN (-0x3f0) + +typedef enum { + FH_VENC_TYPE_JPEG = 0x01, + FH_VENC_TYPE_MJPEG = 0x02, + FH_VENC_TYPE_H264 = 0x04, + FH_VENC_TYPE_H264B = 0x08, /* variant that also needs a BGM gop threshold */ + FH_VENC_TYPE_H265 = 0x10, + FH_VENC_TYPE_H265B = 0x20 +} fh_venc_type; + +typedef enum { + FH_VENC_RC_H264_CBR = 3, /* reported as VBR by /proc/driver/enc but it is the working CBR path; mode 8 boot-loops */ + FH_VENC_RC_H264_VBR = 4, + FH_VENC_RC_H264_FIXQP = 5, + FH_VENC_RC_H264_AVBR = 6, + FH_VENC_RC_H265_CBR = 7, + FH_VENC_RC_H265_VBR = 8 +} fh_venc_rcmode; + +typedef struct { + unsigned int width; + unsigned int height; +} fh_common_dim; diff --git a/src/hal/fh/fh_compat.c b/src/hal/fh/fh_compat.c new file mode 100644 index 00000000..d3918e9d --- /dev/null +++ b/src/hal/fh/fh_compat.c @@ -0,0 +1,86 @@ +#if defined(__arm__) && !defined(__ARM_PCS_VFP) && __ARM_ARCH == 6 + +/* + * The Fullhan 3.0.8 kernels oops in rtnl_fill_ifinfo() on any netlink + * interface dump, which kills the calling process. musl implements + * getifaddrs() over netlink, so replace it with the SIOCGIFCONF ioctl + * interface for this platform. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct fh_ifaddr { + struct ifaddrs ifa; + char name[IFNAMSIZ]; + struct sockaddr_in addr, netmask, broadcast; +}; + +int getifaddrs(struct ifaddrs **ifap) +{ + struct ifconf ifc; + struct ifreq *reqs; + int fd, count, len = 32 * sizeof(struct ifreq); + struct fh_ifaddr *list = NULL, *last = NULL; + + *ifap = NULL; + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + return -1; + if (!(reqs = malloc(len))) { + close(fd); + return -1; + } + ifc.ifc_len = len; + ifc.ifc_req = reqs; + if (ioctl(fd, SIOCGIFCONF, &ifc) < 0) { + free(reqs); + close(fd); + return -1; + } + count = ifc.ifc_len / sizeof(struct ifreq); + + for (int i = 0; i < count; i++) { + struct ifreq req = reqs[i]; + struct fh_ifaddr *entry = calloc(1, sizeof(*entry)); + if (!entry) break; + strncpy(entry->name, req.ifr_name, IFNAMSIZ - 1); + entry->ifa.ifa_name = entry->name; + memcpy(&entry->addr, &req.ifr_addr, sizeof(entry->addr)); + entry->ifa.ifa_addr = (struct sockaddr*)&entry->addr; + if (!ioctl(fd, SIOCGIFFLAGS, &req)) + entry->ifa.ifa_flags = req.ifr_flags; + if (!ioctl(fd, SIOCGIFNETMASK, &req)) { + memcpy(&entry->netmask, &req.ifr_netmask, sizeof(entry->netmask)); + entry->ifa.ifa_netmask = (struct sockaddr*)&entry->netmask; + } + if (!ioctl(fd, SIOCGIFBRDADDR, &req)) { + memcpy(&entry->broadcast, &req.ifr_broadaddr, sizeof(entry->broadcast)); + entry->ifa.ifa_broadaddr = (struct sockaddr*)&entry->broadcast; + } + if (last) last->ifa.ifa_next = &entry->ifa; + else list = entry; + last = entry; + } + + free(reqs); + close(fd); + *ifap = list ? &list->ifa : NULL; + return 0; +} + +void freeifaddrs(struct ifaddrs *ifa) +{ + while (ifa) { + struct ifaddrs *next = ifa->ifa_next; + free(ifa); + ifa = next; + } +} + +#endif diff --git a/src/hal/fh/fh_hal.c b/src/hal/fh/fh_hal.c new file mode 100644 index 00000000..4f9435ad --- /dev/null +++ b/src/hal/fh/fh_hal.c @@ -0,0 +1,1154 @@ +#if defined(__arm__) && !defined(__ARM_PCS_VFP) && __ARM_ARCH == 6 + +#include "fh_hal.h" +#include "../../gpio.h" +#include + +#include +#include +#include + +fh_aud_impl fh_aud; +fh_isp_impl fh_isp; +fh_sys_impl fh_sys; +fh_venc_impl fh_venc; +fh_vpss_impl fh_vpss; + +hal_chnstate fh_state[FH_VENC_CHN_NUM] = {0}; +int (*fh_aud_cb)(hal_audframe*); +int (*fh_vid_cb)(char, hal_vidstream*); + +static fh_snr_driver *_fh_snr; +static fh_isp_snrops *_fh_snr_ops; +static fh_common_dim _fh_snr_dim; +static int _fh_snr_fmt; +static char _fh_isp_on, _fh_isp_busy; +static unsigned int _fh_venc_type[FH_VENC_CHN_NUM]; +static fh_common_dim _fh_venc_made[FH_VENC_CHN_NUM]; /* size FH_VENC_CreateChn reserved */ +static fh_common_dim _fh_vpss_made[FH_VPSS_CHN_NUM]; /* size FH_VPSS_ChnInitMem reserved */ +static char _fh_vpss_open[FH_VPSS_CHN_NUM]; +static fh_common_dim _fh_vpss_dim[FH_VPSS_CHN_NUM]; +static char _fh_vpss_fps[FH_VPSS_CHN_NUM]; +static char _fh_param_path[128]; +static int _fh_vpss_count = FH_VPSS_CHN_NUM; +static fh_common_dim _fh_want_dim[FH_VENC_CHN_NUM]; +static signed char _fh_vpss_src[FH_VENC_CHN_NUM]; /* scaler channel feeding each encoder */ +static char fh_vpss_pick(char index); + +/* Graphic overlay: one 16-bit ARGB1555 plane at sensor resolution, blended before scaling */ +typedef struct { + unsigned int enable; + unsigned int physAddr; + unsigned int alpha; + unsigned int reserved3, reserved4; + unsigned int width, height; + unsigned int x, y; + unsigned int reserved9, reserved10; + unsigned int stride; /* bytes per line */ +} fh_vpss_graph; +#define FH_OSD_MAX 8 +typedef struct { char used; hal_rect rect; } fh_osd_rgn; +static fh_osd_rgn _fh_osd[FH_OSD_MAX]; +static unsigned int _fh_osd_phys, _fh_osd_size; +static unsigned short *_fh_osd_virt; +static char _fh_osd_on; +static pthread_mutex_t _fh_strm_mtx = PTHREAD_MUTEX_INITIALIZER; +static char _fh_aud_on; +static unsigned int _fh_aud_frame, _fh_aud_rate; +static char _fh_smartir, _fh_night_prev; +static unsigned char *_fh_mjpeg_cache; static unsigned int _fh_mjpeg_len, _fh_mjpeg_cap; static pthread_mutex_t _fh_mjpeg_mtx = PTHREAD_MUTEX_INITIALIZER; + +/* libc compatibility shims for the SDK libraries live in fh_compat.c */ + +static void fh_proc_write(const char *path, const char *value) +{ + FILE *file = fopen(path, "w"); + if (!file) { + HAL_WARNING("fh_hal", "Cannot write %s to %s!\n", value, path); + return; + } + fputs(value, file); + fclose(file); +} + +void fh_hal_deinit(void) +{ + fh_aud_unload(&fh_aud); + fh_venc_unload(&fh_venc); + fh_vpss_unload(&fh_vpss); + fh_isp_unload(&fh_isp); + fh_sys_unload(&fh_sys); +} + +int fh_hal_init(void) +{ + int ret; + + if (ret = fh_sys_load(&fh_sys)) + return ret; + if (ret = fh_isp_load(&fh_isp)) + return ret; + if (ret = fh_vpss_load(&fh_vpss)) + return ret; + if (ret = fh_venc_load(&fh_venc)) + return ret; + if (fh_aud_load(&fh_aud)) + HAL_WARNING("fh_hal", "Audio library unavailable, audio disabled\n"); + + return EXIT_SUCCESS; +} + +void fh_audio_deinit(void) +{ + if (!_fh_aud_on) return; + _fh_aud_on = 0; + fh_aud.fnDisable(); + fh_aud.fnDeinit(); +} + +int fh_audio_init(int samplerate) +{ + int ret; + + if (!fh_aud.handle) + HAL_ERROR("fh_aud", "Audio library is not loaded!\n"); + + if (ret = fh_aud.fnInit()) { + HAL_DANGER("fh_aud", "FH_AC_Init failed with %#x\n", ret); + return ret; + } + + { + /* A sample rate the config parser rejected arrives here as 0 (a saved + * "srate: 0" is outside the 8000..96000 range); fall back to 8 kHz rather + * than asking the codec for zero-length frames */ + if (!samplerate) samplerate = 8000; + /* 40 ms frames of 16-bit mono PCM, as the vendor application does */ + _fh_aud_frame = samplerate / 25; + _fh_aud_rate = samplerate; + fh_aud_cnf config = { .ioType = 0, .sampleRate = samplerate, .bitWidth = 16, + .encFormat = 0, .channels = 1, .frameSamples = _fh_aud_frame, .volume = 85 }; + if (ret = fh_aud.fnSetConfig(&config)) { + HAL_DANGER("fh_aud", "FH_AC_Set_Config(%d Hz, %u samples) failed with %#x\n", samplerate, _fh_aud_frame, ret); + return ret; + } + } + if (ret = fh_aud.fnEnable()) { + HAL_DANGER("fh_aud", "FH_AC_AI_Enable failed with %#x\n", ret); + return ret; + } + fh_aud.fnSetMicVolume(2); + fh_aud.fnSetVolume(85); + + _fh_aud_on = 1; + return EXIT_SUCCESS; +} + +void *fh_audio_thread(void) +{ + unsigned char *buf = malloc(_fh_aud_frame * 2 + 64); + unsigned int seq = 0; + + while (keepRunning && audioOn && _fh_aud_on) { + fh_aud_frm frame = { .length = 0, .data = buf }; + unsigned long long pts = 0; + int ret = fh_aud.fnGetFrame(&frame, &pts); + if (ret || !frame.length) { + usleep(5000); + continue; + } + { + /* The board picks up mains hum (50 Hz and harmonics) on the mic + * path; a 2nd-order Butterworth high-pass at ~120 Hz removes most + * of it and leaves speech intact. Coefficients follow the sample rate. */ + static float x1, x2, y1, y2, b0, b1, b2, a1, a2; static unsigned int forRate; + if (forRate != _fh_aud_rate) { + float w0 = 2.0f * 3.14159265f * 120.0f / (float)_fh_aud_rate, c = cosf(w0); + float alpha = sinf(w0) / (2.0f * 0.7071f), a0 = 1.0f + alpha; + b0 = (1.0f + c) / 2.0f / a0; b1 = -(1.0f + c) / a0; b2 = b0; + a1 = -2.0f * c / a0; a2 = (1.0f - alpha) / a0; + x1 = x2 = y1 = y2 = 0; forRate = _fh_aud_rate; + } + short *pcm = (short*)buf; + for (unsigned int i = 0; i < frame.length / 2; i++) { + float x0 = pcm[i]; + float y0 = b0 * x0 + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2; + x2 = x1; x1 = x0; y2 = y1; y1 = y0; + pcm[i] = y0 > 32767 ? 32767 : y0 < -32768 ? -32768 : (short)y0; + } + } + if (fh_aud_cb) { + hal_audframe outFrame; + memset(&outFrame, 0, sizeof(outFrame)); + outFrame.channelCnt = 1; + outFrame.data[0] = buf; + outFrame.length[0] = frame.length; + outFrame.seq = seq++; + outFrame.timestamp = pts / 1000; + (fh_aud_cb)(&outFrame); + } + } + free(buf); + HAL_INFO("fh_aud", "Shutting down capture thread...\n"); + return NULL; +} + +int fh_channel_bind(char index) +{ + int ret; + char src = _fh_vpss_src[index] < 0 ? fh_vpss_pick(index) : _fh_vpss_src[index]; + + for (int i = 0; i < 5; i++) { + if (!(ret = fh_sys.fnBindVpu2Enc(src, index))) + return EXIT_SUCCESS; + usleep(100000); + } + HAL_DANGER("fh_hal", "Binding scaler channel %d to encoder %d failed with %#x!\n", src, index, ret); + return ret; +} + +static int fh_vpss_setup(char index, short width, short height, char framerate) +{ + int ret; + int snrFps = _fh_snr->fnFramerateForFormat(_fh_snr_fmt); + if (framerate > snrFps) framerate = snrFps; + + if (_fh_vpss_open[index] && _fh_vpss_dim[index].width == width && + _fh_vpss_dim[index].height == height && _fh_vpss_fps[index] == framerate) + return EXIT_SUCCESS; + + if (_fh_vpss_open[index]) { + fh_vpss.fnCloseChannel(index); + _fh_vpss_open[index] = 0; + } + + { + fh_common_dim dim = { .width = width, .height = height }; + /* Like the encoder pools, a scaler channel's memory cannot be released + * again (a second ChnInitMem fails with -1), so allocate once and keep + * reconfiguring within that size */ + if (_fh_vpss_made[index].width) { + if (dim.width > _fh_vpss_made[index].width || dim.height > _fh_vpss_made[index].height) + HAL_ERROR("fh_hal", "Scaler channel %d was sized %ux%u; %ux%u needs a restart\n", index, + _fh_vpss_made[index].width, _fh_vpss_made[index].height, dim.width, dim.height); + } else { + int (*fnInitChannelMem)(unsigned int, unsigned int, unsigned int) = + dlsym(fh_vpss.handle, "FH_VPSS_ChnInitMem"); + if (fnInitChannelMem && (ret = fnInitChannelMem(index, dim.width, dim.height))) + HAL_WARNING("fh_hal", "Allocating scaler channel %d memory failed with %#x!\n", index, ret); + else _fh_vpss_made[index] = dim; + } + if (ret = fh_vpss.fnSetChannelConfig(index, &dim)) + return ret; + _fh_vpss_dim[index] = dim; + } + + { + fh_vpss_framectrl ctrl = { .srcRate = framerate, .dstRate = 1 }; + if (ret = fh_vpss.fnSetFrameControl(index, &ctrl)) + return ret; + _fh_vpss_fps[index] = framerate; + } + + if (ret = fh_vpss.fnOpenChannel(index)) + return ret; + _fh_vpss_open[index] = 1; + + return EXIT_SUCCESS; +} + +static char fh_vpss_pick(char index) +{ + /* Prefer an open scaler channel with the same size, else the main one */ + for (char i = 0; i < _fh_vpss_count; i++) + if (_fh_vpss_open[i] && _fh_vpss_dim[i].width == _fh_want_dim[index].width && + _fh_vpss_dim[i].height == _fh_want_dim[index].height) + return i; + return 0; +} + +int fh_channel_create(char index, short width, short height, char framerate, char jpeg) +{ + if (index >= FH_VENC_CHN_NUM) + HAL_ERROR("fh_hal", "Only %d encoder channels are available!\n", FH_VENC_CHN_NUM); + + _fh_want_dim[index].width = width; + _fh_want_dim[index].height = height; + if (index >= _fh_vpss_count) { + _fh_vpss_src[index] = -1; + HAL_INFO("fh_hal", "Channel %d: no free scaler channel, it will share one at bind time\n", index); + return EXIT_SUCCESS; + } + _fh_vpss_src[index] = index; + + if (width > _fh_snr_dim.width || height > _fh_snr_dim.height) { + HAL_WARNING("fh_hal", "Channel %d: requested %dx%d too large, using sensor %dx%d\n", + index, width, height, _fh_snr_dim.width, _fh_snr_dim.height); + width = _fh_snr_dim.width; + height = _fh_snr_dim.height; + } + + return fh_vpss_setup(index, width, height, framerate); +} + +void fh_channel_destroy(char index) +{ + /* Keep the scaler channel open: closing and reopening it around a codec or + * bitrate change left it delivering no frames on the FH8856 (encoder frame + * count stayed at 0, main-channel snapshots timed out), and fh_vpss_setup() + * reuses an open channel of the same size and rate anyway. */ +} + +/* + * Colour vs monochrome is the ISP saturation record (20 bytes: enable word, + * then the curve). The tuning file sets a valid colour record; a zeroed record + * is monochrome (what the vendor's mono path writes). libadvapi's + * FHAdv_Isp_SetColorMode() cannot be used here: its "restore colour" writes + * the library's own copy, which is never filled on this build, so it zeroed + * the record instead. Keep the record from the tuning load and write it back. + */ +static unsigned char _fh_sat_colour[32]; +static char _fh_sat_ok, _fh_gray_want; + +static void fh_grayscale_apply(void) +{ + unsigned char zero[32] = {0}; + if (_fh_gray_want) + fh_isp.fnSetSaturation(zero); + else if (_fh_sat_ok) + fh_isp.fnSetSaturation(_fh_sat_colour); + /* else: nothing captured yet, the ISP is still in its tuned colour state */ +} + +int fh_channel_grayscale(char enable) +{ + _fh_gray_want = enable ? 1 : 0; + fh_grayscale_apply(); + return EXIT_SUCCESS; +} + +int fh_channel_unbind(char index) +{ + fh_sys.fnUnbindByDst(index); + + return EXIT_SUCCESS; +} + +/* + * ISP tuning ("_attr.hex", 3268 bytes): the vendor files are + * generated for a newer SDK build; the older library only warns about the + * version word and still loads them. + */ +int fh_config_load(char *path) +{ + static unsigned char param[4096]; + FILE *file; + size_t len; + + if (EQUALS(path, _fh_param_path)) + return EXIT_SUCCESS; + if (!(file = fopen(path, "rb"))) + return EXIT_FAILURE; + memset(param, 0, sizeof(param)); + len = fread(param, 1, sizeof(param), file); + fclose(file); + if (len < 0x2c) + return EXIT_FAILURE; + + HAL_INFO("fh_hal", "Loading ISP parameters from %s (%zu bytes)\n", path, len); + if (fh_isp.fnLoadParam(param)) + return EXIT_FAILURE; + { + unsigned char sat[32] = {0}; + if (!fh_isp.fnGetSaturation(sat) && sat[5]) { + memcpy(_fh_sat_colour, sat, sizeof(_fh_sat_colour)); + _fh_sat_ok = 1; + fh_grayscale_apply(); /* honour a mode requested before the tuning load */ + } else + HAL_WARNING("fh_hal", "Could not read the colour saturation record; grayscale off will be a no-op\n"); + } + strncpy(_fh_param_path, path, sizeof(_fh_param_path) - 1); + { + int ret = fh_isp.fnAdvInit(); + return ret; + } +} + +void *fh_image_thread(void) +{ + while (keepRunning) { + if (_fh_isp_on) { + _fh_isp_busy = 1; + fh_isp.fnRun(); + _fh_isp_busy = 0; + usleep(10000); + } else usleep(100000); + } + HAL_INFO("fh_hal", "Shutting down ISP thread...\n"); + return NULL; +} + +/* Anti-banding: the ISP AE stores a 2-bit flicker mode at param offset 0x3a + * bits 6-7 (0 off, 1 = 50 Hz, 2 = 60 Hz), set through AE command 0xa. The + * newer vendor SDK adds dedicated commands 0x1b-0x1d, absent from these libs. */ +int fh_set_antiflicker(char hz) +{ + unsigned int mode = hz >= 60 ? 2 : hz >= 50 ? 1 : 0; + if (!fh_isp.fnAeSendCmd) + return EXIT_FAILURE; + return fh_isp.fnAeSendCmd(0x0a, &mode); +} + +int fh_pipeline_create(short width, short height, char mirror, char flip, char framerate, char antiflicker) +{ + int ret; + char path[128]; + + /* Kernel-side buffer sizing must precede FH_SYS_Init() */ + { + char value[64]; + snprintf(value, sizeof(value), "vi_%u_%u", _fh_snr_dim.width, _fh_snr_dim.height); + fh_proc_write("/proc/driver/vpu", value); + /* Scaler channel memory is allocated on demand in fh_channel_create() */ + for (int i = 0; i < FH_VPSS_CHN_NUM; i++) { + snprintf(value, sizeof(value), "cap_%d_0_0", i); + fh_proc_write("/proc/driver/vpu", value); + snprintf(value, sizeof(value), "buf_%d_2", i); + fh_proc_write("/proc/driver/vpu", value); + } + fh_proc_write("/proc/driver/vpu", "support4k_off"); + fh_proc_write("/proc/driver/isp", "support4k_off"); + fh_proc_write("/proc/driver/vpu", "sublimit_off"); + fh_proc_write("/proc/driver/isp", "sublimit_off"); + fh_proc_write("/proc/driver/isp", "wdr_off"); + fh_proc_write("/proc/driver/isp", "cir_off"); + fh_proc_write("/proc/driver/enc", "stm_2621440"); + fh_proc_write("/proc/driver/hevc", "stm_2621440"); + fh_proc_write("/proc/driver/hevc", "usebfrm_0"); + /* JPEG snapshot buffer: the driver's own rule is width*height/2, forcing 128 KiB + * (the vendor value) overruns at 2560x1440 and crashes the JPEG thread */ + snprintf(value, sizeof(value), "mem_1_%u", ALIGN_UP((unsigned int)width * height / 2 + 8, 4096)); + fh_proc_write("/proc/driver/jpeg", value); + /* Motion JPEG pool: three 512 KiB slots (larger pools fail to ioremap on 32 MiB systems) */ + { + unsigned int jpgSize = 512 * 1024; + snprintf(value, sizeof(value), "mjpg_%u_%u", jpgSize * 3, jpgSize); + fh_proc_write("/proc/driver/jpeg", value); + } + } + + if (ret = fh_sys.fnInit()) + return ret; + + { + int (*fnGetCapability)(unsigned int, void *) = dlsym(fh_vpss.handle, "FH_VPSS_GetChnCapality"); + unsigned int cap[8]; + _fh_vpss_count = 0; + while (fnGetCapability && _fh_vpss_count < FH_VPSS_CHN_NUM && !fnGetCapability(_fh_vpss_count, cap)) + _fh_vpss_count++; + if (!_fh_vpss_count) _fh_vpss_count = 1; + HAL_INFO("fh_hal", "%d scaler channels available\n", _fh_vpss_count); + } + + if (ret = fh_vpss.fnSetInputConfig(&_fh_snr_dim)) + return ret; + if (ret = fh_vpss.fnEnable(0)) + return ret; + + _fh_snr_ops = _fh_snr->fnCreate(&fh_isp); + _fh_snr_fmt = _fh_snr->fnFormatForFramerate(framerate); + + /* The ISP only starts delivering frames when a scaler channel is already open */ + if (width > _fh_snr_dim.width) width = _fh_snr_dim.width; + if (height > _fh_snr_dim.height) height = _fh_snr_dim.height; + if (ret = fh_vpss_setup(0, width, height, framerate)) + return ret; + + if (ret = fh_isp.fnMemInit(_fh_snr_dim.width, _fh_snr_dim.height)) + return ret; + if (ret = fh_isp.fnRegisterSensor(0, _fh_snr_ops)) + return ret; + if (ret = fh_isp.fnSensorInit()) + return ret; + if (ret = fh_isp.fnSetSensorFormat(_fh_snr_fmt)) + return ret; + if (ret = fh_isp.fnInit()) + return ret; + + snprintf(path, sizeof(path), "/etc/sensors/%s_attr.hex", _fh_snr->name); + if (fh_config_load(path)) { + snprintf(path, sizeof(path), "/usr/lib/sensors/params/%s_attr.hex", _fh_snr->name); + if (fh_config_load(path)) + HAL_WARNING("fh_hal", "No ISP tuning file found for %s, image quality will suffer!\n", + _fh_snr->name); + } + + if (mirror || flip) + fh_isp.fnSetFlipMirrorEx(mirror ? 1 : 0, flip ? 1 : 0, _fh_snr->bayer); + + fh_set_antiflicker(antiflicker); + + /* Image-gain based day/night detection (no external light sensor needed) */ + if (fh_isp.fnSmartIrInit && !fh_isp.fnSmartIrInit()) { + if (fh_isp.fnSmartIrSetAttr) + fh_isp.fnSmartIrSetAttr(0); + _fh_smartir = fh_isp.fnSmartIrStatus != NULL; + HAL_INFO("fh_hal", "SmartIR day/night detection %s\n", _fh_smartir ? "enabled" : "unavailable"); + if (_fh_smartir && fh_isp.fnSmartIrGetThreshold && fh_isp.fnSmartIrSetThreshold) { + /* The status function re-reads the gain thresholds from the ISP + * parameter block (loaded from the sensor tuning file) on every + * call, which silently replaces the library defaults; the GC4653 + * file's values flipped the PB1 to night in daylight at 1.1x gain. + * The vendor application always writes them back; do the same. */ + unsigned short th[4] = {0}; + fh_isp.fnSmartIrGetThreshold(th); + fh_isp.fnSmartIrSetThreshold(th); + HAL_INFO("fh_hal", "SmartIR thresholds: %u %u %u %u\n", th[0], th[1], th[2], th[3]); + } + } + fh_irled(0); + fh_whitelamp(0); + + _fh_isp_on = 1; + + return EXIT_SUCCESS; +} + +void fh_pipeline_destroy(void) +{ + _fh_isp_on = 0; + for (int i = 0; i < 50 && _fh_isp_busy; i++) + usleep(10000); + + fh_isp.fnExit(); + fh_isp.fnUnregisterSensor(0); + if (_fh_snr_ops && _fh_snr_ops->fnDeinit) + _fh_snr_ops->fnDeinit(); + + for (int i = 0; i < FH_VPSS_CHN_NUM; i++) + fh_channel_destroy(i); + fh_vpss.fnDisable(0); +} + +static void fh_venc_fill_rc(fh_venc_rc *rc, hal_vidconfig *config, int h265, int framerate) +{ + unsigned int bitrate = (unsigned int)config->bitrate << 10; + unsigned int maxBitrate = (unsigned int)MAX(config->bitrate, config->maxBitrate) << 10; + unsigned int fps = (unsigned int)framerate | (1u << 16); + + memset(rc, 0, sizeof(*rc)); + switch (config->mode) { + case HAL_VIDMODE_QP: + if (h265) goto cbr; + rc->mode = FH_VENC_RC_H264_FIXQP; + rc->param[0] = config->maxQual; + rc->param[1] = config->maxQual; + rc->param[2] = fps; + break; + case HAL_VIDMODE_VBR: + case HAL_VIDMODE_ABR: + case HAL_VIDMODE_AVBR: + rc->mode = h265 ? FH_VENC_RC_H265_VBR : FH_VENC_RC_H264_VBR; + rc->param[0] = 35; + rc->param[1] = maxBitrate; + rc->param[2] = fps; + rc->param[3] = 120; + rc->param[6] = 5; + rc->param[7] = 1; + break; + default: +cbr: + rc->mode = h265 ? FH_VENC_RC_H265_CBR : FH_VENC_RC_H264_CBR; + rc->param[0] = 35; + rc->param[1] = bitrate; + rc->param[2] = 28; + rc->param[3] = 50; + rc->param[4] = 35; + rc->param[5] = 50; + rc->param[6] = fps; + rc->param[7] = 120; + rc->param[10] = 5; + rc->param[11] = 1; + break; + } +} + +static int fh_osd_setup(void) +{ + int ret; + /* libvmm helper: fills { phys, virt, size } */ + int (*fnAlloc)(void *, unsigned int, unsigned int, const char *) = + dlsym(fh_sys.handleVmm, "buffer_malloc_withname"); + int (*fnSetGraph)(fh_vpss_graph *) = dlsym(fh_vpss.handle, "FH_VPSS_SetGraph"); + fh_vpss_graph graph; + struct { unsigned int phys; unsigned short *virt; unsigned int size; } buf = { 0 }; + + if (_fh_osd_on) return EXIT_SUCCESS; + if (!fnAlloc || !fnSetGraph) + HAL_ERROR("fh_osd", "Overlay symbols are unavailable!\n"); + + _fh_osd_size = _fh_snr_dim.width * _fh_snr_dim.height * 2; + if (ret = fnAlloc(&buf, _fh_osd_size, 8, "divinus-osd")) + HAL_ERROR("fh_osd", "Allocating the overlay plane failed with %#x!\n", ret); + if (!buf.virt) + HAL_ERROR("fh_osd", "The overlay plane has no user mapping!\n"); + _fh_osd_phys = buf.phys; + _fh_osd_virt = buf.virt; + HAL_INFO("fh_osd", "Overlay plane %ux%u at %#x\n", _fh_snr_dim.width, _fh_snr_dim.height, _fh_osd_phys); + memset(_fh_osd_virt, 0, _fh_osd_size); + + memset(&graph, 0, sizeof(graph)); + graph.enable = 1; + graph.physAddr = _fh_osd_phys; + graph.alpha = 255; + graph.width = _fh_snr_dim.width; + graph.height = _fh_snr_dim.height; + graph.stride = _fh_snr_dim.width * 2; + if (ret = fnSetGraph(&graph)) + HAL_ERROR("fh_osd", "Enabling the overlay plane failed with %#x!\n", ret); + _fh_osd_on = 1; + return EXIT_SUCCESS; +} + +/* divinus positions regions in main-stream pixels; the plane is at sensor size */ +static void fh_osd_scale(hal_rect *rect) +{ + unsigned int sw = _fh_vpss_dim[0].width ? _fh_vpss_dim[0].width : _fh_snr_dim.width; + unsigned int sh = _fh_vpss_dim[0].height ? _fh_vpss_dim[0].height : _fh_snr_dim.height; + rect->x = (unsigned int)rect->x * _fh_snr_dim.width / sw; + rect->y = (unsigned int)rect->y * _fh_snr_dim.height / sh; +} + +static void fh_osd_clear(hal_rect rect) +{ + for (unsigned int y = 0; y < rect.height; y++) { + unsigned int py = rect.y + y; + if (py >= _fh_snr_dim.height) break; + unsigned int w = rect.width; + if (rect.x + w > _fh_snr_dim.width) w = _fh_snr_dim.width - rect.x; + memset(_fh_osd_virt + py * _fh_snr_dim.width + rect.x, 0, w * 2); + } +} + +int fh_region_create(int *handle, hal_rect rect, short opacity) +{ + int ret; + if (ret = fh_osd_setup()) + return ret; + if (*handle < 0 || *handle >= FH_OSD_MAX) { + for (int i = 0; i < FH_OSD_MAX; i++) + if (!_fh_osd[i].used) { *handle = i; break; } + if (*handle < 0 || *handle >= FH_OSD_MAX) + HAL_ERROR("fh_osd", "No free overlay region!\n"); + } + if (_fh_osd[*handle].used) + fh_osd_clear(_fh_osd[*handle].rect); + fh_osd_scale(&rect); + _fh_osd[*handle].used = 1; + _fh_osd[*handle].rect = rect; + return EXIT_SUCCESS; +} + +void fh_region_destroy(int *handle) +{ + if (*handle < 0 || *handle >= FH_OSD_MAX || !_fh_osd[*handle].used) return; + fh_osd_clear(_fh_osd[*handle].rect); + _fh_osd[*handle].used = 0; + *handle = -1; +} + +/* Bitmaps arrive as BGR555LE; set the alpha bit for opaque pixels (ARGB1555) */ +int fh_region_setbitmap(int *handle, hal_bitmap *bitmap) +{ + + if (*handle < 0 || *handle >= FH_OSD_MAX || !_fh_osd[*handle].used || !_fh_osd_virt) + return EXIT_FAILURE; + fh_osd_rgn *rgn = &_fh_osd[*handle]; + fh_osd_clear(rgn->rect); + rgn->rect.width = bitmap->dim.width; + rgn->rect.height = bitmap->dim.height; + unsigned short *src = bitmap->data; + for (unsigned int y = 0; y < bitmap->dim.height; y++) { + unsigned int py = rgn->rect.y + y; + if (py >= _fh_snr_dim.height) break; + unsigned short *dst = _fh_osd_virt + py * _fh_snr_dim.width + rgn->rect.x; + for (unsigned int x = 0; x < bitmap->dim.width && rgn->rect.x + x < _fh_snr_dim.width; x++) { + unsigned short p = src[y * bitmap->dim.width + x]; + dst[x] = p ? (p | 0x8000) : 0; + } + } + return EXIT_SUCCESS; +} + +int fh_video_create(char index, hal_vidconfig *config) +{ + int ret; + unsigned int type; + + if (index >= FH_VENC_CHN_NUM) + HAL_ERROR("fh_hal", "Only %d encoder channels are available!\n", FH_VENC_CHN_NUM); + + switch (config->codec) { + case HAL_VIDCODEC_JPG: type = FH_VENC_TYPE_JPEG; break; + case HAL_VIDCODEC_MJPG: type = FH_VENC_TYPE_MJPEG; break; + case HAL_VIDCODEC_H264: type = FH_VENC_TYPE_H264; break; + case HAL_VIDCODEC_H265: type = FH_VENC_TYPE_H265; break; + default: HAL_ERROR("fh_venc", "This codec is not supported by the hardware!\n"); + } + + if (config->width > _fh_snr_dim.width || config->height > _fh_snr_dim.height) { + config->width = _fh_snr_dim.width; + config->height = _fh_snr_dim.height; + } + + /* The scaler output mode must match the encoder before it is configured */ + if (_fh_vpss_src[index] >= 0) { + if (ret = fh_vpss.fnSetOutputMode(index, type == FH_VENC_TYPE_H265 ? 1 : 0)) + HAL_WARNING("fh_vpss", "Setting output mode on channel %d failed with %#x!\n", index, ret); + } else { + /* Shared scaler channel: the encoder must use that channel's size */ + char src = fh_vpss_pick(index); + config->width = _fh_vpss_dim[src].width; + config->height = _fh_vpss_dim[src].height; + } + + if (_fh_venc_made[index].width) { + /* There is no FH_VENC_DestroyChn: a channel's memory pool stays allocated + * until FH_SYS_Exit and a second CreateChn fails (-0x3f3). The vendor + * application creates each channel once with both codecs reserved and + * switches with SetChnAttr; do the same, but never grow past the pool. */ + if (config->width > _fh_venc_made[index].width || config->height > _fh_venc_made[index].height) + HAL_ERROR("fh_venc", "Channel %d was created at %ux%u; %ux%u needs a restart\n", index, + _fh_venc_made[index].width, _fh_venc_made[index].height, config->width, config->height); + } else { + /* The vendor application always reserves memory for both H.264 and H.265 */ + fh_venc_cfg cfg = { .types = (type & (FH_VENC_TYPE_H264 | FH_VENC_TYPE_H265)) ? + (FH_VENC_TYPE_H264 | FH_VENC_TYPE_H265) : type, .width = config->width, .height = config->height }; + if (ret = fh_venc.fnCreateChannel(index, &cfg)) { + HAL_DANGER("fh_venc", "Creating channel %d (%ux%u) failed with %#x!\n", index, cfg.width, cfg.height, ret); + return ret; + } + _fh_venc_made[index].width = cfg.width; + _fh_venc_made[index].height = cfg.height; + } + + if (type == FH_VENC_TYPE_JPEG) { + fh_venc_jpgattr attr; + memset(&attr, 0, sizeof(attr)); + attr.type = type; + attr.quality = MAX(config->maxQual, 1); + attr.rateIndex = 4; + if (ret = fh_venc.fnSetChannelConfig(index, &attr)) + return ret; + } else if (type == FH_VENC_TYPE_MJPEG) { + /* { type, width, height, rotation(0..3), rateIndex(0..9), ..., rc } */ + fh_venc_attr attr; + unsigned int quality = MAX(config->maxQual, 1); + memset(&attr, 0, sizeof(attr)); + attr.type = type; + attr.profile = config->width; + attr.gop = config->height; + attr.width = 0; /* rotation 0..3 */ + attr.height = 4; /* rate table index */ + if (config->mode == HAL_VIDMODE_QP) { + /* fixed quality: { 0, quality(1..99), fps | den << 16 } */ + attr.rc.mode = 0; + attr.rc.param[0] = quality > 99 ? 99 : quality; + attr.rc.param[1] = (unsigned int)config->framerate | (1u << 16); + } else { + /* rate controlled: { 1, bitrate, ?, fps(u16) | den(u16) << 16 } */ + attr.rc.mode = 1; + attr.rc.param[0] = (unsigned int)config->bitrate << 10; + attr.rc.param[1] = (unsigned int)config->bitrate << 10; + attr.rc.param[2] = (unsigned int)config->framerate | (1u << 16); + } + if (ret = fh_venc.fnSetChannelConfig(index, &attr)) { + HAL_DANGER("fh_venc", "Configuring MJPEG channel %d failed with %#x!\n", index, ret); + return ret; + } + } else { + fh_venc_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.type = type; + if (type == FH_VENC_TYPE_H265) + attr.profile = 1; + else { + /* The vendor application only ever sets Main (77); High (100) is + * rejected by FH_VENC_SetChnAttr with 0x19d and would leave the + * channel closed, so every H.264 profile maps to Main here */ + if (config->profile != HAL_VIDPROFILE_MAIN) + HAL_WARNING("fh_venc", "Channel %d: only the H.264 Main profile is supported, using it\n", index); + attr.profile = 77; + } + attr.gop = config->gop ? config->gop : config->framerate * 2; + attr.width = config->width; + attr.height = config->height; + fh_venc_fill_rc(&attr.rc, config, type == FH_VENC_TYPE_H265, config->framerate); + if (ret = fh_venc.fnSetChannelConfig(index, &attr)) { + HAL_DANGER("fh_venc", "Configuring channel %d failed with %#x!\n", index, ret); + return ret; + } + } + + _fh_venc_type[index] = type; + + if (type != FH_VENC_TYPE_JPEG && (ret = fh_venc.fnStartReceiving(index))) { + HAL_DANGER("fh_venc", "Starting channel %d failed with %#x!\n", index, ret); + return ret; + } + + fh_state[index].payload = config->codec; + + return EXIT_SUCCESS; +} + +int fh_video_destroy(char index) +{ + fh_state[index].enable = 0; + fh_state[index].payload = HAL_VIDCODEC_UNSPEC; + + fh_venc.fnStopReceiving(index); + fh_sys.fnUnbindByDst(index); + _fh_venc_type[index] = 0; + fh_channel_destroy(index); + + return EXIT_SUCCESS; +} + +int fh_video_destroy_all(void) +{ + for (char i = 0; i < FH_VENC_CHN_NUM; i++) + if (fh_state[i].enable) + fh_video_destroy(i); + + return EXIT_SUCCESS; +} + +void fh_video_request_idr(char index) +{ + fh_venc.fnRequestIdr(index); +} + +int fh_video_snapshot_grab(signed char index, hal_jpegdata *jpeg) +{ + int ret = EXIT_FAILURE; + fh_venc_strm strm; + unsigned int type = FH_VENC_TYPE_JPEG; + + /* index < 0: serve the most recent MJPEG frame cached by the video thread. + * A real JPEG encoder shares the main scaler channel at bind time (the VPU + * only has main + one sub), so snapshots come out at the main resolution; + * this path is the fallback if that encoder cannot deliver a frame. */ + if (index < 0) { + for (int w = 0; w < 100; w++) { + pthread_mutex_lock(&_fh_mjpeg_mtx); + if (_fh_mjpeg_len) { + if (_fh_mjpeg_len > jpeg->length) { + jpeg->data = realloc(jpeg->data, _fh_mjpeg_len); + jpeg->length = _fh_mjpeg_len; + } + memcpy(jpeg->data, _fh_mjpeg_cache, _fh_mjpeg_len); + jpeg->jpegSize = _fh_mjpeg_len; + pthread_mutex_unlock(&_fh_mjpeg_mtx); + return EXIT_SUCCESS; + } + pthread_mutex_unlock(&_fh_mjpeg_mtx); + usleep(20000); + } + HAL_ERROR("fh_venc", "No MJPEG frame available for snapshot!\n"); + } else if (_fh_venc_type[index] != FH_VENC_TYPE_JPEG) + HAL_ERROR("fh_venc", "Channel %d is not a JPEG encoder!\n", index); + + pthread_mutex_lock(&_fh_strm_mtx); + + { + char src = _fh_vpss_src[index] < 0 ? fh_vpss_pick(index) : _fh_vpss_src[index]; + if (ret = fh_sys.fnBindVpu2Enc(src, index)) { + HAL_DANGER("fh_venc", "Binding the encoder channel %d failed with %#x!\n", index, ret); + goto fallback; + } + } + + for (int i = 0; i < 100; i++) { + memset(&strm, 0, sizeof(strm)); + strm.type = type; + if (!(ret = fh_venc.fnGetStream(type, &strm))) + break; + usleep(20000); + } + if (ret) { + HAL_DANGER("fh_venc", "Getting a JPEG frame timed out (%#x)!\n", ret); + goto fallback; + } + + { + /* JPEG: base = data address, frameType = length. MJPEG: NALU-style packets. */ + if (type == FH_VENC_TYPE_JPEG) { + unsigned int len = strm.frameType; + if (len > jpeg->length) { jpeg->data = realloc(jpeg->data, len); jpeg->length = len; } + memcpy(jpeg->data, (void*)strm.base, len); + jpeg->jpegSize = len; + } else { + unsigned int total = 0; + for (unsigned int i = 0; i < strm.naluCount && i < 28; i++) + total += strm.nalu[i].length; + if (total > jpeg->length) { jpeg->data = realloc(jpeg->data, total); jpeg->length = total; } + jpeg->jpegSize = 0; + for (unsigned int i = 0; i < strm.naluCount && i < 28; i++) { + memcpy(jpeg->data + jpeg->jpegSize, (void*)strm.nalu[i].addr, strm.nalu[i].length); + jpeg->jpegSize += strm.nalu[i].length; + } + } + } + fh_venc.fnReleaseStream(strm.channel); + ret = EXIT_SUCCESS; + +abort: + fh_sys.fnUnbindByDst(index); + pthread_mutex_unlock(&_fh_strm_mtx); + + return ret; + +fallback: + fh_sys.fnUnbindByDst(index); + pthread_mutex_unlock(&_fh_strm_mtx); + HAL_WARNING("fh_venc", "Falling back to the MJPEG sub-stream for the snapshot\n"); + + return fh_video_snapshot_grab(-1, jpeg); +} + +void *fh_video_thread(void) +{ + unsigned int mask = FH_VENC_TYPE_H264 | FH_VENC_TYPE_H265 | FH_VENC_TYPE_MJPEG; + fh_venc_strm strm; + + while (keepRunning) { + char active = 0; + for (int i = 0; i < FH_VENC_CHN_NUM; i++) + if (fh_state[i].enable && fh_state[i].mainLoop) active = 1; + if (!active) { + usleep(100000); + continue; + } + + memset(&strm, 0, sizeof(strm)); + strm.type = mask; + if (fh_venc.fnGetStreamBlocking(mask, &strm)) { + usleep(10000); + continue; + } + + char index = strm.channel; + /* The stream lives in the SDK's DMA buffer, which is mapped uncached: + * every pass over it (start-code scans, RTP payload copies) reads at + * bus speed and a 300 KB keyframe cost ~400 ms of CPU. Copy each frame + * once into a cached buffer, hand the SDK buffer back at once, and let + * every consumer work on the copy. */ + { + static unsigned char *vbuf; static unsigned int vcap; + unsigned int need = 0, off = 0; + if (strm.type == FH_VENC_TYPE_MJPEG || _fh_venc_type[index] == FH_VENC_TYPE_MJPEG) + need = strm.frameType; + else for (unsigned int i = 0; i < strm.naluCount && i < 28; i++) need += strm.nalu[i].length; + if (need > vcap) { + unsigned char *n = realloc(vbuf, need + 65536); + if (n) { vbuf = n; vcap = need + 65536; } + } + if (vbuf && need <= vcap) { + if (strm.type == FH_VENC_TYPE_MJPEG || _fh_venc_type[index] == FH_VENC_TYPE_MJPEG) { + memcpy(vbuf, (void*)strm.base, need); + strm.base = (unsigned int)vbuf; + } else for (unsigned int i = 0; i < strm.naluCount && i < 28; i++) { + memcpy(vbuf + off, (void*)strm.nalu[i].addr, strm.nalu[i].length); + strm.nalu[i].addr = (unsigned int)(vbuf + off); + off += strm.nalu[i].length; + } + fh_venc.fnReleaseStream(strm.channel); + strm.channel |= 0x100; /* released: skip the release below */ + } + } + if (index < FH_VENC_CHN_NUM && fh_state[index].enable && fh_vid_cb) { + /* The stream record's type field does not match the fh_venc_type the + * channel was configured with for HEVC (VPS/SPS/PPS were parsed with + * H.264 rules and never recognised), so go by what was configured */ + unsigned int chnType = _fh_venc_type[index]; + char h265 = chnType == FH_VENC_TYPE_H265; + hal_vidstream outStrm; + hal_vidpack outPack[28]; + unsigned long long pts = ((unsigned long long)strm.ptsHigh << 32) | strm.ptsLow; + memset(outPack, 0, sizeof(outPack)); + outStrm.seq = 0; + outStrm.pack = outPack; + + if (chnType == FH_VENC_TYPE_MJPEG || strm.type == FH_VENC_TYPE_MJPEG) { + /* MJPEG/JPEG frames arrive whole at base/frameType, not as NALUs */ + unsigned int mlen = strm.frameType; + pthread_mutex_lock(&_fh_mjpeg_mtx); + if (mlen > _fh_mjpeg_cap) { + _fh_mjpeg_cache = realloc(_fh_mjpeg_cache, mlen); + _fh_mjpeg_cap = mlen; + } + if (_fh_mjpeg_cache) { + memcpy(_fh_mjpeg_cache, (void*)strm.base, mlen); + _fh_mjpeg_len = mlen; + } + pthread_mutex_unlock(&_fh_mjpeg_mtx); + outStrm.count = 1; + outPack[0].data = (unsigned char*)strm.base; + outPack[0].length = strm.frameType; + outPack[0].offset = 0; + outPack[0].timestamp = pts; + } else { + outStrm.count = strm.naluCount > 28 ? 28 : strm.naluCount; + for (unsigned int i = 0; i < outStrm.count; i++) { + unsigned char *data = (unsigned char*)strm.nalu[i].addr; + unsigned int len = strm.nalu[i].length; + outPack[i].data = data; + outPack[i].length = len; + outPack[i].offset = 0; + outPack[i].timestamp = pts; + /* The HEVC encoder hands VPS, SPS and PPS bundled into the IDR + * entry (3-byte start codes inside it), and the MP4 and raw + * consumers trust these per-NALU entries, so split each entry + * on start codes; RTP splits on its own anyway */ + unsigned int cnt = 0, pos = 0; + while (pos + 3 < len && cnt < 8) { + unsigned int sc = 0, next = pos + 3; + if (data[pos] == 0 && data[pos + 1] == 0 && data[pos + 2] == 1) sc = 3; + else if (pos + 4 < len && data[pos] == 0 && data[pos + 1] == 0 && + data[pos + 2] == 0 && data[pos + 3] == 1) sc = 4; + if (!sc) break; + for (next = pos + sc + 1; next + 2 < len; next++) + if (data[next] == 0 && data[next + 1] == 0 && + (data[next + 2] == 1 || (next + 3 < len && data[next + 2] == 0 && data[next + 3] == 1))) + break; + if (next + 2 >= len) next = len; + outPack[i].nalu[cnt].offset = pos; + outPack[i].nalu[cnt].length = next - pos; + outPack[i].nalu[cnt].type = h265 ? + ((data[pos + sc] >> 1) & 0x3f) : (data[pos + sc] & 0x1f); + cnt++; + pos = next; + } + if (!cnt) { + /* no start code found: pass the entry through as one unit */ + outPack[i].nalu[0].offset = 0; + outPack[i].nalu[0].length = len; + outPack[i].nalu[0].type = 0; + cnt = 1; + } + outPack[i].naluCnt = cnt; + } + } + (*fh_vid_cb)(index, &outStrm); + } + + if (!(strm.channel & 0x100)) + fh_venc.fnReleaseStream(strm.channel); + } + HAL_INFO("fh_venc", "Shutting down encoding thread...\n"); + return NULL; +} + +void fh_system_deinit(void) +{ + fh_sys.fnExit(); +} + +int fh_system_init(char *snrConfig) +{ + { + fh_sys_ver version; + if (!fh_sys.fnGetVersion(&version)) + HAL_INFO("fh_hal", "SDK build %08x (%08x), package id %#x\n", + version.date, version.commit, version.package); + } + + _fh_snr = NULL; + for (int i = 0; fh_snr_drivers[i]; i++) { + if (fh_snr_drivers[i]->fnProbe()) continue; + _fh_snr = fh_snr_drivers[i]; + break; + } + if (!_fh_snr) + HAL_ERROR("fh_hal", "No supported sensor found on the I2C bus!\n"); + + _fh_snr_dim = _fh_snr->dim; + strncpy(sensor, _fh_snr->name, sizeof(sensor) - 1); + HAL_INFO("fh_hal", "Detected sensor %s (%ux%u)\n", _fh_snr->name, + _fh_snr_dim.width, _fh_snr_dim.height); + + return EXIT_SUCCESS; +} + +int fh_night_available(void) +{ + return _fh_smartir; +} + +/* 1 = scene is dark (night), 0 = day. Fed the previous decision for hysteresis. */ +/* + * Illuminators (from the vendor application's gpio table and lamp code): each + * lamp has an enable GPIO and a brightness PWM, set through /proc/driver/pwm as + * "id,output_mask,duty_ns,period_ns,pulses,delay_ns,phase_ns,stop" (the mask is + * written raw to GLOBAL_CTRL2, so 1 << id). IR: GPIO7 + PWM3. White light: + * GPIO11 + PWM7 (the board file calls GPIO11 "PHY reset"; it is not wired as + * one on the PB1). GPIO11 is pulled up, so the white light is on from reset + * until something drives it low. + */ +#define FH_PWM_ON(id, mask) id "," mask ",60,200,0,0,0,0" /* 30%, the driver default */ +#define FH_PWM_OFF(id, mask) id "," mask ",0,200,0,0,0,0" /* held low */ +#define FH_WHITELAMP_GPIO 11 + +int fh_irled(char enable) +{ + /* Brightness only; the enable is GPIO7, which divinus drives as ir_led_pin */ + fh_proc_write("/proc/driver/pwm", enable ? FH_PWM_ON("3", "8") : FH_PWM_OFF("3", "8")); + return EXIT_SUCCESS; +} + +int fh_whitelamp(char enable) +{ + fh_proc_write("/proc/driver/pwm", enable ? FH_PWM_ON("7", "128") : FH_PWM_OFF("7", "128")); + gpio_write(FH_WHITELAMP_GPIO, enable); + return EXIT_SUCCESS; +} + +/* Override SmartIR's gain thresholds (th[0] day->night, th[1] night->day); 0 keeps the default */ +int fh_night_thresholds(unsigned short night, unsigned short day) +{ + unsigned short th[4] = {0}; + if (!_fh_smartir || !fh_isp.fnSmartIrGetThreshold || !fh_isp.fnSmartIrSetThreshold) + return EXIT_FAILURE; + if (fh_isp.fnSmartIrGetThreshold(th)) + return EXIT_FAILURE; + if (night) th[0] = night; + if (day) th[1] = day; + HAL_INFO("fh_hal", "SmartIR thresholds set to %u %u %u %u\n", th[0], th[1], th[2], th[3]); + return fh_isp.fnSmartIrSetThreshold(th); +} + +int fh_night_status(void) +{ + if (!_fh_smartir) + return 0; + { + static int lastRaw = -1, calls = 0; + int raw = fh_isp.fnSmartIrStatus(_fh_night_prev); + /* Log every verdict change and a sample every ~10 min (at 40 ms polls) with + * the exposure/gain/total the library decided on and the live thresholds */ + if (raw != lastRaw || !(++calls % 15000)) { + unsigned int ae[16] = {0}; + unsigned short th[4] = {0}; + if (fh_isp.fnGetAeInfo) fh_isp.fnGetAeInfo(ae); + if (fh_isp.fnSmartIrGetThreshold) fh_isp.fnSmartIrGetThreshold(th); + HAL_INFO("fh_hal", "SmartIR raw status %d (prev %d): exp %u gain %u total %u, thresholds %u %u\n", + raw, _fh_night_prev, ae[0], ae[1], ae[4], th[0], th[1]); + lastRaw = raw; + } + _fh_night_prev = raw ? 1 : 0; + } + return _fh_night_prev; +} + +#endif diff --git a/src/hal/fh/fh_hal.h b/src/hal/fh/fh_hal.h new file mode 100644 index 00000000..36931106 --- /dev/null +++ b/src/hal/fh/fh_hal.h @@ -0,0 +1,57 @@ +#pragma once + +#include "fh_common.h" +#include "fh_aud.h" +#include "fh_isp.h" +#include "fh_snr.h" +#include "fh_sys.h" +#include "fh_venc.h" +#include "fh_vpss.h" + +#include "../config.h" +#include "../globals.h" + +#include + +extern hal_chnstate fh_state[FH_VENC_CHN_NUM]; +extern int (*fh_aud_cb)(hal_audframe*); +extern int (*fh_vid_cb)(char, hal_vidstream*); + +void fh_hal_deinit(void); +int fh_hal_init(void); + +void fh_audio_deinit(void); +int fh_audio_init(int samplerate); +void *fh_audio_thread(void); + +int fh_channel_bind(char index); +int fh_channel_create(char index, short width, short height, char framerate, char jpeg); +int fh_channel_grayscale(char enable); +int fh_channel_unbind(char index); + +int fh_config_load(char *path); + +void *fh_image_thread(void); + +int fh_pipeline_create(short width, short height, char mirror, char flip, char framerate, char antiflicker); +int fh_set_antiflicker(char hz); +int fh_night_available(void); +int fh_night_status(void); +int fh_night_thresholds(unsigned short night, unsigned short day); +int fh_irled(char enable); +int fh_whitelamp(char enable); +void fh_pipeline_destroy(void); + +int fh_region_create(int *handle, hal_rect rect, short opacity); +void fh_region_destroy(int *handle); +int fh_region_setbitmap(int *handle, hal_bitmap *bitmap); + +int fh_video_create(char index, hal_vidconfig *config); +int fh_video_destroy(char index); +int fh_video_destroy_all(void); +void fh_video_request_idr(char index); +int fh_video_snapshot_grab(signed char index, hal_jpegdata *jpeg); +void *fh_video_thread(void); + +void fh_system_deinit(void); +int fh_system_init(char *snrConfig); diff --git a/src/hal/fh/fh_isp.h b/src/hal/fh/fh_isp.h new file mode 100644 index 00000000..ffdcae05 --- /dev/null +++ b/src/hal/fh/fh_isp.h @@ -0,0 +1,226 @@ +#pragma once + +#include "fh_common.h" + +/* Video input attributes as produced by the sensor driver and consumed by the ISP (28 bytes) */ +typedef struct { + unsigned short vts; /* frame length in lines */ + unsigned short hts; /* line length in pixel clocks */ + unsigned short height; + unsigned short width; + unsigned short xOffset; + unsigned short yOffset; + unsigned short height2; /* equal to height when not in WDR */ + unsigned short width2; + unsigned int reserved; + unsigned int format; /* bayer/mode word, low 2 bits used */ + unsigned int lanes; /* stored as (lanes - 4) & 0xf */ +} fh_isp_viattr; + +/* libmipi mipi_init() argument */ +typedef struct { + int freqRange; + int sensorMode; + int rawType; + int longFrameVc; /* 0xff = single frame */ + int shortFrameVc; + int laneNum; +} fh_isp_mipi; + +/* + * Sensor operations table registered with API_ISP_SensorRegCb() (0x68 bytes, + * copied by value into the ISP core). Optional entries may be NULL. + */ +typedef struct { + const char *name; /* 0x00 */ + int (*fnSetGain)(unsigned int gain); /* 0x04 gain in 1/64 steps (64 = 1x) */ + int (*fnGetInputAttr)(fh_isp_viattr *attr); /* 0x08 */ + int (*fnGetGain)(unsigned int *gain); /* 0x0c */ + int (*fnSetIntegration)(unsigned int lines); /* 0x10 */ + int (*fnSetFrameHeight)(int multiplier); /* 0x14 VTS = nominal VTS * multiplier */ + int (*fnGetIntegration)(unsigned int *lines); /* 0x18 */ + int (*fnSetFlipMirror)(unsigned int bits); /* 0x1c bit0 flip, bit1 mirror */ + int (*fnGetFlipMirror)(unsigned int *bits); /* 0x20 */ + void *reserved24; + int (*fnInit)(void); /* 0x28 */ + int (*fnReset)(void); /* 0x2c */ + int (*fnDeinit)(void); /* 0x30 */ + int (*fnSetFormat)(int format); /* 0x34 */ + int (*fnKick)(void); /* 0x38 optional */ + int (*fnSetRegister)(unsigned int reg, unsigned int val); /* 0x3c */ + void *reserved40; + int (*fnSetExposureRatio)(void *ratio); /* 0x44 WDR, optional */ + int (*fnGetExposureRatio)(void *ratio); /* 0x48 WDR, optional */ + void *reserved4c; + int (*fnSetChipId)(unsigned int id); /* 0x50 */ + int (*fnGetRegister)(unsigned int reg, unsigned short *val); /* 0x54 */ + int (*fnGetAwbGain)(unsigned int rgb[3]); /* 0x58 optional */ + int (*fnSetAwbGain)(unsigned int rgb[3]); /* 0x5c optional */ + void *reserved60; + void *reserved64; +} fh_isp_snrops; + +typedef struct { + unsigned int frameStart; /* counters */ + unsigned int frameEnd; + unsigned int framerate; + unsigned int width; + unsigned int height; + unsigned int reserved; + unsigned int overflow; +} fh_isp_vistate; + +typedef struct { + void *handle, *handleCore, *handleMipi, *handleAdv; + + int (*fnEnableAe)(unsigned int enable); + int (*fnAeSendCmd)(unsigned int cmd, void *arg); + int (*fnEnableAwb)(unsigned int enable); + int (*fnExit)(void); + int (*fnGetInputAttr)(fh_isp_viattr *attr); + int (*fnGetInputState)(fh_isp_vistate *state); + int (*fnInit)(void); + int (*fnLoadParam)(void *param); + int (*fnMemInit)(unsigned int width, unsigned int height); + int (*fnRegisterSensor)(unsigned int index, fh_isp_snrops *ops); + int (*fnRun)(void); + int (*fnSensorInit)(void); + int (*fnSetFlipMirror)(unsigned int mirror, unsigned int flip); + int (*fnSetFlipMirrorEx)(unsigned int mirror, unsigned int flip, unsigned int bayer); + int (*fnSetFramerateDiv)(unsigned int div); + int (*fnSetSensorFormat)(unsigned int format); + int (*fnUnregisterSensor)(unsigned int index); + + int (*fnAdvInit)(void); + int (*fnSmartIrInit)(void); + int (*fnSmartIrSetAttr)(int rgbir); + unsigned char (*fnSmartIrStatus)(int prevStatus); + int (*fnSmartIrGetThreshold)(unsigned short *th); /* 4 x u16, optional */ + int (*fnSmartIrSetThreshold)(unsigned short *th); + int (*fnGetAeInfo)(void *info); /* [1] gain, [3] exposure, [4] total (1/64) */ + int (*fnAdvSetColorMode)(int color); + int (*fnGetSaturation)(void *sat); /* 20-byte record, byte 5 nonzero = colour */ + int (*fnSetSaturation)(void *sat); + void (*fnMipiInit)(fh_isp_mipi *config); +} fh_isp_impl; + +static int fh_isp_load(fh_isp_impl *isp_lib) { + /* libisp resolves isp_core_* against libispcore at load time */ + if (!(isp_lib->handleCore = dlopen("libispcore.so", RTLD_NOW | RTLD_GLOBAL))) + HAL_ERROR("fh_isp", "Failed to load library!\nError: %s\n", dlerror()); + + if (!(isp_lib->handle = dlopen("libisp.so", RTLD_NOW | RTLD_GLOBAL))) + HAL_ERROR("fh_isp", "Failed to load library!\nError: %s\n", dlerror()); + + if (!(isp_lib->handleMipi = dlopen("libmipi.so", RTLD_NOW | RTLD_GLOBAL))) + HAL_ERROR("fh_isp", "Failed to load library!\nError: %s\n", dlerror()); + + if (!(isp_lib->handleAdv = dlopen("libadvapi.so", RTLD_NOW | RTLD_GLOBAL))) + HAL_ERROR("fh_isp", "Failed to load library!\nError: %s\n", dlerror()); + + if (!(isp_lib->fnEnableAe = (int(*)(unsigned int enable)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_AEAlgEn"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnAeSendCmd = (int(*)(unsigned int cmd, void *arg)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_AESendCmd"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnEnableAwb = (int(*)(unsigned int enable)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_AWBAlgEn"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnExit = (int(*)(void)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_Exit"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnGetInputAttr = (int(*)(fh_isp_viattr *attr)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_GetViAttr"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnGetInputState = (int(*)(fh_isp_vistate *state)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_GetVIState"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnInit = (int(*)(void)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_Init"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnLoadParam = (int(*)(void *param)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_LoadIspParam"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnMemInit = (int(*)(unsigned int width, unsigned int height)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_MemInit"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnRegisterSensor = (int(*)(unsigned int index, fh_isp_snrops *ops)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_SensorRegCb"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnRun = (int(*)(void)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_Run"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnSensorInit = (int(*)(void)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_SensorInit"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnSetFlipMirror = (int(*)(unsigned int mirror, unsigned int flip)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_SetMirrorAndflip"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnSetFlipMirrorEx = (int(*)(unsigned int mirror, unsigned int flip, unsigned int bayer)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_SetMirrorAndflipEx"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnSetFramerateDiv = (int(*)(unsigned int div)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_SetSensorFrameRate"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnSetSensorFormat = (int(*)(unsigned int format)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_SetSensorFmt"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnUnregisterSensor = (int(*)(unsigned int index)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_SensorUnRegCb"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnAdvInit = (int(*)(void)) + hal_symbol_load("fh_isp", isp_lib->handleAdv, "FHAdv_Isp_Init"))) + return EXIT_FAILURE; + + /* SmartIR: image-gain based day/night detection, optional */ + isp_lib->fnSmartIrInit = (int(*)(void))dlsym(isp_lib->handleAdv, "FHAdv_SmartIR_Init"); + isp_lib->fnSmartIrSetAttr = (int(*)(int))dlsym(isp_lib->handleAdv, "FHAdv_SmartIR_SetAttr"); + isp_lib->fnSmartIrStatus = (unsigned char(*)(int))dlsym(isp_lib->handleAdv, "FHAdv_SmartIR_GetDayNightStatus"); + isp_lib->fnSmartIrGetThreshold = (int(*)(unsigned short*))dlsym(isp_lib->handleAdv, "FHAdv_SmartIR_Getthreshold"); + isp_lib->fnSmartIrSetThreshold = (int(*)(unsigned short*))dlsym(isp_lib->handleAdv, "FHAdv_SmartIR_Setthreshold"); + isp_lib->fnGetAeInfo = (int(*)(void*))dlsym(isp_lib->handle, "API_ISP_GetAeInfo"); + + if (!(isp_lib->fnGetSaturation = (int(*)(void *sat)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_GetSaturation"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnSetSaturation = (int(*)(void *sat)) + hal_symbol_load("fh_isp", isp_lib->handle, "API_ISP_SetSaturation"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnAdvSetColorMode = (int(*)(int color)) + hal_symbol_load("fh_isp", isp_lib->handleAdv, "FHAdv_Isp_SetColorMode"))) + return EXIT_FAILURE; + + if (!(isp_lib->fnMipiInit = (void(*)(fh_isp_mipi *config)) + hal_symbol_load("fh_isp", isp_lib->handleMipi, "mipi_init"))) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} + +static void fh_isp_unload(fh_isp_impl *isp_lib) { + if (isp_lib->handleAdv) dlclose(isp_lib->handleAdv); + if (isp_lib->handleMipi) dlclose(isp_lib->handleMipi); + if (isp_lib->handle) dlclose(isp_lib->handle); + if (isp_lib->handleCore) dlclose(isp_lib->handleCore); + memset(isp_lib, 0, sizeof(*isp_lib)); +} diff --git a/src/hal/fh/fh_snr.h b/src/hal/fh/fh_snr.h new file mode 100644 index 00000000..8a620645 --- /dev/null +++ b/src/hal/fh/fh_snr.h @@ -0,0 +1,25 @@ +#pragma once + +#include "fh_isp.h" + +/* + * Sensor drivers for the Fullhan ISP live in user space: the ISP core calls + * back through an fh_isp_snrops table for exposure, gain, VTS and flip control, + * and the driver programs the sensor over /dev/i2c-0 itself. + */ +typedef struct { + const char *name; /* matches the ISP tuning file prefix, e.g. "gc4653_mipi" */ + fh_common_dim dim; /* native output size */ + unsigned int bayer; /* value for API_ISP_SetMirrorAndflipEx() */ + int (*fnProbe)(void); /* returns 0 if the sensor answers on the bus */ + int (*fnFormatForFramerate)(int framerate); /* fh sensor format code */ + int (*fnFramerateForFormat)(int format); + fh_isp_snrops *(*fnCreate)(fh_isp_impl *isp); +} fh_snr_driver; + +extern fh_snr_driver fh_snr_gc4653; + +static fh_snr_driver *fh_snr_drivers[] = { + &fh_snr_gc4653, + NULL +}; diff --git a/src/hal/fh/fh_snr_gc4653.c b/src/hal/fh/fh_snr_gc4653.c new file mode 100644 index 00000000..3a16f1f6 --- /dev/null +++ b/src/hal/fh/fh_snr_gc4653.c @@ -0,0 +1,299 @@ +#if defined(__arm__) && !defined(__ARM_PCS_VFP) && __ARM_ARCH == 6 + +/* + * GalaxyCore GC4653 (2560x1440, RAW10, 2-lane MIPI) driver for the Fullhan + * V100 ISP sensor callback interface. Registers are programmed through the + * standard Linux i2c-dev interface on /dev/i2c-0 (16-bit address, 8-bit data). + */ +#include "fh_snr.h" +#include "fh_snr_gc4653_regs.h" + +#include +#include +#include +#include +#include + +#define GC4653_CHIP_ID 0x4653 +#define GC4653_REG_ID_H 0x03f0 +#define GC4653_REG_ID_L 0x03f1 +#define GC4653_REG_FLIP 0x0101 +#define GC4653_REG_EXP_H 0x0202 +#define GC4653_REG_EXP_L 0x0203 +#define GC4653_REG_DIG_H 0x020e +#define GC4653_REG_DIG_L 0x020f +#define GC4653_REG_VTS_H 0x0340 +#define GC4653_REG_VTS_L 0x0341 + +/* Fullhan sensor format codes understood by this driver */ +#define GC4653_FMT_15FPS 0x601 +#define GC4653_FMT_20FPS 0x602 +#define GC4653_FMT_25FPS 0x603 +#define GC4653_FMT_30FPS 0x604 + +#define GC4653_GAIN_ROWS (sizeof(gc4653_gain_level) / sizeof(*gc4653_gain_level)) + +static const unsigned char gc4653_i2c_addrs[] = { 0x29, 0x10 }; +static const unsigned short gc4653_gain_reg_addr[7] = + { 0x02b3, 0x02b4, 0x02b8, 0x02b9, 0x0515, 0x0519, 0x02d9 }; + +static fh_isp_impl *_gc4653_isp; +static int _gc4653_fd = -1; +static unsigned char _gc4653_addr; +static int _gc4653_fmt = GC4653_FMT_25FPS; +static unsigned int _gc4653_gain = 64, _gc4653_intt = 0x6f; +static char _gc4653_ready; + +static int gc4653_read(unsigned short reg) +{ + unsigned char wbuf[2] = { reg >> 8, reg & 0xff }, rbuf[1] = { 0 }; + struct i2c_msg msgs[2] = { + { .addr = _gc4653_addr, .flags = 0, .len = 2, .buf = wbuf }, + { .addr = _gc4653_addr, .flags = I2C_M_RD, .len = 1, .buf = rbuf }, + }; + struct i2c_rdwr_ioctl_data data = { .msgs = msgs, .nmsgs = 2 }; + if (ioctl(_gc4653_fd, I2C_RDWR, &data) < 0) + return -1; + return rbuf[0]; +} + +static int gc4653_write(unsigned short reg, unsigned char val) +{ + unsigned char wbuf[3] = { reg >> 8, reg & 0xff, val }; + struct i2c_msg msg = { .addr = _gc4653_addr, .flags = 0, .len = 3, .buf = wbuf }; + struct i2c_rdwr_ioctl_data data = { .msgs = &msg, .nmsgs = 1 }; + if (ioctl(_gc4653_fd, I2C_RDWR, &data) < 0) { + HAL_WARNING("fh_snr", "GC4653 write of register %#x failed!\n", reg); + return -1; + } + return 0; +} + +static void gc4653_write_table(const gc_reg *table, int count) +{ + for (int i = 0; i < count; i++) + gc4653_write(table[i].reg, table[i].val); +} + +static int gc4653_open(unsigned char addr) +{ + if (_gc4653_fd >= 0) + close(_gc4653_fd); + if ((_gc4653_fd = open("/dev/i2c-0", O_RDWR)) < 0) + return -1; + _gc4653_addr = addr; + ioctl(_gc4653_fd, I2C_TENBIT, 0); + ioctl(_gc4653_fd, I2C_SLAVE, addr); + return 0; +} + +static int gc4653_probe(void) +{ + for (unsigned int i = 0; i < sizeof(gc4653_i2c_addrs); i++) { + if (gc4653_open(gc4653_i2c_addrs[i])) + return -1; + int high = gc4653_read(GC4653_REG_ID_H), low = gc4653_read(GC4653_REG_ID_L); + if (high >= 0 && low >= 0 && ((high << 8) | low) == GC4653_CHIP_ID) + return 0; + } + close(_gc4653_fd); + _gc4653_fd = -1; + return -1; +} + +static int gc4653_set_gain(unsigned int gain) +{ + unsigned int row = 0; + if (gain < gc4653_gain_level[0]) + gain = gc4653_gain_level[0]; + while (row + 1 < GC4653_GAIN_ROWS && gain >= gc4653_gain_level[row + 1]) + row++; + _gc4653_gain = gain; + for (int i = 0; i < 7; i++) + gc4653_write(gc4653_gain_reg_addr[i], gc4653_gain_regs[row][i]); + unsigned int digital = (gain << 6) / gc4653_gain_level[row]; /* 6.6 fixed point */ + gc4653_write(GC4653_REG_DIG_H, digital >> 6); + gc4653_write(GC4653_REG_DIG_L, (digital & 0x3f) << 2); + return 0; +} + +static int gc4653_get_gain(unsigned int *gain) +{ + *gain = _gc4653_gain; + return 0; +} + +static int gc4653_get_input_attr(fh_isp_viattr *attr) +{ + if (!attr) + return -0xbba; + memset(attr, 0, sizeof(*attr)); + switch (_gc4653_fmt) { + case GC4653_FMT_15FPS: attr->vts = 1500; attr->hts = 9600; break; + case GC4653_FMT_20FPS: attr->vts = 2250; attr->hts = 4800; break; + case GC4653_FMT_25FPS: attr->vts = 1800; attr->hts = 4800; break; + case GC4653_FMT_30FPS: attr->vts = 1500; attr->hts = 4800; break; + default: return -0xbbd; + } + attr->width = attr->width2 = 2560; + attr->height = attr->height2 = 1440; + attr->format = 1; + return 0; +} + +static int gc4653_set_integration(unsigned int lines) +{ + _gc4653_intt = lines; + gc4653_write(GC4653_REG_EXP_H, (lines >> 8) & 0xff); + gc4653_write(GC4653_REG_EXP_L, lines & 0xff); + return 0; +} + +static int gc4653_get_integration(unsigned int *lines) +{ + *lines = _gc4653_intt; + return 0; +} + +static int gc4653_set_frame_height(int multiplier) +{ + fh_isp_viattr attr; + gc4653_get_input_attr(&attr); + unsigned int vts = attr.vts * multiplier; + gc4653_write(GC4653_REG_VTS_H, (vts >> 8) & 0xff); + gc4653_write(GC4653_REG_VTS_L, vts & 0xff); + return 0; +} + +/* ISP passes bit 0 = flip, bit 1 = mirror; the sensor register has bit 0 = mirror, bit 1 = flip */ +static int gc4653_set_flip_mirror(unsigned int bits) +{ + int val = gc4653_read(GC4653_REG_FLIP); + if (val < 0) return -1; + return gc4653_write(GC4653_REG_FLIP, (val & 0xfc) | ((bits >> 1) & 1) | ((bits & 1) << 1)); +} + +static int gc4653_get_flip_mirror(unsigned int *bits) +{ + int val = gc4653_read(GC4653_REG_FLIP); + if (val < 0) return -1; + *bits = ((val >> 1) & 1) | ((val & 1) << 1); + return 0; +} + +static int gc4653_init(void) +{ + if (gc4653_probe()) + return -1; + _gc4653_gain = 64; + _gc4653_intt = 0x6f; + _gc4653_ready = 1; + return 0; +} + +static int gc4653_reset(void) { return 0; } + +static int gc4653_deinit(void) +{ + if (_gc4653_fd >= 0) close(_gc4653_fd); + _gc4653_fd = -1; + _gc4653_ready = 0; + return 0; +} + +static int gc4653_set_format(int format) +{ + fh_isp_mipi mipi = { .freqRange = 8, .sensorMode = 0, .rawType = 0, + .longFrameVc = 0xff, .shortFrameVc = 0, .laneNum = 2 }; + _gc4653_fmt = format; + _gc4653_isp->fnMipiInit(&mipi); + if (!_gc4653_ready) + return 0; + switch (format) { + case GC4653_FMT_15FPS: gc4653_write_table(gc4653_init_15fps, sizeof(gc4653_init_15fps) / sizeof(gc_reg)); break; + case GC4653_FMT_20FPS: gc4653_write_table(gc4653_init_20fps, sizeof(gc4653_init_20fps) / sizeof(gc_reg)); break; + case GC4653_FMT_25FPS: gc4653_write_table(gc4653_init_25fps, sizeof(gc4653_init_25fps) / sizeof(gc_reg)); break; + case GC4653_FMT_30FPS: gc4653_write_table(gc4653_init_30fps, sizeof(gc4653_init_30fps) / sizeof(gc_reg)); break; + default: return -1; + } + gc4653_set_integration(500); + gc4653_set_gain(64); + return 0; +} + +static int gc4653_set_register(unsigned int reg, unsigned int val) +{ + return gc4653_write(reg, val); +} + +static int gc4653_get_register(unsigned int reg, unsigned short *val) +{ + int ret = gc4653_read(reg); + if (ret < 0) return -1; + *val = ret; + return 0; +} + +static int gc4653_set_chip_id(unsigned int id) { (void)id; return 0; } + +static fh_isp_snrops gc4653_ops = { + .name = "gc4653_mipi", + .fnSetGain = gc4653_set_gain, + .fnGetInputAttr = gc4653_get_input_attr, + .fnGetGain = gc4653_get_gain, + .fnSetIntegration = gc4653_set_integration, + .fnSetFrameHeight = gc4653_set_frame_height, + .fnGetIntegration = gc4653_get_integration, + .fnSetFlipMirror = gc4653_set_flip_mirror, + .fnGetFlipMirror = gc4653_get_flip_mirror, + .fnInit = gc4653_init, + .fnReset = gc4653_reset, + .fnDeinit = gc4653_deinit, + .fnSetFormat = gc4653_set_format, + .fnSetRegister = gc4653_set_register, + .fnSetChipId = gc4653_set_chip_id, + .fnGetRegister = gc4653_get_register, +}; + +static int gc4653_format_for_framerate(int framerate) +{ + if (framerate <= 15) return GC4653_FMT_15FPS; + if (framerate <= 20) return GC4653_FMT_20FPS; + if (framerate <= 25) return GC4653_FMT_25FPS; + return GC4653_FMT_30FPS; +} + +static int gc4653_framerate_for_format(int format) +{ + switch (format) { + case GC4653_FMT_15FPS: return 15; + case GC4653_FMT_20FPS: return 20; + case GC4653_FMT_25FPS: return 25; + default: return 30; + } +} + +static int gc4653_probe_once(void) +{ + int ret = gc4653_probe(); + gc4653_deinit(); + return ret; +} + +static fh_isp_snrops *gc4653_create(fh_isp_impl *isp) +{ + _gc4653_isp = isp; + return &gc4653_ops; +} + +fh_snr_driver fh_snr_gc4653 = { + .name = "gc4653_mipi", + .dim = { .width = 2560, .height = 1440 }, + .bayer = 1, + .fnProbe = gc4653_probe_once, + .fnFormatForFramerate = gc4653_format_for_framerate, + .fnFramerateForFormat = gc4653_framerate_for_format, + .fnCreate = gc4653_create, +}; + +#endif diff --git a/src/hal/fh/fh_snr_gc4653_regs.h b/src/hal/fh/fh_snr_gc4653_regs.h new file mode 100644 index 00000000..5f8a101c --- /dev/null +++ b/src/hal/fh/fh_snr_gc4653_regs.h @@ -0,0 +1,132 @@ +/* GC4653 register tables: GalaxyCore reference settings, 2560x1440 RAW10 over 2-lane MIPI, for 15/20/25/30 fps */ +#pragma once +#include +typedef struct { uint16_t reg, val; } gc_reg; +static const gc_reg gc4653_init_15fps[] = { + {0x03fe,0xf0}, {0x03fe,0x00}, {0x0317,0x00}, {0x0320,0x77}, {0x0324,0xc8}, {0x0325,0x06}, + {0x0326,0x6c}, {0x0327,0x03}, {0x0334,0x40}, {0x0336,0x6c}, {0x0337,0x82}, {0x0315,0x25}, + {0x031c,0xc6}, {0x0287,0x18}, {0x0084,0x00}, {0x0087,0x50}, {0x029d,0x08}, {0x0290,0x00}, + {0x0340,0x05}, {0x0341,0xdc}, {0x0345,0x06}, {0x034b,0xb0}, {0x0352,0x08}, {0x0354,0x08}, + {0x02d1,0xb0}, {0x023c,0x05}, {0x0223,0xfb}, {0x0232,0xc4}, {0x0279,0x53}, {0x02d3,0x01}, + {0x0243,0x06}, {0x02ce,0xbf}, {0x02ee,0x30}, {0x026f,0x70}, {0x0257,0x09}, {0x0211,0x02}, + {0x0219,0x09}, {0x023f,0x2d}, {0x0518,0x00}, {0x0519,0x01}, {0x0515,0x08}, {0x02d9,0x3f}, + {0x02da,0x02}, {0x02db,0xe8}, {0x02e6,0x20}, {0x021b,0x10}, {0x0252,0x22}, {0x024e,0x22}, + {0x02c4,0x01}, {0x021d,0x07}, {0x024a,0x01}, {0x02ca,0x02}, {0x0262,0x10}, {0x029a,0x20}, + {0x021c,0x0e}, {0x0298,0x03}, {0x029c,0x00}, {0x027e,0x14}, {0x02c2,0x10}, {0x0540,0x20}, + {0x0546,0x01}, {0x0548,0x01}, {0x0544,0x01}, {0x0242,0x1b}, {0x02c0,0x1b}, {0x02c3,0x20}, + {0x02e4,0x10}, {0x022e,0x00}, {0x027b,0x3f}, {0x0269,0x0f}, {0x02d2,0x40}, {0x027c,0x08}, + {0x023a,0x2e}, {0x0245,0xce}, {0x0530,0x20}, {0x0531,0x02}, {0x0228,0x28}, {0x02ab,0x00}, + {0x0250,0x00}, {0x0221,0x28}, {0x02ac,0x00}, {0x02a5,0x02}, {0x0260,0x0b}, {0x0216,0x04}, + {0x0299,0x1c}, {0x02bb,0x0d}, {0x02a3,0x02}, {0x02a4,0x02}, {0x021e,0x02}, {0x024f,0x08}, + {0x028c,0x08}, {0x0532,0x3f}, {0x0533,0x02}, {0x0277,0x58}, {0x0276,0x70}, {0x0239,0xc0}, + {0x0202,0x05}, {0x0203,0xd0}, {0x0205,0xc0}, {0x02b0,0x90}, {0x0002,0xa9}, {0x0004,0x01}, + {0x0342,0x0c}, {0x0343,0x80}, {0x03fe,0x10}, {0x03fe,0x00}, {0x0106,0x78}, {0x0108,0x0c}, + {0x0114,0x01}, {0x0115,0x12}, {0x0180,0x46}, {0x0181,0x30}, {0x0182,0x05}, {0x0185,0x01}, + {0x03fe,0x10}, {0x03fe,0x00}, {0x0100,0x09}, {0x021a,0x98}, {0x0266,0xc0}, {0x0020,0x01}, + {0x0021,0x03}, {0x0022,0x00}, {0x0023,0x04}, +}; +static const gc_reg gc4653_init_20fps[] = { + {0x03fe,0xf0}, {0x03fe,0x00}, {0x0317,0x00}, {0x0320,0x77}, {0x0324,0xc8}, {0x0325,0x06}, + {0x0326,0x6c}, {0x0327,0x03}, {0x0334,0x40}, {0x0336,0x6c}, {0x0337,0x82}, {0x0315,0x25}, + {0x031c,0xc6}, {0x0287,0x18}, {0x0084,0x00}, {0x0087,0x50}, {0x029d,0x08}, {0x0290,0x00}, + {0x0340,0x08}, {0x0341,0xca}, {0x0345,0x06}, {0x034b,0xb0}, {0x0352,0x08}, {0x0354,0x08}, + {0x02d1,0xe0}, {0x0223,0xf2}, {0x0238,0xa4}, {0x02ce,0x7f}, {0x0232,0xc4}, {0x02d3,0x05}, + {0x0243,0x06}, {0x02ee,0x30}, {0x026f,0x70}, {0x0257,0x09}, {0x0211,0x02}, {0x0219,0x09}, + {0x023f,0x2d}, {0x0518,0x00}, {0x0519,0x01}, {0x0515,0x08}, {0x02d9,0x3f}, {0x02da,0x02}, + {0x02db,0xe8}, {0x02e6,0x20}, {0x021b,0x10}, {0x0252,0x22}, {0x024e,0x22}, {0x02c4,0x01}, + {0x021d,0x17}, {0x024a,0x01}, {0x02ca,0x02}, {0x0262,0x10}, {0x029a,0x20}, {0x021c,0x0e}, + {0x0298,0x03}, {0x029c,0x00}, {0x027e,0x14}, {0x02c2,0x10}, {0x0540,0x20}, {0x0546,0x01}, + {0x0548,0x01}, {0x0544,0x01}, {0x0242,0x1b}, {0x02c0,0x1b}, {0x02c3,0x20}, {0x02e4,0x10}, + {0x022e,0x00}, {0x027b,0x3f}, {0x0269,0x0f}, {0x02d2,0x40}, {0x027c,0x08}, {0x023a,0x2e}, + {0x0245,0xce}, {0x0530,0x20}, {0x0531,0x02}, {0x0228,0x50}, {0x02ab,0x00}, {0x0250,0x00}, + {0x0221,0x50}, {0x02ac,0x00}, {0x02a5,0x02}, {0x0260,0x0b}, {0x0216,0x04}, {0x0299,0x1c}, + {0x02bb,0x0d}, {0x02a3,0x02}, {0x02a4,0x02}, {0x021e,0x02}, {0x024f,0x08}, {0x028c,0x08}, + {0x0532,0x3f}, {0x0533,0x02}, {0x0277,0x58}, {0x0276,0xc0}, {0x0239,0xc0}, {0x0202,0x05}, + {0x0203,0xd0}, {0x0205,0xc0}, {0x02b0,0x68}, {0x0002,0xa9}, {0x0004,0x01}, {0x021a,0x98}, + {0x0266,0xa0}, {0x0020,0x01}, {0x0021,0x03}, {0x0022,0x00}, {0x0023,0x04}, {0x0342,0x06}, + {0x0343,0x40}, {0x03fe,0x10}, {0x03fe,0x00}, {0x0106,0x78}, {0x0108,0x0c}, {0x0114,0x01}, + {0x0115,0x12}, {0x0180,0x46}, {0x0181,0x30}, {0x0182,0x05}, {0x0185,0x01}, {0x03fe,0x10}, + {0x03fe,0x00}, {0x0100,0x09}, {0x000f,0x00}, {0x0080,0x02}, {0x0097,0x0a}, {0x0098,0x10}, + {0x0099,0x05}, {0x009a,0xb0}, {0x0317,0x08}, {0x0a67,0x80}, {0x0a70,0x03}, {0x0a82,0x00}, + {0x0a83,0x10}, {0x0a80,0x2b}, {0x05be,0x00}, {0x05a9,0x01}, {0x0313,0x80}, {0x05be,0x01}, + {0x0317,0x00}, {0x0a67,0x00}, +}; +static const gc_reg gc4653_init_25fps[] = { + {0x03fe,0xf0}, {0x03fe,0x00}, {0x0317,0x00}, {0x0320,0x77}, {0x0324,0xc8}, {0x0325,0x06}, + {0x0326,0x6c}, {0x0327,0x03}, {0x0334,0x40}, {0x0336,0x6c}, {0x0337,0x82}, {0x0315,0x25}, + {0x031c,0xc6}, {0x0287,0x18}, {0x0084,0x00}, {0x0087,0x50}, {0x029d,0x08}, {0x0290,0x00}, + {0x0340,0x07}, {0x0341,0x08}, {0x0345,0x06}, {0x034b,0xb0}, {0x0352,0x08}, {0x0354,0x08}, + {0x02d1,0xe0}, {0x0223,0xf2}, {0x0238,0xa4}, {0x02ce,0x7f}, {0x0232,0xc4}, {0x02d3,0x05}, + {0x0243,0x06}, {0x02ee,0x30}, {0x026f,0x70}, {0x0257,0x09}, {0x0211,0x02}, {0x0219,0x09}, + {0x023f,0x2d}, {0x0518,0x00}, {0x0519,0x01}, {0x0515,0x08}, {0x02d9,0x3f}, {0x02da,0x02}, + {0x02db,0xe8}, {0x02e6,0x20}, {0x021b,0x10}, {0x0252,0x22}, {0x024e,0x22}, {0x02c4,0x01}, + {0x021d,0x17}, {0x024a,0x01}, {0x02ca,0x02}, {0x0262,0x10}, {0x029a,0x20}, {0x021c,0x0e}, + {0x0298,0x03}, {0x029c,0x00}, {0x027e,0x14}, {0x02c2,0x10}, {0x0540,0x20}, {0x0546,0x01}, + {0x0548,0x01}, {0x0544,0x01}, {0x0242,0x1b}, {0x02c0,0x1b}, {0x02c3,0x20}, {0x02e4,0x10}, + {0x022e,0x00}, {0x027b,0x3f}, {0x0269,0x0f}, {0x02d2,0x40}, {0x027c,0x08}, {0x023a,0x2e}, + {0x0245,0xce}, {0x0530,0x20}, {0x0531,0x02}, {0x0228,0x50}, {0x02ab,0x00}, {0x0250,0x00}, + {0x0221,0x50}, {0x02ac,0x00}, {0x02a5,0x02}, {0x0260,0x0b}, {0x0216,0x04}, {0x0299,0x1c}, + {0x02bb,0x0d}, {0x02a3,0x02}, {0x02a4,0x02}, {0x021e,0x02}, {0x024f,0x08}, {0x028c,0x08}, + {0x0532,0x3f}, {0x0533,0x02}, {0x0277,0x58}, {0x0276,0xc0}, {0x0239,0xc0}, {0x0202,0x05}, + {0x0203,0xd0}, {0x0205,0xc0}, {0x02b0,0x68}, {0x0002,0xa9}, {0x0004,0x01}, {0x021a,0x98}, + {0x0266,0xa0}, {0x0020,0x01}, {0x0021,0x03}, {0x0022,0x00}, {0x0023,0x04}, {0x0342,0x06}, + {0x0343,0x40}, {0x03fe,0x10}, {0x03fe,0x00}, {0x0106,0x78}, {0x0108,0x0c}, {0x0114,0x01}, + {0x0115,0x12}, {0x0180,0x46}, {0x0181,0x30}, {0x0182,0x05}, {0x0185,0x01}, {0x03fe,0x10}, + {0x03fe,0x00}, {0x0100,0x09}, {0x000f,0x00}, {0x0080,0x02}, {0x0097,0x0a}, {0x0098,0x10}, + {0x0099,0x05}, {0x009a,0xb0}, {0x0317,0x08}, {0x0a67,0x80}, {0x0a70,0x03}, {0x0a82,0x00}, + {0x0a83,0x10}, {0x0a80,0x2b}, {0x05be,0x00}, {0x05a9,0x01}, {0x0313,0x80}, {0x05be,0x01}, + {0x0317,0x00}, {0x0a67,0x00}, +}; +static const gc_reg gc4653_init_30fps[] = { + {0x03fe,0xf0}, {0x03fe,0x00}, {0x0317,0x00}, {0x0320,0x77}, {0x0324,0xc8}, {0x0325,0x06}, + {0x0326,0x6c}, {0x0327,0x03}, {0x0334,0x40}, {0x0336,0x6c}, {0x0337,0x82}, {0x0315,0x25}, + {0x031c,0xc6}, {0x0287,0x18}, {0x0084,0x00}, {0x0087,0x50}, {0x029d,0x08}, {0x0290,0x00}, + {0x0340,0x05}, {0x0341,0xdc}, {0x0345,0x06}, {0x034b,0xb0}, {0x0352,0x08}, {0x0354,0x08}, + {0x02d1,0xe0}, {0x0223,0xf2}, {0x0238,0xa4}, {0x02ce,0x7f}, {0x0232,0xc4}, {0x02d3,0x05}, + {0x0243,0x06}, {0x02ee,0x30}, {0x026f,0x70}, {0x0257,0x09}, {0x0211,0x02}, {0x0219,0x09}, + {0x023f,0x2d}, {0x0518,0x00}, {0x0519,0x01}, {0x0515,0x08}, {0x02d9,0x3f}, {0x02da,0x02}, + {0x02db,0xe8}, {0x02e6,0x20}, {0x021b,0x10}, {0x0252,0x22}, {0x024e,0x22}, {0x02c4,0x01}, + {0x021d,0x17}, {0x024a,0x01}, {0x02ca,0x02}, {0x0262,0x10}, {0x029a,0x20}, {0x021c,0x0e}, + {0x0298,0x03}, {0x029c,0x00}, {0x027e,0x14}, {0x02c2,0x10}, {0x0540,0x20}, {0x0546,0x01}, + {0x0548,0x01}, {0x0544,0x01}, {0x0242,0x1b}, {0x02c0,0x1b}, {0x02c3,0x20}, {0x02e4,0x10}, + {0x022e,0x00}, {0x027b,0x3f}, {0x0269,0x0f}, {0x02d2,0x40}, {0x027c,0x08}, {0x023a,0x2e}, + {0x0245,0xce}, {0x0530,0x20}, {0x0531,0x02}, {0x0228,0x50}, {0x02ab,0x00}, {0x0250,0x00}, + {0x0221,0x50}, {0x02ac,0x00}, {0x02a5,0x02}, {0x0260,0x0b}, {0x0216,0x04}, {0x0299,0x1c}, + {0x02bb,0x0d}, {0x02a3,0x02}, {0x02a4,0x02}, {0x021e,0x02}, {0x024f,0x08}, {0x028c,0x08}, + {0x0532,0x3f}, {0x0533,0x02}, {0x0277,0x58}, {0x0276,0xc0}, {0x0239,0xc0}, {0x0202,0x05}, + {0x0203,0xd0}, {0x0205,0xc0}, {0x02b0,0x90}, {0x0002,0xa9}, {0x0004,0x01}, {0x021a,0x98}, + {0x0266,0xa0}, {0x0020,0x01}, {0x0021,0x03}, {0x0022,0x00}, {0x0023,0x04}, {0x0342,0x06}, + {0x0343,0x40}, {0x03fe,0x10}, {0x03fe,0x00}, {0x0106,0x78}, {0x0108,0x0c}, {0x0114,0x01}, + {0x0115,0x12}, {0x0180,0x46}, {0x0181,0x30}, {0x0182,0x05}, {0x0185,0x01}, {0x03fe,0x10}, + {0x03fe,0x00}, {0x0100,0x09}, +}; +static const unsigned gc4653_gain_level[26] = {64, 75, 89, 106, 128, 151, 179, 212, 256, 303, 358, 424, 512, 606, 716, 849, 1024, 1213, 1433, 1698, 2048, 2426, 2867, 3397, 4096, 4853}; +/* regs 0x2b3 0x2b4 0x2b8 0x2b9 0x515 0x519 0x2d9 (one byte each) */ +static const uint8_t gc4653_gain_regs[26][7] = { + {0x00, 0x00, 0x01, 0x00, 0x30, 0x1e, 0x5c}, + {0x20, 0x00, 0x01, 0x0b, 0x30, 0x1e, 0x5c}, + {0x01, 0x00, 0x01, 0x19, 0x30, 0x1d, 0x5b}, + {0x21, 0x00, 0x01, 0x2a, 0x30, 0x1e, 0x5c}, + {0x02, 0x00, 0x02, 0x00, 0x30, 0x1e, 0x5c}, + {0x22, 0x00, 0x02, 0x17, 0x30, 0x1d, 0x5b}, + {0x03, 0x00, 0x02, 0x33, 0x20, 0x16, 0x54}, + {0x23, 0x00, 0x03, 0x14, 0x20, 0x17, 0x55}, + {0x04, 0x00, 0x04, 0x00, 0x20, 0x17, 0x55}, + {0x24, 0x00, 0x04, 0x2f, 0x20, 0x19, 0x57}, + {0x05, 0x00, 0x05, 0x26, 0x20, 0x19, 0x57}, + {0x25, 0x00, 0x06, 0x28, 0x20, 0x1b, 0x59}, + {0x0c, 0x00, 0x08, 0x00, 0x20, 0x1d, 0x5b}, + {0x2c, 0x00, 0x09, 0x1e, 0x20, 0x1f, 0x5d}, + {0x0d, 0x00, 0x0b, 0x0c, 0x20, 0x21, 0x5f}, + {0x2d, 0x00, 0x0d, 0x11, 0x20, 0x24, 0x62}, + {0x1c, 0x00, 0x10, 0x00, 0x20, 0x26, 0x64}, + {0x3c, 0x00, 0x12, 0x3d, 0x18, 0x2a, 0x68}, + {0x5c, 0x00, 0x16, 0x19, 0x18, 0x2c, 0x6a}, + {0x7c, 0x00, 0x1a, 0x22, 0x18, 0x2e, 0x6c}, + {0x9c, 0x00, 0x20, 0x00, 0x18, 0x32, 0x70}, + {0xbc, 0x00, 0x25, 0x3a, 0x18, 0x35, 0x73}, + {0xdc, 0x00, 0x2c, 0x33, 0x10, 0x36, 0x74}, + {0xfc, 0x00, 0x35, 0x05, 0x10, 0x38, 0x76}, + {0x1c, 0x01, 0x40, 0x00, 0x10, 0x3c, 0x7a}, + {0x3c, 0x01, 0x4b, 0x35, 0x10, 0x42, 0x80}, +}; diff --git a/src/hal/fh/fh_sys.h b/src/hal/fh/fh_sys.h new file mode 100644 index 00000000..3badbf8f --- /dev/null +++ b/src/hal/fh/fh_sys.h @@ -0,0 +1,61 @@ +#pragma once + +#include "fh_common.h" + +#define FH_SYS_API "1.2.0" + +typedef struct { + unsigned int date; /* 0x20200324 */ + unsigned int commit; /* git hash */ + unsigned int package; /* pkg_id, 0x7840d on FH8856 */ + unsigned int reserved; +} fh_sys_ver; + +typedef struct { + void *handle, *handleVmm; + + int (*fnBindVpu2Enc)(unsigned int vpssChn, unsigned int vencChn); + int (*fnUnbindByDst)(unsigned int vencChn); + int (*fnExit)(void); + int (*fnGetVersion)(fh_sys_ver *version); + int (*fnInit)(void); +} fh_sys_impl; + +static int fh_sys_load(fh_sys_impl *sys_lib) { + /* libdsp needs the allocator from libvmm; it must be resolvable globally */ + if (!(sys_lib->handleVmm = dlopen("libvmm.so", RTLD_NOW | RTLD_GLOBAL))) + HAL_ERROR("fh_sys", "Failed to load library!\nError: %s\n", dlerror()); + + if (!(sys_lib->handle = dlopen("libdsp.so", RTLD_NOW | RTLD_GLOBAL))) + HAL_ERROR("fh_sys", "Failed to load library!\nError: %s\n", dlerror()); + + if (!(sys_lib->fnBindVpu2Enc = (int(*)(unsigned int vpssChn, unsigned int vencChn)) + hal_symbol_load("fh_sys", sys_lib->handle, "FH_SYS_BindVpu2Enc"))) + return EXIT_FAILURE; + + if (!(sys_lib->fnUnbindByDst = (int(*)(unsigned int vencChn)) + hal_symbol_load("fh_sys", sys_lib->handle, "FH_SYS_UnBindbyDst"))) + return EXIT_FAILURE; + + if (!(sys_lib->fnExit = (int(*)(void)) + hal_symbol_load("fh_sys", sys_lib->handle, "FH_SYS_Exit"))) + return EXIT_FAILURE; + + if (!(sys_lib->fnGetVersion = (int(*)(fh_sys_ver *version)) + hal_symbol_load("fh_sys", sys_lib->handle, "FH_SYS_GetVersion"))) + return EXIT_FAILURE; + + if (!(sys_lib->fnInit = (int(*)(void)) + hal_symbol_load("fh_sys", sys_lib->handle, "FH_SYS_Init"))) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} + +static void fh_sys_unload(fh_sys_impl *sys_lib) { + if (sys_lib->handle) dlclose(sys_lib->handle); + sys_lib->handle = NULL; + if (sys_lib->handleVmm) dlclose(sys_lib->handleVmm); + sys_lib->handleVmm = NULL; + memset(sys_lib, 0, sizeof(*sys_lib)); +} diff --git a/src/hal/fh/fh_venc.h b/src/hal/fh/fh_venc.h new file mode 100644 index 00000000..ce219ded --- /dev/null +++ b/src/hal/fh/fh_venc.h @@ -0,0 +1,125 @@ +#pragma once + +#include "fh_common.h" + +/* Channel creation: which encoders may later be selected, and the maximum frame size */ +typedef struct { + unsigned int types; /* OR of fh_venc_type */ + unsigned int width; + unsigned int height; +} fh_venc_cfg; + +/* + * Rate control block, also the payload of FH_VENC_SetRCAttr(). + * Layouts (vendor application defaults): + * H264/H265 CBR: { mode, 35, bitrate_bps, minQp(28), maxQp(50), targetQp, 50, fps | den << 16, 120, 0, 0, 5, 1, 0 } + * H264/H265 VBR: { mode, 35, bitrate_bps, fps | den << 16, 120, 0, 0, 5, 1 } + * H264 fixed QP: { mode, iQp, pQp, fps | den << 16 } + */ +typedef struct { + unsigned int mode; /* fh_venc_rcmode */ + unsigned int param[21]; +} fh_venc_rc; + +/* FH_VENC_SetChnAttr() payload, 0xac bytes */ +typedef struct { + unsigned int type; /* single fh_venc_type; 0 destroys the encoder */ + unsigned int profile; /* H264: 66 baseline, 77 main, 100 high; H265: 1 */ + unsigned int gop; + unsigned int width; + unsigned int height; + unsigned int reserved[16]; + fh_venc_rc rc; +} fh_venc_attr; + +/* JPEG snapshot encoder attributes share the same 0xac-byte buffer */ +typedef struct { + unsigned int type; /* FH_VENC_TYPE_JPEG */ + unsigned int quality; /* 1..99 */ + unsigned int reserved; + unsigned int rateIndex; /* 0..9, vendor uses 4 */ + unsigned int pad[39]; +} fh_venc_jpgattr; + +typedef struct { + unsigned int type; /* H264: 7 SPS, 8 PPS, 1 slice (IDR too, check frame type) */ + unsigned int length; + unsigned int addr; /* user virtual address, starts with the 4-byte start code */ +} fh_venc_nalu; + +/* Output of FH_VENC_GetStream[_Block](), 0x174 bytes. The first word is the type mask on input. */ +typedef struct { + unsigned int type; /* fh_venc_type of the returned frame */ + unsigned int reserved1; + unsigned int channel; + unsigned int base; /* JPEG: data address */ + unsigned int frameType; /* 2 = I frame, 0 = P frame; JPEG: data length */ + unsigned int length; /* total length of the frame */ + unsigned int ptsLow; /* microseconds */ + unsigned int ptsHigh; + unsigned int naluCount; + fh_venc_nalu nalu[28]; +} fh_venc_strm; + +typedef struct { + void *handle; + + int (*fnCreateChannel)(unsigned int channel, fh_venc_cfg *config); + int (*fnGetStream)(unsigned int typeMask, fh_venc_strm *stream); + int (*fnGetStreamBlocking)(unsigned int typeMask, fh_venc_strm *stream); + int (*fnReleaseStream)(unsigned int channel); + int (*fnRequestIdr)(unsigned int channel); + int (*fnSetChannelConfig)(unsigned int channel, void *config); + int (*fnSetRateControl)(unsigned int channel, fh_venc_rc *rc); + int (*fnStartReceiving)(unsigned int channel); + int (*fnStopReceiving)(unsigned int channel); +} fh_venc_impl; + +static int fh_venc_load(fh_venc_impl *venc_lib) { + if (!(venc_lib->handle = dlopen("libdsp.so", RTLD_NOW | RTLD_GLOBAL))) + HAL_ERROR("fh_venc", "Failed to load library!\nError: %s\n", dlerror()); + + if (!(venc_lib->fnCreateChannel = (int(*)(unsigned int channel, fh_venc_cfg *config)) + hal_symbol_load("fh_venc", venc_lib->handle, "FH_VENC_CreateChn"))) + return EXIT_FAILURE; + + if (!(venc_lib->fnGetStream = (int(*)(unsigned int typeMask, fh_venc_strm *stream)) + hal_symbol_load("fh_venc", venc_lib->handle, "FH_VENC_GetStream"))) + return EXIT_FAILURE; + + if (!(venc_lib->fnGetStreamBlocking = (int(*)(unsigned int typeMask, fh_venc_strm *stream)) + hal_symbol_load("fh_venc", venc_lib->handle, "FH_VENC_GetStream_Block"))) + return EXIT_FAILURE; + + if (!(venc_lib->fnReleaseStream = (int(*)(unsigned int channel)) + hal_symbol_load("fh_venc", venc_lib->handle, "FH_VENC_ReleaseStream"))) + return EXIT_FAILURE; + + if (!(venc_lib->fnRequestIdr = (int(*)(unsigned int channel)) + hal_symbol_load("fh_venc", venc_lib->handle, "FH_VENC_RequestIDR"))) + return EXIT_FAILURE; + + if (!(venc_lib->fnSetChannelConfig = (int(*)(unsigned int channel, void *config)) + hal_symbol_load("fh_venc", venc_lib->handle, "FH_VENC_SetChnAttr"))) + return EXIT_FAILURE; + + if (!(venc_lib->fnSetRateControl = (int(*)(unsigned int channel, fh_venc_rc *rc)) + hal_symbol_load("fh_venc", venc_lib->handle, "FH_VENC_SetRCAttr"))) + return EXIT_FAILURE; + + if (!(venc_lib->fnStartReceiving = (int(*)(unsigned int channel)) + hal_symbol_load("fh_venc", venc_lib->handle, "FH_VENC_StartRecvPic"))) + return EXIT_FAILURE; + + if (!(venc_lib->fnStopReceiving = (int(*)(unsigned int channel)) + hal_symbol_load("fh_venc", venc_lib->handle, "FH_VENC_StopRecvPic"))) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} + +static void fh_venc_unload(fh_venc_impl *venc_lib) { + if (venc_lib->handle) dlclose(venc_lib->handle); + venc_lib->handle = NULL; + memset(venc_lib, 0, sizeof(*venc_lib)); +} diff --git a/src/hal/fh/fh_vpss.h b/src/hal/fh/fh_vpss.h new file mode 100644 index 00000000..512a8fb7 --- /dev/null +++ b/src/hal/fh/fh_vpss.h @@ -0,0 +1,86 @@ +#pragma once + +#include "fh_common.h" + +typedef struct { + unsigned short srcRate; /* input frames considered */ + unsigned short dstRate; /* output frames kept */ +} fh_vpss_framectrl; + +typedef struct { + void *handle; + + int (*fnCloseChannel)(unsigned int channel); + int (*fnDisable)(unsigned int group); + int (*fnEnable)(unsigned int group); + int (*fnFreezeVideo)(void); + int (*fnGetChannelConfig)(unsigned int channel, fh_common_dim *dim); + int (*fnGetFrameControl)(unsigned int channel, fh_vpss_framectrl *ctrl); + int (*fnOpenChannel)(unsigned int channel); + int (*fnSetChannelConfig)(unsigned int channel, fh_common_dim *dim); + int (*fnSetFrameControl)(unsigned int channel, fh_vpss_framectrl *ctrl); + int (*fnSetInputConfig)(fh_common_dim *dim); + int (*fnSetOutputMode)(unsigned int channel, unsigned int mode); + int (*fnUnfreezeVideo)(void); +} fh_vpss_impl; + +static int fh_vpss_load(fh_vpss_impl *vpss_lib) { + if (!(vpss_lib->handle = dlopen("libdsp.so", RTLD_NOW | RTLD_GLOBAL))) + HAL_ERROR("fh_vpss", "Failed to load library!\nError: %s\n", dlerror()); + + if (!(vpss_lib->fnCloseChannel = (int(*)(unsigned int channel)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_CloseChn"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnDisable = (int(*)(unsigned int group)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_Disable"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnEnable = (int(*)(unsigned int group)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_Enable"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnFreezeVideo = (int(*)(void)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_FreezeVideo"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnGetChannelConfig = (int(*)(unsigned int channel, fh_common_dim *dim)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_GetChnAttr"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnGetFrameControl = (int(*)(unsigned int channel, fh_vpss_framectrl *ctrl)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_GetFramectrl"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnOpenChannel = (int(*)(unsigned int channel)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_OpenChn"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnSetChannelConfig = (int(*)(unsigned int channel, fh_common_dim *dim)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_SetChnAttr"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnSetFrameControl = (int(*)(unsigned int channel, fh_vpss_framectrl *ctrl)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_SetFramectrl"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnSetInputConfig = (int(*)(fh_common_dim *dim)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_SetViAttr"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnSetOutputMode = (int(*)(unsigned int channel, unsigned int mode)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_SetVOMode"))) + return EXIT_FAILURE; + + if (!(vpss_lib->fnUnfreezeVideo = (int(*)(void)) + hal_symbol_load("fh_vpss", vpss_lib->handle, "FH_VPSS_UnfreezeVideo"))) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} + +static void fh_vpss_unload(fh_vpss_impl *vpss_lib) { + if (vpss_lib->handle) dlclose(vpss_lib->handle); + vpss_lib->handle = NULL; + memset(vpss_lib, 0, sizeof(*vpss_lib)); +} diff --git a/src/hal/hisi/quirks.c b/src/hal/hisi/quirks.c index 7a0d97fe..5c614240 100644 --- a/src/hal/hisi/quirks.c +++ b/src/hal/hisi/quirks.c @@ -7,9 +7,13 @@ int (*fnISP_AlgRegisterDrc)(int); int (*fnISP_AlgRegisterLdci)(int); int (*fnMPI_ISP_IrAutoRunOnce)(int, void*); +/* The Fullhan ISP core library exports its own isp_malloc(); with -rdynamic + * this trampoline would shadow it, so leave it out of ARMv6 (Fullhan) builds */ +#if __ARM_ARCH != 6 void *isp_malloc(unsigned long size) { return fnIsp_Malloc(size); } +#endif int isp_alg_register_acs(int pipeId) { return fnISP_AlgRegisterAcs(pipeId); } diff --git a/src/hal/support.c b/src/hal/support.c index e261bca6..98cd463e 100644 --- a/src/hal/support.c +++ b/src/hal/support.c @@ -139,6 +139,26 @@ void hal_identify(void) { #endif #if defined(__arm__) && !defined(__ARM_PCS_VFP) +#if __ARM_ARCH == 6 + if (file = fopen("/proc/driver/chip", "r")) { + char name[16] = {0}; + while (fgets(line, 200, file)) + if (sscanf(line, "chip_name : %15s", name) == 1) break; + fclose(file); + if (!strncmp(name, "FH", 2)) { + plat = HAL_PLATFORM_FH; + strncpy(chip, name, sizeof(chip) - 1); + strcpy(family, "fullhan"); + chnCount = FH_VENC_CHN_NUM; + chnState = (hal_chnstate*)fh_state; + aud_thread = fh_audio_thread; + isp_thread = fh_image_thread; + vid_thread = fh_video_thread; + return; + } + } +#endif + if (!access("/dev/vpd", F_OK)) { plat = HAL_PLATFORM_GM; strcpy(chip, "GM813x"); diff --git a/src/hal/support.h b/src/hal/support.h index 26a3ea0e..d2132910 100644 --- a/src/hal/support.h +++ b/src/hal/support.h @@ -15,6 +15,9 @@ #include "hisi/v2_hal.h" #include "hisi/v3_hal.h" #include "hisi/v4_hal.h" +#if __ARM_ARCH == 6 +#include "fh/fh_hal.h" +#endif #elif defined(__mips__) #include "inge/t31_hal.h" #elif defined(__riscv) || defined(__riscv__) diff --git a/src/hal/types.h b/src/hal/types.h index 4b0714b8..baa3d9c5 100644 --- a/src/hal/types.h +++ b/src/hal/types.h @@ -6,6 +6,7 @@ typedef enum { HAL_PLATFORM_UNK, HAL_PLATFORM_AK, HAL_PLATFORM_CVI, + HAL_PLATFORM_FH, HAL_PLATFORM_GM, HAL_PLATFORM_I3, HAL_PLATFORM_I6, diff --git a/src/jpeg.c b/src/jpeg.c index babf71f9..24074c92 100644 --- a/src/jpeg.c +++ b/src/jpeg.c @@ -47,6 +47,9 @@ int jpeg_init() { case HAL_PLATFORM_V2: ret = v2_video_create(jpeg_index, &config); break; case HAL_PLATFORM_V3: ret = v3_video_create(jpeg_index, &config); break; case HAL_PLATFORM_V4: ret = v4_video_create(jpeg_index, &config); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: ret = fh_video_create(jpeg_index, &config); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: ret = t31_video_create(jpeg_index, &config); break; #elif defined(__riscv) || defined(__riscv__) @@ -87,6 +90,11 @@ void jpeg_deinit() { case HAL_PLATFORM_V2: v2_video_destroy(jpeg_index); break; case HAL_PLATFORM_V3: v3_video_destroy(jpeg_index); break; case HAL_PLATFORM_V4: v4_video_destroy(jpeg_index); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: + fh_video_destroy(jpeg_index); + break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: if (app_config.mjpeg_enable) goto active; @@ -127,6 +135,9 @@ int jpeg_get(short width, short height, char quality, char grayscale, case HAL_PLATFORM_V2: ret = v2_video_snapshot_grab(jpeg_index, jpeg); break; case HAL_PLATFORM_V3: ret = v3_video_snapshot_grab(jpeg_index, jpeg); break; case HAL_PLATFORM_V4: ret = v4_video_snapshot_grab(jpeg_index, jpeg); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: ret = fh_video_snapshot_grab(jpeg_index, jpeg); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: ret = t31_video_snapshot_grab(app_config.mjpeg_enable ? -1 : jpeg_index, jpeg); break; diff --git a/src/media.c b/src/media.c index f3d6f58f..a9b649eb 100644 --- a/src/media.c +++ b/src/media.c @@ -268,6 +268,9 @@ void request_idr(void) { case HAL_PLATFORM_V2: v2_video_request_idr(index); break; case HAL_PLATFORM_V3: v3_video_request_idr(index); break; case HAL_PLATFORM_V4: v4_video_request_idr(index); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: fh_video_request_idr(index); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_video_request_idr(index); break; #elif defined(__riscv) || defined(__riscv__) @@ -291,6 +294,9 @@ void set_grayscale(bool active) { case HAL_PLATFORM_V2: v2_channel_grayscale(active); break; case HAL_PLATFORM_V3: v3_channel_grayscale(active); break; case HAL_PLATFORM_V4: v4_channel_grayscale(active); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: fh_channel_grayscale(active); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_channel_grayscale(active); break; #elif defined(__riscv) || defined(__riscv__) @@ -332,6 +338,10 @@ int create_channel(char index, short width, short height, char framerate, char j app_config.mirror, app_config.flip, framerate); case HAL_PLATFORM_V4: return v4_channel_create(index, app_config.mirror, app_config.flip, framerate); +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: return fh_channel_create(index, width, height, + framerate, jpeg); +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: return t31_channel_create(index, width, height, framerate, jpeg); @@ -356,6 +366,9 @@ int bind_channel(char index, char framerate, char jpeg) { case HAL_PLATFORM_V2: return v2_channel_bind(index); case HAL_PLATFORM_V3: return v3_channel_bind(index); case HAL_PLATFORM_V4: return v4_channel_bind(index); +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: return fh_channel_bind(index); +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: return t31_channel_bind(index); #elif defined(__riscv) || defined(__riscv__) @@ -378,6 +391,9 @@ int unbind_channel(char index, char jpeg) { case HAL_PLATFORM_V2: return v2_channel_unbind(index); case HAL_PLATFORM_V3: return v3_channel_unbind(index); case HAL_PLATFORM_V4: return v4_channel_unbind(index); +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: return fh_channel_unbind(index); +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: return t31_channel_unbind(index); #elif defined(__riscv) || defined(__riscv__) @@ -400,6 +416,9 @@ int media_video_disable(char index, char jpeg) { case HAL_PLATFORM_V2: return v2_video_destroy(index); case HAL_PLATFORM_V3: return v3_video_destroy(index); case HAL_PLATFORM_V4: return v4_video_destroy(index); +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: return fh_video_destroy(index); +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: return t31_video_destroy(index); #elif defined(__riscv) || defined(__riscv__) @@ -431,6 +450,9 @@ void media_audio_disable(void) { case HAL_PLATFORM_V2: v2_audio_deinit(); break; case HAL_PLATFORM_V3: v3_audio_deinit(); break; case HAL_PLATFORM_V4: v4_audio_deinit(); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: fh_audio_deinit(); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_audio_deinit(); break; #elif defined(__riscv) || defined(__riscv__) @@ -459,6 +481,9 @@ int media_audio_enable(void) { case HAL_PLATFORM_V2: ret = v2_audio_init(app_config.audio_srate); break; case HAL_PLATFORM_V3: ret = v3_audio_init(app_config.audio_srate); break; case HAL_PLATFORM_V4: ret = v4_audio_init(app_config.audio_srate); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: ret = fh_audio_init(app_config.audio_srate); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: ret = t31_audio_init(app_config.audio_srate); break; #elif defined(__riscv) || defined(__riscv__) @@ -574,6 +599,9 @@ int media_mjpeg_enable(void) { case HAL_PLATFORM_V2: ret = v2_video_create(index, &config); break; case HAL_PLATFORM_V3: ret = v3_video_create(index, &config); break; case HAL_PLATFORM_V4: ret = v4_video_create(index, &config); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: ret = fh_video_create(index, &config); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: ret = t31_video_create(index, &config); break; #elif defined(__riscv) || defined(__riscv__) @@ -649,6 +677,9 @@ int media_mp4_enable(void) { case HAL_PLATFORM_V2: ret = v2_video_create(index, &config); break; case HAL_PLATFORM_V3: ret = v3_video_create(index, &config); break; case HAL_PLATFORM_V4: ret = v4_video_create(index, &config); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: ret = fh_video_create(index, &config); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: ret = t31_video_create(index, &config); break; #elif defined(__riscv) || defined(__riscv__) @@ -689,6 +720,9 @@ int sdk_start(void) { case HAL_PLATFORM_V2: ret = v2_hal_init(); break; case HAL_PLATFORM_V3: ret = v3_hal_init(); break; case HAL_PLATFORM_V4: ret = v4_hal_init(); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: ret = fh_hal_init(); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: ret = t31_hal_init(); break; #elif defined(__riscv) || defined(__riscv__) @@ -738,6 +772,12 @@ int sdk_start(void) { v4_aud_cb = save_audio_stream; v4_vid_cb = save_video_stream; break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: + fh_aud_cb = save_audio_stream; + fh_vid_cb = save_video_stream; + break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_aud_cb = save_audio_stream; @@ -765,6 +805,9 @@ int sdk_start(void) { case HAL_PLATFORM_V2: ret = v2_system_init(app_config.sensor_config); break; case HAL_PLATFORM_V3: ret = v3_system_init(app_config.sensor_config); break; case HAL_PLATFORM_V4: ret = v4_system_init(app_config.sensor_config); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: ret = fh_system_init(app_config.sensor_config); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: ret = t31_system_init(); break; #elif defined(__riscv) || defined(__riscv__) @@ -777,9 +820,18 @@ int sdk_start(void) { if (app_config.audio_enable) { ret = media_audio_enable(); - if (ret) + if (ret) { +#if defined(__arm__) && !defined(__ARM_PCS_VFP) && __ARM_ARCH == 6 + /* Fullhan only: a broken mic/audio config must not abort the SDK start, + * which on this watchdog-guarded SoC means a reboot loop */ + HAL_WARNING("media", "Audio initialization failed with %#x, continuing without audio!\n%s\n", + ret, errstr(ret)); + app_config.audio_enable = false; +#else HAL_ERROR("media", "Audio initialization failed with %#x!\n%s\n", ret, errstr(ret)); +#endif + } } short width = MAX(app_config.mp4_width, app_config.mjpeg_width); @@ -804,6 +856,10 @@ int sdk_start(void) { case HAL_PLATFORM_V2: ret = v2_pipeline_create(); break; case HAL_PLATFORM_V3: ret = v3_pipeline_create(); break; case HAL_PLATFORM_V4: ret = v4_pipeline_create(); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: ret = fh_pipeline_create(width, height, + app_config.mirror, app_config.flip, framerate, app_config.antiflicker); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: ret = t31_pipeline_create(app_config.mirror, app_config.flip, app_config.antiflicker, framerate); break; @@ -863,6 +919,8 @@ int sdk_start(void) { case HAL_PLATFORM_I6: i6_config_load(app_config.sensor_config); break; case HAL_PLATFORM_I6C: i6c_config_load(app_config.sensor_config); break; case HAL_PLATFORM_M6: m6_config_load(app_config.sensor_config); break; +#elif defined(__arm__) && !defined(__ARM_PCS_VFP) && __ARM_ARCH == 6 + case HAL_PLATFORM_FH: fh_config_load(app_config.sensor_config); break; #elif defined(__mips__) case HAL_PLATFORM_T31: t31_config_load(app_config.sensor_config); break; #endif @@ -892,6 +950,9 @@ int sdk_stop(void) { case HAL_PLATFORM_V2: v2_video_destroy_all(); break; case HAL_PLATFORM_V3: v3_video_destroy_all(); break; case HAL_PLATFORM_V4: v4_video_destroy_all(); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: fh_video_destroy_all(); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_video_destroy_all(); break; #elif defined(__riscv) || defined(__riscv__) @@ -912,6 +973,9 @@ int sdk_stop(void) { case HAL_PLATFORM_V2: v2_pipeline_destroy(); break; case HAL_PLATFORM_V3: v3_pipeline_destroy(); break; case HAL_PLATFORM_V4: v4_pipeline_destroy(); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: fh_pipeline_destroy(); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_pipeline_destroy(); break; #elif defined(__riscv) || defined(__riscv__) @@ -939,6 +1003,9 @@ int sdk_stop(void) { case HAL_PLATFORM_V2: v2_system_deinit(); break; case HAL_PLATFORM_V3: v3_system_deinit(); break; case HAL_PLATFORM_V4: v4_system_deinit(); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: fh_system_deinit(); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_system_deinit(); break; #elif defined(__riscv) || defined(__riscv__) @@ -969,6 +1036,9 @@ int sdk_stop(void) { case HAL_PLATFORM_V2: v2_hal_deinit(); break; case HAL_PLATFORM_V3: v3_hal_deinit(); break; case HAL_PLATFORM_V4: v4_hal_deinit(); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: fh_hal_deinit(); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_hal_deinit(); break; #elif defined(__riscv) || defined(__riscv__) diff --git a/src/night.c b/src/night.c index a0c8c44c..5da2236e 100644 --- a/src/night.c +++ b/src/night.c @@ -29,6 +29,9 @@ void night_ircut(bool enable) { } void night_irled(bool enable) { +#if defined(__arm__) && !defined(__ARM_PCS_VFP) && __ARM_ARCH == 6 + if (plat == HAL_PLATFORM_FH) fh_irled(enable); /* PWM3 brightness; GPIO7 enable follows */ +#endif gpio_write(app_config.ir_led_pin, enable); irled = enable; } @@ -37,6 +40,23 @@ void night_manual(bool enable) { manual = enable; } void night_mode(bool enable) { HAL_INFO("night", "Changing mode to %s\n", enable ? "NIGHT" : "DAY"); +#if defined(__arm__) && !defined(__ARM_PCS_VFP) && __ARM_ARCH == 6 + if (plat == HAL_PLATFORM_FH && EQUALS(app_config.night_lamp, "white")) { + /* Colour night vision: light the scene with the white lamp and keep the + * IR-cut filter in and the image in colour; the IR lamp stays off */ + night_grayscale(false); + night_ircut(true); + night_irled(false); + fh_whitelamp(enable); + return; + } + if (plat == HAL_PLATFORM_FH && EQUALS(app_config.night_lamp, "none")) { + night_grayscale(enable); + night_ircut(!enable); + night_irled(false); + return; + } +#endif night_grayscale(enable); night_ircut(!enable); night_irled(enable); @@ -48,6 +68,24 @@ void *night_thread(void) { night_mode(night_mode_on()); +#if defined(__arm__) && !defined(__ARM_PCS_VFP) && __ARM_ARCH == 6 + if (plat == HAL_PLATFORM_FH && fh_night_available()) { + HAL_INFO("night", "Using SmartIR (image gain) for day/night switching\n"); + if (app_config.smartir_gain_night || app_config.smartir_gain_day) + fh_night_thresholds(app_config.smartir_gain_night, app_config.smartir_gain_day); + while (keepRunning && nightOn) { + /* SmartIR is polled; only touch the IR-cut/LED GPIOs when the verdict changes, + * otherwise night_mode() pulses the IR-cut solenoid every interval */ + static int applied = -1; + int state = fh_night_status(); + if (manual) applied = -1; + else if (state != applied) { night_mode(state); applied = state; } + /* SmartIR's debounce counters are per call and sized for the vendor's + * 40 ms polling loop, so don't pace it with check_interval_s */ + usleep(40000); + } + } else +#endif if (app_config.adc_device[0]) { int adc_fd = -1; fd_set adc_fds; diff --git a/src/region.c b/src/region.c index 23785663..01007a11 100644 --- a/src/region.c +++ b/src/region.c @@ -426,6 +426,12 @@ found_font:; v4_region_create(id, rect, osds[id].opal); v4_region_setbitmap(id, &bitmap); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: + fh_region_create(&osds[id].hand, rect, osds[id].opal); + fh_region_setbitmap(&osds[id].hand, &bitmap); + break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_region_create(&osds[id].hand, rect, osds[id].opal); @@ -493,6 +499,12 @@ found_font:; v4_region_create(id, rect, osds[id].opal); v4_region_setbitmap(id, &bitmap); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: + fh_region_create(&osds[id].hand, rect, osds[id].opal); + fh_region_setbitmap(&osds[id].hand, &bitmap); + break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_region_create(&osds[id].hand, rect, osds[id].opal); @@ -516,6 +528,9 @@ found_font:; case HAL_PLATFORM_V2: v2_region_destroy(id); break; case HAL_PLATFORM_V3: v3_region_destroy(id); break; case HAL_PLATFORM_V4: v4_region_destroy(id); break; +#if __ARM_ARCH == 6 + case HAL_PLATFORM_FH: fh_region_destroy(&osds[id].hand); break; +#endif #elif defined(__mips__) case HAL_PLATFORM_T31: t31_region_destroy(&osds[id].hand); break; #endif From 6612479a35c5df1c130be2cfbe42f7d6b4e56b6c Mon Sep 17 00:00:00 2001 From: kasperiio <101901857+kasperiio@users.noreply.github.com> Date: Sat, 5 Sep 2026 20:24:16 +0300 Subject: [PATCH 2/2] hal/fh: address review of the previous commit fh_osd_clear() derived its clipped width from rect.x before checking rect.x was inside the plane. hal_rect.x is an unsigned short and OSD positions are only range-checked against SHRT_MAX, so a region placed past the frame wrapped _fh_snr_dim.width - rect.x and memset most of memory. The rectangle is now rejected up front and the width computed once. fh_region_setbitmap() scaled a region's position from main-stream to sensor coordinates but copied its bitmap at the original size, so whenever the main stream was smaller than the sensor the overlay was placed for one resolution and drawn at another. The bitmap is now sampled nearest neighbour through the same ratio, and the stored clear bounds match what was drawn. fh_region_create() took an opacity and ignored it while the plane alpha stayed at 255. The hardware has one alpha for the whole graphics plane and ARGB1555 carries only an opaque/transparent bit per pixel, so a per-region value cannot be honoured individually; the highest opacity among active regions is applied to the plane, which is exact for the usual single region and never leaves one more transparent than configured. The three snapshot buffer growths assigned realloc() straight to jpeg->data, losing the old buffer and memcpy()ing to NULL under memory pressure. They now keep the old pointer until the allocation succeeds and fail the snapshot otherwise. night_mode.lamp was read by the unbounded parse_param_value() into an eight-byte field, so a longer configured value overwrote neighbouring AppConfig members at startup. It is parsed into a temporary and accepted only as ir, white or none. --- src/app_config.c | 13 ++++- src/hal/fh/fh_hal.c | 122 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 119 insertions(+), 16 deletions(-) diff --git a/src/app_config.c b/src/app_config.c index d14ffdac..71fc5edd 100644 --- a/src/app_config.c +++ b/src/app_config.c @@ -375,8 +375,17 @@ enum ConfigError app_config_parse(void) { &app_config.pin_switch_delay_us); parse_param_value( &ini, "night_mode", "adc_device", app_config.adc_device); - parse_param_value( - &ini, "night_mode", "lamp", app_config.night_lamp); + { + /* parse_param_value() sprintf()s into the caller's buffer with no + * size, so read into a temporary the size of the largest config + * string in this file and accept only the lamps the HAL drives. */ + char lamp[128] = {0}; + if (parse_param_value(&ini, "night_mode", "lamp", lamp) == CONFIG_OK && + (EQUALS(lamp, "ir") || EQUALS(lamp, "white") || EQUALS(lamp, "none"))) { + strncpy(app_config.night_lamp, lamp, sizeof(app_config.night_lamp) - 1); + app_config.night_lamp[sizeof(app_config.night_lamp) - 1] = 0; + } + } parse_int( &ini, "night_mode", "adc_threshold", INT_MIN, INT_MAX, &app_config.adc_threshold); diff --git a/src/hal/fh/fh_hal.c b/src/hal/fh/fh_hal.c index 4f9435ad..17f76aa4 100644 --- a/src/hal/fh/fh_hal.c +++ b/src/hal/fh/fh_hal.c @@ -47,11 +47,12 @@ typedef struct { unsigned int stride; /* bytes per line */ } fh_vpss_graph; #define FH_OSD_MAX 8 -typedef struct { char used; hal_rect rect; } fh_osd_rgn; +typedef struct { char used; hal_rect rect; unsigned char opal; } fh_osd_rgn; static fh_osd_rgn _fh_osd[FH_OSD_MAX]; static unsigned int _fh_osd_phys, _fh_osd_size; static unsigned short *_fh_osd_virt; static char _fh_osd_on; +static unsigned char _fh_osd_alpha = 255; static pthread_mutex_t _fh_strm_mtx = PTHREAD_MUTEX_INITIALIZER; static char _fh_aud_on; static unsigned int _fh_aud_frame, _fh_aud_rate; @@ -600,7 +601,7 @@ static int fh_osd_setup(void) memset(&graph, 0, sizeof(graph)); graph.enable = 1; graph.physAddr = _fh_osd_phys; - graph.alpha = 255; + graph.alpha = _fh_osd_alpha; graph.width = _fh_snr_dim.width; graph.height = _fh_snr_dim.height; graph.stride = _fh_snr_dim.width * 2; @@ -610,6 +611,37 @@ static int fh_osd_setup(void) return EXIT_SUCCESS; } +/* + * The hardware carries one alpha for the whole graphics plane, and the pixel + * format is ARGB1555 whose alpha bit only says opaque or transparent, so a + * per-region opacity cannot be honoured individually. Apply the highest + * opacity any active region asks for: a region is then never more transparent + * than configured, and with the usual single OSD region it is exact. + */ +static void fh_osd_apply_alpha(void) +{ + int (*fnSetGraph)(fh_vpss_graph *) = dlsym(fh_vpss.handle, "FH_VPSS_SetGraph"); + fh_vpss_graph graph; + unsigned char alpha = 0; + int any = 0; + + for (int i = 0; i < FH_OSD_MAX; i++) + if (_fh_osd[i].used) { any = 1; if (_fh_osd[i].opal > alpha) alpha = _fh_osd[i].opal; } + if (!any) alpha = 255; + if (!_fh_osd_on || !fnSetGraph || alpha == _fh_osd_alpha) { _fh_osd_alpha = alpha; return; } + + _fh_osd_alpha = alpha; + memset(&graph, 0, sizeof(graph)); + graph.enable = 1; + graph.physAddr = _fh_osd_phys; + graph.alpha = _fh_osd_alpha; + graph.width = _fh_snr_dim.width; + graph.height = _fh_snr_dim.height; + graph.stride = _fh_snr_dim.width * 2; + if (fnSetGraph(&graph)) + HAL_WARNING("fh_osd", "Could not set the overlay alpha to %u\n", _fh_osd_alpha); +} + /* divinus positions regions in main-stream pixels; the plane is at sensor size */ static void fh_osd_scale(hal_rect *rect) { @@ -621,11 +653,22 @@ static void fh_osd_scale(hal_rect *rect) static void fh_osd_clear(hal_rect rect) { + unsigned int w; + + /* rect.x/y are unsigned short and OSD positions are only range-checked + * against SHRT_MAX, so a region placed past the plane made + * _fh_snr_dim.width - rect.x wrap and memset most of memory. Reject the + * rectangle before it is used to derive any width or offset. */ + if (!_fh_osd_virt) return; + if (rect.x >= _fh_snr_dim.width || rect.y >= _fh_snr_dim.height) return; + + w = rect.width; + if ((unsigned int)rect.x + w > _fh_snr_dim.width) + w = _fh_snr_dim.width - rect.x; + for (unsigned int y = 0; y < rect.height; y++) { - unsigned int py = rect.y + y; + unsigned int py = (unsigned int)rect.y + y; if (py >= _fh_snr_dim.height) break; - unsigned int w = rect.width; - if (rect.x + w > _fh_snr_dim.width) w = _fh_snr_dim.width - rect.x; memset(_fh_osd_virt + py * _fh_snr_dim.width + rect.x, 0, w * 2); } } @@ -646,6 +689,8 @@ int fh_region_create(int *handle, hal_rect rect, short opacity) fh_osd_scale(&rect); _fh_osd[*handle].used = 1; _fh_osd[*handle].rect = rect; + _fh_osd[*handle].opal = opacity < 0 ? 0 : (opacity > 255 ? 255 : (unsigned char)opacity); + fh_osd_apply_alpha(); return EXIT_SUCCESS; } @@ -655,6 +700,7 @@ void fh_region_destroy(int *handle) fh_osd_clear(_fh_osd[*handle].rect); _fh_osd[*handle].used = 0; *handle = -1; + fh_osd_apply_alpha(); } /* Bitmaps arrive as BGR555LE; set the alpha bit for opaque pixels (ARGB1555) */ @@ -665,15 +711,35 @@ int fh_region_setbitmap(int *handle, hal_bitmap *bitmap) return EXIT_FAILURE; fh_osd_rgn *rgn = &_fh_osd[*handle]; fh_osd_clear(rgn->rect); - rgn->rect.width = bitmap->dim.width; - rgn->rect.height = bitmap->dim.height; + if (rgn->rect.x >= _fh_snr_dim.width || rgn->rect.y >= _fh_snr_dim.height) + return EXIT_SUCCESS; + + /* The bitmap is rendered in main-stream pixels while this plane covers the + * whole sensor frame, so it is scaled by the same ratio fh_osd_scale() + * applies to the position. Scaling only the position left the overlay + * placed for one resolution and drawn at another whenever the main stream + * was smaller than the sensor. Nearest neighbour is enough for OSD text. */ + unsigned int sw = _fh_vpss_dim[0].width ? _fh_vpss_dim[0].width : _fh_snr_dim.width; + unsigned int sh = _fh_vpss_dim[0].height ? _fh_vpss_dim[0].height : _fh_snr_dim.height; + unsigned int bw = bitmap->dim.width, bh = bitmap->dim.height; + unsigned int dw = bw * _fh_snr_dim.width / sw; + unsigned int dh = bh * _fh_snr_dim.height / sh; + if (!bw || !bh) return EXIT_SUCCESS; + if (!dw) dw = 1; + if (!dh) dh = 1; + if (dw > 0xffff) dw = 0xffff; + if (dh > 0xffff) dh = 0xffff; + + rgn->rect.width = dw; + rgn->rect.height = dh; unsigned short *src = bitmap->data; - for (unsigned int y = 0; y < bitmap->dim.height; y++) { - unsigned int py = rgn->rect.y + y; + for (unsigned int y = 0; y < dh; y++) { + unsigned int py = (unsigned int)rgn->rect.y + y; if (py >= _fh_snr_dim.height) break; + unsigned int sy = y * bh / dh; unsigned short *dst = _fh_osd_virt + py * _fh_snr_dim.width + rgn->rect.x; - for (unsigned int x = 0; x < bitmap->dim.width && rgn->rect.x + x < _fh_snr_dim.width; x++) { - unsigned short p = src[y * bitmap->dim.width + x]; + for (unsigned int x = 0; x < dw && (unsigned int)rgn->rect.x + x < _fh_snr_dim.width; x++) { + unsigned short p = src[sy * bw + (x * bw / dw)]; dst[x] = p ? (p | 0x8000) : 0; } } @@ -844,7 +910,15 @@ int fh_video_snapshot_grab(signed char index, hal_jpegdata *jpeg) pthread_mutex_lock(&_fh_mjpeg_mtx); if (_fh_mjpeg_len) { if (_fh_mjpeg_len > jpeg->length) { - jpeg->data = realloc(jpeg->data, _fh_mjpeg_len); + /* keep the old buffer if this fails; assigning the result + * straight to jpeg->data leaked it and then memcpy'd NULL */ + unsigned char *grown = realloc(jpeg->data, _fh_mjpeg_len); + if (!grown) { + pthread_mutex_unlock(&_fh_mjpeg_mtx); + HAL_ERROR("fh_venc", "Growing the snapshot buffer to %u bytes failed!\n", + _fh_mjpeg_len); + } + jpeg->data = grown; jpeg->length = _fh_mjpeg_len; } memcpy(jpeg->data, _fh_mjpeg_cache, _fh_mjpeg_len); @@ -885,14 +959,34 @@ int fh_video_snapshot_grab(signed char index, hal_jpegdata *jpeg) /* JPEG: base = data address, frameType = length. MJPEG: NALU-style packets. */ if (type == FH_VENC_TYPE_JPEG) { unsigned int len = strm.frameType; - if (len > jpeg->length) { jpeg->data = realloc(jpeg->data, len); jpeg->length = len; } + if (len > jpeg->length) { + unsigned char *grown = realloc(jpeg->data, len); + if (!grown) { + HAL_DANGER("fh_venc", "Growing the snapshot buffer to %u bytes failed!\n", len); + fh_venc.fnReleaseStream(strm.channel); + ret = EXIT_FAILURE; + goto abort; + } + jpeg->data = grown; + jpeg->length = len; + } memcpy(jpeg->data, (void*)strm.base, len); jpeg->jpegSize = len; } else { unsigned int total = 0; for (unsigned int i = 0; i < strm.naluCount && i < 28; i++) total += strm.nalu[i].length; - if (total > jpeg->length) { jpeg->data = realloc(jpeg->data, total); jpeg->length = total; } + if (total > jpeg->length) { + unsigned char *grown = realloc(jpeg->data, total); + if (!grown) { + HAL_DANGER("fh_venc", "Growing the snapshot buffer to %u bytes failed!\n", total); + fh_venc.fnReleaseStream(strm.channel); + ret = EXIT_FAILURE; + goto abort; + } + jpeg->data = grown; + jpeg->length = total; + } jpeg->jpegSize = 0; for (unsigned int i = 0; i < strm.naluCount && i < 28; i++) { memcpy(jpeg->data + jpeg->jpegSize, (void*)strm.nalu[i].addr, strm.nalu[i].length);