<div dir="ltr">looks good<br><div class="gmail_extra"><br><div class="gmail_quote">On Wed, May 13, 2015 at 10:27 PM, Francois Gouget <span dir="ltr"><<a href="mailto:fgouget@codeweavers.com" target="_blank">fgouget@codeweavers.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">This patch simply replaces the mjpeg_encoder_xxx() call points with a<br>
more object oriented design.<br>
---<br>
server/Makefile.am | 2 +-<br>
server/mjpeg_encoder.c | 134 +++++++++++++++++++++++++++++++++++++---<br>
server/mjpeg_encoder.h | 114 ----------------------------------<br>
server/red_worker.c | 156 ++++++++++++++---------------------------------<br>
server/video_encoder.h | 162 +++++++++++++++++++++++++++++++++++++++++++++++++<br>
5 files changed, 331 insertions(+), 237 deletions(-)<br>
delete mode 100644 server/mjpeg_encoder.h<br>
create mode 100644 server/video_encoder.h<br>
<br>
diff --git a/server/Makefile.am b/server/Makefile.am<br>
index 89a375c..36b6d45 100644<br>
--- a/server/Makefile.am<br>
+++ b/server/Makefile.am<br>
@@ -81,7 +81,6 @@ libspice_server_la_SOURCES = \<br>
main_channel.c \<br>
main_channel.h \<br>
mjpeg_encoder.c \<br>
- mjpeg_encoder.h \<br>
red_bitmap_utils.h \<br>
red_channel.c \<br>
red_channel.h \<br>
@@ -115,6 +114,7 @@ libspice_server_la_SOURCES = \<br>
spicevmc.c \<br>
spice_timer_queue.c \<br>
spice_timer_queue.h \<br>
+ video_encoder.h \<br>
zlib_encoder.c \<br>
zlib_encoder.h \<br>
spice_bitmap_utils.h \<br>
diff --git a/server/mjpeg_encoder.c b/server/mjpeg_encoder.c<br>
index 9a41ef3..d734520 100644<br>
--- a/server/mjpeg_encoder.c<br>
+++ b/server/mjpeg_encoder.c<br>
@@ -20,7 +20,11 @@<br>
#endif<br>
<br>
#include "red_common.h"<br>
-#include "mjpeg_encoder.h"<br>
+<br>
+typedef struct MJpegEncoder MJpegEncoder;<br>
+#define VIDEO_ENCODER_T MJpegEncoder<br>
+#include "video_encoder.h"<br>
+<br>
#include <jerror.h><br>
#include <jpeglib.h><br>
#include <inttypes.h><br>
@@ -154,6 +158,7 @@ typedef struct MJpegEncoderRateControl {<br>
} MJpegEncoderRateControl;<br>
<br>
struct MJpegEncoder {<br>
+ VideoEncoder base;<br>
uint8_t *row;<br>
uint32_t row_size;<br>
int first_frame;<br>
@@ -165,7 +170,7 @@ struct MJpegEncoder {<br>
void (*pixel_converter)(uint8_t *src, uint8_t *dest);<br>
<br>
MJpegEncoderRateControl rate_control;<br>
- MJpegEncoderRateControlCbs cbs;<br>
+ VideoEncoderRateControlCbs cbs;<br>
void *cbs_opaque;<br>
<br>
/* stats */<br>
@@ -174,6 +179,22 @@ struct MJpegEncoder {<br>
uint32_t num_frames;<br>
};<br>
<br>
+static void mjpeg_encoder_destroy(MJpegEncoder *encoder);<br>
+static int mjpeg_encoder_encode_frame(MJpegEncoder *encoder,<br>
+ const SpiceBitmap *bitmap, int width, int height,<br>
+ const SpiceRect *src, int top_down, uint32_t frame_mm_time,<br>
+ uint8_t **outbuf, size_t *outbuf_size, int *data_size);<br>
+static void mjpeg_encoder_client_stream_report(MJpegEncoder *encoder,<br>
+ uint32_t num_frames,<br>
+ uint32_t num_drops,<br>
+ uint32_t start_frame_mm_time,<br>
+ uint32_t end_frame_mm_time,<br>
+ int32_t end_frame_delay,<br>
+ uint32_t audio_delay);<br>
+static void mjpeg_encoder_notify_server_frame_drop(MJpegEncoder *encoder);<br>
+static uint64_t mjpeg_encoder_get_bit_rate(MJpegEncoder *encoder);<br>
+static void mjpeg_encoder_get_stats(MJpegEncoder *encoder, VideoEncoderStats *stats);<br>
+<br>
static inline void mjpeg_encoder_reset_quality(MJpegEncoder *encoder,<br>
int quality_id,<br>
uint32_t fps,<br>
@@ -189,8 +210,9 @@ static inline int rate_control_is_active(MJpegEncoder* encoder)<br>
return encoder->cbs.get_roundtrip_ms != NULL;<br>
}<br>
<br>
-MJpegEncoder *mjpeg_encoder_new(uint64_t starting_bit_rate,<br>
- MJpegEncoderRateControlCbs *cbs, void *opaque)<br>
+MJpegEncoder *create_mjpeg_encoder(uint64_t starting_bit_rate,<br>
+ VideoEncoderRateControlCbs *cbs,<br>
+ void *cbs_opaque)<br>
{<br>
MJpegEncoder *enc;<br>
<br>
@@ -198,6 +220,12 @@ MJpegEncoder *mjpeg_encoder_new(uint64_t starting_bit_rate,<br>
<br>
enc = spice_new0(MJpegEncoder, 1);<br>
<br>
+ enc->base.destroy = &mjpeg_encoder_destroy;<br>
+ enc->base.encode_frame = &mjpeg_encoder_encode_frame;<br>
+ enc->base.client_stream_report = &mjpeg_encoder_client_stream_report;<br>
+ enc->base.notify_server_frame_drop = &mjpeg_encoder_notify_server_frame_drop;<br>
+ enc->base.get_bit_rate = &mjpeg_encoder_get_bit_rate;<br>
+ enc->base.get_stats = &mjpeg_encoder_get_stats;<br>
enc->first_frame = TRUE;<br>
enc->rate_control.byte_rate = starting_bit_rate / 8;<br>
enc->starting_bit_rate = starting_bit_rate;<br>
@@ -207,7 +235,7 @@ MJpegEncoder *mjpeg_encoder_new(uint64_t starting_bit_rate,<br>
<br>
clock_gettime(CLOCK_MONOTONIC, &time);<br>
enc->cbs = *cbs;<br>
- enc->cbs_opaque = opaque;<br>
+ enc->cbs_opaque = cbs_opaque;<br>
mjpeg_encoder_reset_quality(enc, MJPEG_QUALITY_SAMPLE_NUM / 2, 5, 0);<br>
enc->rate_control.during_quality_eval = TRUE;<br>
enc->rate_control.quality_eval_data.type = MJPEG_QUALITY_EVAL_TYPE_SET;<br>
@@ -754,7 +782,7 @@ int mjpeg_encoder_start_frame(MJpegEncoder *encoder, SpiceBitmapFmt format,<br>
interval = (now - rate_control->bit_rate_info.last_frame_time);<br>
<br>
if (interval < (1000*1000*1000) / rate_control->adjusted_fps) {<br>
- return MJPEG_ENCODER_FRAME_DROP;<br>
+ return VIDEO_ENCODER_FRAME_DROP;<br>
}<br>
<br>
mjpeg_encoder_adjust_params_to_bit_rate(encoder);<br>
@@ -802,14 +830,14 @@ int mjpeg_encoder_start_frame(MJpegEncoder *encoder, SpiceBitmapFmt format,<br>
break;<br>
default:<br>
spice_debug("unsupported format %d", format);<br>
- return MJPEG_ENCODER_FRAME_UNSUPPORTED;<br>
+ return VIDEO_ENCODER_FRAME_UNSUPPORTED;<br>
}<br>
<br>
if (encoder->pixel_converter != NULL) {<br>
unsigned int stride = width * 3;<br>
/* check for integer overflow */<br>
if (stride < width) {<br>
- return MJPEG_ENCODER_FRAME_UNSUPPORTED;<br>
+ return VIDEO_ENCODER_FRAME_UNSUPPORTED;<br>
}<br>
if (encoder->row_size < stride) {<br>
encoder->row = spice_realloc(encoder->row, stride);<br>
@@ -829,7 +857,7 @@ int mjpeg_encoder_start_frame(MJpegEncoder *encoder, SpiceBitmapFmt format,<br>
<br>
encoder->num_frames++;<br>
encoder->avg_quality += quality;<br>
- return MJPEG_ENCODER_FRAME_ENCODE_START;<br>
+ return VIDEO_ENCODER_FRAME_ENCODE_DONE;<br>
}<br>
<br>
int mjpeg_encoder_encode_scanline(MJpegEncoder *encoder, uint8_t *src_pixels,<br>
@@ -888,6 +916,92 @@ size_t mjpeg_encoder_end_frame(MJpegEncoder *encoder)<br>
return encoder->rate_control.last_enc_size;<br>
}<br>
<br>
+static inline uint8_t *get_image_line(SpiceChunks *chunks, size_t *offset,<br>
+ int *chunk_nr, int stride)<br>
+{<br>
+ uint8_t *ret;<br>
+ SpiceChunk *chunk;<br>
+<br>
+ chunk = &chunks->chunk[*chunk_nr];<br>
+<br>
+ if (*offset == chunk->len) {<br>
+ if (*chunk_nr == chunks->num_chunks - 1) {<br>
+ return NULL; /* Last chunk */<br>
+ }<br>
+ *offset = 0;<br>
+ (*chunk_nr)++;<br>
+ chunk = &chunks->chunk[*chunk_nr];<br>
+ }<br>
+<br>
+ if (chunk->len - *offset < stride) {<br>
+ spice_warning("bad chunk alignment");<br>
+ return NULL;<br>
+ }<br>
+ ret = chunk->data + *offset;<br>
+ *offset += stride;<br>
+ return ret;<br>
+}<br>
+<br>
+<br>
+<br>
+static int encode_mjpeg_frame(MJpegEncoder *encoder, const SpiceRect *src,<br>
+ const SpiceBitmap *image, int top_down)<br>
+{<br>
+ SpiceChunks *chunks;<br>
+ uint32_t image_stride;<br>
+ size_t offset;<br>
+ int i, chunk;<br>
+<br>
+ chunks = image->data;<br>
+ offset = 0;<br>
+ chunk = 0;<br>
+ image_stride = image->stride;<br>
+<br>
+ const int skip_lines = top_down ? src->top : image->y - (src->bottom - 0);<br>
+ for (i = 0; i < skip_lines; i++) {<br>
+ get_image_line(chunks, &offset, &chunk, image_stride);<br>
+ }<br>
+<br>
+ const unsigned int stream_height = src->bottom - src->top;<br>
+ const unsigned int stream_width = src->right - src->left;<br>
+<br>
+ for (i = 0; i < stream_height; i++) {<br>
+ uint8_t *src_line = (uint8_t *)get_image_line(chunks, &offset, &chunk, image_stride);<br>
+<br>
+ if (!src_line) {<br>
+ return FALSE;<br>
+ }<br>
+<br>
+ src_line += src->left * mjpeg_encoder_get_bytes_per_pixel(encoder);<br>
+ if (mjpeg_encoder_encode_scanline(encoder, src_line, stream_width) == 0)<br>
+ return FALSE;<br>
+ }<br>
+<br>
+ return TRUE;<br>
+}<br>
+<br>
+int mjpeg_encoder_encode_frame(MJpegEncoder *encoder,<br>
+ const SpiceBitmap *bitmap, int width, int height,<br>
+ const SpiceRect *src, int top_down, uint32_t frame_mm_time,<br>
+ uint8_t **outbuf, size_t *outbuf_size, int *data_size)<br>
+{<br>
+ int ret;<br>
+<br>
+ ret = mjpeg_encoder_start_frame(encoder, bitmap->format,<br>
+ width, height, outbuf, outbuf_size,<br>
+ frame_mm_time);<br>
+ if (ret != VIDEO_ENCODER_FRAME_ENCODE_DONE)<br>
+ return ret;<br>
+<br>
+ if (!encode_mjpeg_frame(encoder, src, bitmap, top_down))<br>
+ return VIDEO_ENCODER_FRAME_UNSUPPORTED;<br>
+<br>
+ *data_size = mjpeg_encoder_end_frame(encoder);<br>
+<br>
+ return VIDEO_ENCODER_FRAME_ENCODE_DONE;<br>
+}<br>
+<br>
+<br>
static void mjpeg_encoder_quality_eval_stop(MJpegEncoder *encoder)<br>
{<br>
MJpegEncoderRateControl *rate_control = &encoder->rate_control;<br>
@@ -1267,7 +1381,7 @@ uint64_t mjpeg_encoder_get_bit_rate(MJpegEncoder *encoder)<br>
return encoder->rate_control.byte_rate * 8;<br>
}<br>
<br>
-void mjpeg_encoder_get_stats(MJpegEncoder *encoder, MJpegEncoderStats *stats)<br>
+void mjpeg_encoder_get_stats(MJpegEncoder *encoder, VideoEncoderStats *stats)<br>
{<br>
spice_assert(encoder != NULL && stats != NULL);<br>
stats->starting_bit_rate = encoder->starting_bit_rate;<br>
diff --git a/server/mjpeg_encoder.h b/server/mjpeg_encoder.h<br>
deleted file mode 100644<br>
index d584b92..0000000<br>
--- a/server/mjpeg_encoder.h<br>
+++ /dev/null<br>
@@ -1,114 +0,0 @@<br>
-/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */<br>
-/*<br>
- Copyright (C) 2009 Red Hat, Inc.<br>
-<br>
- This library is free software; you can redistribute it and/or<br>
- modify it under the terms of the GNU Lesser General Public<br>
- License as published by the Free Software Foundation; either<br>
- version 2.1 of the License, or (at your option) any later version.<br>
-<br>
- This library is distributed in the hope that it will be useful,<br>
- but WITHOUT ANY WARRANTY; without even the implied warranty of<br>
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU<br>
- Lesser General Public License for more details.<br>
-<br>
- You should have received a copy of the GNU Lesser General Public<br>
- License along with this library; if not, see <<a href="http://www.gnu.org/licenses/" target="_blank">http://www.gnu.org/licenses/</a>>.<br>
-*/<br>
-<br>
-#ifndef _H_MJPEG_ENCODER<br>
-#define _H_MJPEG_ENCODER<br>
-<br>
-#include "red_common.h"<br>
-<br>
-enum {<br>
- MJPEG_ENCODER_FRAME_UNSUPPORTED = -1,<br>
- MJPEG_ENCODER_FRAME_DROP,<br>
- MJPEG_ENCODER_FRAME_ENCODE_START,<br>
-};<br>
-<br>
-typedef struct MJpegEncoder MJpegEncoder;<br>
-<br>
-/*<br>
- * Callbacks required for controling and adjusting<br>
- * the stream bit rate:<br>
- * get_roundtrip_ms: roundtrip time in milliseconds<br>
- * get_source_fps: the input frame rate (#frames per second), i.e.,<br>
- * the rate of frames arriving from the guest to spice-server,<br>
- * before any drops.<br>
- */<br>
-typedef struct MJpegEncoderRateControlCbs {<br>
- uint32_t (*get_roundtrip_ms)(void *opaque);<br>
- uint32_t (*get_source_fps)(void *opaque);<br>
- void (*update_client_playback_delay)(void *opaque, uint32_t delay_ms);<br>
-} MJpegEncoderRateControlCbs;<br>
-<br>
-typedef struct MJpegEncoderStats {<br>
- uint64_t starting_bit_rate;<br>
- uint64_t cur_bit_rate;<br>
- double avg_quality;<br>
-} MJpegEncoderStats;<br>
-<br>
-MJpegEncoder *mjpeg_encoder_new(uint64_t starting_bit_rate,<br>
- MJpegEncoderRateControlCbs *cbs, void *opaque);<br>
-void mjpeg_encoder_destroy(MJpegEncoder *encoder);<br>
-<br>
-uint8_t mjpeg_encoder_get_bytes_per_pixel(MJpegEncoder *encoder);<br>
-<br>
-/*<br>
- * dest must be either NULL or allocated by malloc, since it might be freed<br>
- * during the encoding, if its size is too small.<br>
- *<br>
- * return:<br>
- * MJPEG_ENCODER_FRAME_UNSUPPORTED : frame cannot be encoded<br>
- * MJPEG_ENCODER_FRAME_DROP : frame should be dropped. This value can only be returned<br>
- * if mjpeg rate control is active.<br>
- * MJPEG_ENCODER_FRAME_ENCODE_START: frame encoding started. Continue with<br>
- * mjpeg_encoder_encode_scanline.<br>
- */<br>
-int mjpeg_encoder_start_frame(MJpegEncoder *encoder, SpiceBitmapFmt format,<br>
- int width, int height,<br>
- uint8_t **dest, size_t *dest_len,<br>
- uint32_t frame_mm_time);<br>
-int mjpeg_encoder_encode_scanline(MJpegEncoder *encoder, uint8_t *src_pixels,<br>
- size_t image_width);<br>
-size_t mjpeg_encoder_end_frame(MJpegEncoder *encoder);<br>
-<br>
-/*<br>
- * bit rate control<br>
- */<br>
-<br>
-/*<br>
- * Data that should be periodically obtained from the client. The report contains:<br>
- * num_frames : the number of frames that reached the client during the time<br>
- * the report is referring to.<br>
- * num_drops : the part of the above frames that was dropped by the client due to<br>
- * late arrival time.<br>
- * start_frame_mm_time: the mm_time of the first frame included in the report<br>
- * end_frame_mm_time : the mm_time of the last_frame included in the report<br>
- * end_frame_delay : (end_frame_mm_time - client_mm_time)<br>
- * audio delay : the latency of the audio playback.<br>
- * If there is no audio playback, set it to MAX_UINT.<br>
- *<br>
- */<br>
-void mjpeg_encoder_client_stream_report(MJpegEncoder *encoder,<br>
- uint32_t num_frames,<br>
- uint32_t num_drops,<br>
- uint32_t start_frame_mm_time,<br>
- uint32_t end_frame_mm_time,<br>
- int32_t end_frame_delay,<br>
- uint32_t audio_delay);<br>
-<br>
-/*<br>
- * Notify the encoder each time a frame is dropped due to pipe<br>
- * congestion.<br>
- * We can deduce the client state by the frame dropping rate in the server.<br>
- * Monitoring the frame drops can help in fine tuning the playback parameters<br>
- * when the client reports are delayed.<br>
- */<br>
-void mjpeg_encoder_notify_server_frame_drop(MJpegEncoder *encoder);<br>
-<br>
-uint64_t mjpeg_encoder_get_bit_rate(MJpegEncoder *encoder);<br>
-void mjpeg_encoder_get_stats(MJpegEncoder *encoder, MJpegEncoderStats *stats);<br>
-<br>
-#endif<br>
diff --git a/server/red_worker.c b/server/red_worker.c<br>
index e0ff8e9..c6b39cb 100644<br>
--- a/server/red_worker.c<br>
+++ b/server/red_worker.c<br>
@@ -70,7 +70,7 @@<br>
#include "glz_encoder.h"<br>
#include "stat.h"<br>
#include "reds.h"<br>
-#include "mjpeg_encoder.h"<br>
+#include "video_encoder.h"<br>
#include "red_memslots.h"<br>
#include "red_parse_qxl.h"<br>
#include "jpeg_encoder.h"<br>
@@ -484,7 +484,7 @@ typedef struct StreamAgent {<br>
PipeItem destroy_item;<br>
Stream *stream;<br>
uint64_t last_send_time;<br>
- MJpegEncoder *mjpeg_encoder;<br>
+ VideoEncoder *video_encoder;<br>
DisplayChannelClient *dcc;<br>
<br>
int frames;<br>
@@ -723,7 +723,7 @@ struct DisplayChannelClient {<br>
QRegion surface_client_lossy_region[NUM_SURFACES];<br>
<br>
StreamAgent stream_agents[NUM_STREAMS];<br>
- int use_mjpeg_encoder_rate_control;<br>
+ int use_video_encoder_rate_control;<br>
uint32_t streams_max_latency;<br>
uint64_t streams_max_bit_rate;<br>
};<br>
@@ -2642,10 +2642,10 @@ static void red_print_stream_stats(DisplayChannelClient *dcc, StreamAgent *agent<br>
#ifdef STREAM_STATS<br>
StreamStats *stats = &agent->stats;<br>
double passed_mm_time = (stats->end - stats->start) / 1000.0;<br>
- MJpegEncoderStats encoder_stats = {0};<br>
+ VideoEncoderStats encoder_stats = {0};<br>
<br>
- if (agent->mjpeg_encoder) {<br>
- mjpeg_encoder_get_stats(agent->mjpeg_encoder, &encoder_stats);<br>
+ if (agent->video_encoder) {<br>
+ agent->video_encoder->get_stats(agent->video_encoder, &encoder_stats);<br>
}<br>
<br>
spice_debug("stream=%"PRIdPTR" dim=(%dx%d) #in-frames=%"PRIu64" #in-avg-fps=%.2f #out-frames=%"PRIu64" "<br>
@@ -2688,8 +2688,8 @@ static void red_stop_stream(RedWorker *worker, Stream *stream)<br>
region_clear(&stream_agent->vis_region);<br>
region_clear(&stream_agent->clip);<br>
spice_assert(!pipe_item_is_linked(&stream_agent->destroy_item));<br>
- if (stream_agent->mjpeg_encoder && dcc->use_mjpeg_encoder_rate_control) {<br>
- uint64_t stream_bit_rate = mjpeg_encoder_get_bit_rate(stream_agent->mjpeg_encoder);<br>
+ if (stream_agent->video_encoder && dcc->use_video_encoder_rate_control) {<br>
+ uint64_t stream_bit_rate = stream_agent->video_encoder->get_bit_rate(stream_agent->video_encoder);<br>
<br>
if (stream_bit_rate > dcc->streams_max_bit_rate) {<br>
spice_debug("old max-bit-rate=%.2f new=%.2f",<br>
@@ -2996,7 +2996,7 @@ static uint64_t red_stream_get_initial_bit_rate(DisplayChannelClient *dcc,<br>
stream->width * stream->height) / dcc->common.worker->streams_size_total;<br>
}<br>
<br>
-static uint32_t red_stream_mjpeg_encoder_get_roundtrip(void *opaque)<br>
+static uint32_t red_stream_video_encoder_get_roundtrip(void *opaque)<br>
{<br>
StreamAgent *agent = opaque;<br>
int roundtrip;<br>
@@ -3017,7 +3017,7 @@ static uint32_t red_stream_mjpeg_encoder_get_roundtrip(void *opaque)<br>
return roundtrip;<br>
}<br>
<br>
-static uint32_t red_stream_mjpeg_encoder_get_source_fps(void *opaque)<br>
+static uint32_t red_stream_video_encoder_get_source_fps(void *opaque)<br>
{<br>
StreamAgent *agent = opaque;<br>
<br>
@@ -3040,7 +3040,7 @@ static void red_display_update_streams_max_latency(DisplayChannelClient *dcc, St<br>
}<br>
for (i = 0; i < NUM_STREAMS; i++) {<br>
StreamAgent *other_agent = &dcc->stream_agents[i];<br>
- if (other_agent == remove_agent || !other_agent->mjpeg_encoder) {<br>
+ if (other_agent == remove_agent || !other_agent->video_encoder) {<br>
continue;<br>
}<br>
if (other_agent->client_required_latency > new_max_latency) {<br>
@@ -3053,9 +3053,9 @@ static void red_display_update_streams_max_latency(DisplayChannelClient *dcc, St<br>
static void red_display_stream_agent_stop(DisplayChannelClient *dcc, StreamAgent *agent)<br>
{<br>
red_display_update_streams_max_latency(dcc, agent);<br>
- if (agent->mjpeg_encoder) {<br>
- mjpeg_encoder_destroy(agent->mjpeg_encoder);<br>
- agent->mjpeg_encoder = NULL;<br>
+ if (agent->video_encoder) {<br>
+ agent->video_encoder->destroy(agent->video_encoder);<br>
+ agent->video_encoder = NULL;<br>
}<br>
}<br>
<br>
@@ -3091,18 +3091,18 @@ static void red_display_create_stream(DisplayChannelClient *dcc, Stream *stream)<br>
agent->fps = MAX_FPS;<br>
agent->dcc = dcc;<br>
<br>
- if (dcc->use_mjpeg_encoder_rate_control) {<br>
- MJpegEncoderRateControlCbs mjpeg_cbs;<br>
+ if (dcc->use_video_encoder_rate_control) {<br>
+ VideoEncoderRateControlCbs video_cbs;<br>
uint64_t initial_bit_rate;<br>
<br>
- mjpeg_cbs.get_roundtrip_ms = red_stream_mjpeg_encoder_get_roundtrip;<br>
- mjpeg_cbs.get_source_fps = red_stream_mjpeg_encoder_get_source_fps;<br>
- mjpeg_cbs.update_client_playback_delay = red_stream_update_client_playback_latency;<br>
+ video_cbs.get_roundtrip_ms = red_stream_video_encoder_get_roundtrip;<br>
+ video_cbs.get_source_fps = red_stream_video_encoder_get_source_fps;<br>
+ video_cbs.update_client_playback_delay = red_stream_update_client_playback_latency;<br>
<br>
initial_bit_rate = red_stream_get_initial_bit_rate(dcc, stream);<br>
- agent->mjpeg_encoder = mjpeg_encoder_new(initial_bit_rate, &mjpeg_cbs, agent);<br>
+ agent->video_encoder = create_mjpeg_encoder(initial_bit_rate, &video_cbs, agent);<br>
} else {<br>
- agent->mjpeg_encoder = mjpeg_encoder_new(0, NULL, NULL);<br>
+ agent->video_encoder = create_mjpeg_encoder(0, NULL, NULL);<br>
}<br>
red_channel_client_pipe_add(&dcc->common.base, &agent->create_item);<br>
<br>
@@ -3209,7 +3209,7 @@ static void red_display_client_init_streams(DisplayChannelClient *dcc)<br>
red_channel_pipe_item_init(channel, &agent->create_item, PIPE_ITEM_TYPE_STREAM_CREATE);<br>
red_channel_pipe_item_init(channel, &agent->destroy_item, PIPE_ITEM_TYPE_STREAM_DESTROY);<br>
}<br>
- dcc->use_mjpeg_encoder_rate_control =<br>
+ dcc->use_video_encoder_rate_control =<br>
red_channel_client_test_remote_cap(&dcc->common.base, SPICE_DISPLAY_CAP_STREAM_REPORT);<br>
}<br>
<br>
@@ -3221,9 +3221,9 @@ static void red_display_destroy_streams_agents(DisplayChannelClient *dcc)<br>
StreamAgent *agent = &dcc->stream_agents[i];<br>
region_destroy(&agent->vis_region);<br>
region_destroy(&agent->clip);<br>
- if (agent->mjpeg_encoder) {<br>
- mjpeg_encoder_destroy(agent->mjpeg_encoder);<br>
- agent->mjpeg_encoder = NULL;<br>
+ if (agent->video_encoder) {<br>
+ agent->video_encoder->destroy(agent->video_encoder);<br>
+ agent->video_encoder = NULL;<br>
}<br>
}<br>
}<br>
@@ -3350,7 +3350,7 @@ static inline void pre_stream_item_swap(RedWorker *worker, Stream *stream, Drawa<br>
dcc = dpi->dcc;<br>
agent = &dcc->stream_agents[index];<br>
<br>
- if (!dcc->use_mjpeg_encoder_rate_control &&<br>
+ if (!dcc->use_video_encoder_rate_control &&<br>
!dcc->common.is_low_bandwidth) {<br>
continue;<br>
}<br>
@@ -3359,8 +3359,8 @@ static inline void pre_stream_item_swap(RedWorker *worker, Stream *stream, Drawa<br>
#ifdef STREAM_STATS<br>
agent->stats.num_drops_pipe++;<br>
#endif<br>
- if (dcc->use_mjpeg_encoder_rate_control) {<br>
- mjpeg_encoder_notify_server_frame_drop(agent->mjpeg_encoder);<br>
+ if (dcc->use_video_encoder_rate_control) {<br>
+ agent->video_encoder->notify_server_frame_drop(agent->video_encoder);<br>
} else {<br>
++agent->drops;<br>
}<br>
@@ -3373,7 +3373,7 @@ static inline void pre_stream_item_swap(RedWorker *worker, Stream *stream, Drawa<br>
<br>
agent = &dcc->stream_agents[index];<br>
<br>
- if (dcc->use_mjpeg_encoder_rate_control) {<br>
+ if (dcc->use_video_encoder_rate_control) {<br>
continue;<br>
}<br>
if (agent->frames / agent->fps < FPS_TEST_INTERVAL) {<br>
@@ -8481,70 +8481,6 @@ static inline void display_begin_send_message(RedChannelClient *rcc)<br>
red_channel_client_begin_send_message(rcc);<br>
}<br>
<br>
-static inline uint8_t *red_get_image_line(SpiceChunks *chunks, size_t *offset,<br>
- int *chunk_nr, int stride)<br>
-{<br>
- uint8_t *ret;<br>
- SpiceChunk *chunk;<br>
-<br>
- chunk = &chunks->chunk[*chunk_nr];<br>
-<br>
- if (*offset == chunk->len) {<br>
- if (*chunk_nr == chunks->num_chunks - 1) {<br>
- return NULL; /* Last chunk */<br>
- }<br>
- *offset = 0;<br>
- (*chunk_nr)++;<br>
- chunk = &chunks->chunk[*chunk_nr];<br>
- }<br>
-<br>
- if (chunk->len - *offset < stride) {<br>
- spice_warning("bad chunk alignment");<br>
- return NULL;<br>
- }<br>
- ret = chunk->data + *offset;<br>
- *offset += stride;<br>
- return ret;<br>
-}<br>
-<br>
-static int encode_frame(DisplayChannelClient *dcc, const SpiceRect *src,<br>
- const SpiceBitmap *image, Stream *stream)<br>
-{<br>
- SpiceChunks *chunks;<br>
- uint32_t image_stride;<br>
- size_t offset;<br>
- int i, chunk;<br>
- StreamAgent *agent = &dcc->stream_agents[stream - dcc->common.worker->streams_buf];<br>
-<br>
- chunks = image->data;<br>
- offset = 0;<br>
- chunk = 0;<br>
- image_stride = image->stride;<br>
-<br>
- const int skip_lines = stream->top_down ? src->top : image->y - (src->bottom - 0);<br>
- for (i = 0; i < skip_lines; i++) {<br>
- red_get_image_line(chunks, &offset, &chunk, image_stride);<br>
- }<br>
-<br>
- const unsigned int stream_height = src->bottom - src->top;<br>
- const unsigned int stream_width = src->right - src->left;<br>
-<br>
- for (i = 0; i < stream_height; i++) {<br>
- uint8_t *src_line =<br>
- (uint8_t *)red_get_image_line(chunks, &offset, &chunk, image_stride);<br>
-<br>
- if (!src_line) {<br>
- return FALSE;<br>
- }<br>
-<br>
- src_line += src->left * mjpeg_encoder_get_bytes_per_pixel(agent->mjpeg_encoder);<br>
- if (mjpeg_encoder_encode_scanline(agent->mjpeg_encoder, src_line, stream_width) == 0)<br>
- return FALSE;<br>
- }<br>
-<br>
- return TRUE;<br>
-}<br>
-<br>
static inline int red_marshall_stream_data(RedChannelClient *rcc,<br>
SpiceMarshaller *base_marshaller, Drawable *drawable)<br>
{<br>
@@ -8589,7 +8525,7 @@ static inline int red_marshall_stream_data(RedChannelClient *rcc,<br>
uint64_t time_now = red_now();<br>
size_t outbuf_size;<br>
<br>
- if (!dcc->use_mjpeg_encoder_rate_control) {<br>
+ if (!dcc->use_video_encoder_rate_control) {<br>
if (time_now - agent->last_send_time < (1000 * 1000 * 1000) / agent->fps) {<br>
agent->frames--;<br>
#ifdef STREAM_STATS<br>
@@ -8603,33 +8539,29 @@ static inline int red_marshall_stream_data(RedChannelClient *rcc,<br>
frame_mm_time = drawable->red_drawable->mm_time ?<br>
drawable->red_drawable->mm_time :<br>
reds_get_mm_time();<br>
+<br>
outbuf_size = dcc->send_data.stream_outbuf_size;<br>
- ret = mjpeg_encoder_start_frame(agent->mjpeg_encoder, image->u.bitmap.format,<br>
- width, height,<br>
- &dcc->send_data.stream_outbuf,<br>
- &outbuf_size,<br>
- frame_mm_time);<br>
+ ret = agent->video_encoder->encode_frame(agent->video_encoder,<br>
+ &image->u.bitmap, width, height,<br>
+ &drawable->red_drawable->u.copy.src_area,<br>
+ stream->top_down, frame_mm_time,<br>
+ &dcc->send_data.stream_outbuf, &outbuf_size, &n);<br>
+<br>
switch (ret) {<br>
- case MJPEG_ENCODER_FRAME_DROP:<br>
- spice_assert(dcc->use_mjpeg_encoder_rate_control);<br>
+ case VIDEO_ENCODER_FRAME_DROP:<br>
+ spice_assert(dcc->use_video_encoder_rate_control);<br>
#ifdef STREAM_STATS<br>
agent->stats.num_drops_fps++;<br>
#endif<br>
return TRUE;<br>
- case MJPEG_ENCODER_FRAME_UNSUPPORTED:<br>
+ case VIDEO_ENCODER_FRAME_UNSUPPORTED:<br>
return FALSE;<br>
- case MJPEG_ENCODER_FRAME_ENCODE_START:<br>
+ case VIDEO_ENCODER_FRAME_ENCODE_DONE:<br>
break;<br>
default:<br>
- spice_error("bad return value (%d) from mjpeg_encoder_start_frame", ret);<br>
- return FALSE;<br>
- }<br>
-<br>
- if (!encode_frame(dcc, &drawable->red_drawable->u.copy.src_area,<br>
- &image->u.bitmap, stream)) {<br>
+ spice_error("bad return value (%d) from VideoEncoder::encode_frame", ret);<br>
return FALSE;<br>
}<br>
- n = mjpeg_encoder_end_frame(agent->mjpeg_encoder);<br>
dcc->send_data.stream_outbuf_size = outbuf_size;<br>
<br>
if (!drawable->sized_stream) {<br>
@@ -10255,7 +10187,7 @@ static int display_channel_handle_stream_report(DisplayChannelClient *dcc,<br>
return FALSE;<br>
}<br>
stream_agent = &dcc->stream_agents[stream_report->stream_id];<br>
- if (!stream_agent->mjpeg_encoder) {<br>
+ if (!stream_agent->video_encoder) {<br>
spice_info("stream_report: no encoder for stream id %u."<br>
"Probably the stream has been destroyed", stream_report->stream_id);<br>
return TRUE;<br>
@@ -10266,7 +10198,7 @@ static int display_channel_handle_stream_report(DisplayChannelClient *dcc,<br>
stream_agent->report_id, stream_report->unique_id);<br>
return TRUE;<br>
}<br>
- mjpeg_encoder_client_stream_report(stream_agent->mjpeg_encoder,<br>
+ stream_agent->video_encoder->client_stream_report(stream_agent->video_encoder,<br>
stream_report->num_frames,<br>
stream_report->num_drops,<br>
stream_report->start_frame_mm_time,<br>
diff --git a/server/video_encoder.h b/server/video_encoder.h<br>
new file mode 100644<br>
index 0000000..8a9f9c6<br>
--- /dev/null<br>
+++ b/server/video_encoder.h<br>
@@ -0,0 +1,162 @@<br>
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */<br>
+/*<br>
+ Copyright (C) 2009 Red Hat, Inc.<br>
+ Copyright (C) 2015 Jeremy White<br>
+ Copyright (C) 2015 Francois Gouget<br>
+<br>
+ This library is free software; you can redistribute it and/or<br>
+ modify it under the terms of the GNU Lesser General Public<br>
+ License as published by the Free Software Foundation; either<br>
+ version 2.1 of the License, or (at your option) any later version.<br>
+<br>
+ This library is distributed in the hope that it will be useful,<br>
+ but WITHOUT ANY WARRANTY; without even the implied warranty of<br>
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU<br>
+ Lesser General Public License for more details.<br>
+<br>
+ You should have received a copy of the GNU Lesser General Public<br>
+ License along with this library; if not, see <<A HREF="<a href="http://www.gnu.org/licenses/" target="_blank">http://www.gnu.org/licenses/</a>"><a href="http://www.gnu.org/licenses/" target="_blank">http://www.gnu.org/licenses/</a></A>>.<br>
+*/<br>
+<br>
+#ifndef _H_video_encoder<br>
+#define _H_video_encoder<br>
+<br>
+#include "red_common.h"<br>
+<br>
+enum {<br>
+ VIDEO_ENCODER_FRAME_UNSUPPORTED = -1,<br>
+ VIDEO_ENCODER_FRAME_DROP,<br>
+ VIDEO_ENCODER_FRAME_ENCODE_DONE,<br>
+};<br>
+<br>
+typedef struct VideoEncoderStats {<br>
+ uint64_t starting_bit_rate;<br>
+ uint64_t cur_bit_rate;<br>
+ double avg_quality;<br>
+} VideoEncoderStats;<br>
+<br>
+typedef struct VideoEncoder VideoEncoder;<br>
+#ifndef VIDEO_ENCODER_T<br>
+# define VIDEO_ENCODER_T VideoEncoder<br>
+#endif<br>
+<br>
+struct VideoEncoder {<br>
+ /* Releases the video encoder's resources */<br>
+ void (*destroy)(VIDEO_ENCODER_T *encoder);<br>
+<br>
+ /* Compresses the specified src image area into the outbuf buffer.<br>
+ *<br>
+ * @encoder: The video encoder.<br>
+ * @bitmap: The Spice screen.<br>
+ * @width: The width of the Spice screen.<br>
+ * @height: The heigth of the Spice screen.<br></blockquote><div><br></div><div>It's not the screen, it's the video area. It's not clear to me what would happen if the "src" size doesn't match the width&height.<br><br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
+ * @src: A rectangle specifying the area occupied by the video.<br>
+ * @top_down: If true the first video line is specified by src.top.<br>
+ * @outbuf: The buffer for the compressed frame. This must either be<br>
+ * NULL or point to a buffer allocated by malloc since it may be<br>
+ * reallocated, if its size is too small.<br>
+ * @outbuf_size: The size of the outbuf buffer.<br>
+ * @data_size: The size of the compressed frame.<br>
+ * @return:<br>
+ * VIDEO_ENCODER_FRAME_ENCODE_DONE if successful.<br>
+ * VIDEO_ENCODER_FRAME_UNSUPPORTED if the frame cannot be encoded.<br>
+ * VIDEO_ENCODER_FRAME_DROP if the frame was dropped. This value can<br>
+ * only hapen if rate control is active.<br>
+ */<br>
+ int (*encode_frame)(VIDEO_ENCODER_T *encoder, const SpiceBitmap *bitmap,<br>
+ int width, int height, const SpiceRect *src,<br>
+ int top_down, uint32_t frame_mm_time,uint8_t **outbuf,<br>
+ size_t *outbuf_size, int *data_size);<br>
+<br>
+ /*<br>
+ * Bit rate control methods.<br>
+ */<br>
+<br>
+ /* When rate control is active statistics are periodically obtained from<br>
+ * the client and sent to the video encoder through this method.<br>
+ *<br>
+ * @encoder: The video encoder.<br>
+ * @num_frames: The number of frames that reached the client during the<br>
+ * time period the report is referring to.<br>
+ * @num_drops: The part of the above frames that was dropped by the client<br>
+ * due to late arrival time.<br>
+ * @start_frame_mm_time: The mm_time of the first frame included in the<br>
+ * report.<br>
+ * @end_frame_mm_time: The mm_time of the last frame included in the report.<br>
+ * @end_frame_delay: (end_frame_mm_time - client_mm_time)<br>
+ * @audio delay: The latency of the audio playback. If there is no audio<br>
+ * playback this is MAX_UINT.<br>
+ *<br>
+ */<br>
+ void (*client_stream_report)(VIDEO_ENCODER_T *encoder,<br>
+ uint32_t num_frames, uint32_t num_drops,<br>
+ uint32_t start_frame_mm_time,<br>
+ uint32_t end_frame_mm_time,<br>
+ int32_t end_frame_delay, uint32_t audio_delay);<br>
+<br>
+ /* This notifies the video encoder each time a frame is dropped due to pipe<br>
+ * congestion.<br>
+ *<br>
+ * We can deduce the client state by the frame dropping rate in the server.<br>
+ * Monitoring the frame drops can help in fine tuning the playback<br>
+ * parameters when the client reports are delayed.<br>
+ *<br>
+ * @encoder: The video encoder.<br>
+ */<br>
+ void (*notify_server_frame_drop)(VIDEO_ENCODER_T *encoder);<br>
+<br>
+ /* This queries the video encoder's current bit rate.<br>
+ *<br>
+ * @encoder: The video encoder.<br>
+ * @return: The current bit rate in bits per second.<br>
+ */<br>
+ uint64_t (*get_bit_rate)(VIDEO_ENCODER_T *encoder);<br>
+<br>
+ /* Collects video statistics.<br>
+ *<br>
+ * @encoder: The video encoder.<br>
+ * @stats: A VideoEncoderStats structure to fill with the collected<br>
+ * statistics.<br>
+ */<br>
+ void (*get_stats)(VIDEO_ENCODER_T *encoder, VideoEncoderStats *stats);<br>
+};<br>
+<br>
+<br>
+/* When rate control is active the video encoder can use these callbacks to<br>
+ * figure out how to adjust the stream bit rate and adjust some stream<br>
+ * parameters.<br>
+ */<br>
+typedef struct VideoEncoderRateControlCbs {<br>
+ /* Returns the stream's estimated roundtrip time in milliseconds. */<br>
+ uint32_t (*get_roundtrip_ms)(void *opaque);<br>
+<br>
+ /* Returns the estimated input frame rate.<br>
+ *<br>
+ * This is the number of frames per second arriving from the guest to<br>
+ * spice-server, before any drops.<br>
+ */<br>
+ uint32_t (*get_source_fps)(void *opaque);<br>
+<br>
+ /* Informs the client of the minimum playback delay.<br>
+ *<br>
+ * @delay_ms: The minimum number of milliseconds required for the frames<br>
+ * to reach the client.<br>
+ */<br>
+ void (*update_client_playback_delay)(void *opaque, uint32_t delay_ms);<br>
+} VideoEncoderRateControlCbs;<br>
+<br>
+<br>
+/* Instantiates the builtin MJPEG video encoder.<br>
+ *<br>
+ * @starting_bit_rate: An initial estimate of the available stream bit rate .<br>
+ * @bit_rate_control: True if the client supports rate control.<br>
+ * @cbs: A set of callback methods to be used for rate control.<br>
+ * @cbs_opaque: A pointer to be passed to the rate control callbacks.<br>
+ * @return: A pointer to a structure implementing the VideoEncoder<br>
+ * methods.<br>
+ */<br>
+VIDEO_ENCODER_T* create_mjpeg_encoder(uint64_t starting_bit_rate,<br>
+ VideoEncoderRateControlCbs *cbs,<br>
+ void *cbs_opaque);<br>
+<br>
+#endif<br>
<span class="HOEnZb"><font color="#888888">--<br>
2.1.4<br>
<br>
_______________________________________________<br>
Spice-devel mailing list<br>
<a href="mailto:Spice-devel@lists.freedesktop.org">Spice-devel@lists.freedesktop.org</a><br>
<a href="http://lists.freedesktop.org/mailman/listinfo/spice-devel" target="_blank">http://lists.freedesktop.org/mailman/listinfo/spice-devel</a><br>
</font></span></blockquote></div><br><br clear="all"><br>-- <br><div class="gmail_signature">Marc-André Lureau</div>
</div></div>