mirror of
https://github.com/jetkvm/kvm.git
synced 2026-05-21 05:20:35 +00:00
fix(video): plumb source vrefresh into MPP encoder rate control
The MPP H.264/H.265 rate-control structure has Src/Dst FrameRateNum/Den fields the firmware never set, so MPP defaulted to fps_fix [30/1]. That made the encoder size its bitrate budget for 30 fps and behave unpredictably when 120 fps arrived from the capture chip. Now run_detect_format rounds the v4l2 dv-timings vrefresh to an integer (stored in detected_fps), and run_video_stream passes that value through venc_start -> populate_venc_attr where it's written into both Src and Dst FrameRate fields. GOP is sized to fps/2, which keeps the IDR cadence at ~0.5s for any source refresh — same WebRTC recovery latency at 60 Hz and 120 Hz. run_detect_format now also restarts the streaming pipeline when the rounded fps changes by more than ±1 fps, so an EDID swap that keeps resolution but changes refresh (e.g. 720p60 -> 720p120) actually reconfigures the encoder. The ±1 tolerance absorbs CVT-RB rounding (119.91 fps and 119.87 fps both round to 120). Verified on device: before: fps fix [30/1] -> fix [30/1] gop i [30] after: fps fix [120/1] -> fix [120/1] gop i [60] WebRTC inbound-rtp framesPerSecond now sustains ~120 with 0 dropped packets at 720p120, 19 ms jitter buffer, glass-to-glass delay halved on a 120 Hz panel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -116,21 +116,30 @@ double calculate_bitrate(float bitrate_factor, int width, int height)
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
static void populate_venc_attr(VENC_CHN_ATTR_S *stAttr, RK_U32 bitrate, RK_U32 max_bitrate, RK_U32 width, RK_U32 height)
|
||||
static void populate_venc_attr(VENC_CHN_ATTR_S *stAttr, RK_U32 bitrate, RK_U32 max_bitrate, RK_U32 width, RK_U32 height, RK_U32 fps)
|
||||
{
|
||||
memset(stAttr, 0, sizeof(VENC_CHN_ATTR_S));
|
||||
|
||||
RK_U32 min_bitrate = bitrate / 2;
|
||||
if (min_bitrate < 2) min_bitrate = 2;
|
||||
|
||||
// GOP scales with framerate so IDR cadence stays ~0.5s regardless of source
|
||||
// refresh — keeps WebRTC recovery latency bounded at 60 Hz and 120 Hz alike.
|
||||
RK_U32 gop = fps > 0 ? fps / 2 : 30;
|
||||
if (gop < 1) gop = 1;
|
||||
|
||||
if (codec_type == 1) {
|
||||
// H.265 (HEVC)
|
||||
stAttr->stRcAttr.enRcMode = VENC_RC_MODE_H265VBR;
|
||||
stAttr->stRcAttr.stH265Vbr.u32BitRate = bitrate;
|
||||
stAttr->stRcAttr.stH265Vbr.u32MaxBitRate = max_bitrate;
|
||||
stAttr->stRcAttr.stH265Vbr.u32MinBitRate = min_bitrate;
|
||||
stAttr->stRcAttr.stH265Vbr.u32Gop = 30;
|
||||
stAttr->stRcAttr.stH265Vbr.u32Gop = gop;
|
||||
stAttr->stRcAttr.stH265Vbr.u32StatTime = 2;
|
||||
stAttr->stRcAttr.stH265Vbr.u32SrcFrameRateNum = fps;
|
||||
stAttr->stRcAttr.stH265Vbr.u32SrcFrameRateDen = 1;
|
||||
stAttr->stRcAttr.stH265Vbr.fr32DstFrameRateNum = fps;
|
||||
stAttr->stRcAttr.stH265Vbr.fr32DstFrameRateDen = 1;
|
||||
stAttr->stVencAttr.enType = RK_VIDEO_ID_HEVC;
|
||||
stAttr->stVencAttr.u32Profile = H265E_PROFILE_MAIN;
|
||||
} else {
|
||||
@@ -139,8 +148,12 @@ static void populate_venc_attr(VENC_CHN_ATTR_S *stAttr, RK_U32 bitrate, RK_U32 m
|
||||
stAttr->stRcAttr.stH264Vbr.u32BitRate = bitrate;
|
||||
stAttr->stRcAttr.stH264Vbr.u32MaxBitRate = max_bitrate;
|
||||
stAttr->stRcAttr.stH264Vbr.u32MinBitRate = min_bitrate;
|
||||
stAttr->stRcAttr.stH264Vbr.u32Gop = 30;
|
||||
stAttr->stRcAttr.stH264Vbr.u32Gop = gop;
|
||||
stAttr->stRcAttr.stH264Vbr.u32StatTime = 2;
|
||||
stAttr->stRcAttr.stH264Vbr.u32SrcFrameRateNum = fps;
|
||||
stAttr->stRcAttr.stH264Vbr.u32SrcFrameRateDen = 1;
|
||||
stAttr->stRcAttr.stH264Vbr.fr32DstFrameRateNum = fps;
|
||||
stAttr->stRcAttr.stH264Vbr.fr32DstFrameRateDen = 1;
|
||||
stAttr->stVencAttr.enType = RK_VIDEO_ID_AVC;
|
||||
stAttr->stVencAttr.u32Profile = H264E_PROFILE_HIGH;
|
||||
}
|
||||
@@ -157,11 +170,11 @@ static void populate_venc_attr(VENC_CHN_ATTR_S *stAttr, RK_U32 bitrate, RK_U32 m
|
||||
|
||||
pthread_t *venc_read_thread = NULL;
|
||||
volatile bool venc_running = false;
|
||||
static int32_t venc_start(int32_t bitrate, int32_t max_bitrate, int32_t width, int32_t height)
|
||||
static int32_t venc_start(int32_t bitrate, int32_t max_bitrate, int32_t width, int32_t height, int32_t fps)
|
||||
{
|
||||
int32_t ret;
|
||||
VENC_CHN_ATTR_S stAttr;
|
||||
populate_venc_attr(&stAttr, bitrate, max_bitrate, width, height);
|
||||
populate_venc_attr(&stAttr, bitrate, max_bitrate, width, height, fps);
|
||||
|
||||
ret = RK_MPI_VENC_CreateChn(VENC_CHANNEL, &stAttr);
|
||||
if (ret < 0)
|
||||
@@ -366,6 +379,9 @@ static void *venc_read_stream(void *arg)
|
||||
}
|
||||
|
||||
uint32_t detected_width, detected_height;
|
||||
// detected_fps is the rounded source vrefresh from the latest dv-timings query.
|
||||
// 60 is a safe default until the first SOURCE_CHANGE event fires.
|
||||
uint32_t detected_fps = 60;
|
||||
bool detected_signal = false, streaming_flag = false;
|
||||
|
||||
bool streaming_stopped = true;
|
||||
@@ -444,6 +460,7 @@ void *run_video_stream(void *arg)
|
||||
|
||||
uint32_t width = detected_width;
|
||||
uint32_t height = detected_height;
|
||||
uint32_t fps = detected_fps;
|
||||
struct v4l2_format fmt;
|
||||
memset(&fmt, 0, sizeof(struct v4l2_format));
|
||||
fmt.type = type;
|
||||
@@ -553,7 +570,7 @@ void *run_video_stream(void *arg)
|
||||
|
||||
// Set VENC parameters
|
||||
int32_t bitrate = calculate_bitrate(quality_factor, width, height);
|
||||
RK_S32 ret = venc_start(bitrate, bitrate * 3 / 2, width, height);
|
||||
RK_S32 ret = venc_start(bitrate, bitrate * 3 / 2, width, height, fps);
|
||||
if (ret != RK_SUCCESS)
|
||||
{
|
||||
log_error("Set VENC parameters failed with %#x", ret);
|
||||
@@ -861,10 +878,19 @@ void *run_detect_format(void *arg)
|
||||
dv_timings.bt.hbackporch));
|
||||
log_info("Frames per second: %.2f fps", frames_per_second);
|
||||
|
||||
bool should_restart = dv_timings.bt.width != detected_width || dv_timings.bt.height != detected_height || !detected_signal;
|
||||
// Round to nearest integer for the encoder rate control. CVT-RB
|
||||
// sources land a touch under (e.g. 119.91); rounding keeps the
|
||||
// encoder sized for the nominal rate.
|
||||
uint32_t fps_int = (uint32_t)(frames_per_second + 0.5);
|
||||
if (fps_int < 1) fps_int = 1;
|
||||
// Tolerate ±1 fps wobble between SOURCE_CHANGE events without
|
||||
// tearing down the pipeline.
|
||||
bool fps_changed = fps_int > detected_fps + 1 || fps_int + 1 < detected_fps;
|
||||
bool should_restart = dv_timings.bt.width != detected_width || dv_timings.bt.height != detected_height || fps_changed || !detected_signal;
|
||||
|
||||
detected_width = dv_timings.bt.width;
|
||||
detected_height = dv_timings.bt.height;
|
||||
detected_fps = fps_int;
|
||||
detected_signal = true;
|
||||
video_report_format(true, NULL, detected_width, detected_height, frames_per_second);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user