summaryrefslogtreecommitdiffstats
path: root/server/stream.c
diff options
context:
space:
mode:
Diffstat (limited to 'server/stream.c')
-rw-r--r--server/stream.c359
1 files changed, 359 insertions, 0 deletions
diff --git a/server/stream.c b/server/stream.c
index 7ef78767..97b9e0a8 100644
--- a/server/stream.c
+++ b/server/stream.c
@@ -21,6 +21,12 @@
#include "stream.h"
#include "display-channel.h"
+#define FPS_TEST_INTERVAL 1
+#define FOREACH_STREAMS(display, item) \
+ for (item = ring_get_head(&(display)->streams); \
+ item != NULL; \
+ item = ring_next(&(display)->streams, item))
+
void stream_agent_stats_print(StreamAgent *agent)
{
#ifdef STREAM_STATS
@@ -68,3 +74,356 @@ StreamClipItem *stream_clip_item_new(DisplayChannelClient* dcc, StreamAgent *age
item->refs = 1;
return item;
}
+
+static int is_stream_start(Drawable *drawable)
+{
+ return ((drawable->frames_count >= RED_STREAM_FRAMES_START_CONDITION) &&
+ (drawable->gradual_frames_count >=
+ (RED_STREAM_GRADUAL_FRAMES_START_CONDITION * drawable->frames_count)));
+}
+
+static void update_copy_graduality(DisplayChannel *display, Drawable *drawable)
+{
+ SpiceBitmap *bitmap;
+ spice_return_if_fail(drawable->red_drawable->type == QXL_DRAW_COPY);
+
+ if (display->stream_video != SPICE_STREAM_VIDEO_FILTER) {
+ drawable->copy_bitmap_graduality = BITMAP_GRADUAL_INVALID;
+ return;
+ }
+
+ if (drawable->copy_bitmap_graduality != BITMAP_GRADUAL_INVALID) {
+ return; // already set
+ }
+
+ bitmap = &drawable->red_drawable->u.copy.src_bitmap->u.bitmap;
+
+ if (!bitmap_fmt_has_graduality(bitmap->format) || bitmap_has_extra_stride(bitmap) ||
+ (bitmap->data->flags & SPICE_CHUNKS_FLAGS_UNSTABLE)) {
+ drawable->copy_bitmap_graduality = BITMAP_GRADUAL_NOT_AVAIL;
+ } else {
+ drawable->copy_bitmap_graduality = bitmap_get_graduality_level(bitmap);
+ }
+}
+
+static int is_next_stream_frame(DisplayChannel *display,
+ const Drawable *candidate,
+ const int other_src_width,
+ const int other_src_height,
+ const SpiceRect *other_dest,
+ const red_time_t other_time,
+ const Stream *stream,
+ int container_candidate_allowed)
+{
+ RedDrawable *red_drawable;
+ int is_frame_container = FALSE;
+
+ if (!candidate->streamable) {
+ return STREAM_FRAME_NONE;
+ }
+
+ if (candidate->creation_time - other_time >
+ (stream ? RED_STREAM_CONTINUS_MAX_DELTA : RED_STREAM_DETACTION_MAX_DELTA)) {
+ return STREAM_FRAME_NONE;
+ }
+
+ red_drawable = candidate->red_drawable;
+ if (!container_candidate_allowed) {
+ SpiceRect* candidate_src;
+
+ if (!rect_is_equal(&red_drawable->bbox, other_dest)) {
+ return STREAM_FRAME_NONE;
+ }
+
+ candidate_src = &red_drawable->u.copy.src_area;
+ if (candidate_src->right - candidate_src->left != other_src_width ||
+ candidate_src->bottom - candidate_src->top != other_src_height) {
+ return STREAM_FRAME_NONE;
+ }
+ } else {
+ if (rect_contains(&red_drawable->bbox, other_dest)) {
+ int candidate_area = rect_get_area(&red_drawable->bbox);
+ int other_area = rect_get_area(other_dest);
+ /* do not stream drawables that are significantly
+ * bigger than the original frame */
+ if (candidate_area > 2 * other_area) {
+ spice_debug("too big candidate:");
+ spice_debug("prev box ==>");
+ rect_debug(other_dest);
+ spice_debug("new box ==>");
+ rect_debug(&red_drawable->bbox);
+ return STREAM_FRAME_NONE;
+ }
+
+ if (candidate_area > other_area) {
+ is_frame_container = TRUE;
+ }
+ } else {
+ return STREAM_FRAME_NONE;
+ }
+ }
+
+ if (stream) {
+ SpiceBitmap *bitmap = &red_drawable->u.copy.src_bitmap->u.bitmap;
+ if (stream->top_down != !!(bitmap->flags & SPICE_BITMAP_FLAGS_TOP_DOWN)) {
+ return STREAM_FRAME_NONE;
+ }
+ }
+ if (is_frame_container) {
+ return STREAM_FRAME_CONTAINER;
+ } else {
+ return STREAM_FRAME_NATIVE;
+ }
+}
+
+static void before_reattach_stream(DisplayChannel *display,
+ Stream *stream, Drawable *new_frame)
+{
+ DrawablePipeItem *dpi;
+ DisplayChannelClient *dcc;
+ int index;
+ StreamAgent *agent;
+ RingItem *ring_item, *next;
+
+ spice_return_if_fail(stream->current);
+
+ if (!red_channel_is_connected(RED_CHANNEL(display))) {
+ return;
+ }
+
+ if (new_frame->process_commands_generation == stream->current->process_commands_generation) {
+ spice_debug("ignoring drop, same process_commands_generation as previous frame");
+ return;
+ }
+
+ index = get_stream_id(display, stream);
+ DRAWABLE_FOREACH_DPI_SAFE(stream->current, ring_item, next, dpi) {
+ dcc = dpi->dcc;
+ agent = &dcc->stream_agents[index];
+
+ if (!dcc->use_mjpeg_encoder_rate_control &&
+ !dcc->common.is_low_bandwidth) {
+ continue;
+ }
+
+ if (pipe_item_is_linked(&dpi->dpi_pipe_item)) {
+#ifdef STREAM_STATS
+ agent->stats.num_drops_pipe++;
+#endif
+ if (dcc->use_mjpeg_encoder_rate_control) {
+ mjpeg_encoder_notify_server_frame_drop(agent->mjpeg_encoder);
+ } else {
+ ++agent->drops;
+ }
+ }
+ }
+
+
+ FOREACH_DCC(display, ring_item, next, dcc) {
+ double drop_factor;
+
+ agent = &dcc->stream_agents[index];
+
+ if (dcc->use_mjpeg_encoder_rate_control) {
+ continue;
+ }
+ if (agent->frames / agent->fps < FPS_TEST_INTERVAL) {
+ agent->frames++;
+ continue;
+ }
+ drop_factor = ((double)agent->frames - (double)agent->drops) /
+ (double)agent->frames;
+ spice_debug("stream %d: #frames %u #drops %u", index, agent->frames, agent->drops);
+ if (drop_factor == 1) {
+ if (agent->fps < MAX_FPS) {
+ agent->fps++;
+ spice_debug("stream %d: fps++ %u", index, agent->fps);
+ }
+ } else if (drop_factor < 0.9) {
+ if (agent->fps > 1) {
+ agent->fps--;
+ spice_debug("stream %d: fps--%u", index, agent->fps);
+ }
+ }
+ agent->frames = 1;
+ agent->drops = 0;
+ }
+}
+
+static Stream *display_channel_stream_try_new(DisplayChannel *display)
+{
+ Stream *stream;
+ if (!display->free_streams) {
+ return NULL;
+ }
+ stream = display->free_streams;
+ display->free_streams = display->free_streams->next;
+ return stream;
+}
+
+static void display_channel_create_stream(DisplayChannel *display, Drawable *drawable)
+{
+ DisplayChannelClient *dcc;
+ RingItem *dcc_ring_item, *next;
+ Stream *stream;
+ SpiceRect* src_rect;
+
+ spice_assert(!drawable->stream);
+
+ if (!(stream = display_channel_stream_try_new(display))) {
+ return;
+ }
+
+ spice_assert(drawable->red_drawable->type == QXL_DRAW_COPY);
+ src_rect = &drawable->red_drawable->u.copy.src_area;
+
+ ring_add(&display->streams, &stream->link);
+ stream->current = drawable;
+ stream->last_time = drawable->creation_time;
+ stream->width = src_rect->right - src_rect->left;
+ stream->height = src_rect->bottom - src_rect->top;
+ stream->dest_area = drawable->red_drawable->bbox;
+ stream->refs = 1;
+ SpiceBitmap *bitmap = &drawable->red_drawable->u.copy.src_bitmap->u.bitmap;
+ stream->top_down = !!(bitmap->flags & SPICE_BITMAP_FLAGS_TOP_DOWN);
+ drawable->stream = stream;
+ stream->input_fps = MAX_FPS;
+ stream->num_input_frames = 0;
+ stream->input_fps_start_time = drawable->creation_time;
+ display->streams_size_total += stream->width * stream->height;
+ display->stream_count++;
+ FOREACH_DCC(display, dcc_ring_item, next, dcc) {
+ dcc_create_stream(dcc, stream);
+ }
+ spice_debug("stream %d %dx%d (%d, %d) (%d, %d)",
+ (int)(stream - display->streams_buf), stream->width,
+ stream->height, stream->dest_area.left, stream->dest_area.top,
+ stream->dest_area.right, stream->dest_area.bottom);
+ return;
+}
+
+// returns whether a stream was created
+static int stream_add_frame(DisplayChannel *display,
+ Drawable *frame_drawable,
+ int frames_count,
+ int gradual_frames_count,
+ int last_gradual_frame)
+{
+ update_copy_graduality(display, frame_drawable);
+ frame_drawable->frames_count = frames_count + 1;
+ frame_drawable->gradual_frames_count = gradual_frames_count;
+
+ if (frame_drawable->copy_bitmap_graduality != BITMAP_GRADUAL_LOW) {
+ if ((frame_drawable->frames_count - last_gradual_frame) >
+ RED_STREAM_FRAMES_RESET_CONDITION) {
+ frame_drawable->frames_count = 1;
+ frame_drawable->gradual_frames_count = 1;
+ } else {
+ frame_drawable->gradual_frames_count++;
+ }
+
+ frame_drawable->last_gradual_frame = frame_drawable->frames_count;
+ } else {
+ frame_drawable->last_gradual_frame = last_gradual_frame;
+ }
+
+ if (is_stream_start(frame_drawable)) {
+ display_channel_create_stream(display, frame_drawable);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* TODO: document the difference between the 2 functions below */
+void stream_trace_update(DisplayChannel *display, Drawable *drawable)
+{
+ ItemTrace *trace;
+ ItemTrace *trace_end;
+ RingItem *item;
+
+ if (drawable->stream || !drawable->streamable || drawable->frames_count) {
+ return;
+ }
+
+ FOREACH_STREAMS(display, item) {
+ Stream *stream = SPICE_CONTAINEROF(item, Stream, link);
+ int is_next_frame = is_next_stream_frame(display,
+ drawable,
+ stream->width,
+ stream->height,
+ &stream->dest_area,
+ stream->last_time,
+ stream,
+ TRUE);
+ if (is_next_frame != STREAM_FRAME_NONE) {
+ if (stream->current) {
+ stream->current->streamable = FALSE; //prevent item trace
+ before_reattach_stream(display, stream, drawable);
+ detach_stream(display, stream, FALSE);
+ }
+ attach_stream(display, drawable, stream);
+ if (is_next_frame == STREAM_FRAME_CONTAINER) {
+ drawable->sized_stream = stream;
+ }
+ return;
+ }
+ }
+
+ trace = display->items_trace;
+ trace_end = trace + NUM_TRACE_ITEMS;
+ for (; trace < trace_end; trace++) {
+ if (is_next_stream_frame(display, drawable, trace->width, trace->height,
+ &trace->dest_area, trace->time, NULL, FALSE) !=
+ STREAM_FRAME_NONE) {
+ if (stream_add_frame(display, drawable,
+ trace->frames_count,
+ trace->gradual_frames_count,
+ trace->last_gradual_frame)) {
+ return;
+ }
+ }
+ }
+}
+
+void stream_maintenance(DisplayChannel *display,
+ Drawable *candidate, Drawable *prev)
+{
+ int is_next_frame;
+
+ if (candidate->stream) {
+ return;
+ }
+
+ if (prev->stream) {
+ Stream *stream = prev->stream;
+
+ is_next_frame = is_next_stream_frame(display, candidate,
+ stream->width, stream->height,
+ &stream->dest_area, stream->last_time,
+ stream, TRUE);
+ if (is_next_frame != STREAM_FRAME_NONE) {
+ before_reattach_stream(display, stream, candidate);
+ detach_stream(display, stream, FALSE);
+ prev->streamable = FALSE; //prevent item trace
+ attach_stream(display, candidate, stream);
+ if (is_next_frame == STREAM_FRAME_CONTAINER) {
+ candidate->sized_stream = stream;
+ }
+ }
+ } else if (candidate->streamable) {
+ SpiceRect* prev_src = &prev->red_drawable->u.copy.src_area;
+
+ is_next_frame =
+ is_next_stream_frame(display, candidate, prev_src->right - prev_src->left,
+ prev_src->bottom - prev_src->top,
+ &prev->red_drawable->bbox, prev->creation_time,
+ prev->stream,
+ FALSE);
+ if (is_next_frame != STREAM_FRAME_NONE) {
+ stream_add_frame(display, candidate,
+ prev->frames_count,
+ prev->gradual_frames_count,
+ prev->last_gradual_frame);
+ }
+ }
+}