[Spice-devel] [PATCH spice-server 06/28] mjpeg_encoder: modify stream bit rate based on server side pipe congestion
Yonit Halperin
yhalperi at redhat.com
Tue Feb 26 10:03:52 PST 2013
Downgrading stream bit rate when the input frame rate in the server
exceeds the output frame rate, and frames are being dropped from the
output pipe.
---
server/mjpeg_encoder.c | 103 ++++++++++++++++++++++++++++++++++++++-----------
server/mjpeg_encoder.h | 10 +++++
2 files changed, 91 insertions(+), 22 deletions(-)
diff --git a/server/mjpeg_encoder.c b/server/mjpeg_encoder.c
index 6e80929..d50a5d1 100644
--- a/server/mjpeg_encoder.c
+++ b/server/mjpeg_encoder.c
@@ -40,6 +40,9 @@ static const int mjpeg_quality_samples[MJPEG_QUALITY_SAMPLE_NUM] = {20, 30, 40,
#define MJPEG_BIT_RATE_EVAL_MIN_NUM_FRAMES 3
#define MJPEG_LOW_FPS_RATE_TH 3
+#define MJPEG_SERVER_STATUS_EVAL_FPS_INTERVAL 1
+#define MJPEG_SERVER_STATUS_DOWNGRADE_DROP_FACTOR_TH 0.1
+
/*
* acting on positive client reports only if enough frame mm time
* has passed since the last bit rate change and the report.
@@ -80,6 +83,11 @@ typedef struct MJpegEncoderClientState {
uint32_t max_audio_latency;
} MJpegEncoderClientState;
+typedef struct MJpegEncoderServerState {
+ uint32_t num_frames_encoded;
+ uint32_t num_frames_dropped;
+} MJpegEncoderServerState;
+
typedef struct MJpegEncoderBitRateInfo {
uint64_t change_start_time;
uint64_t last_frame_time;
@@ -106,6 +114,7 @@ typedef struct MJpegEncoderRateControl {
MJpegEncoderQualityEval quality_eval_data;
MJpegEncoderBitRateInfo bit_rate_info;
MJpegEncoderClientState client_state;
+ MJpegEncoderServerState server_state;
uint64_t byte_rate;
int quality_id;
@@ -141,6 +150,7 @@ static inline void mjpeg_encoder_reset_quality(MJpegEncoder *encoder,
uint32_t fps,
uint64_t frame_enc_size);
static uint32_t get_max_fps(uint64_t frame_size, uint64_t bytes_per_sec, uint32_t latency_ms);
+static void mjpeg_encoder_process_server_drops(MJpegEncoder *encoder);
MJpegEncoder *mjpeg_encoder_new(int bit_rate_control, uint64_t starting_bit_rate,
MJpegEncoderRateControlCbs *cbs, void *opaque)
@@ -343,6 +353,10 @@ static inline void mjpeg_encoder_reset_quality(MJpegEncoder *encoder,
rate_control->last_enc_size = 0;
}
+
+ if (rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_RATE_CHANGE) {
+ memset(&rate_control->server_state, 0, sizeof(MJpegEncoderServerState));
+ }
rate_control->quality_id = quality_id;
memset(&rate_control->quality_eval_data, 0, sizeof(MJpegEncoderQualityEval));
rate_control->quality_eval_data.max_quality_id = MJPEG_QUALITY_SAMPLE_NUM - 1;
@@ -523,7 +537,7 @@ static void mjpeg_encoder_adjust_params_to_bit_rate(MJpegEncoder *encoder)
{
MJpegEncoderRateControl *rate_control;
MJpegEncoderQualityEval *quality_eval;
- uint64_t new_avg_enc_size;
+ uint64_t new_avg_enc_size = 0;
uint32_t new_fps;
uint32_t latency = 0;
uint32_t src_fps;
@@ -550,7 +564,7 @@ static void mjpeg_encoder_adjust_params_to_bit_rate(MJpegEncoder *encoder)
if (rate_control->num_recent_enc_frames < MJPEG_AVERAGE_SIZE_WINDOW &&
rate_control->num_recent_enc_frames < rate_control->fps) {
- return;
+ goto end;
}
latency = mjpeg_encoder_get_latency(encoder);
@@ -591,10 +605,12 @@ static void mjpeg_encoder_adjust_params_to_bit_rate(MJpegEncoder *encoder)
rate_control->quality_id,
rate_control->fps);
}
-
+end:
if (rate_control->during_quality_eval) {
quality_eval->encoded_size_by_quality[rate_control->quality_id] = new_avg_enc_size;
mjpeg_encoder_eval_quality(encoder);
+ } else {
+ mjpeg_encoder_process_server_drops(encoder);
}
}
@@ -603,10 +619,31 @@ int mjpeg_encoder_start_frame(MJpegEncoder *encoder, SpiceBitmapFmt format,
uint8_t **dest, size_t *dest_len,
uint32_t frame_mm_time)
{
- MJpegEncoderBitRateInfo *bit_rate_info;
uint32_t quality;
- mjpeg_encoder_adjust_params_to_bit_rate(encoder);
+ if (encoder->rate_control_is_active) {
+ MJpegEncoderRateControl *rate_control = &encoder->rate_control;
+ struct timespec time;
+ uint64_t now;
+
+ clock_gettime(CLOCK_MONOTONIC, &time);
+ now = ((uint64_t) time.tv_sec) * 1000000000 + time.tv_nsec;
+
+ mjpeg_encoder_adjust_params_to_bit_rate(encoder);
+
+ if (!rate_control->during_quality_eval ||
+ rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE) {
+ MJpegEncoderBitRateInfo *bit_rate_info;
+
+ bit_rate_info = &encoder->rate_control.bit_rate_info;
+
+ if (!bit_rate_info->change_start_time) {
+ bit_rate_info->change_start_time = now;
+ bit_rate_info->change_start_mm_time = frame_mm_time;
+ }
+ bit_rate_info->last_frame_time = now;
+ }
+ }
encoder->cinfo.in_color_space = JCS_RGB;
encoder->cinfo.input_components = 3;
@@ -654,23 +691,6 @@ int mjpeg_encoder_start_frame(MJpegEncoder *encoder, SpiceBitmapFmt format,
spice_jpeg_mem_dest(&encoder->cinfo, dest, dest_len);
- if (!encoder->rate_control.during_quality_eval ||
- encoder->rate_control.quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE) {
- struct timespec time;
- uint64_t now;
-
- clock_gettime(CLOCK_MONOTONIC, &time);
- now = ((uint64_t) time.tv_sec) * 1000000000 + time.tv_nsec;
-
- bit_rate_info = &encoder->rate_control.bit_rate_info;
-
- if (!bit_rate_info->change_start_time) {
- bit_rate_info->change_start_time = now;
- bit_rate_info->change_start_mm_time = frame_mm_time;
- }
- bit_rate_info->last_frame_time = now;
- }
-
encoder->cinfo.image_width = width;
encoder->cinfo.image_height = height;
jpeg_set_defaults(&encoder->cinfo);
@@ -718,6 +738,7 @@ size_t mjpeg_encoder_end_frame(MJpegEncoder *encoder)
encoder->first_frame = FALSE;
rate_control->last_enc_size = dest->pub.next_output_byte - dest->buffer;
+ rate_control->server_state.num_frames_encoded++;
if (!rate_control->during_quality_eval ||
rate_control->quality_eval_data.reason == MJPEG_QUALITY_EVAL_REASON_SIZE_CHANGE) {
@@ -1059,3 +1080,41 @@ void mjpeg_encoder_client_stream_report(MJpegEncoder *encoder,
}
}
}
+
+void mjpeg_encoder_notify_server_frame_drop(MJpegEncoder *encoder)
+{
+ encoder->rate_control.server_state.num_frames_dropped++;
+ mjpeg_encoder_process_server_drops(encoder);
+}
+
+/*
+ * decrease the bit rate if the drop rate on the sever side exceeds a pre defined
+ * threshold.
+ */
+static void mjpeg_encoder_process_server_drops(MJpegEncoder *encoder)
+{
+ MJpegEncoderServerState *server_state = &encoder->rate_control.server_state;
+ uint32_t num_frames_total;
+ double drop_factor;
+ uint32_t fps;
+
+ fps = MIN(encoder->rate_control.fps, encoder->cbs.get_source_fps(encoder->cbs_opaque));
+ if (server_state->num_frames_encoded < fps * MJPEG_SERVER_STATUS_EVAL_FPS_INTERVAL) {
+ return;
+ }
+
+ num_frames_total = server_state->num_frames_dropped + server_state->num_frames_encoded;
+ drop_factor = ((double)server_state->num_frames_dropped) / num_frames_total;
+
+ spice_debug("#drops %u total %u fps %u src-fps %u",
+ server_state->num_frames_dropped,
+ num_frames_total,
+ encoder->rate_control.fps,
+ encoder->cbs.get_source_fps(encoder->cbs_opaque));
+
+ if (drop_factor > MJPEG_SERVER_STATUS_DOWNGRADE_DROP_FACTOR_TH) {
+ mjpeg_encoder_decrease_bit_rate(encoder);
+ }
+ server_state->num_frames_encoded = 0;
+ server_state->num_frames_dropped = 0;
+}
diff --git a/server/mjpeg_encoder.h b/server/mjpeg_encoder.h
index cc49edf..bc7f01c 100644
--- a/server/mjpeg_encoder.h
+++ b/server/mjpeg_encoder.h
@@ -84,4 +84,14 @@ void mjpeg_encoder_client_stream_report(MJpegEncoder *encoder,
uint32_t end_frame_mm_time,
int32_t end_frame_delay,
uint32_t audio_delay);
+
+/*
+ * Notify the encoder each time a frame is dropped due to pipe
+ * congestion.
+ * We can deduce the client state by the frame dropping rate in the server.
+ * Monitoring the frame drops can help in fine tuning the playback parameters
+ * when the client reports are delayed.
+ */
+void mjpeg_encoder_notify_server_frame_drop(MJpegEncoder *encoder);
+
#endif
--
1.8.1
More information about the Spice-devel
mailing list