mirror of https://github.com/jetkvm/kvm.git
feat: add video frame handling
This commit is contained in:
parent
eb5827ccf9
commit
2484620d20
7
Makefile
7
Makefile
|
@ -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
|
||||
CGO_ENABLED=1
|
||||
# GO_RELEASE_BUILD_ARGS := $(GO_RELEASE_BUILD_ARGS) -x -work
|
||||
endif
|
||||
|
||||
GO_CMD := $(GO_ARGS) go
|
||||
|
|
52
display.go
52
display.go
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
@ -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_ */
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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": {},
|
||||
"1": {},
|
||||
"2": {},
|
||||
"3": {},
|
||||
"4": {
|
||||
"0": {
|
||||
"$selected": true
|
||||
"0": {
|
||||
"0": {},
|
||||
"1": {},
|
||||
"2": {},
|
||||
"3": {},
|
||||
"4": {
|
||||
"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,
|
||||
|
|
|
@ -1,30 +1,70 @@
|
|||
package native
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type Native struct {
|
||||
ready chan struct{}
|
||||
l *zerolog.Logger
|
||||
lD *zerolog.Logger
|
||||
SystemVersion *semver.Version
|
||||
AppVersion *semver.Version
|
||||
ready chan struct{}
|
||||
l *zerolog.Logger
|
||||
lD *zerolog.Logger
|
||||
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,
|
||||
ready: make(chan struct{}),
|
||||
l: nativeLogger,
|
||||
lD: displayLogger,
|
||||
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) {
|
||||
|
|
|
@ -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))
|
||||
}
|
21
native.go
21
native.go
|
@ -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()
|
||||
}
|
||||
|
|
14
video.go
14
video.go
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue