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
ifneq ($(wildcard $(BUILDKIT_PATH)),)
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_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_CFLAGS="-I$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/include -I$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/sysroot/usr/include" \
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" \
LD="$(BUILDKIT_PATH)/bin/$(BUILDKIT_FLAVOR)-ld" \
CGO_ENABLED=1
# GO_RELEASE_BUILD_ARGS := $(GO_RELEASE_BUILD_ARGS) -x -work
endif
GO_CMD := $(GO_ARGS) go

View File

@ -30,25 +30,35 @@ var (
func updateDisplay() {
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())
if usbState == "configured" {
nativeInstance.UpdateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Connected")
_, _ = nativeInstance.ObjSetState("ui_Home_Footer_Usb_Status_Label", "LV_STATE_DEFAULT")
nativeInstance.UpdateLabelIfChanged("usb_status_label", "Connected")
_, _ = nativeInstance.ObjSetState("usb_status", "LV_STATE_DEFAULT")
} else {
nativeInstance.UpdateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Disconnected")
_, _ = nativeInstance.ObjSetState("ui_Home_Footer_Usb_Status_Label", "LV_STATE_USER_2")
nativeInstance.UpdateLabelIfChanged("usb_status_label", "Disconnected")
_, _ = nativeInstance.ObjSetState("usb_status", "LV_STATE_USER_2")
}
if lastVideoState.Ready {
nativeInstance.UpdateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Connected")
_, _ = nativeInstance.ObjSetState("ui_Home_Footer_Hdmi_Status_Label", "LV_STATE_DEFAULT")
nativeInstance.UpdateLabelIfChanged("hdmi_status_label", "Connected")
_, _ = nativeInstance.ObjSetState("hdmi_status", "LV_STATE_DEFAULT")
} else {
nativeInstance.UpdateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Disconnected")
_, _ = nativeInstance.ObjSetState("ui_Home_Footer_Hdmi_Status_Label", "LV_STATE_USER_2")
nativeInstance.UpdateLabelIfChanged("hdmi_status_label", "Disconnected")
_, _ = 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() {
nativeInstance.SwitchToScreenIf("home_screen", []string{"no_network_screen", "boot_screen"})
@ -57,20 +67,20 @@ func updateDisplay() {
}
if cloudConnectionState == CloudConnectionStateNotConfigured {
_, _ = nativeInstance.ObjHide("ui_Home_Header_Cloud_Status_Icon")
_, _ = nativeInstance.ObjHide("cloud_status_icon")
} else {
_, _ = nativeInstance.ObjShow("ui_Home_Header_Cloud_Status_Icon")
_, _ = nativeInstance.ObjShow("cloud_status_icon")
}
switch cloudConnectionState {
case CloudConnectionStateDisconnected:
_, _ = nativeInstance.ImgSetSrc("ui_Home_Header_Cloud_Status_Icon", "cloud_disconnected.png")
_, _ = nativeInstance.ImgSetSrc("cloud_status_icon", "cloud_disconnected")
stopCloudBlink()
case CloudConnectionStateConnecting:
_, _ = nativeInstance.ImgSetSrc("ui_Home_Header_Cloud_Status_Icon", "cloud.png")
_, _ = nativeInstance.ImgSetSrc("cloud_status_icon", "cloud")
startCloudBlink()
case CloudConnectionStateConnected:
_, _ = nativeInstance.ImgSetSrc("ui_Home_Header_Cloud_Status_Icon", "cloud.png")
_, _ = nativeInstance.ImgSetSrc("cloud_status_icon", "cloud")
stopCloudBlink()
}
}
@ -94,9 +104,9 @@ func startCloudBlink() {
if cloudConnectionState != CloudConnectionStateConnecting {
continue
}
_, _ = nativeInstance.ObjFadeOut("ui_Home_Header_Cloud_Status_Icon", 1000)
_, _ = nativeInstance.ObjFadeOut("cloud_status_icon", 1000)
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)
}
}()
@ -146,14 +156,14 @@ func waitCtrlAndRequestDisplayUpdate(shouldWakeDisplay bool) {
func updateStaticContents() {
//contents that never change
nativeInstance.UpdateLabelIfChanged("ui_Home_Content_Mac", networkState.MACString())
nativeInstance.UpdateLabelIfChanged("home_info_mac_addr", networkState.MACString())
systemVersion, appVersion, err := GetLocalVersion()
if err == nil {
nativeInstance.UpdateLabelIfChanged("ui_About_Content_Operating_System_Version_ContentLabel", systemVersion.String())
nativeInstance.UpdateLabelIfChanged("ui_About_Content_App_Version_Content_Label", appVersion.String())
nativeInstance.UpdateLabelIfChanged("boot_screen_version", systemVersion.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

View File

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

View File

@ -41,10 +41,10 @@ FetchContent_Declare(
FetchContent_MakeAvailable(lv_drivers)
# 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$")
add_library(jknative STATIC ${sources} ${CMAKE_CURRENT_SOURCE_DIR}/jknative.h)
add_library(jknative STATIC ${sources} ${CMAKE_CURRENT_SOURCE_DIR}/ctrl.h)
# Include directories
target_include_directories(jknative PRIVATE

View File

@ -7,16 +7,28 @@
#include <pthread.h>
#include <stdint.h>
#include <fcntl.h>
#include "frozen.h"
#include "video.h"
#include "screen.h"
#include "edid.h"
#include "ctrl.h"
#include <lvgl.h>
#include "log.h"
#include "log_handler.h"
jetkvm_video_state_t state;
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)
{
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
*
@ -284,6 +311,10 @@ int jetkvm_video_set_quality_factor(float quality_factor) {
return 0;
}
float jetkvm_video_get_quality_factor() {
return video_get_quality_factor();
}
int jetkvm_video_set_edid(const char *edid_hex) {
uint8_t edid[256];
int edid_len = hex_to_bytes(edid_hex, edid, 256);

View File

@ -2,6 +2,7 @@
#define VIDEO_DAEMON_CTRL_H
#include <stdbool.h>
#include <stdint.h>
#include <sys/types.h>
typedef struct
@ -14,6 +15,12 @@ typedef struct
} jetkvm_video_state_t;
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_tick();
@ -37,6 +44,7 @@ void jetkvm_video_shutdown();
void jetkvm_video_start();
void jetkvm_video_stop();
int jetkvm_video_set_quality_factor(float quality_factor);
float jetkvm_video_get_quality_factor();
int jetkvm_video_set_edid(const char *edid_hex);
char *jetkvm_video_get_edid_hex();
jetkvm_video_state_t *jetkvm_video_get_status();

View File

@ -1,4 +1,5 @@
#include "edid.h"
#include "log.h"
#include <stdio.h>
#include <stdbool.h>
@ -172,7 +173,7 @@ const char *videoc_log_status()
}
else
{
printf("Failed to read kernel log\n");
log_error("Failed to read kernel log\n");
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 <unistd.h>
#include "log.h"
#include "screen.h"
#include <lvgl.h>
#include <display/fbdev.h>
@ -17,7 +18,10 @@ static lv_disp_drv_t disp_drv;
static lv_indev_drv_t indev_drv;
void lvgl_init(void) {
log_trace("initalizing lvgl");
lv_init();
log_trace("initalizing fbdev");
fbdev_init();
lv_disp_draw_buf_init(&disp_buf, buf, NULL, DISP_BUF_SIZE);
lv_disp_drv_init(&disp_drv);
@ -31,6 +35,7 @@ void lvgl_init(void) {
lv_disp_drv_register(&disp_drv);
log_trace("initalizing evdev");
evdev_init();
evdev_set_file("/dev/input/event1");
@ -39,7 +44,10 @@ void lvgl_init(void) {
indev_drv.read_cb = evdev_read;
lv_indev_drv_register(&indev_drv);
log_trace("initalizing ui");
ui_init();
log_info("ui initalized");
// lv_label_set_text(ui_Boot_Screen_Version, "");
// lv_label_set_text(ui_Home_Content_Ip, "...");
// 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) {
lv_obj_t *obj = ui_get_obj(name);
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;
}
lv_label_set_text(obj, text);

View File

@ -24,6 +24,7 @@
#include <sys/socket.h>
#include "video.h"
#include "ctrl.h"
#include "log.h"
#define VIDEO_DEV "/dev/video0"
#define SUB_DEV "/dev/v4l-subdev2"
@ -181,75 +182,39 @@ static int32_t buf_init()
{
return -1;
}
printf("Created memory pool\n");
log_info("created memory pool");
return RK_SUCCESS;
}
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()
{
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");
return -1;
log_error("RK_MPI_SYS_Init failed");
return RK_FAILURE;
}
printf("Connected to video socket\n");
if (sub_dev_fd < 0)
{
sub_dev_fd = open(SUB_DEV, O_RDWR);
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;
}
printf("Opened control sub device %s\n", SUB_DEV);
log_info("opened control sub device %s", SUB_DEV);
}
int32_t ret = buf_init();
if (ret != RK_SUCCESS)
{
RK_LOGE("buf_init failed with error: %d", ret);
log_error("buf_init failed with error: %d", ret);
return ret;
}
printf("buf_init completed successfully\n");
log_info("buf_init completed successfully");
format_thread = malloc(sizeof(pthread_t));
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);
if (s32Ret != RK_SUCCESS)
{
RK_LOGE("RK_MPI_VENC_ReleaseStream fail %x", s32Ret);
log_error("RK_MPI_VENC_ReleaseStream fail %x", s32Ret);
}
loopCount++;
}
@ -325,11 +290,11 @@ static void *venc_read_stream(void *arg)
{
continue;
}
RK_LOGE("RK_MPI_VENC_GetStream fail %x", s32Ret);
log_error("RK_MPI_VENC_GetStream fail %x", s32Ret);
break;
}
}
printf("exiting venc_read_stream\n");
log_info("exiting venc_read_stream");
free(stFrame.pstPack);
return NULL;
}
@ -350,6 +315,8 @@ void *run_video_stream(void *arg)
{
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
log_info("running video stream");
while (streaming_flag)
{
if (detected_signal == false)
@ -361,11 +328,11 @@ void *run_video_stream(void *arg)
int video_dev_fd = open(VIDEO_DEV, O_RDWR);
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);
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 height = detected_height;
@ -397,10 +364,10 @@ void *run_video_stream(void *arg)
perror("VIDIOC_REQBUFS failed");
return errno;
}
printf("VIDIOC_REQBUFS successful\n");
log_info("VIDIOC_REQBUFS successful");
struct buffer buffers[3] = {};
printf("Allocated buffers\n");
log_info("allocated buffers");
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);
if (ret != RK_SUCCESS)
{
RK_LOGE("Set VENC parameters failed with %#x", ret);
log_error("Set VENC parameters failed with %#x", ret);
goto cleanup;
}
@ -495,7 +462,7 @@ void *run_video_stream(void *arg)
r = select(video_dev_fd + 1, &fds, NULL, NULL, &tv);
if (r == 0)
{
printf("select timeout \n");
log_info("select timeout \n");
break;
}
if (r == -1)
@ -632,7 +599,7 @@ void video_start_streaming()
{
if (streaming_thread != NULL)
{
printf("video streaming already started\n");
log_info("video streaming already started");
return;
}
streaming_thread = malloc(sizeof(pthread_t));
@ -649,7 +616,7 @@ void video_stop_streaming()
pthread_join(*streaming_thread, NULL);
free(streaming_thread);
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;
if (ioctl(sub_dev_fd, VIDIOC_SUBSCRIBE_EVENT, &sub) == -1)
{
log_error("cannot subscribe to event");
perror("Cannot subscribe to event");
goto exit;
}
@ -676,13 +644,13 @@ void *run_detect_format(void *arg)
if (errno == ENOLINK)
{
// 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);
}
else if (errno == ENOLCK)
{
// 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);
}
else if (errno == ERANGE)
@ -700,21 +668,21 @@ void *run_detect_format(void *arg)
}
else
{
printf("Active width: %d\n", dv_timings.bt.width);
printf("Active height: %d\n", dv_timings.bt.height);
log_info("Active width: %d", dv_timings.bt.width);
log_info("Active height: %d", dv_timings.bt.height);
double frames_per_second = (double)dv_timings.bt.pixelclock /
((dv_timings.bt.height + dv_timings.bt.vfrontporch + dv_timings.bt.vsync +
dv_timings.bt.vbackporch) *
(dv_timings.bt.width + dv_timings.bt.hfrontporch + dv_timings.bt.hsync +
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_height = dv_timings.bt.height;
detected_signal = true;
report_video_format(true, NULL, detected_width, detected_height, frames_per_second);
if (streaming_flag == true)
{
printf("restarting on going video streaming\n");
log_info("restarting on going video streaming");
video_stop_streaming();
video_start_streaming();
}
@ -723,15 +691,16 @@ void *run_detect_format(void *arg)
memset(&ev, 0, sizeof(ev));
if (ioctl(sub_dev_fd, VIDIOC_DQEVENT, &ev) != 0)
{
log_error("failed to VIDIOC_DQEVENT");
perror("failed to VIDIOC_DQEVENT");
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)
{
continue;
}
printf("source change detected!\n");
log_info("source change detected!");
}
exit:
close(sub_dev_fd);
@ -747,8 +716,12 @@ void video_set_quality_factor(float factor)
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_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_set_quality_factor(float factor);
float video_get_quality_factor();
#endif //VIDEO_DAEMON_VIDEO_H

View File

@ -5,34 +5,128 @@ package native
import (
"fmt"
"strconv"
"sync"
"time"
"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 <stdlib.h>
// typedef const char cchar_t;
// typedef const uint8_t cuint8_t;
// extern void jetkvm_video_state_handler(jetkvm_video_state_t *state);
// static inline void jetkvm_setup_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"
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
func jetkvm_video_state_handler(state *C.jetkvm_video_state_t) {
nativeLogger.Info().Msg("video state handler")
nativeLogger.Info().Msg(fmt.Sprintf("state: %+v", state))
jkInstanceLock.RLock()
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() {
C.jetkvm_setup_video_state_handler()
}
func setLogHandler() {
C.jetkvm_setup_log_handler()
}
func setVideoHandler() {
C.jetkvm_setup_video_handler()
}
func (n *Native) StartNativeVideo() {
setUpJkInstance(n)
setVideoStateHandler()
setLogHandler()
setVideoHandler()
C.jetkvm_ui_init()
n.UpdateLabelIfChanged("boot_screen_version", n.AppVersion.String())
n.UpdateLabelIfChanged("boot_screen_version", n.appVersion.String())
go func() {
for {
@ -45,6 +139,7 @@ func (n *Native) StartNativeVideo() {
nativeLogger.Error().Msg("failed to initialize video")
return
}
C.jetkvm_video_start()
close(n.ready)
@ -163,17 +258,23 @@ func (n *Native) DispSetRotation(rotation string) (bool, 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 {
C.jetkvm_video_set_quality_factor(C.float(factor))
return nil
}
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 {
edidCStr := C.CString(edid)
defer C.free(unsafe.Pointer(edidCStr))
C.jetkvm_video_set_edid(edidCStr)
return nil
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,6 +1,8 @@
package native
import (
"time"
"github.com/Masterminds/semver/v3"
"github.com/rs/zerolog"
)
@ -9,22 +11,60 @@ type Native struct {
ready chan struct{}
l *zerolog.Logger
lD *zerolog.Logger
SystemVersion *semver.Version
AppVersion *semver.Version
systemVersion *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{
ready: make(chan struct{}),
l: nativeLogger,
lD: displayLogger,
SystemVersion: systemVersion,
AppVersion: appVersion,
systemVersion: opts.SystemVersion,
appVersion: opts.AppVersion,
onVideoStateChange: opts.OnVideoStateChange,
onVideoFrameReceived: opts.OnVideoFrameReceived,
}
}
func (n *Native) Start() {
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) {

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
import (
"time"
"github.com/Masterminds/semver/v3"
"github.com/jetkvm/kvm/internal/native"
"github.com/pion/webrtc/v4/pkg/media"
)
var nativeInstance *native.Native
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()
}

View File

@ -1,5 +1,7 @@
package kvm
import "github.com/jetkvm/kvm/internal/native"
// max frame size for 1080p video, specified in mpp venc setting
const maxFrameSize = 1920 * 1080 / 2
@ -7,15 +9,7 @@ func writeCtrlAction(action string) error {
return nil
}
type VideoInputState 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"`
}
var lastVideoState VideoInputState
var lastVideoState native.VideoState
func triggerVideoStateUpdate() {
go func() {
@ -35,6 +29,6 @@ func triggerVideoStateUpdate() {
// requestDisplayUpdate(true)
// }
func rpcGetVideoState() (VideoInputState, error) {
func rpcGetVideoState() (native.VideoState, error) {
return lastVideoState, nil
}