feat(video): add video rate control settings and UI integration

Signed-off-by: luckfox-eng29 <eng29@luckfox.com>
This commit is contained in:
luckfox-eng29
2026-04-30 15:12:23 +08:00
parent d3c7f6e01b
commit a3f65e4893
2 changed files with 543 additions and 21 deletions

View File

@@ -236,6 +236,124 @@ func rpcSetStreamEncodecType(encodecType string) error {
return nil return nil
} }
type RcQpParams struct {
S32FirstFrameStartQp int `json:"s32FirstFrameStartQp"`
U32StepQp int `json:"u32StepQp"`
U32MinQp int `json:"u32MinQp"`
U32MaxQp int `json:"u32MaxQp"`
U32MinIQp int `json:"u32MinIQp"`
U32MaxIQp int `json:"u32MaxIQp"`
S32DeltIpQp int `json:"s32DeltIpQp"`
S32MaxReEncodeTimes int `json:"s32MaxReEncodeTimes"`
U32FrmMaxQp int `json:"u32FrmMaxQp"`
U32FrmMinQp int `json:"u32FrmMinQp"`
U32FrmMinIQp int `json:"u32FrmMinIQp"`
U32FrmMaxIQp int `json:"u32FrmMaxIQp"`
U32MotionStaticSwitchFrmQp int `json:"u32MotionStaticSwitchFrmQp"`
}
type VideoRcConfigParams struct {
H264 RcQpParams `json:"h264"`
H265 RcQpParams `json:"h265"`
}
func rpcSetVideoRc(params VideoRcConfigParams) error {
logger.Info().Interface("params", params).Msg("Setting video RC params")
rcParams := map[string]interface{}{
"h264": map[string]interface{}{
"s32FirstFrameStartQp": params.H264.S32FirstFrameStartQp,
"u32StepQp": params.H264.U32StepQp,
"u32MinQp": params.H264.U32MinQp,
"u32MaxQp": params.H264.U32MaxQp,
"u32MinIQp": params.H264.U32MinIQp,
"u32MaxIQp": params.H264.U32MaxIQp,
"s32DeltIpQp": params.H264.S32DeltIpQp,
"s32MaxReEncodeTimes": params.H264.S32MaxReEncodeTimes,
"u32FrmMaxQp": params.H264.U32FrmMaxQp,
"u32FrmMinQp": params.H264.U32FrmMinQp,
"u32FrmMinIQp": params.H264.U32FrmMinIQp,
"u32FrmMaxIQp": params.H264.U32FrmMaxIQp,
"u32MotionStaticSwitchFrmQp": params.H264.U32MotionStaticSwitchFrmQp,
},
"h265": map[string]interface{}{
"s32FirstFrameStartQp": params.H265.S32FirstFrameStartQp,
"u32StepQp": params.H265.U32StepQp,
"u32MinQp": params.H265.U32MinQp,
"u32MaxQp": params.H265.U32MaxQp,
"u32MinIQp": params.H265.U32MinIQp,
"u32MaxIQp": params.H265.U32MaxIQp,
"s32DeltIpQp": params.H265.S32DeltIpQp,
"s32MaxReEncodeTimes": params.H265.S32MaxReEncodeTimes,
"u32FrmMaxQp": params.H265.U32FrmMaxQp,
"u32FrmMinQp": params.H265.U32FrmMinQp,
"u32FrmMinIQp": params.H265.U32FrmMinIQp,
"u32FrmMaxIQp": params.H265.U32FrmMaxIQp,
"u32MotionStaticSwitchFrmQp": params.H265.U32MotionStaticSwitchFrmQp,
},
}
var _, err = CallCtrlAction("set_video_rc", rcParams)
return err
}
func rpcGetVideoRc() (VideoRcConfigParams, error) {
resp, err := CallCtrlAction("get_video_rc", nil)
if err != nil {
return VideoRcConfigParams{}, err
}
result := resp.Result
if result == nil {
return VideoRcConfigParams{}, errors.New("invalid response format")
}
h264Map, _ := result["h264"].(map[string]interface{})
h265Map, _ := result["h265"].(map[string]interface{})
getInt := func(m map[string]interface{}, k string) int {
if v, ok := m[k].(float64); ok {
return int(v)
}
return 0
}
getUint := func(m map[string]interface{}, k string) int {
return getInt(m, k)
}
rc := VideoRcConfigParams{
H264: RcQpParams{
S32FirstFrameStartQp: getInt(h264Map, "s32FirstFrameStartQp"),
U32StepQp: getUint(h264Map, "u32StepQp"),
U32MinQp: getUint(h264Map, "u32MinQp"),
U32MaxQp: getUint(h264Map, "u32MaxQp"),
U32MinIQp: getUint(h264Map, "u32MinIQp"),
U32MaxIQp: getUint(h264Map, "u32MaxIQp"),
S32DeltIpQp: getInt(h264Map, "s32DeltIpQp"),
S32MaxReEncodeTimes: getInt(h264Map, "s32MaxReEncodeTimes"),
U32FrmMaxQp: getUint(h264Map, "u32FrmMaxQp"),
U32FrmMinQp: getUint(h264Map, "u32FrmMinQp"),
U32FrmMinIQp: getUint(h264Map, "u32FrmMinIQp"),
U32FrmMaxIQp: getUint(h264Map, "u32FrmMaxIQp"),
U32MotionStaticSwitchFrmQp: getUint(h264Map, "u32MotionStaticSwitchFrmQp"),
},
H265: RcQpParams{
S32FirstFrameStartQp: getInt(h265Map, "s32FirstFrameStartQp"),
U32StepQp: getUint(h265Map, "u32StepQp"),
U32MinQp: getUint(h265Map, "u32MinQp"),
U32MaxQp: getUint(h265Map, "u32MaxQp"),
U32MinIQp: getUint(h265Map, "u32MinIQp"),
U32MaxIQp: getUint(h265Map, "u32MaxIQp"),
S32DeltIpQp: getInt(h265Map, "s32DeltIpQp"),
S32MaxReEncodeTimes: getInt(h265Map, "s32MaxReEncodeTimes"),
U32FrmMaxQp: getUint(h265Map, "u32FrmMaxQp"),
U32FrmMinQp: getUint(h265Map, "u32FrmMinQp"),
U32FrmMinIQp: getUint(h265Map, "u32FrmMinIQp"),
U32FrmMaxIQp: getUint(h265Map, "u32FrmMaxIQp"),
U32MotionStaticSwitchFrmQp: getUint(h265Map, "u32MotionStaticSwitchFrmQp"),
},
}
return rc, nil
}
func rpcSetNpuAppStatus(enable bool) error { func rpcSetNpuAppStatus(enable bool) error {
logger.Info().Bool("enable", enable).Msg("Setting NPU app status") logger.Info().Bool("enable", enable).Msg("Setting NPU app status")
var _, err = CallCtrlAction("set_yolo_enable", map[string]interface{}{"enable": enable}) var _, err = CallCtrlAction("set_yolo_enable", map[string]interface{}{"enable": enable})
@@ -1441,7 +1559,7 @@ var rpcHandlers = map[string]RPCHandler{
"resetSDStorage": {Func: rpcResetSDStorage}, "resetSDStorage": {Func: rpcResetSDStorage},
"mountSDStorage": {Func: rpcMountSDStorage}, "mountSDStorage": {Func: rpcMountSDStorage},
"unmountSDStorage": {Func: rpcUnmountSDStorage}, "unmountSDStorage": {Func: rpcUnmountSDStorage},
"formatSDStorage": {Func: rpcFormatSDStorage, Params: []string{"confirm"}}, "formatSDStorage": {Func: rpcFormatSDStorage, Params: []string{"confirm", "fsType"}},
"mountWithHTTP": {Func: rpcMountWithHTTP, Params: []string{"url", "mode"}}, "mountWithHTTP": {Func: rpcMountWithHTTP, Params: []string{"url", "mode"}},
"mountWithWebRTC": {Func: rpcMountWithWebRTC, Params: []string{"filename", "size", "mode"}}, "mountWithWebRTC": {Func: rpcMountWithWebRTC, Params: []string{"filename", "size", "mode"}},
"mountWithStorage": {Func: rpcMountWithStorage, Params: []string{"filename", "mode"}}, "mountWithStorage": {Func: rpcMountWithStorage, Params: []string{"filename", "mode"}},
@@ -1527,8 +1645,16 @@ var rpcHandlers = map[string]RPCHandler{
"stopCloudflared": {Func: rpcStopCloudflared}, "stopCloudflared": {Func: rpcStopCloudflared},
"getCloudflaredStatus": {Func: rpcGetCloudflaredStatus}, "getCloudflaredStatus": {Func: rpcGetCloudflaredStatus},
"getCloudflaredLog": {Func: rpcGetCloudflaredLog}, "getCloudflaredLog": {Func: rpcGetCloudflaredLog},
"getVpnToolSystemInfo": {Func: rpcGetVpnToolSystemInfo},
"getVpnToolStatus": {Func: rpcGetVpnToolStatus, Params: []string{"tool"}},
"listVpnToolReleases": {Func: rpcListVpnToolReleases, Params: []string{"tool"}},
"installVpnTool": {Func: rpcInstallVpnTool, Params: []string{"tool", "version", "assetName", "downloadURL"}},
"useVpnToolVersion": {Func: rpcUseVpnToolVersion, Params: []string{"tool", "version"}},
"uninstallVpnToolVersion": {Func: rpcUninstallVpnToolVersion, Params: []string{"tool", "version"}},
"getStreamEncodecType": {Func: rpcGetStreamEncodecType}, "getStreamEncodecType": {Func: rpcGetStreamEncodecType},
"setStreamEncodecType": {Func: rpcSetStreamEncodecType, Params: []string{"encodecType"}}, "setStreamEncodecType": {Func: rpcSetStreamEncodecType, Params: []string{"encodecType"}},
"setVideoRc": {Func: rpcSetVideoRc, Params: []string{"params"}},
"getVideoRc": {Func: rpcGetVideoRc},
"setNpuAppStatus": {Func: rpcSetNpuAppStatus, Params: []string{"enable"}}, "setNpuAppStatus": {Func: rpcSetNpuAppStatus, Params: []string{"enable"}},
"getNpuAppStatus": {Func: rpcGetNpuAppStatus}, "getNpuAppStatus": {Func: rpcGetNpuAppStatus},
"startWireguard": {Func: rpcStartWireguard, Params: []string{"configFile"}}, "startWireguard": {Func: rpcStartWireguard, Params: []string{"configFile"}},

View File

@@ -1,5 +1,5 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { Button as AntdButton , Slider , Checkbox, Select } from "antd"; import { Button as AntdButton , Slider , Checkbox, Select, Modal, InputNumber, Tabs, Typography } from "antd";
import { useReactAt } from "i18n-auto-extractor/react"; import { useReactAt } from "i18n-auto-extractor/react";
import { isMobile } from "react-device-detect"; import { isMobile } from "react-device-detect";
@@ -10,6 +10,7 @@ import { SettingsItem, SettingsItemNew } from "@components/Settings/SettingsView
import notifications from "../../../notifications"; import notifications from "../../../notifications";
const { Text } = Typography;
@@ -45,6 +46,133 @@ const streamQualityOptions = [
{ value: "0.1", label: "Low" }, { value: "0.1", label: "Low" },
]; ];
type RcQpParams = {
s32FirstFrameStartQp: number;
u32StepQp: number;
u32MinQp: number;
u32MaxQp: number;
u32MinIQp: number;
u32MaxIQp: number;
s32DeltIpQp: number;
s32MaxReEncodeTimes: number;
u32FrmMaxQp: number;
u32FrmMinQp: number;
u32FrmMinIQp: number;
u32FrmMaxIQp: number;
u32MotionStaticSwitchFrmQp: number;
};
type VideoRcConfig = {
h264: RcQpParams;
h265: RcQpParams;
};
type RcSliderValues = {
stepQp: number;
minQp: number;
minIQp: number;
deltIpQp: number;
};
type RcSliderState = {
h264: RcSliderValues;
h265: RcSliderValues;
};
const clamp = (value: number, min: number, max: number) =>
Math.min(max, Math.max(min, value));
const sliderValueToNumber = (value: number | number[]) =>
Array.isArray(value) ? value[0] : value;
const DEFAULT_RC_CODEC: RcQpParams = {
s32FirstFrameStartQp: 0,
u32StepQp: 48,
u32MinQp: 48,
u32MaxQp: 51,
u32MinIQp: 48,
u32MaxIQp: 51,
s32DeltIpQp: 7,
s32MaxReEncodeTimes: 2,
u32FrmMaxQp: 51,
u32FrmMinQp: 48,
u32FrmMinIQp: 51,
u32FrmMaxIQp: 48,
u32MotionStaticSwitchFrmQp: 50,
};
const DEFAULT_VIDEO_RC_CONFIG: VideoRcConfig = {
h264: { ...DEFAULT_RC_CODEC },
h265: { ...DEFAULT_RC_CODEC },
};
const isObjectRecord = (value: unknown): value is Record<string, unknown> =>
typeof value === "object" && value !== null;
const toVideoRcConfigOrDefault = (value: unknown): VideoRcConfig => {
if (!isObjectRecord(value)) return DEFAULT_VIDEO_RC_CONFIG;
if (!("h264" in value) || !("h265" in value)) return DEFAULT_VIDEO_RC_CONFIG;
return value as VideoRcConfig;
};
const RC_LIMITS = {
stepQp: { min: 1, max: 51 },
maxQp: { min: 1, max: 51 },
maxIQp: { min: 1, max: 51 },
deltIpQp: { min: -7, max: 7 },
maxReEncodeTimes: { min: 0, max: 3 },
frmQp: { min: 1, max: 51 },
};
const normalizeRcCodec = (codec: RcQpParams): RcQpParams => {
const maxQp = clamp(Number(codec.u32MaxQp), RC_LIMITS.maxQp.min, RC_LIMITS.maxQp.max);
const maxIQp = clamp(Number(codec.u32MaxIQp), RC_LIMITS.maxIQp.min, RC_LIMITS.maxIQp.max);
const minQp = clamp(Number(codec.u32MinQp), RC_LIMITS.maxQp.min, maxQp);
const minIQp = clamp(Number(codec.u32MinIQp), RC_LIMITS.maxIQp.min, maxIQp);
return {
...codec,
s32FirstFrameStartQp: clamp(Number(codec.s32FirstFrameStartQp), RC_LIMITS.frmQp.min, RC_LIMITS.frmQp.max),
u32StepQp: clamp(Number(codec.u32StepQp), RC_LIMITS.stepQp.min, RC_LIMITS.stepQp.max),
u32MaxQp: maxQp,
u32MinQp: minQp,
u32MaxIQp: maxIQp,
u32MinIQp: minIQp,
s32DeltIpQp: clamp(Number(codec.s32DeltIpQp), RC_LIMITS.deltIpQp.min, RC_LIMITS.deltIpQp.max),
s32MaxReEncodeTimes: clamp(
Number(codec.s32MaxReEncodeTimes),
RC_LIMITS.maxReEncodeTimes.min,
RC_LIMITS.maxReEncodeTimes.max,
),
u32FrmMaxQp: clamp(Number(codec.u32FrmMaxQp), RC_LIMITS.frmQp.min, RC_LIMITS.frmQp.max),
u32FrmMinQp: clamp(Number(codec.u32FrmMinQp), RC_LIMITS.frmQp.min, RC_LIMITS.frmQp.max),
u32FrmMinIQp: clamp(Number(codec.u32FrmMinIQp), RC_LIMITS.frmQp.min, RC_LIMITS.frmQp.max),
u32FrmMaxIQp: clamp(Number(codec.u32FrmMaxIQp), RC_LIMITS.frmQp.min, RC_LIMITS.frmQp.max),
u32MotionStaticSwitchFrmQp: clamp(
Number(codec.u32MotionStaticSwitchFrmQp),
RC_LIMITS.frmQp.min,
RC_LIMITS.frmQp.max,
),
};
};
const normalizeVideoRcConfig = (config: VideoRcConfig): VideoRcConfig => ({
h264: normalizeRcCodec(config.h264),
h265: normalizeRcCodec(config.h265),
});
const sliderValuesFromCodec = (codec: RcQpParams): RcSliderValues => ({
stepQp: clamp(Number(codec.u32StepQp), 1, 50),
minQp: clamp(Number(codec.u32MinQp), 1, 50),
minIQp: clamp(Number(codec.u32MinIQp), 1, 50),
deltIpQp: clamp(Number(codec.s32DeltIpQp), -7, 7),
});
const sliderStateFromConfig = (config: VideoRcConfig): RcSliderState => ({
h264: sliderValuesFromCodec(config.h264),
h265: sliderValuesFromCodec(config.h265),
});
export default function SettingsVideoSide() { export default function SettingsVideoSide() {
const { $at } = useReactAt(); const { $at } = useReactAt();
const [send] = useJsonRpc(); const [send] = useJsonRpc();
@@ -54,6 +182,12 @@ export default function SettingsVideoSide() {
const [customEdidValue, setCustomEdidValue] = useState<string | null>(null); const [customEdidValue, setCustomEdidValue] = useState<string | null>(null);
const [edid, setEdid] = useState<string | null>(null); const [edid, setEdid] = useState<string | null>(null);
const [forceHpd, setForceHpd] = useState(false); const [forceHpd, setForceHpd] = useState(false);
const [videoRcConfig, setVideoRcConfig] = useState<VideoRcConfig>(DEFAULT_VIDEO_RC_CONFIG);
const [rcSliderValues, setRcSliderValues] = useState<RcSliderState>(
sliderStateFromConfig(DEFAULT_VIDEO_RC_CONFIG),
);
const [showRcAdvanced, setShowRcAdvanced] = useState(false);
const [rcDraftConfig, setRcDraftConfig] = useState<VideoRcConfig | null>(null);
// Video enhancement settings from store // Video enhancement settings from store
const videoSaturation = useSettingsStore(state => state.videoSaturation); const videoSaturation = useSettingsStore(state => state.videoSaturation);
@@ -63,6 +197,129 @@ export default function SettingsVideoSide() {
const videoContrast = useSettingsStore(state => state.videoContrast); const videoContrast = useSettingsStore(state => state.videoContrast);
const setVideoContrast = useSettingsStore(state => state.setVideoContrast); const setVideoContrast = useSettingsStore(state => state.setVideoContrast);
const currentCodec: "h264" | "h265" = streamEncodecType === "hevc" ? "h265" : "h264";
const currentSliders = rcSliderValues[currentCodec];
const applySliderToCodec = (codec: RcQpParams, sliders: RcSliderValues): RcQpParams => ({
...codec,
u32StepQp: sliders.stepQp,
u32MinQp: sliders.minQp,
u32MinIQp: sliders.minIQp,
s32DeltIpQp: sliders.deltIpQp,
});
const applyRcBasicConfig = () => {
const nextRcConfig = normalizeVideoRcConfig({
...videoRcConfig,
[currentCodec]: applySliderToCodec(videoRcConfig[currentCodec], currentSliders),
});
send("setVideoRc", { params: nextRcConfig }, resp => {
if ("error" in resp) {
notifications.error(`Failed to set video RC: ${resp.error.data || "Unknown error"}`);
return;
}
setVideoRcConfig(nextRcConfig);
setRcDraftConfig(nextRcConfig);
setRcSliderValues(sliderStateFromConfig(nextRcConfig));
notifications.success("Video RC updated");
});
};
const updateRcDraftField = (
codec: "h264" | "h265",
field: keyof RcQpParams,
value: number,
) => {
setRcDraftConfig(prev => {
if (!prev) return prev;
const nextCodec = { ...prev[codec], [field]: value };
const normalizedCodec = normalizeRcCodec(nextCodec);
return {
...prev,
[codec]: normalizedCodec,
};
});
};
const openRcAdvancedModal = () => {
const nextDraft = normalizeVideoRcConfig({
...videoRcConfig,
[currentCodec]: applySliderToCodec(videoRcConfig[currentCodec], currentSliders),
});
setRcDraftConfig(nextDraft);
setShowRcAdvanced(true);
};
const applyRcAdvancedConfig = () => {
if (!rcDraftConfig) {
return;
}
const normalized = normalizeVideoRcConfig(rcDraftConfig);
send("setVideoRc", { params: normalized }, resp => {
if ("error" in resp) {
notifications.error(`Failed to set video RC: ${resp.error.data || "Unknown error"}`);
return;
}
setVideoRcConfig(normalized);
setRcDraftConfig(normalized);
setRcSliderValues(sliderStateFromConfig(normalized));
notifications.success("Video RC updated");
setShowRcAdvanced(false);
});
};
const renderRcAdvancedForm = (codec: "h264" | "h265") => {
const current = rcDraftConfig?.[codec];
if (!current) return null;
const fields: Array<{
key: keyof RcQpParams;
label: string;
min?: number;
max?: number;
}> = [
{ key: "s32FirstFrameStartQp", label: "FirstFrameStartQp", min: 1, max: 51 },
{ key: "u32StepQp", label: "StepQp", min: 1, max: 51 },
{ key: "u32MaxQp", label: "MaxQp", min: 1, max: 51 },
{ key: "u32MinQp", label: "MinQp", min: 1, max: Number(current.u32MaxQp) || 51 },
{ key: "u32MaxIQp", label: "MaxIQp", min: 1, max: 51 },
{ key: "u32MinIQp", label: "MinIQp", min: 1, max: Number(current.u32MaxIQp) || 51 },
{ key: "s32DeltIpQp", label: "DeltIpQp", min: -7, max: 7 },
{ key: "s32MaxReEncodeTimes", label: "MaxReEncodeTimes", min: 0, max: 3 },
{ key: "u32FrmMaxQp", label: "FrmMaxQp", min: 1, max: 51 },
{ key: "u32FrmMinQp", label: "FrmMinQp", min: 1, max: 51 },
{ key: "u32FrmMaxIQp", label: "FrmMaxIQp", min: 1, max: 51 },
{ key: "u32FrmMinIQp", label: "FrmMinIQp", min: 1, max: 51 },
{ key: "u32MotionStaticSwitchFrmQp", label: "MotionStaticSwitchFrmQp", min: 1, max: 51 },
];
return (
<div className="grid grid-cols-1 gap-3">
{fields.map(field => (
<div key={`${codec}-${field.key}`} className="flex items-center justify-between gap-3">
<Text className="text-xs">{field.label}</Text>
<InputNumber
size="small"
value={current[field.key]}
min={field.min}
max={field.max}
step={1}
style={{ width: 140 }}
onChange={val => {
if (typeof val !== "number") return;
const safeValue =
field.min !== undefined && field.max !== undefined
? clamp(Number(val), field.min, field.max)
: Number(val);
updateRcDraftField(codec, field.key, safeValue);
}}
/>
</div>
))}
</div>
);
};
useEffect(() => { useEffect(() => {
send("getNpuAppStatus", {}, resp => { send("getNpuAppStatus", {}, resp => {
if ("error" in resp) return; if ("error" in resp) return;
@@ -79,6 +336,20 @@ export default function SettingsVideoSide() {
setStreamQuality(String(resp.result)); setStreamQuality(String(resp.result));
}); });
send("getVideoRc", {}, resp => {
if ("error" in resp) {
notifications.error(`Failed to get video RC: ${resp.error.data || "Unknown error"}`);
const fallbackRc = normalizeVideoRcConfig(DEFAULT_VIDEO_RC_CONFIG);
setVideoRcConfig(fallbackRc);
setRcSliderValues(sliderStateFromConfig(fallbackRc));
return;
}
const rc = normalizeVideoRcConfig(toVideoRcConfigOrDefault(resp.result));
setVideoRcConfig(rc);
setRcSliderValues(sliderStateFromConfig(rc));
});
send("getEDID", {}, resp => { send("getEDID", {}, resp => {
if ("error" in resp) { if ("error" in resp) {
notifications.error(`Failed to get EDID: ${resp.error.data || "Unknown error"}`); notifications.error(`Failed to get EDID: ${resp.error.data || "Unknown error"}`);
@@ -218,6 +489,116 @@ export default function SettingsVideoSide() {
/> />
</SettingsItem> </SettingsItem>
<SettingsItem
title={$at("RC Control")}
description={$at("Adjust rate control QP settings for better balance between quality and bitrate")}
/>
<div className="space-y-4">
<SettingsItemNew
title={$at("StepQp")}
description={String(currentSliders.stepQp)}
className={"flex-col w-full h-[40px]"}
>
<Slider
min={1}
max={51}
step={1}
value={currentSliders.stepQp}
onChange={value => {
const nextStepQp = clamp(sliderValueToNumber(value), 1, 50);
setRcSliderValues(prev => ({
...prev,
[currentCodec]: {
...prev[currentCodec],
stepQp: nextStepQp,
},
}));
}}
className={"w-full"}
/>
</SettingsItemNew>
<SettingsItemNew
title={$at("MinQp")}
description={String(currentSliders.minQp)}
className={"flex-col w-full h-[40px]"}
>
<Slider
min={1}
max={50}
step={1}
value={currentSliders.minQp}
onChange={value => {
const nextMinQp = clamp(sliderValueToNumber(value), 1, 50);
setRcSliderValues(prev => ({
...prev,
[currentCodec]: {
...prev[currentCodec],
minQp: nextMinQp,
},
}));
}}
className={"w-full"}
/>
</SettingsItemNew>
<SettingsItemNew
title={$at("MinIQp")}
description={String(currentSliders.minIQp)}
className={"flex-col w-full h-[40px]"}
>
<Slider
min={1}
max={50}
step={1}
value={currentSliders.minIQp}
onChange={value => {
const nextMinIQp = clamp(sliderValueToNumber(value), 1, 50);
setRcSliderValues(prev => ({
...prev,
[currentCodec]: {
...prev[currentCodec],
minIQp: nextMinIQp,
},
}));
}}
className={"w-full"}
/>
</SettingsItemNew>
<SettingsItemNew
title={$at("DetlpQp")}
description={String(currentSliders.deltIpQp)}
className={"flex-col w-full h-[40px]"}
>
<Slider
min={-7}
max={7}
step={1}
value={currentSliders.deltIpQp}
onChange={value => {
const nextDeltIpQp = clamp(sliderValueToNumber(value), -7, 7);
setRcSliderValues(prev => ({
...prev,
[currentCodec]: {
...prev[currentCodec],
deltIpQp: nextDeltIpQp,
},
}));
}}
className={"w-full"}
/>
</SettingsItemNew>
<div className="flex justify-end gap-2">
<AntdButton onClick={openRcAdvancedModal}>
{$at("Advanced")}
</AntdButton>
<AntdButton type="primary" onClick={applyRcBasicConfig}>
{$at("Apply")}
</AntdButton>
</div>
</div>
<SettingsItem <SettingsItem
title={$at("NPU Application")} title={$at("NPU Application")}
@@ -362,25 +743,7 @@ export default function SettingsVideoSide() {
}} }}
options={[...edids, { value: "custom", label: "Custom" }]} options={[...edids, { value: "custom", label: "Custom" }]}
/> />
{/* options={[...edids, { value: "custom", label: "Custom" }]}*/}
</SettingsItem> </SettingsItem>
{/*<SelectMenuBasic*/}
{/* size="SM"*/}
{/* label=""*/}
{/* fullWidth*/}
{/* value={customEdidValue ? "custom" : edid || "asd"}*/}
{/* onChange={e => {*/}
{/* console.log(e.target.value)*/}
{/* if (e.target.value === "custom") {*/}
{/* setEdid("custom");*/}
{/* setCustomEdidValue("");*/}
{/* } else {*/}
{/* setCustomEdidValue(null);*/}
{/* handleEDIDChange(e.target.value as string);*/}
{/* }*/}
{/* }}*/}
{/* options={[...edids, { value: "custom", label: "Custom" }]}*/}
{/*/>*/}
{customEdidValue !== null && ( {customEdidValue !== null && (
<> <>
@@ -418,6 +781,39 @@ export default function SettingsVideoSide() {
)} )}
</div> </div>
<div className={"h-[10vh]"}></div> <div className={"h-[10vh]"}></div>
<Modal
title={
<Text strong style={{ fontSize: "16px" }}>
{$at("RC Advanced Config")}
</Text>
}
open={showRcAdvanced}
onCancel={() => setShowRcAdvanced(false)}
onOk={applyRcAdvancedConfig}
okText={$at("Apply")}
cancelText={$at("Cancel")}
maskClosable={true}
keyboard={true}
width={520}
styles={{
body: {
padding: "20px 24px",
},
header: {
borderBottom: "1px solid #f0f0f0",
padding: "16px 24px",
marginBottom: 0,
}
}}
>
<Tabs
items={[
{ key: "h264", label: "H264", children: renderRcAdvancedForm("h264") },
{ key: "h265", label: "H265", children: renderRcAdvancedForm("h265") },
]}
/>
</Modal>
</div> </div>
); );
} }