From 8688fc3742d0608191db0943f5cf6b7c1ee40617 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Tue, 30 Sep 2025 17:05:15 +0900 Subject: [PATCH] better bitrate control --- video_encoder/encoder_tav.c | 73 ++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 26 deletions(-) diff --git a/video_encoder/encoder_tav.c b/video_encoder/encoder_tav.c index 31e6744..59984c2 100644 --- a/video_encoder/encoder_tav.c +++ b/video_encoder/encoder_tav.c @@ -252,9 +252,10 @@ typedef struct tav_encoder_s { float pid_integral; // PID integral term float pid_prev_error; // PID previous error for derivative float pid_filtered_derivative; // Low-pass filtered derivative for smoothing - int adjusted_quantiser_y; // PID-adjusted qY value for current frame + float adjusted_quantiser_y_float; // Float precision qY for smooth control size_t prev_frame_size; // Previous frame compressed size for scene change detection int scene_change_cooldown; // Frames to wait after scene change before responding + float dither_accumulator; // Accumulated dithering error for error diffusion // Flags // int progressive; // no interlaced mode for TAV @@ -365,14 +366,14 @@ static float get_video_rate_kbps(tav_encoder_t *enc) { static void adjust_quantiser_for_bitrate(tav_encoder_t *enc) { if (!enc->bitrate_mode) { // Not in bitrate mode, use base quantiser - enc->adjusted_quantiser_y = enc->quantiser_y; + enc->adjusted_quantiser_y_float = (float)enc->quantiser_y; return; } // Need at least a few frames to measure bitrate if (enc->video_rate_bin_size < (enc->video_rate_bin_capacity / 2)) { // Not enough data yet, use base quantiser - enc->adjusted_quantiser_y = enc->quantiser_y; + enc->adjusted_quantiser_y_float = (float)enc->quantiser_y; return; } @@ -434,17 +435,17 @@ static void adjust_quantiser_for_bitrate(tav_encoder_t *enc) { float scale_factor = 50.0f; // Base: ~50 kbps error = 1 quantiser step int max_change = MAX_QY_CHANGE; - if (enc->adjusted_quantiser_y < 5) { + if (enc->adjusted_quantiser_y_float < 5.0f) { // Extreme lossless (qY 0-4) - be very conservative but still responsive // At qY=0, QLUT[0]=1, which is essentially lossless and bitrate is huge // Use fixed scale factor to ensure controller can actually respond scale_factor = 150.0f; // ~150 kbps error = 1 step max_change = 1; - } else if (enc->adjusted_quantiser_y < 15) { + } else if (enc->adjusted_quantiser_y_float < 15.0f) { // Very near lossless (qY 5-14) - very conservative scale_factor = 300.0f; // ~300 kbps error = 1 step max_change = 1; - } else if (enc->adjusted_quantiser_y < 30) { + } else if (enc->adjusted_quantiser_y_float < 30.0f) { // Near lossless range (qY 15-29) - be conservative scale_factor = 120.0f; // ~120 kbps error = 1 step max_change = 2; @@ -453,21 +454,22 @@ static void adjust_quantiser_for_bitrate(tav_encoder_t *enc) { scale_factor = 30.0f; } - int adjustment = (int)(pid_output / scale_factor); + // Calculate float adjustment (no integer quantization yet) + float adjustment_float = pid_output / scale_factor; // Limit maximum change per frame to prevent wild swings (adaptive limit) - adjustment = CLAMP(adjustment, -max_change, max_change); + adjustment_float = FCLAMP(adjustment_float, -(float)max_change, (float)max_change); // Apply logarithmic scaling to adjustment based on current qY // At low qY (0-10), QLUT is exponential so we need much smaller steps // At high qY (40+), bitrate changes are small so we can take larger steps // This makes it "hard to reach towards 1, easy to reach towards large value" float log_scale = 1.0f; - int current_qy = enc->adjusted_quantiser_y; + float current_qy = enc->adjusted_quantiser_y_float; // Only apply log scaling when moving deeper into low qY region // If we're at low qY and want to move up (increase qY), use faster response - int wants_to_increase = (adjustment > 0); + int wants_to_increase = (adjustment_float > 0); if (current_qy < 10 && !wants_to_increase) { // Moving down into very near lossless - be very careful @@ -484,27 +486,44 @@ static void adjust_quantiser_for_bitrate(tav_encoder_t *enc) { } // else: qY >= 40, use full scale (1.0) - adjustment = (int)(adjustment * log_scale); + adjustment_float *= log_scale; - // Ensure at least ±1 step if PID wants to move strongly (prevent sticking) - if (adjustment == 0 && fabsf(pid_output / scale_factor) > 0.8f) { - adjustment = (pid_output > 0) ? 1 : -1; - } - - // Calculate adjusted quantiser (but don't modify base quantiser) - int new_quantiser_y = enc->adjusted_quantiser_y + adjustment; + // Update float quantiser value (no integer quantization, keeps full precision) + float new_quantiser_y_float = enc->adjusted_quantiser_y_float + adjustment_float; // Avoid extremely low qY values where QLUT is exponential and causes wild swings // For 5000 kbps target, qY < 3 is usually too low and causes oscillation - int min_qy = (target_bitrate >= 8000) ? 0 : (target_bitrate >= 4000) ? 3 : 5; - new_quantiser_y = CLAMP(new_quantiser_y, min_qy, 254); // Max index is 254 + float min_qy = (target_bitrate >= 8000) ? 0.0f : (target_bitrate >= 4000) ? 3.0f : 5.0f; + new_quantiser_y_float = FCLAMP(new_quantiser_y_float, min_qy, 254.0f); // Max index is 254 + + enc->adjusted_quantiser_y_float = new_quantiser_y_float; if (enc->verbose) { - printf("Bitrate control: %.1f kbps (target: %.1f kbps) -> qY %d->%d (adj: %d, err: %.1f%%)\n", - current_bitrate, target_bitrate, enc->adjusted_quantiser_y, new_quantiser_y, adjustment, error_percent * 100); + printf("Bitrate control: %.1f kbps (target: %.1f kbps) -> qY %.2f->%.2f (adj: %.3f, err: %.1f%%)\n", + current_bitrate, target_bitrate, current_qy, new_quantiser_y_float, adjustment_float, error_percent * 100); } +} - enc->adjusted_quantiser_y = new_quantiser_y; +// Convert float qY to integer with error diffusion dithering +// This prevents the controller from getting stuck at integer boundaries +static int quantiser_float_to_int_dithered(tav_encoder_t *enc) { + float qy_float = enc->adjusted_quantiser_y_float; + + // Add accumulated dithering error + float qy_with_error = qy_float + enc->dither_accumulator; + + // Round to nearest integer + int qy_int = (int)(qy_with_error + 0.5f); + + // Calculate quantization error and accumulate for next frame + // This is Floyd-Steinberg style error diffusion + float quantization_error = qy_with_error - (float)qy_int; + enc->dither_accumulator = quantization_error * 0.5f; // Diffuse 50% of error to next frame + + // Clamp to valid range + qy_int = CLAMP(qy_int, 0, 254); + + return qy_int; } // Swap ping-pong frame buffers (eliminates need for memcpy) @@ -733,7 +752,8 @@ static int initialise_encoder(tav_encoder_t *enc) { enc->video_rate_bin_size = 0; enc->pid_integral = 0.0f; enc->pid_prev_error = 0.0f; - enc->adjusted_quantiser_y = enc->quantiser_y; // Start with base quantiser + enc->adjusted_quantiser_y_float = (float)enc->quantiser_y; // Start with base quantiser + enc->dither_accumulator = 0.0f; if (!enc->video_rate_bin) { return -1; @@ -1574,7 +1594,7 @@ static size_t serialise_tile_data(tav_encoder_t *enc, int tile_x, int tile_y, buffer[offset++] = mode; // Use adjusted quantiser from bitrate control, or base quantiser if not in bitrate mode - int qY_override = enc->bitrate_mode ? enc->adjusted_quantiser_y : enc->quantiser_y; + int qY_override = enc->bitrate_mode ? quantiser_float_to_int_dithered(enc) : enc->quantiser_y; buffer[offset++] = (!enc->bitrate_mode) ? 0 : qY_override + 1; // qY override; must be stored with bias of 1 buffer[offset++] = 0; // qCo override, currently unused @@ -3596,8 +3616,9 @@ int main(int argc, char *argv[]) { (now.tv_usec - enc->start_time.tv_usec) / 1000000.0; double fps = frame_count / elapsed; + int display_qY = enc->bitrate_mode ? quantiser_float_to_int_dithered(enc) : enc->quantiser_y; printf("Encoded frame %d (%s, %.1f fps, qY=%d)\n", frame_count, - is_keyframe ? "I-frame" : "P-frame", fps, QLUT[enc->bitrate_mode ? enc->adjusted_quantiser_y : enc->quantiser_y]); + is_keyframe ? "I-frame" : "P-frame", fps, QLUT[display_qY]); } }