1 /* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3 Copyright (C) 2017 Red Hat, Inc.
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 */
18 #include <config.h>
19
20 #include <common/generated_server_marshallers.h>
21 #include <common/recorder.h>
22 #include <spice/stream-device.h>
23
24 #include "red-channel-client.h"
25 #include "stream-channel.h"
26 #include "reds.h"
27 #include "common-graphics-channel.h"
28 #include "display-limits.h"
29 #include "video-stream.h" // TODO remove, put common stuff
30
31 /* we need to inherit from CommonGraphicsChannelClient
32 * to get buffer handling */
33 class StreamChannelClient final: public CommonGraphicsChannelClient
34 {
35 protected:
36 ~StreamChannelClient() override;
37 public:
38 using CommonGraphicsChannelClient::CommonGraphicsChannelClient;
39
40 /* current video stream id, <0 if not initialized or
41 * we are not sending a stream */
42 int stream_id = -1;
43 private:
get_channel()44 StreamChannel* get_channel()
45 {
46 return static_cast<StreamChannel*>(CommonGraphicsChannelClient::get_channel());
47 }
48 /* Array with SPICE_VIDEO_CODEC_TYPE_ENUM_END elements, with the client
49 * preference order (index) as value */
50 GArray *client_preferred_video_codecs;
51 bool handle_preferred_video_codec_type(SpiceMsgcDisplayPreferredVideoCodecType *msg);
52 void marshall_monitors_config(StreamChannel *channel, SpiceMarshaller *m);
53 void fill_base(SpiceMarshaller *m, const StreamChannel *channel);
54 void on_disconnect() override;
55 bool handle_message(uint16_t type, uint32_t size, void *msg) override;
56 void send_item(RedPipeItem *pipe_item) override;
57 };
58
59 enum {
60 RED_PIPE_ITEM_TYPE_SURFACE_CREATE = RED_PIPE_ITEM_TYPE_COMMON_LAST,
61 RED_PIPE_ITEM_TYPE_SURFACE_DESTROY,
62 RED_PIPE_ITEM_TYPE_FILL_SURFACE,
63 RED_PIPE_ITEM_TYPE_STREAM_CREATE,
64 RED_PIPE_ITEM_TYPE_STREAM_DATA,
65 RED_PIPE_ITEM_TYPE_STREAM_DESTROY,
66 RED_PIPE_ITEM_TYPE_STREAM_ACTIVATE_REPORT,
67 RED_PIPE_ITEM_TYPE_MONITORS_CONFIG,
68 };
69
70 struct StreamCreateItem: public RedPipeItemNum<RED_PIPE_ITEM_TYPE_STREAM_CREATE> {
71 SpiceMsgDisplayStreamCreate stream_create;
72 };
73
74 struct StreamDataItem: public RedPipeItemNum<RED_PIPE_ITEM_TYPE_STREAM_DATA> {
75 ~StreamDataItem() override;
76
77 StreamChannel *channel;
78 // NOTE: this must be the last field in the structure
79 SpiceMsgDisplayStreamData data;
80 };
81
82 #define PRIMARY_SURFACE_ID 0
83
84 RECORDER(stream_channel_data, 32, "Stream channel data packet");
85
~StreamChannelClient()86 StreamChannelClient::~StreamChannelClient()
87 {
88 g_clear_pointer(&client_preferred_video_codecs, g_array_unref);
89 }
90
request_new_stream(StreamMsgStartStop * start)91 void StreamChannel::request_new_stream(StreamMsgStartStop *start)
92 {
93 if (start_cb) {
94 start_cb(start_opaque, start, this);
95 }
96 }
97
98 void
on_disconnect()99 StreamChannelClient::on_disconnect()
100 {
101 StreamChannel *channel = get_channel();
102
103 // if there are still some client connected keep streaming
104 // TODO, maybe would be worth sending new codecs if they are better
105 if (channel->is_connected()) {
106 return;
107 }
108
109 channel->stream_id = -1;
110 channel->width = 0;
111 channel->height = 0;
112
113 // send stream stop to device
114 StreamMsgStartStop stop = { 0, };
115 get_channel()->request_new_stream(&stop);
116 }
117
118 static StreamChannelClient*
stream_channel_client_new(StreamChannel * channel,RedClient * client,RedStream * stream,int mig_target,RedChannelCapabilities * caps)119 stream_channel_client_new(StreamChannel *channel, RedClient *client, RedStream *stream,
120 int mig_target, RedChannelCapabilities *caps)
121 {
122 auto rcc =
123 red::make_shared<StreamChannelClient>(channel, client, stream, caps);
124 if (!rcc->init()) {
125 return nullptr;
126 }
127 return rcc.get();
128 }
129
130 void
fill_base(SpiceMarshaller * m,const StreamChannel * channel)131 StreamChannelClient::fill_base(SpiceMarshaller *m, const StreamChannel *channel)
132 {
133 SpiceMsgDisplayBase base;
134
135 base.surface_id = PRIMARY_SURFACE_ID;
136 base.box = (SpiceRect) { 0, 0, channel->width, channel->height };
137 base.clip = (SpiceClip) { SPICE_CLIP_TYPE_NONE, nullptr };
138
139 spice_marshall_DisplayBase(m, &base);
140 }
141
142 void
marshall_monitors_config(StreamChannel * channel,SpiceMarshaller * m)143 StreamChannelClient::marshall_monitors_config(StreamChannel *channel, SpiceMarshaller *m)
144 {
145 struct {
146 SpiceMsgDisplayMonitorsConfig config;
147 SpiceHead head;
148 } msg = {
149 { 1, 1, },
150 {
151 // monitor ID. These IDs are allocated per channel starting from 0
152 0,
153 PRIMARY_SURFACE_ID,
154 channel->width, channel->height,
155 0, 0,
156 0 // flags
157 }
158 };
159
160 init_send_data(SPICE_MSG_DISPLAY_MONITORS_CONFIG);
161 spice_marshall_msg_display_monitors_config(m, &msg.config);
162 }
163
send_item(RedPipeItem * pipe_item)164 void StreamChannelClient::send_item(RedPipeItem *pipe_item)
165 {
166 SpiceMarshaller *m = get_marshaller();
167 StreamChannel *channel = get_channel();
168
169 switch (pipe_item->type) {
170 case RED_PIPE_ITEM_TYPE_SURFACE_CREATE: {
171 init_send_data(SPICE_MSG_DISPLAY_SURFACE_CREATE);
172 SpiceMsgSurfaceCreate surface_create = {
173 PRIMARY_SURFACE_ID,
174 channel->width, channel->height,
175 SPICE_SURFACE_FMT_32_xRGB, SPICE_SURFACE_FLAGS_PRIMARY
176 };
177
178 // give an hint to client that we are sending just streaming
179 // see spice.proto for capability check here
180 if (test_remote_cap(SPICE_DISPLAY_CAP_MULTI_CODEC)) {
181 surface_create.flags |= SPICE_SURFACE_FLAGS_STREAMING_MODE;
182 }
183
184 spice_marshall_msg_display_surface_create(m, &surface_create);
185 break;
186 }
187 case RED_PIPE_ITEM_TYPE_MONITORS_CONFIG:
188 if (!test_remote_cap(SPICE_DISPLAY_CAP_MONITORS_CONFIG)) {
189 return;
190 }
191 marshall_monitors_config(channel, m);
192 break;
193 case RED_PIPE_ITEM_TYPE_SURFACE_DESTROY: {
194 init_send_data(SPICE_MSG_DISPLAY_SURFACE_DESTROY);
195 SpiceMsgSurfaceDestroy surface_destroy = { PRIMARY_SURFACE_ID };
196 spice_marshall_msg_display_surface_destroy(m, &surface_destroy);
197 break;
198 }
199 case RED_PIPE_ITEM_TYPE_FILL_SURFACE: {
200 init_send_data(SPICE_MSG_DISPLAY_DRAW_FILL);
201
202 fill_base(m, channel);
203
204 SpiceFill fill;
205 fill.brush = (SpiceBrush) { SPICE_BRUSH_TYPE_SOLID, { .color = 0 } };
206 fill.rop_descriptor = SPICE_ROPD_OP_PUT;
207 fill.mask = (SpiceQMask) { 0, { 0, 0 }, nullptr };
208 SpiceMarshaller *brush_pat_out, *mask_bitmap_out;
209 spice_marshall_Fill(m, &fill, &brush_pat_out, &mask_bitmap_out);
210 break;
211 }
212 case RED_PIPE_ITEM_TYPE_STREAM_CREATE: {
213 auto item = static_cast<StreamCreateItem*>(pipe_item);
214 stream_id = item->stream_create.id;
215 init_send_data(SPICE_MSG_DISPLAY_STREAM_CREATE);
216 spice_marshall_msg_display_stream_create(m, &item->stream_create);
217 break;
218 }
219 case RED_PIPE_ITEM_TYPE_STREAM_ACTIVATE_REPORT: {
220 if (stream_id < 0
221 || !test_remote_cap(SPICE_DISPLAY_CAP_STREAM_REPORT)) {
222 return;
223 }
224 SpiceMsgDisplayStreamActivateReport msg;
225 msg.stream_id = stream_id;
226 msg.unique_id = 1; // TODO useful ?
227 msg.max_window_size = RED_STREAM_CLIENT_REPORT_WINDOW;
228 msg.timeout_ms = RED_STREAM_CLIENT_REPORT_TIMEOUT;
229 init_send_data(SPICE_MSG_DISPLAY_STREAM_ACTIVATE_REPORT);
230 spice_marshall_msg_display_stream_activate_report(m, &msg);
231 break;
232 }
233 case RED_PIPE_ITEM_TYPE_STREAM_DATA: {
234 auto item = static_cast<StreamDataItem*>(pipe_item);
235 init_send_data(SPICE_MSG_DISPLAY_STREAM_DATA);
236 spice_marshall_msg_display_stream_data(m, &item->data);
237 pipe_item->add_to_marshaller(m, item->data.data, item->data.data_size);
238 record(stream_channel_data, "Stream data packet size %u mm_time %u",
239 item->data.data_size, item->data.base.multi_media_time);
240 break;
241 }
242 case RED_PIPE_ITEM_TYPE_STREAM_DESTROY: {
243 if (stream_id < 0) {
244 return;
245 }
246 SpiceMsgDisplayStreamDestroy stream_destroy = { stream_id };
247 init_send_data(SPICE_MSG_DISPLAY_STREAM_DESTROY);
248 spice_marshall_msg_display_stream_destroy(m, &stream_destroy);
249 stream_id = -1;
250 break;
251 }
252 default:
253 spice_error("invalid pipe item type");
254 }
255
256 begin_send_message();
257 }
258
handle_message(uint16_t type,uint32_t size,void * msg)259 bool StreamChannelClient::handle_message(uint16_t type, uint32_t size, void *msg)
260 {
261 switch (type) {
262 case SPICE_MSGC_DISPLAY_INIT:
263 case SPICE_MSGC_DISPLAY_PREFERRED_COMPRESSION:
264 return true;
265 case SPICE_MSGC_DISPLAY_STREAM_REPORT:
266 /* TODO these will help tune the streaming reducing/increasing quality */
267 return true;
268 case SPICE_MSGC_DISPLAY_GL_DRAW_DONE:
269 /* client should not send this message */
270 return false;
271 case SPICE_MSGC_DISPLAY_PREFERRED_VIDEO_CODEC_TYPE:
272 return handle_preferred_video_codec_type(
273 (SpiceMsgcDisplayPreferredVideoCodecType *)msg);
274 default:
275 return CommonGraphicsChannelClient::handle_message(type, size, msg);
276 }
277 }
278
279
280 red::shared_ptr<StreamChannel>
stream_channel_new(RedsState * server,uint32_t id)281 stream_channel_new(RedsState *server, uint32_t id)
282 {
283 // TODO this id should be after all qxl devices
284 return red::make_shared<StreamChannel>(server, id);
285 }
286
287 #define MAX_SUPPORTED_CODECS SPICE_VIDEO_CODEC_TYPE_ENUM_END
288
289 // find common codecs supported by all clients
290 static uint8_t
stream_channel_get_supported_codecs(StreamChannel * channel,uint8_t * out_codecs)291 stream_channel_get_supported_codecs(StreamChannel *channel, uint8_t *out_codecs)
292 {
293 RedChannelClient *rcc;
294 int codec;
295
296 static const uint16_t codec2cap[] = {
297 0, // invalid
298 SPICE_DISPLAY_CAP_CODEC_MJPEG,
299 SPICE_DISPLAY_CAP_CODEC_VP8,
300 SPICE_DISPLAY_CAP_CODEC_H264,
301 SPICE_DISPLAY_CAP_CODEC_VP9,
302 SPICE_DISPLAY_CAP_CODEC_H265,
303 };
304
305 bool supported[SPICE_N_ELEMENTS(codec2cap)];
306
307 for (codec = 0; codec < SPICE_N_ELEMENTS(codec2cap); ++codec) {
308 supported[codec] = true;
309 }
310
311 FOREACH_CLIENT(channel, rcc) {
312 for (codec = 1; codec < SPICE_N_ELEMENTS(codec2cap); ++codec) {
313 // if do not support codec delete from list
314 if (!rcc->test_remote_cap(codec2cap[codec])) {
315 supported[codec] = false;
316 }
317 }
318 }
319
320 // surely mjpeg is supported
321 supported[SPICE_VIDEO_CODEC_TYPE_MJPEG] = true;
322
323 int num = 0;
324 for (codec = 1; codec < SPICE_N_ELEMENTS(codec2cap); ++codec) {
325 if (supported[codec]) {
326 out_codecs[num++] = codec;
327 }
328 }
329
330 return num;
331 }
332
333 bool
handle_preferred_video_codec_type(SpiceMsgcDisplayPreferredVideoCodecType * msg)334 StreamChannelClient::handle_preferred_video_codec_type(SpiceMsgcDisplayPreferredVideoCodecType *msg)
335 {
336 if (msg->num_of_codecs == 0) {
337 return true;
338 }
339
340 g_clear_pointer(&client_preferred_video_codecs, g_array_unref);
341 client_preferred_video_codecs = video_stream_parse_preferred_codecs(msg);
342
343 return true;
344 }
345
on_connect(RedClient * red_client,RedStream * stream,int migration,RedChannelCapabilities * caps)346 void StreamChannel::on_connect(RedClient *red_client, RedStream *stream,
347 int migration, RedChannelCapabilities *caps)
348 {
349 StreamChannelClient *client;
350 struct {
351 StreamMsgStartStop base;
352 uint8_t codecs_buffer[MAX_SUPPORTED_CODECS];
353 } start_msg;
354 StreamMsgStartStop *const start = &start_msg.base;
355
356 spice_return_if_fail(stream != nullptr);
357
358 client = stream_channel_client_new(this, red_client, stream, migration, caps);
359 if (client == nullptr) {
360 return;
361 }
362
363 // request new stream
364 start->num_codecs = stream_channel_get_supported_codecs(this, start->codecs);
365 // send in any case, even if list is not changed
366 // notify device about changes
367 request_new_stream(start);
368
369
370 // see guest_set_client_capabilities
371 RedChannelClient *rcc = client;
372 rcc->push_set_ack();
373
374 // TODO what should happen on migration, dcc return if on migration wait ??
375 rcc->ack_zero_messages_window();
376
377 // "emulate" dcc_start
378 rcc->pipe_add_empty_msg(SPICE_MSG_DISPLAY_INVAL_ALL_PALETTES);
379
380 // only if "surface"
381 if (width == 0 || height == 0) {
382 return;
383 }
384
385 // pass proper data
386 rcc->pipe_add_type(RED_PIPE_ITEM_TYPE_SURFACE_CREATE);
387 rcc->pipe_add_type(RED_PIPE_ITEM_TYPE_MONITORS_CONFIG);
388 // surface data
389 rcc->pipe_add_type(RED_PIPE_ITEM_TYPE_FILL_SURFACE);
390 // TODO monitor configs ??
391 rcc->pipe_add_empty_msg(SPICE_MSG_DISPLAY_MARK);
392 }
393
StreamChannel(RedsState * reds,uint32_t id)394 StreamChannel::StreamChannel(RedsState *reds, uint32_t id):
395 RedChannel(reds, SPICE_CHANNEL_DISPLAY, id, RedChannel::HandleAcks)
396 {
397 set_cap(SPICE_DISPLAY_CAP_MONITORS_CONFIG);
398 set_cap(SPICE_DISPLAY_CAP_STREAM_REPORT);
399 set_cap(SPICE_DISPLAY_CAP_PREF_VIDEO_CODEC_TYPE);
400
401 reds_register_channel(reds, this);
402 }
403
404 void
change_format(const StreamMsgFormat * fmt)405 StreamChannel::change_format(const StreamMsgFormat *fmt)
406 {
407 // send destroy old stream
408 pipes_add_type(RED_PIPE_ITEM_TYPE_STREAM_DESTROY);
409
410 // send new create surface if required
411 if (width != fmt->width || height != fmt->height) {
412 if (width != 0 && height != 0) {
413 pipes_add_type(RED_PIPE_ITEM_TYPE_SURFACE_DESTROY);
414 }
415 width = fmt->width;
416 height = fmt->height;
417 pipes_add_type(RED_PIPE_ITEM_TYPE_SURFACE_CREATE);
418 pipes_add_type(RED_PIPE_ITEM_TYPE_MONITORS_CONFIG);
419 // TODO monitors config ??
420 pipes_add_empty_msg(SPICE_MSG_DISPLAY_MARK);
421 }
422
423 // allocate a new stream id
424 stream_id = (stream_id + 1) % NUM_STREAMS;
425
426 // send create stream
427 auto item = red::make_shared<StreamCreateItem>();
428 item->stream_create.id = stream_id;
429 item->stream_create.flags = SPICE_STREAM_FLAGS_TOP_DOWN;
430 item->stream_create.codec_type = fmt->codec;
431 item->stream_create.stream_width = fmt->width;
432 item->stream_create.stream_height = fmt->height;
433 item->stream_create.src_width = fmt->width;
434 item->stream_create.src_height = fmt->height;
435 item->stream_create.dest = (SpiceRect) { 0, 0, fmt->width, fmt->height };
436 item->stream_create.clip = (SpiceClip) { SPICE_CLIP_TYPE_NONE, nullptr };
437 pipes_add(item);
438
439 // activate stream report if possible
440 pipes_add_type(RED_PIPE_ITEM_TYPE_STREAM_ACTIVATE_REPORT);
441 }
442
443 inline void
update_queue_stat(int32_t num_diff,int32_t size_diff)444 StreamChannel::update_queue_stat(int32_t num_diff, int32_t size_diff)
445 {
446 queue_stat.num_items += num_diff;
447 queue_stat.size += size_diff;
448 if (queue_cb) {
449 queue_cb(queue_opaque, &queue_stat, this);
450 }
451 }
452
~StreamDataItem()453 StreamDataItem::~StreamDataItem()
454 {
455 channel->update_queue_stat(-1, -data.data_size);
456 }
457
458 void
send_data(const void * data,size_t size,uint32_t mm_time)459 StreamChannel::send_data(const void *data, size_t size, uint32_t mm_time)
460 {
461 if (stream_id < 0) {
462 // this condition can happen if the guest didn't handle
463 // the format stop that we send so think the stream is still
464 // started
465 return;
466 }
467
468 auto item = new (size) StreamDataItem();
469 item->data.base.id = stream_id;
470 item->data.base.multi_media_time = mm_time;
471 item->data.data_size = size;
472 item->channel = this;
473 update_queue_stat(1, size);
474 // TODO try to optimize avoiding the copy
475 memcpy(item->data.data, data, size);
476 pipes_add(red::shared_ptr<StreamDataItem>(item));
477 }
478
479 void
register_start_cb(stream_channel_start_proc cb,void * opaque)480 StreamChannel::register_start_cb(stream_channel_start_proc cb, void *opaque)
481 {
482 start_cb = cb;
483 start_opaque = opaque;
484 }
485
486 void
register_queue_stat_cb(stream_channel_queue_stat_proc cb,void * opaque)487 StreamChannel::register_queue_stat_cb(stream_channel_queue_stat_proc cb, void *opaque)
488 {
489 queue_cb = cb;
490 queue_opaque = opaque;
491 }
492
493 void
reset()494 StreamChannel::reset()
495 {
496 struct {
497 StreamMsgStartStop base;
498 uint8_t codecs_buffer[MAX_SUPPORTED_CODECS];
499 } start_msg;
500 StreamMsgStartStop *const start = &start_msg.base;
501
502 // send destroy old stream
503 pipes_add_type(RED_PIPE_ITEM_TYPE_STREAM_DESTROY);
504
505 // destroy display surface
506 if (width != 0 && height != 0) {
507 pipes_add_type(RED_PIPE_ITEM_TYPE_SURFACE_DESTROY);
508 }
509
510 stream_id = -1;
511 width = 0;
512 height = 0;
513
514 if (!is_connected()) {
515 return;
516 }
517
518 // try to request a new stream, this should start a new stream
519 // if the guest is connected to the device and a client is already connected
520 start->num_codecs = stream_channel_get_supported_codecs(this, start->codecs);
521 // send in any case, even if list is not changed
522 // notify device about changes
523 request_new_stream(start);
524 }
525