feat: add video frame handling

This commit is contained in:
Siyuan Miao 2025-06-21 22:37:10 +00:00
parent eb5827ccf9
commit 2484620d20
22 changed files with 2065 additions and 2382 deletions

View File

@ -24,11 +24,12 @@ GO_ARGS := GOOS=linux GOARCH=arm GOARM=7 ARCHFLAGS="-arch arm"
# if BUILDKIT_PATH exists, use buildkit to build # if BUILDKIT_PATH exists, use buildkit to build
ifneq ($(wildcard $(BUILDKIT_PATH)),) ifneq ($(wildcard $(BUILDKIT_PATH)),)
GO_ARGS := $(GO_ARGS) \ GO_ARGS := $(GO_ARGS) \
CGO_CFLAGS="-Ijetkvm-native -Ijetkvm-native/ui -I$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/include -I$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/sysroot/usr/include" \ CGO_CFLAGS="-I$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/include -I$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/sysroot/usr/include" \
CGO_LDFLAGS="-Ljetkvm-native/build -Ljetkvm-native/build/lib -L$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/lib -L$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/sysroot/usr/lib -ljknative -lrockit -lrockchip_mpp -lrga -lpthread -lm -llvgl" \ CGO_LDFLAGS="-L$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/lib -L$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/sysroot/usr/lib -lrockit -lrockchip_mpp -lrga -lpthread -lm" \
CC="$(BUILDKIT_PATH)/bin/$(BUILDKIT_FLAVOR)-gcc" \ CC="$(BUILDKIT_PATH)/bin/$(BUILDKIT_FLAVOR)-gcc" \
LD="$(BUILDKIT_PATH)/bin/$(BUILDKIT_FLAVOR)-ld" \ LD="$(BUILDKIT_PATH)/bin/$(BUILDKIT_FLAVOR)-ld" \
CGO_ENABLED=1 CGO_ENABLED=1
# GO_RELEASE_BUILD_ARGS := $(GO_RELEASE_BUILD_ARGS) -x -work
endif endif
GO_CMD := $(GO_ARGS) go GO_CMD := $(GO_ARGS) go

View File

@ -30,25 +30,35 @@ var (
func updateDisplay() { func updateDisplay() {
nativeInstance.UpdateLabelIfChanged("home_info_ipv4_addr", networkState.IPv4String()) nativeInstance.UpdateLabelIfChanged("home_info_ipv4_addr", networkState.IPv4String())
nativeInstance.UpdateLabelIfChanged("home_info_ipv6_addr", networkState.IPv6String()) ipv6 := networkState.IPv6String()
if ipv6 != "" {
nativeInstance.UpdateLabelIfChanged("home_info_ipv6_addr", ipv6)
nativeInstance.ObjShow("home_info_ipv6_addr")
} else {
nativeInstance.UpdateLabelIfChanged("home_info_ipv6_addr", "")
nativeInstance.ObjHide("home_info_ipv6_addr")
}
nativeInstance.ObjHide("menu_btn_network")
nativeInstance.ObjHide("menu_btn_access")
nativeInstance.UpdateLabelIfChanged("home_info_mac_addr", networkState.MACString()) nativeInstance.UpdateLabelIfChanged("home_info_mac_addr", networkState.MACString())
if usbState == "configured" { if usbState == "configured" {
nativeInstance.UpdateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Connected") nativeInstance.UpdateLabelIfChanged("usb_status_label", "Connected")
_, _ = nativeInstance.ObjSetState("ui_Home_Footer_Usb_Status_Label", "LV_STATE_DEFAULT") _, _ = nativeInstance.ObjSetState("usb_status", "LV_STATE_DEFAULT")
} else { } else {
nativeInstance.UpdateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Disconnected") nativeInstance.UpdateLabelIfChanged("usb_status_label", "Disconnected")
_, _ = nativeInstance.ObjSetState("ui_Home_Footer_Usb_Status_Label", "LV_STATE_USER_2") _, _ = nativeInstance.ObjSetState("usb_status", "LV_STATE_USER_2")
} }
if lastVideoState.Ready { if lastVideoState.Ready {
nativeInstance.UpdateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Connected") nativeInstance.UpdateLabelIfChanged("hdmi_status_label", "Connected")
_, _ = nativeInstance.ObjSetState("ui_Home_Footer_Hdmi_Status_Label", "LV_STATE_DEFAULT") _, _ = nativeInstance.ObjSetState("hdmi_status", "LV_STATE_DEFAULT")
} else { } else {
nativeInstance.UpdateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Disconnected") nativeInstance.UpdateLabelIfChanged("hdmi_status_label", "Disconnected")
_, _ = nativeInstance.ObjSetState("ui_Home_Footer_Hdmi_Status_Label", "LV_STATE_USER_2") _, _ = nativeInstance.ObjSetState("hdmi_status", "LV_STATE_USER_2")
} }
nativeInstance.UpdateLabelIfChanged("ui_Home_Header_Cloud_Status_Label", fmt.Sprintf("%d active", actionSessions)) nativeInstance.UpdateLabelIfChanged("cloud_status_label", fmt.Sprintf("%d active", actionSessions))
if networkState.IsUp() { if networkState.IsUp() {
nativeInstance.SwitchToScreenIf("home_screen", []string{"no_network_screen", "boot_screen"}) nativeInstance.SwitchToScreenIf("home_screen", []string{"no_network_screen", "boot_screen"})
@ -57,20 +67,20 @@ func updateDisplay() {
} }
if cloudConnectionState == CloudConnectionStateNotConfigured { if cloudConnectionState == CloudConnectionStateNotConfigured {
_, _ = nativeInstance.ObjHide("ui_Home_Header_Cloud_Status_Icon") _, _ = nativeInstance.ObjHide("cloud_status_icon")
} else { } else {
_, _ = nativeInstance.ObjShow("ui_Home_Header_Cloud_Status_Icon") _, _ = nativeInstance.ObjShow("cloud_status_icon")
} }
switch cloudConnectionState { switch cloudConnectionState {
case CloudConnectionStateDisconnected: case CloudConnectionStateDisconnected:
_, _ = nativeInstance.ImgSetSrc("ui_Home_Header_Cloud_Status_Icon", "cloud_disconnected.png") _, _ = nativeInstance.ImgSetSrc("cloud_status_icon", "cloud_disconnected")
stopCloudBlink() stopCloudBlink()
case CloudConnectionStateConnecting: case CloudConnectionStateConnecting:
_, _ = nativeInstance.ImgSetSrc("ui_Home_Header_Cloud_Status_Icon", "cloud.png") _, _ = nativeInstance.ImgSetSrc("cloud_status_icon", "cloud")
startCloudBlink() startCloudBlink()
case CloudConnectionStateConnected: case CloudConnectionStateConnected:
_, _ = nativeInstance.ImgSetSrc("ui_Home_Header_Cloud_Status_Icon", "cloud.png") _, _ = nativeInstance.ImgSetSrc("cloud_status_icon", "cloud")
stopCloudBlink() stopCloudBlink()
} }
} }
@ -94,9 +104,9 @@ func startCloudBlink() {
if cloudConnectionState != CloudConnectionStateConnecting { if cloudConnectionState != CloudConnectionStateConnecting {
continue continue
} }
_, _ = nativeInstance.ObjFadeOut("ui_Home_Header_Cloud_Status_Icon", 1000) _, _ = nativeInstance.ObjFadeOut("cloud_status_icon", 1000)
time.Sleep(1000 * time.Millisecond) time.Sleep(1000 * time.Millisecond)
_, _ = nativeInstance.ObjFadeIn("ui_Home_Header_Cloud_Status_Icon", 1000) _, _ = nativeInstance.ObjFadeIn("cloud_status_icon", 1000)
time.Sleep(1000 * time.Millisecond) time.Sleep(1000 * time.Millisecond)
} }
}() }()
@ -146,14 +156,14 @@ func waitCtrlAndRequestDisplayUpdate(shouldWakeDisplay bool) {
func updateStaticContents() { func updateStaticContents() {
//contents that never change //contents that never change
nativeInstance.UpdateLabelIfChanged("ui_Home_Content_Mac", networkState.MACString()) nativeInstance.UpdateLabelIfChanged("home_info_mac_addr", networkState.MACString())
systemVersion, appVersion, err := GetLocalVersion() systemVersion, appVersion, err := GetLocalVersion()
if err == nil { if err == nil {
nativeInstance.UpdateLabelIfChanged("ui_About_Content_Operating_System_Version_ContentLabel", systemVersion.String()) nativeInstance.UpdateLabelIfChanged("boot_screen_version", systemVersion.String())
nativeInstance.UpdateLabelIfChanged("ui_About_Content_App_Version_Content_Label", appVersion.String()) nativeInstance.UpdateLabelIfChanged("boot_screen_app_version", appVersion.String())
} }
nativeInstance.UpdateLabelIfChanged("ui_Status_Content_Device_Id_Content_Label", GetDeviceID()) nativeInstance.UpdateLabelIfChanged("boot_screen_device_id", GetDeviceID())
} }
// setDisplayBrightness sets /sys/class/backlight/backlight/brightness to alter // setDisplayBrightness sets /sys/class/backlight/backlight/brightness to alter

View File

@ -128,7 +128,7 @@
this.statsElement = statsElement; this.statsElement = statsElement;
this.stream = null; this.stream = null;
this.reconnectAttempts = 0; this.reconnectAttempts = 0;
this.maxReconnectAttempts = 10; this.maxReconnectAttempts = 500;
this.reconnectDelay = 1000; // Start with 1 second this.reconnectDelay = 1000; // Start with 1 second
this.maxReconnectDelay = 30000; // Max 30 seconds this.maxReconnectDelay = 30000; // Max 30 seconds
this.isConnecting = false; this.isConnecting = false;

View File

@ -41,10 +41,10 @@ FetchContent_Declare(
FetchContent_MakeAvailable(lv_drivers) FetchContent_MakeAvailable(lv_drivers)
# Get source files, excluding CMake generated files # Get source files, excluding CMake generated files
file(GLOB_RECURSE sources CONFIGURE_DEPENDS "*.c") file(GLOB_RECURSE sources CONFIGURE_DEPENDS "*.c" "ui/*.c")
list(FILTER sources EXCLUDE REGEX "CMakeFiles.*CompilerId.*\\.c$") list(FILTER sources EXCLUDE REGEX "CMakeFiles.*CompilerId.*\\.c$")
add_library(jknative STATIC ${sources} ${CMAKE_CURRENT_SOURCE_DIR}/jknative.h) add_library(jknative STATIC ${sources} ${CMAKE_CURRENT_SOURCE_DIR}/ctrl.h)
# Include directories # Include directories
target_include_directories(jknative PRIVATE target_include_directories(jknative PRIVATE

View File

@ -7,16 +7,28 @@
#include <pthread.h> #include <pthread.h>
#include <stdint.h> #include <stdint.h>
#include <fcntl.h> #include <fcntl.h>
#include "frozen.h"
#include "video.h" #include "video.h"
#include "screen.h" #include "screen.h"
#include "edid.h" #include "edid.h"
#include "ctrl.h" #include "ctrl.h"
#include <lvgl.h> #include <lvgl.h>
#include "log.h"
#include "log_handler.h"
jetkvm_video_state_t state; jetkvm_video_state_t state;
jetkvm_video_state_handler_t *video_state_handler = NULL; jetkvm_video_state_handler_t *video_state_handler = NULL;
jetkvm_video_handler_t *video_handler = NULL;
void jetkvm_set_log_handler(jetkvm_log_handler_t *handler) {
log_set_handler(handler);
}
void jetkvm_set_video_handler(jetkvm_video_handler_t *handler) {
video_handler = handler;
}
void report_video_format(bool ready, const char *error, u_int16_t width, u_int16_t height, double frame_per_second) void report_video_format(bool ready, const char *error, u_int16_t width, u_int16_t height, double frame_per_second)
{ {
state.ready = ready; state.ready = ready;
@ -29,6 +41,21 @@ void report_video_format(bool ready, const char *error, u_int16_t width, u_int16
} }
} }
int socket_send_frame(const uint8_t *frame, ssize_t len)
{
if (video_handler != NULL) {
(*video_handler)(frame, len);
} else {
log_error("video handler is not set");
}
return 0;
// if (video_client_fd <= 0)
// {
// return -1;
// }
// return send(video_client_fd, frame, len, 0);
}
/** /**
* @brief Convert a hexadecimal string to an array of uint8_t bytes * @brief Convert a hexadecimal string to an array of uint8_t bytes
* *
@ -284,6 +311,10 @@ int jetkvm_video_set_quality_factor(float quality_factor) {
return 0; return 0;
} }
float jetkvm_video_get_quality_factor() {
return video_get_quality_factor();
}
int jetkvm_video_set_edid(const char *edid_hex) { int jetkvm_video_set_edid(const char *edid_hex) {
uint8_t edid[256]; uint8_t edid[256];
int edid_len = hex_to_bytes(edid_hex, edid, 256); int edid_len = hex_to_bytes(edid_hex, edid, 256);

View File

@ -2,6 +2,7 @@
#define VIDEO_DAEMON_CTRL_H #define VIDEO_DAEMON_CTRL_H
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h>
#include <sys/types.h> #include <sys/types.h>
typedef struct typedef struct
@ -14,6 +15,12 @@ typedef struct
} jetkvm_video_state_t; } jetkvm_video_state_t;
typedef void (jetkvm_video_state_handler_t)(jetkvm_video_state_t *state); typedef void (jetkvm_video_state_handler_t)(jetkvm_video_state_t *state);
typedef void (jetkvm_log_handler_t)(int level, const char *filename, const char *funcname, int line, const char *message);
typedef void (jetkvm_video_handler_t)(const uint8_t *frame, ssize_t len);
void jetkvm_set_log_handler(jetkvm_log_handler_t *handler);
void jetkvm_set_video_handler(jetkvm_video_handler_t *handler);
void jetkvm_ui_init(); void jetkvm_ui_init();
void jetkvm_ui_tick(); void jetkvm_ui_tick();
@ -37,6 +44,7 @@ void jetkvm_video_shutdown();
void jetkvm_video_start(); void jetkvm_video_start();
void jetkvm_video_stop(); void jetkvm_video_stop();
int jetkvm_video_set_quality_factor(float quality_factor); int jetkvm_video_set_quality_factor(float quality_factor);
float jetkvm_video_get_quality_factor();
int jetkvm_video_set_edid(const char *edid_hex); int jetkvm_video_set_edid(const char *edid_hex);
char *jetkvm_video_get_edid_hex(); char *jetkvm_video_get_edid_hex();
jetkvm_video_state_t *jetkvm_video_get_status(); jetkvm_video_state_t *jetkvm_video_get_status();

View File

@ -1,4 +1,5 @@
#include "edid.h" #include "edid.h"
#include "log.h"
#include <stdio.h> #include <stdio.h>
#include <stdbool.h> #include <stdbool.h>
@ -172,7 +173,7 @@ const char *videoc_log_status()
} }
else else
{ {
printf("Failed to read kernel log\n"); log_error("Failed to read kernel log\n");
return NULL; return NULL;
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,349 +0,0 @@
/*
* Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
* Copyright (c) 2018 Cesanta Software Limited
* All rights reserved
*
* Licensed under the Apache License, Version 2.0 (the ""License"");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an ""AS IS"" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef CS_FROZEN_FROZEN_H_
#define CS_FROZEN_FROZEN_H_
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <limits.h>
#if defined(_WIN32) && _MSC_VER < 1700
typedef int bool;
enum { false = 0, true = 1 };
#else
#include <stdbool.h>
#endif
/* JSON token type */
enum json_token_type {
JSON_TYPE_INVALID = 0, /* memsetting to 0 should create INVALID value */
JSON_TYPE_STRING,
JSON_TYPE_NUMBER,
JSON_TYPE_TRUE,
JSON_TYPE_FALSE,
JSON_TYPE_NULL,
JSON_TYPE_OBJECT_START,
JSON_TYPE_OBJECT_END,
JSON_TYPE_ARRAY_START,
JSON_TYPE_ARRAY_END,
JSON_TYPES_CNT
};
/*
* Structure containing token type and value. Used in `json_walk()` and
* `json_scanf()` with the format specifier `%T`.
*/
struct json_token {
const char *ptr; /* Points to the beginning of the value */
int len; /* Value length */
enum json_token_type type; /* Type of the token, possible values are above */
};
#define JSON_INVALID_TOKEN \
{ 0, 0, JSON_TYPE_INVALID }
/* Error codes */
#define JSON_STRING_INVALID -1
#define JSON_STRING_INCOMPLETE -2
#define JSON_DEPTH_LIMIT -3
/*
* Callback-based SAX-like API.
*
* Property name and length is given only if it's available: i.e. if current
* event is an object's property. In other cases, `name` is `NULL`. For
* example, name is never given:
* - For the first value in the JSON string;
* - For events JSON_TYPE_OBJECT_END and JSON_TYPE_ARRAY_END
*
* E.g. for the input `{ "foo": 123, "bar": [ 1, 2, { "baz": true } ] }`,
* the sequence of callback invocations will be as follows:
*
* - type: JSON_TYPE_OBJECT_START, name: NULL, path: "", value: NULL
* - type: JSON_TYPE_NUMBER, name: "foo", path: ".foo", value: "123"
* - type: JSON_TYPE_ARRAY_START, name: "bar", path: ".bar", value: NULL
* - type: JSON_TYPE_NUMBER, name: "0", path: ".bar[0]", value: "1"
* - type: JSON_TYPE_NUMBER, name: "1", path: ".bar[1]", value: "2"
* - type: JSON_TYPE_OBJECT_START, name: "2", path: ".bar[2]", value: NULL
* - type: JSON_TYPE_TRUE, name: "baz", path: ".bar[2].baz", value: "true"
* - type: JSON_TYPE_OBJECT_END, name: NULL, path: ".bar[2]", value: "{ \"baz\":
*true }"
* - type: JSON_TYPE_ARRAY_END, name: NULL, path: ".bar", value: "[ 1, 2, {
*\"baz\": true } ]"
* - type: JSON_TYPE_OBJECT_END, name: NULL, path: "", value: "{ \"foo\": 123,
*\"bar\": [ 1, 2, { \"baz\": true } ] }"
*/
typedef void (*json_walk_callback_t)(void *callback_data, const char *name,
size_t name_len, const char *path,
const struct json_token *token);
/*
* Parse `json_string`, invoking `callback` in a way similar to SAX parsers;
* see `json_walk_callback_t`.
* Return number of processed bytes, or a negative error code.
*/
int json_walk(const char *json_string, int json_string_length,
json_walk_callback_t callback, void *callback_data);
/*
* Extensible argument passing interface
*/
struct frozen_args {
json_walk_callback_t callback;
void *callback_data;
int limit;
};
int json_walk_args(const char *json_string, int json_string_length,
const struct frozen_args *args);
#define INIT_FROZEN_ARGS(ptr) do { \
memset((ptr), 0, sizeof(*(ptr))); \
(ptr)->limit = INT_MAX; \
} while(0)
/*
* JSON generation API.
* struct json_out abstracts output, allowing alternative printing plugins.
*/
struct json_out {
int (*printer)(struct json_out *, const char *str, size_t len);
union {
struct {
char *buf;
size_t size;
size_t len;
} buf;
void *data;
FILE *fp;
} u;
};
extern int json_printer_buf(struct json_out *, const char *, size_t);
extern int json_printer_file(struct json_out *, const char *, size_t);
#define JSON_OUT_BUF(buf, len) \
{ \
json_printer_buf, { \
{ buf, len, 0 } \
} \
}
#define JSON_OUT_FILE(fp) \
{ \
json_printer_file, { \
{ (char *) fp, 0, 0 } \
} \
}
typedef int (*json_printf_callback_t)(struct json_out *, va_list *ap);
/*
* Generate formatted output into a given sting buffer.
* This is a superset of printf() function, with extra format specifiers:
* - `%B` print json boolean, `true` or `false`. Accepts an `int`.
* - `%Q` print quoted escaped string or `null`. Accepts a `const char *`.
* - `%.*Q` same as `%Q`, but with length. Accepts `int`, `const char *`
* - `%V` print quoted base64-encoded string. Accepts a `const char *`, `int`.
* - `%H` print quoted hex-encoded string. Accepts a `int`, `const char *`.
* - `%M` invokes a json_printf_callback_t function. That callback function
* can consume more parameters.
*
* Return number of bytes printed. If the return value is bigger than the
* supplied buffer, that is an indicator of overflow. In the overflow case,
* overflown bytes are not printed.
*/
int json_printf(struct json_out *, const char *fmt, ...);
int json_vprintf(struct json_out *, const char *fmt, va_list ap);
/*
* Same as json_printf, but prints to a file.
* File is created if does not exist. File is truncated if already exists.
*/
int json_fprintf(const char *file_name, const char *fmt, ...);
int json_vfprintf(const char *file_name, const char *fmt, va_list ap);
/*
* Print JSON into an allocated 0-terminated string.
* Return allocated string, or NULL on error.
* Example:
*
* ```c
* char *str = json_asprintf("{a:%H}", 3, "abc");
* printf("%s\n", str); // Prints "616263"
* free(str);
* ```
*/
char *json_asprintf(const char *fmt, ...);
char *json_vasprintf(const char *fmt, va_list ap);
/*
* Helper %M callback that prints contiguous C arrays.
* Consumes void *array_ptr, size_t array_size, size_t elem_size, char *fmt
* Return number of bytes printed.
*/
int json_printf_array(struct json_out *, va_list *ap);
/*
* Scan JSON string `str`, performing scanf-like conversions according to `fmt`.
* This is a `scanf()` - like function, with following differences:
*
* 1. Object keys in the format string may be not quoted, e.g. "{key: %d}"
* 2. Order of keys in an object is irrelevant.
* 3. Several extra format specifiers are supported:
* - %B: consumes `int *` (or `char *`, if `sizeof(bool) == sizeof(char)`),
* expects boolean `true` or `false`.
* - %Q: consumes `char **`, expects quoted, JSON-encoded string. Scanned
* string is malloc-ed, caller must free() the string.
* - %V: consumes `char **`, `int *`. Expects base64-encoded string.
* Result string is base64-decoded, malloced and NUL-terminated.
* The length of result string is stored in `int *` placeholder.
* Caller must free() the result.
* - %H: consumes `int *`, `char **`.
* Expects a hex-encoded string, e.g. "fa014f".
* Result string is hex-decoded, malloced and NUL-terminated.
* The length of the result string is stored in `int *` placeholder.
* Caller must free() the result.
* - %M: consumes custom scanning function pointer and
* `void *user_data` parameter - see json_scanner_t definition.
* - %T: consumes `struct json_token *`, fills it out with matched token.
*
* Return number of elements successfully scanned & converted.
* Negative number means scan error.
*/
int json_scanf(const char *str, int str_len, const char *fmt, ...);
int json_vscanf(const char *str, int str_len, const char *fmt, va_list ap);
/* json_scanf's %M handler */
typedef void (*json_scanner_t)(const char *str, int len, void *user_data);
/*
* Helper function to scan array item with given path and index.
* Fills `token` with the matched JSON token.
* Return -1 if no array element found, otherwise non-negative token length.
*/
int json_scanf_array_elem(const char *s, int len, const char *path, int index,
struct json_token *token);
/*
* Unescape JSON-encoded string src,slen into dst, dlen.
* src and dst may overlap.
* If destination buffer is too small (or zero-length), result string is not
* written but the length is counted nevertheless (similar to snprintf).
* Return the length of unescaped string in bytes.
*/
int json_unescape(const char *src, int slen, char *dst, int dlen);
/*
* Escape a string `str`, `str_len` into the printer `out`.
* Return the number of bytes printed.
*/
int json_escape(struct json_out *out, const char *str, size_t str_len);
/*
* Read the whole file in memory.
* Return malloc-ed file content, or NULL on error. The caller must free().
*/
char *json_fread(const char *file_name);
/*
* Update given JSON string `s,len` by changing the value at given `json_path`.
* The result is saved to `out`. If `json_fmt` == NULL, that deletes the key.
* If path is not present, missing keys are added. Array path without an
* index pushes a value to the end of an array.
* Return 1 if the string was changed, 0 otherwise.
*
* Example: s is a JSON string { "a": 1, "b": [ 2 ] }
* json_setf(s, len, out, ".a", "7"); // { "a": 7, "b": [ 2 ] }
* json_setf(s, len, out, ".b", "7"); // { "a": 1, "b": 7 }
* json_setf(s, len, out, ".b[]", "7"); // { "a": 1, "b": [ 2,7 ] }
* json_setf(s, len, out, ".b", NULL); // { "a": 1 }
*/
int json_setf(const char *s, int len, struct json_out *out,
const char *json_path, const char *json_fmt, ...);
int json_vsetf(const char *s, int len, struct json_out *out,
const char *json_path, const char *json_fmt, va_list ap);
/*
* Pretty-print JSON string `s,len` into `out`.
* Return number of processed bytes in `s`.
*/
int json_prettify(const char *s, int len, struct json_out *out);
/*
* Prettify JSON file `file_name`.
* Return number of processed bytes, or negative number of error.
* On error, file content is not modified.
*/
int json_prettify_file(const char *file_name);
/*
* Iterate over an object at given JSON `path`.
* On each iteration, fill the `key` and `val` tokens. It is OK to pass NULL
* for `key`, or `val`, in which case they won't be populated.
* Return an opaque value suitable for the next iteration, or NULL when done.
*
* Example:
*
* ```c
* void *h = NULL;
* struct json_token key, val;
* while ((h = json_next_key(s, len, h, ".foo", &key, &val)) != NULL) {
* printf("[%.*s] -> [%.*s]\n", key.len, key.ptr, val.len, val.ptr);
* }
* ```
*/
void *json_next_key(const char *s, int len, void *handle, const char *path,
struct json_token *key, struct json_token *val);
/*
* Iterate over an array at given JSON `path`.
* Similar to `json_next_key`, but fills array index `idx` instead of `key`.
*/
void *json_next_elem(const char *s, int len, void *handle, const char *path,
int *idx, struct json_token *val);
#ifndef JSON_MAX_PATH_LEN
#define JSON_MAX_PATH_LEN 256
#endif
#ifndef JSON_MINIMAL
#define JSON_MINIMAL 0
#endif
#ifndef JSON_ENABLE_BASE64
#define JSON_ENABLE_BASE64 !JSON_MINIMAL
#endif
#ifndef JSON_ENABLE_HEX
#define JSON_ENABLE_HEX !JSON_MINIMAL
#endif
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* CS_FROZEN_FROZEN_H_ */

130
internal/native/cgo/log.h Normal file
View File

@ -0,0 +1,130 @@
#ifndef VIDEO_DAEMON_LOG_H
#define VIDEO_DAEMON_LOG_H
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "log_handler.h"
/* Default level */
#ifndef LOG_LEVEL
#define LOG_LEVEL LEVEL_INFO
#endif
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
void jetkvm_log(const char *message);
/* Log to screen */
#define emit_log(level, file, func, line, ...) do { \
/* call the log handler */ \
char msg_buffer[1024]; \
sprintf(msg_buffer, __VA_ARGS__); \
log_message(level, file, func, line, msg_buffer); \
} while (0)
// /* display the level */ \
// printf("%10s%s", level, " "); \
// \
// /* display the function doing the logging */ \
// printf("%s%s", func, " " : ""); \
// \
// /* display the file and/or the line number */ \
// printf( \
// "%s%s%s%.d%s%s", \
// DISPLAY_FUNC && (DISPLAY_FILE || DISPLAY_LINE) ? "(" : "", \
// DISPLAY_FILE ? file : "", \
// DISPLAY_FILE && DISPLAY_LINE ? ":" : "", \
// DISPLAY_LINE ? line : 0, \
// DISPLAY_FUNC && (DISPLAY_FILE || DISPLAY_LINE) ? ") " : "", \
// !DISPLAY_FUNC && (DISPLAY_FILE || DISPLAY_LINE) ? " " : "" \
// ); \
// \
// /* display message border */ \
// printf("%s%s", DISPLAY_BORDER ? BORDER : "", DISPLAY_BORDER ? " " : ""); \
// \
// /* display the callee's message */ \
// if (DISPLAY_MESSAGE) printf(__VA_ARGS__); \
// \
// /* add the message ending (usually '\n') */ \
// printf("%s", DISPLAY_ENDING ? MSG_ENDING : ""); \
// \
// /* reset the colour */ \
// printf("%s", DISPLAY_RESET ? RESET_COLOUR : ""); \
// \
// /* add a newline */ \
// printf("\n"); \
// fflush(stdout); \
/* Level enum */
#define LEVEL_PANIC 5
#define LEVEL_FATAL 4
#define LEVEL_ERROR 3
#define LEVEL_WARN 2
#define LEVEL_INFO 1
#define LEVEL_DEBUG 0
#define LEVEL_TRACE -1
/* TRACE LOG */
#define log_trace(...) do { \
if (LOG_LEVEL <= LEVEL_TRACE) { \
emit_log( \
LEVEL_TRACE, __FILENAME__, __func__, __LINE__, __VA_ARGS__ \
); \
} \
} while (0)
/* DEBUG LOG */
#define log_debug(...) do { \
if (LOG_LEVEL <= LEVEL_DEBUG) { \
emit_log( \
LEVEL_DEBUG, __FILENAME__, __func__, __LINE__, __VA_ARGS__ \
); \
} \
} while (0)
/* INFO LOG */
#define log_info(...) do { \
if (LOG_LEVEL <= LEVEL_INFO) { \
emit_log( \
LEVEL_INFO, __FILENAME__, __func__, __LINE__, __VA_ARGS__ \
); \
} \
} while (0)
/* NOTICE LOG */
#define log_notice(...) do { \
if (LOG_LEVEL <= LEVEL_NOTICE) { \
emit_log( \
LEVEL_NOTICE, __FILENAME__, __func__, __LINE__, __VA_ARGS__ \
); \
} \
} while (0)
/* WARN LOG */
#define log_warn(...) do { \
if (LOG_LEVEL <= LEVEL_WARN) { \
emit_log( \
LEVEL_WARN, __FILENAME__, __func__, __LINE__, __VA_ARGS__ \
); \
} \
} while (0)
/* ERROR LOG */
#define log_error(...) do { \
if (LOG_LEVEL <= LEVEL_ERROR) { \
emit_log( \
LEVEL_ERROR, __FILENAME__, __func__, __LINE__, __VA_ARGS__ \
); \
} \
} while (0)
/* PANIC LOG */
#define log_panic(...) do { \
if (LOG_LEVEL <= LEVEL_PANIC) { \
emit_log( \
LEVEL_PANIC, __FILENAME__, __func__, __LINE__, __VA_ARGS__\
); \
} \
} while (0)
#endif //VIDEO_DAEMON_LOG_H

View File

@ -0,0 +1,15 @@
#include <stddef.h>
#include "log_handler.h"
/* Log handler */
jetkvm_log_handler_t *log_handler = NULL;
void log_message(int level, const char *filename, const char *funcname, const int line, const char *message) {
if (log_handler != NULL) {
log_handler(level, filename, funcname, line, message);
}
}
void log_set_handler(jetkvm_log_handler_t *handler) {
log_handler = handler;
}

View File

@ -0,0 +1,9 @@
#ifndef LOG_HANDLER_H
#define LOG_HANDLER_H
typedef void (jetkvm_log_handler_t)(int level, const char *filename, const char *funcname, const int line, const char *message);
void log_message(int level, const char *filename, const char *funcname, const int line, const char *message);
void log_set_handler(jetkvm_log_handler_t *handler);
#endif

View File

@ -3,6 +3,7 @@
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include "log.h"
#include "screen.h" #include "screen.h"
#include <lvgl.h> #include <lvgl.h>
#include <display/fbdev.h> #include <display/fbdev.h>
@ -17,7 +18,10 @@ static lv_disp_drv_t disp_drv;
static lv_indev_drv_t indev_drv; static lv_indev_drv_t indev_drv;
void lvgl_init(void) { void lvgl_init(void) {
log_trace("initalizing lvgl");
lv_init(); lv_init();
log_trace("initalizing fbdev");
fbdev_init(); fbdev_init();
lv_disp_draw_buf_init(&disp_buf, buf, NULL, DISP_BUF_SIZE); lv_disp_draw_buf_init(&disp_buf, buf, NULL, DISP_BUF_SIZE);
lv_disp_drv_init(&disp_drv); lv_disp_drv_init(&disp_drv);
@ -31,6 +35,7 @@ void lvgl_init(void) {
lv_disp_drv_register(&disp_drv); lv_disp_drv_register(&disp_drv);
log_trace("initalizing evdev");
evdev_init(); evdev_init();
evdev_set_file("/dev/input/event1"); evdev_set_file("/dev/input/event1");
@ -39,7 +44,10 @@ void lvgl_init(void) {
indev_drv.read_cb = evdev_read; indev_drv.read_cb = evdev_read;
lv_indev_drv_register(&indev_drv); lv_indev_drv_register(&indev_drv);
log_trace("initalizing ui");
ui_init(); ui_init();
log_info("ui initalized");
// lv_label_set_text(ui_Boot_Screen_Version, ""); // lv_label_set_text(ui_Boot_Screen_Version, "");
// lv_label_set_text(ui_Home_Content_Ip, "..."); // lv_label_set_text(ui_Home_Content_Ip, "...");
// lv_label_set_text(ui_Home_Header_Cloud_Status_Label, "0 active"); // lv_label_set_text(ui_Home_Header_Cloud_Status_Label, "0 active");
@ -102,7 +110,7 @@ lv_img_dsc_t *ui_get_image(const char *name) {
void ui_set_text(const char *name, const char *text) { void ui_set_text(const char *name, const char *text) {
lv_obj_t *obj = ui_get_obj(name); lv_obj_t *obj = ui_get_obj(name);
if(obj == NULL) { if(obj == NULL) {
printf("ui_set_text %s %s, obj not found\n", name, text); log_error("ui_set_text %s %s, obj not found\n", name, text);
return; return;
} }
lv_label_set_text(obj, text); lv_label_set_text(obj, text);

View File

@ -24,6 +24,7 @@
#include <sys/socket.h> #include <sys/socket.h>
#include "video.h" #include "video.h"
#include "ctrl.h" #include "ctrl.h"
#include "log.h"
#define VIDEO_DEV "/dev/video0" #define VIDEO_DEV "/dev/video0"
#define SUB_DEV "/dev/v4l-subdev2" #define SUB_DEV "/dev/v4l-subdev2"
@ -181,75 +182,39 @@ static int32_t buf_init()
{ {
return -1; return -1;
} }
printf("Created memory pool\n"); log_info("created memory pool");
return RK_SUCCESS; return RK_SUCCESS;
} }
pthread_t *format_thread = NULL; pthread_t *format_thread = NULL;
int video_client_fd = 0;
int connect_video_client(const char *path)
{
int client_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (client_fd == -1)
{
perror("can not create socket");
return -1;
}
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
if (connect(client_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
{
perror("can not connect to video socket");
close(client_fd);
return -1;
}
video_client_fd = client_fd;
return 0;
}
int socket_send_frame(const uint8_t *frame, ssize_t len)
{
if (video_client_fd <= 0)
{
return -1;
}
return send(video_client_fd, frame, len, 0);
}
int video_init() int video_init()
{ {
if (connect_video_client("/var/run/jetkvm_video.sock") != 0) if (RK_MPI_SYS_Init() != RK_SUCCESS)
{ {
printf("can not connect to video socket\n"); log_error("RK_MPI_SYS_Init failed");
return -1; return RK_FAILURE;
} }
printf("Connected to video socket\n");
if (sub_dev_fd < 0) if (sub_dev_fd < 0)
{ {
sub_dev_fd = open(SUB_DEV, O_RDWR); sub_dev_fd = open(SUB_DEV, O_RDWR);
if (sub_dev_fd < 0) if (sub_dev_fd < 0)
{ {
printf("failed to open control sub device %s: %s\n", SUB_DEV, strerror(errno)); log_error("failed to open control sub device %s: %s", SUB_DEV, strerror(errno));
return errno; return errno;
} }
printf("Opened control sub device %s\n", SUB_DEV); log_info("opened control sub device %s", SUB_DEV);
} }
int32_t ret = buf_init(); int32_t ret = buf_init();
if (ret != RK_SUCCESS) if (ret != RK_SUCCESS)
{ {
RK_LOGE("buf_init failed with error: %d", ret); log_error("buf_init failed with error: %d", ret);
return ret; return ret;
} }
printf("buf_init completed successfully\n"); log_info("buf_init completed successfully");
format_thread = malloc(sizeof(pthread_t)); format_thread = malloc(sizeof(pthread_t));
pthread_create(format_thread, NULL, run_detect_format, NULL); pthread_create(format_thread, NULL, run_detect_format, NULL);
@ -315,7 +280,7 @@ static void *venc_read_stream(void *arg)
s32Ret = RK_MPI_VENC_ReleaseStream(VENC_CHANNEL, &stFrame); s32Ret = RK_MPI_VENC_ReleaseStream(VENC_CHANNEL, &stFrame);
if (s32Ret != RK_SUCCESS) if (s32Ret != RK_SUCCESS)
{ {
RK_LOGE("RK_MPI_VENC_ReleaseStream fail %x", s32Ret); log_error("RK_MPI_VENC_ReleaseStream fail %x", s32Ret);
} }
loopCount++; loopCount++;
} }
@ -325,11 +290,11 @@ static void *venc_read_stream(void *arg)
{ {
continue; continue;
} }
RK_LOGE("RK_MPI_VENC_GetStream fail %x", s32Ret); log_error("RK_MPI_VENC_GetStream fail %x", s32Ret);
break; break;
} }
} }
printf("exiting venc_read_stream\n"); log_info("exiting venc_read_stream");
free(stFrame.pstPack); free(stFrame.pstPack);
return NULL; return NULL;
} }
@ -350,6 +315,8 @@ void *run_video_stream(void *arg)
{ {
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
log_info("running video stream");
while (streaming_flag) while (streaming_flag)
{ {
if (detected_signal == false) if (detected_signal == false)
@ -361,11 +328,11 @@ void *run_video_stream(void *arg)
int video_dev_fd = open(VIDEO_DEV, O_RDWR); int video_dev_fd = open(VIDEO_DEV, O_RDWR);
if (video_dev_fd < 0) if (video_dev_fd < 0)
{ {
printf("failed to open video capture device %s: %s\n", VIDEO_DEV, strerror(errno)); log_error("failed to open video capture device %s: %s", VIDEO_DEV, strerror(errno));
usleep(1000000); usleep(1000000);
continue; continue;
} }
printf("Opened video capture device %s\n", VIDEO_DEV); log_info("opened video capture device %s", VIDEO_DEV);
uint32_t width = detected_width; uint32_t width = detected_width;
uint32_t height = detected_height; uint32_t height = detected_height;
@ -397,10 +364,10 @@ void *run_video_stream(void *arg)
perror("VIDIOC_REQBUFS failed"); perror("VIDIOC_REQBUFS failed");
return errno; return errno;
} }
printf("VIDIOC_REQBUFS successful\n"); log_info("VIDIOC_REQBUFS successful");
struct buffer buffers[3] = {}; struct buffer buffers[3] = {};
printf("Allocated buffers\n"); log_info("allocated buffers");
for (int i = 0; i < input_buffer_count; i++) for (int i = 0; i < input_buffer_count; i++)
{ {
@ -475,7 +442,7 @@ void *run_video_stream(void *arg)
RK_S32 ret = venc_start(bitrate, bitrate * 2, width, height); RK_S32 ret = venc_start(bitrate, bitrate * 2, width, height);
if (ret != RK_SUCCESS) if (ret != RK_SUCCESS)
{ {
RK_LOGE("Set VENC parameters failed with %#x", ret); log_error("Set VENC parameters failed with %#x", ret);
goto cleanup; goto cleanup;
} }
@ -495,7 +462,7 @@ void *run_video_stream(void *arg)
r = select(video_dev_fd + 1, &fds, NULL, NULL, &tv); r = select(video_dev_fd + 1, &fds, NULL, NULL, &tv);
if (r == 0) if (r == 0)
{ {
printf("select timeout \n"); log_info("select timeout \n");
break; break;
} }
if (r == -1) if (r == -1)
@ -632,7 +599,7 @@ void video_start_streaming()
{ {
if (streaming_thread != NULL) if (streaming_thread != NULL)
{ {
printf("video streaming already started\n"); log_info("video streaming already started");
return; return;
} }
streaming_thread = malloc(sizeof(pthread_t)); streaming_thread = malloc(sizeof(pthread_t));
@ -649,7 +616,7 @@ void video_stop_streaming()
pthread_join(*streaming_thread, NULL); pthread_join(*streaming_thread, NULL);
free(streaming_thread); free(streaming_thread);
streaming_thread = NULL; streaming_thread = NULL;
printf("video streaming stopped\n"); log_info("video streaming stopped");
} }
} }
@ -663,6 +630,7 @@ void *run_detect_format(void *arg)
sub.type = V4L2_EVENT_SOURCE_CHANGE; sub.type = V4L2_EVENT_SOURCE_CHANGE;
if (ioctl(sub_dev_fd, VIDIOC_SUBSCRIBE_EVENT, &sub) == -1) if (ioctl(sub_dev_fd, VIDIOC_SUBSCRIBE_EVENT, &sub) == -1)
{ {
log_error("cannot subscribe to event");
perror("Cannot subscribe to event"); perror("Cannot subscribe to event");
goto exit; goto exit;
} }
@ -676,13 +644,13 @@ void *run_detect_format(void *arg)
if (errno == ENOLINK) if (errno == ENOLINK)
{ {
// No timings could be detected because no signal was found. // No timings could be detected because no signal was found.
printf("HDMI status: no signal\n"); log_info("HDMI status: no signal");
report_video_format(false, "no_signal", 0, 0, 0); report_video_format(false, "no_signal", 0, 0, 0);
} }
else if (errno == ENOLCK) else if (errno == ENOLCK)
{ {
// The signal was unstable and the hardware could not lock on to it. // The signal was unstable and the hardware could not lock on to it.
printf("HDMI status: no lock\n"); log_info("HDMI status: no lock");
report_video_format(false, "no_lock", 0, 0, 0); report_video_format(false, "no_lock", 0, 0, 0);
} }
else if (errno == ERANGE) else if (errno == ERANGE)
@ -700,21 +668,21 @@ void *run_detect_format(void *arg)
} }
else else
{ {
printf("Active width: %d\n", dv_timings.bt.width); log_info("Active width: %d", dv_timings.bt.width);
printf("Active height: %d\n", dv_timings.bt.height); log_info("Active height: %d", dv_timings.bt.height);
double frames_per_second = (double)dv_timings.bt.pixelclock / double frames_per_second = (double)dv_timings.bt.pixelclock /
((dv_timings.bt.height + dv_timings.bt.vfrontporch + dv_timings.bt.vsync + ((dv_timings.bt.height + dv_timings.bt.vfrontporch + dv_timings.bt.vsync +
dv_timings.bt.vbackporch) * dv_timings.bt.vbackporch) *
(dv_timings.bt.width + dv_timings.bt.hfrontporch + dv_timings.bt.hsync + (dv_timings.bt.width + dv_timings.bt.hfrontporch + dv_timings.bt.hsync +
dv_timings.bt.hbackporch)); dv_timings.bt.hbackporch));
printf("Frames per second: %.2f fps\n", frames_per_second); log_info("Frames per second: %.2f fps", frames_per_second);
detected_width = dv_timings.bt.width; detected_width = dv_timings.bt.width;
detected_height = dv_timings.bt.height; detected_height = dv_timings.bt.height;
detected_signal = true; detected_signal = true;
report_video_format(true, NULL, detected_width, detected_height, frames_per_second); report_video_format(true, NULL, detected_width, detected_height, frames_per_second);
if (streaming_flag == true) if (streaming_flag == true)
{ {
printf("restarting on going video streaming\n"); log_info("restarting on going video streaming");
video_stop_streaming(); video_stop_streaming();
video_start_streaming(); video_start_streaming();
} }
@ -723,15 +691,16 @@ void *run_detect_format(void *arg)
memset(&ev, 0, sizeof(ev)); memset(&ev, 0, sizeof(ev));
if (ioctl(sub_dev_fd, VIDIOC_DQEVENT, &ev) != 0) if (ioctl(sub_dev_fd, VIDIOC_DQEVENT, &ev) != 0)
{ {
log_error("failed to VIDIOC_DQEVENT");
perror("failed to VIDIOC_DQEVENT"); perror("failed to VIDIOC_DQEVENT");
break; break;
} }
printf("New event of type %u\n", ev.type); log_info("New event of type %u", ev.type);
if (ev.type != V4L2_EVENT_SOURCE_CHANGE) if (ev.type != V4L2_EVENT_SOURCE_CHANGE)
{ {
continue; continue;
} }
printf("source change detected!\n"); log_info("source change detected!");
} }
exit: exit:
close(sub_dev_fd); close(sub_dev_fd);
@ -747,8 +716,12 @@ void video_set_quality_factor(float factor)
if (streaming_flag == true) if (streaming_flag == true)
{ {
printf("restarting on going video streaming due to quality factor change\n"); log_info("restarting on going video streaming due to quality factor change");
video_stop_streaming(); video_stop_streaming();
video_start_streaming(); video_start_streaming();
} }
} }
float video_get_quality_factor() {
return quality_factor;
}

View File

@ -8,5 +8,6 @@ void video_start_streaming();
void video_stop_streaming(); void video_stop_streaming();
void video_set_quality_factor(float factor); void video_set_quality_factor(float factor);
float video_get_quality_factor();
#endif //VIDEO_DAEMON_VIDEO_H #endif //VIDEO_DAEMON_VIDEO_H

View File

@ -5,34 +5,128 @@ package native
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"sync"
"time" "time"
"unsafe" "unsafe"
"github.com/rs/zerolog"
) )
// #cgo LDFLAGS: -Ljetkvm-native // #cgo LDFLAGS: -Lcgo/build -Lcgo/build/lib -ljknative -llvgl
// #cgo CFLAGS: -Icgo -Icgo/ui -Icgo/build/_deps/lvgl-src
// #include "ctrl.h" // #include "ctrl.h"
// #include <stdlib.h> // #include <stdlib.h>
// typedef const char cchar_t;
// typedef const uint8_t cuint8_t;
// extern void jetkvm_video_state_handler(jetkvm_video_state_t *state); // extern void jetkvm_video_state_handler(jetkvm_video_state_t *state);
// static inline void jetkvm_setup_video_state_handler() { // static inline void jetkvm_setup_video_state_handler() {
// jetkvm_set_video_state_handler(&jetkvm_video_state_handler); // jetkvm_set_video_state_handler(&jetkvm_video_state_handler);
// } // }
// extern void jetkvm_go_log_handler(int level, cchar_t *filename, cchar_t *funcname, int line, cchar_t *message);
// static inline void jetkvm_setup_log_handler() {
// jetkvm_set_log_handler(&jetkvm_go_log_handler);
// }
// extern void jetkvm_video_handler(cuint8_t *frame, ssize_t len);
// static inline void jetkvm_setup_video_handler() {
// jetkvm_set_video_handler(&jetkvm_video_handler);
// }
import "C" import "C"
var (
jkInstance *Native
jkInstanceLock sync.RWMutex
jkVideoChan chan []byte = make(chan []byte)
)
func setUpJkInstance(instance *Native) {
jkInstanceLock.Lock()
defer jkInstanceLock.Unlock()
if jkInstance == nil {
jkInstance = instance
}
if jkInstance != instance {
panic("jkInstance is already set")
}
}
//export jetkvm_video_state_handler //export jetkvm_video_state_handler
func jetkvm_video_state_handler(state *C.jetkvm_video_state_t) { func jetkvm_video_state_handler(state *C.jetkvm_video_state_t) {
nativeLogger.Info().Msg("video state handler") jkInstanceLock.RLock()
nativeLogger.Info().Msg(fmt.Sprintf("state: %+v", state)) defer jkInstanceLock.RUnlock()
if jkInstance != nil {
// convert state to VideoState
videoState := VideoState{
Ready: bool(state.ready),
Error: C.GoString(state.error),
Width: int(state.width),
Height: int(state.height),
FramePerSecond: float64(state.frame_per_second),
}
jkInstance.handleVideoStateMessage(videoState)
}
}
//export jetkvm_go_log_handler
func jetkvm_go_log_handler(level C.int, filename *C.cchar_t, funcname *C.cchar_t, line C.int, message *C.cchar_t) {
l := nativeLogger.With().
Str("file", C.GoString(filename)).
Str("function", C.GoString(funcname)).
Int("line", int(line)).
Logger()
gLevel := zerolog.Level(level)
switch gLevel {
case zerolog.DebugLevel:
l.Debug().Msg(C.GoString(message))
case zerolog.InfoLevel:
l.Info().Msg(C.GoString(message))
case zerolog.WarnLevel:
l.Warn().Msg(C.GoString(message))
case zerolog.ErrorLevel:
l.Error().Msg(C.GoString(message))
case zerolog.PanicLevel:
l.Panic().Msg(C.GoString(message))
case zerolog.FatalLevel:
l.Fatal().Msg(C.GoString(message))
case zerolog.TraceLevel:
l.Trace().Msg(C.GoString(message))
case zerolog.NoLevel:
l.Info().Msg(C.GoString(message))
default:
l.Info().Msg(C.GoString(message))
}
}
//export jetkvm_video_handler
func jetkvm_video_handler(frame *C.cuint8_t, len C.ssize_t) {
jkVideoChan <- C.GoBytes(unsafe.Pointer(frame), C.int(len))
} }
func setVideoStateHandler() { func setVideoStateHandler() {
C.jetkvm_setup_video_state_handler() C.jetkvm_setup_video_state_handler()
} }
func setLogHandler() {
C.jetkvm_setup_log_handler()
}
func setVideoHandler() {
C.jetkvm_setup_video_handler()
}
func (n *Native) StartNativeVideo() { func (n *Native) StartNativeVideo() {
setUpJkInstance(n)
setVideoStateHandler() setVideoStateHandler()
setLogHandler()
setVideoHandler()
C.jetkvm_ui_init() C.jetkvm_ui_init()
n.UpdateLabelIfChanged("boot_screen_version", n.AppVersion.String()) n.UpdateLabelIfChanged("boot_screen_version", n.appVersion.String())
go func() { go func() {
for { for {
@ -45,6 +139,7 @@ func (n *Native) StartNativeVideo() {
nativeLogger.Error().Msg("failed to initialize video") nativeLogger.Error().Msg("failed to initialize video")
return return
} }
C.jetkvm_video_start() C.jetkvm_video_start()
close(n.ready) close(n.ready)
@ -163,17 +258,23 @@ func (n *Native) DispSetRotation(rotation string) (bool, error) {
} }
func (n *Native) GetStreamQualityFactor() (float64, error) { func (n *Native) GetStreamQualityFactor() (float64, error) {
return 1.0, nil factor := C.jetkvm_video_get_quality_factor()
return float64(factor), nil
} }
func (n *Native) SetStreamQualityFactor(factor float64) error { func (n *Native) SetStreamQualityFactor(factor float64) error {
C.jetkvm_video_set_quality_factor(C.float(factor))
return nil return nil
} }
func (n *Native) GetEDID() (string, error) { func (n *Native) GetEDID() (string, error) {
return "", nil edidCStr := C.jetkvm_video_get_edid_hex()
return C.GoString(edidCStr), nil
} }
func (n *Native) SetEDID(edid string) error { func (n *Native) SetEDID(edid string) error {
edidCStr := C.CString(edid)
defer C.free(unsafe.Pointer(edidCStr))
C.jetkvm_video_set_edid(edidCStr)
return nil return nil
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,7 @@
{ {
"navigation": { "navigation": {
"selectedUserPageObject": "[jetkvm.eez-project]:/userPages/1", "selectedUserPageObject": "[jetkvm.eez-project]:/userPages/2",
"selectedActionObject": "[jetkvm.eez-project]:/actions/0", "selectedActionObject": "[jetkvm.eez-project]:/actions/1",
"selectedGlobalVariableObject": "[jetkvm.eez-project]:/variables/globalVariables/-1",
"selectedStyleObject": "[jetkvm.eez-project]:/lvglStyles/styles/0", "selectedStyleObject": "[jetkvm.eez-project]:/lvglStyles/styles/0",
"selectedThemeObject": "[jetkvm.eez-project]:/themes/0", "selectedThemeObject": "[jetkvm.eez-project]:/themes/0",
"selectedFontObject": "[jetkvm.eez-project]:/fonts/1", "selectedFontObject": "[jetkvm.eez-project]:/fonts/1",
@ -129,6 +128,7 @@
"type": "tabset", "type": "tabset",
"id": "#2e3d9a08-c69c-42b5-b434-525109f2a5a7", "id": "#2e3d9a08-c69c-42b5-b434-525109f2a5a7",
"weight": 1, "weight": 1,
"selected": 2,
"enableClose": false, "enableClose": false,
"children": [ "children": [
{ {
@ -194,13 +194,14 @@
{ {
"type": "tabset", "type": "tabset",
"id": "EDITORS", "id": "EDITORS",
"weight": 43.26842349697695, "weight": 63.667074907133006,
"selected": 3,
"enableDeleteWhenEmpty": false, "enableDeleteWhenEmpty": false,
"enableClose": false, "enableClose": false,
"children": [ "children": [
{ {
"type": "tab", "type": "tab",
"id": "#f8258cf3-e12a-4be9-8021-33199674dbe6", "id": "#b444e818-4663-4332-ab40-a34acb670c8c",
"name": "HomeScreen", "name": "HomeScreen",
"component": "editor", "component": "editor",
"config": { "config": {
@ -209,6 +210,17 @@
}, },
"icon": "svg:page" "icon": "svg:page"
}, },
{
"type": "tab",
"id": "#f8258cf3-e12a-4be9-8021-33199674dbe6",
"name": "MenuScreen",
"component": "editor",
"config": {
"objectPath": "[jetkvm.eez-project]:/userPages/2",
"permanent": true
},
"icon": "svg:page"
},
{ {
"type": "tab", "type": "tab",
"id": "#ab5bf7de-d982-416b-b26b-8fffecd56449", "id": "#ab5bf7de-d982-416b-b26b-8fffecd56449",
@ -232,12 +244,13 @@
}, },
"icon": "material:settings" "icon": "material:settings"
} }
] ],
"active": true
}, },
{ {
"type": "row", "type": "row",
"id": "#ee319cf9-6145-49e4-b40e-1d999be897c8", "id": "#ee319cf9-6145-49e4-b40e-1d999be897c8",
"weight": 36.48121981117664, "weight": 16.082568401020577,
"children": [ "children": [
{ {
"type": "tabset", "type": "tabset",
@ -252,8 +265,7 @@
"enableClose": false, "enableClose": false,
"icon": "svg:properties" "icon": "svg:properties"
} }
], ]
"active": true
}, },
{ {
"type": "tabset", "type": "tabset",
@ -1067,7 +1079,11 @@
"0": { "0": {
"1": {} "1": {}
}, },
"1": {}, "1": {
"0": {
"$selected": true
}
},
"2": {}, "2": {},
"3": { "3": {
"0": { "0": {
@ -1076,9 +1092,7 @@
}, },
"1": { "1": {
"0": {}, "0": {},
"1": { "1": {}
"$selected": true
}
} }
} }
} }
@ -1111,13 +1125,13 @@
"0": {} "0": {}
}, },
"1": { "1": {
"0": {}, "0": {
"1": {}, "0": {},
"2": {}, "1": {},
"3": {}, "2": {},
"4": { "3": {},
"0": { "4": {
"$selected": true "0": {}
} }
} }
} }
@ -1126,8 +1140,8 @@
}, },
"transform": { "transform": {
"translate": { "translate": {
"x": -150, "x": -194,
"y": -120 "y": -454
}, },
"scale": 1 "scale": 1
}, },
@ -1154,9 +1168,9 @@
"lvglPart": "MAIN", "lvglPart": "MAIN",
"lvglState": "DEFAULT", "lvglState": "DEFAULT",
"lvglExpandedPropertiesGroup": [ "lvglExpandedPropertiesGroup": [
"MARGIN", "POSITION AND SIZE",
"TEXT", "PADDING",
"POSITION AND SIZE" "MARGIN"
], ],
"showInactiveFlowsInDebugger": true, "showInactiveFlowsInDebugger": true,
"globalFlowZoom": true, "globalFlowZoom": true,

View File

@ -1,30 +1,70 @@
package native package native
import ( import (
"time"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/rs/zerolog" "github.com/rs/zerolog"
) )
type Native struct { type Native struct {
ready chan struct{} ready chan struct{}
l *zerolog.Logger l *zerolog.Logger
lD *zerolog.Logger lD *zerolog.Logger
SystemVersion *semver.Version systemVersion *semver.Version
AppVersion *semver.Version appVersion *semver.Version
onVideoStateChange func(state VideoState)
onVideoFrameReceived func(frame []byte, duration time.Duration)
} }
func NewNative(systemVersion *semver.Version, appVersion *semver.Version) *Native { type NativeOptions struct {
SystemVersion *semver.Version
AppVersion *semver.Version
OnVideoStateChange func(state VideoState)
OnVideoFrameReceived func(frame []byte, duration time.Duration)
}
func NewNative(opts NativeOptions) *Native {
onVideoStateChange := opts.OnVideoStateChange
if onVideoStateChange == nil {
onVideoStateChange = func(state VideoState) {
nativeLogger.Info().Msg("video state changed")
}
}
onVideoFrameReceived := opts.OnVideoFrameReceived
if onVideoFrameReceived == nil {
onVideoFrameReceived = func(frame []byte, duration time.Duration) {
nativeLogger.Info().Msg("video frame received")
}
}
return &Native{ return &Native{
ready: make(chan struct{}), ready: make(chan struct{}),
l: nativeLogger, l: nativeLogger,
lD: displayLogger, lD: displayLogger,
SystemVersion: systemVersion, systemVersion: opts.SystemVersion,
AppVersion: appVersion, appVersion: opts.AppVersion,
onVideoStateChange: opts.OnVideoStateChange,
onVideoFrameReceived: opts.OnVideoFrameReceived,
} }
} }
func (n *Native) Start() { func (n *Native) Start() {
go n.StartNativeVideo() go n.StartNativeVideo()
go n.HandleVideoChan()
}
func (n *Native) HandleVideoChan() {
lastFrame := time.Now()
for {
frame := <-jkVideoChan
now := time.Now()
sinceLastFrame := now.Sub(lastFrame)
lastFrame = now
n.onVideoFrameReceived(frame, sinceLastFrame)
}
} }
// func handleCtrlClient(conn net.Conn) { // func handleCtrlClient(conn net.Conn) {

16
internal/native/video.go Normal file
View File

@ -0,0 +1,16 @@
package native
import "fmt"
type VideoState struct {
Ready bool `json:"ready"`
Error string `json:"error,omitempty"` //no_signal, no_lock, out_of_range
Width int `json:"width"`
Height int `json:"height"`
FramePerSecond float64 `json:"fps"`
}
func (n *Native) handleVideoStateMessage(state VideoState) {
nativeLogger.Info().Msg("video state handler")
nativeLogger.Info().Msg(fmt.Sprintf("state: %+v", state))
}

View File

@ -1,13 +1,32 @@
package kvm package kvm
import ( import (
"time"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/jetkvm/kvm/internal/native" "github.com/jetkvm/kvm/internal/native"
"github.com/pion/webrtc/v4/pkg/media"
) )
var nativeInstance *native.Native var nativeInstance *native.Native
func initNative(systemVersion *semver.Version, appVersion *semver.Version) { func initNative(systemVersion *semver.Version, appVersion *semver.Version) {
nativeInstance = native.NewNative(systemVersion, appVersion) nativeInstance = native.NewNative(native.NativeOptions{
SystemVersion: systemVersion,
AppVersion: appVersion,
OnVideoStateChange: func(state native.VideoState) {
lastVideoState = state
triggerVideoStateUpdate()
requestDisplayUpdate(true)
},
OnVideoFrameReceived: func(frame []byte, duration time.Duration) {
if currentSession != nil {
err := currentSession.VideoTrack.WriteSample(media.Sample{Data: frame, Duration: duration})
if err != nil {
nativeLogger.Warn().Err(err).Msg("error writing sample")
}
}
},
})
nativeInstance.Start() nativeInstance.Start()
} }

View File

@ -1,5 +1,7 @@
package kvm package kvm
import "github.com/jetkvm/kvm/internal/native"
// max frame size for 1080p video, specified in mpp venc setting // max frame size for 1080p video, specified in mpp venc setting
const maxFrameSize = 1920 * 1080 / 2 const maxFrameSize = 1920 * 1080 / 2
@ -7,15 +9,7 @@ func writeCtrlAction(action string) error {
return nil return nil
} }
type VideoInputState struct { var lastVideoState native.VideoState
Ready bool `json:"ready"`
Error string `json:"error,omitempty"` //no_signal, no_lock, out_of_range
Width int `json:"width"`
Height int `json:"height"`
FramePerSecond float64 `json:"fps"`
}
var lastVideoState VideoInputState
func triggerVideoStateUpdate() { func triggerVideoStateUpdate() {
go func() { go func() {
@ -35,6 +29,6 @@ func triggerVideoStateUpdate() {
// requestDisplayUpdate(true) // requestDisplayUpdate(true)
// } // }
func rpcGetVideoState() (VideoInputState, error) { func rpcGetVideoState() (native.VideoState, error) {
return lastVideoState, nil return lastVideoState, nil
} }