spice-pxvdi/server/stream-channel.cpp
Frediano Ziglio 176970f3f1 red-channel-client: Remove GObject type
Make all RedChannelClient hierarchy a C++ class.
This allows to use virtual methods.
Added a normal contructor instead or properties and g_object_new.

As we remove GObject conversion macros I added a macro XXX_CAST
to create a function to replace the old macro.
They will be removed when more type safety is introduced.

There's a new SPICE_CXX_GLIB_ALLOCATOR macro in red-common.h.
This macro, added to a class define the class allocator allowing
to use, in this case, GLib for allocation. This to avoid C++ library
dependency and to initialize all structure to 0 (not all fields
are manually initialized, will be improved with more encapsulation).

Currently the methods are mainly public, access will be modified
when more encapsulation (all functions in method) are done.

Some classes are now defined in the header, C++ uses access to
limit accessibility but for efficiency and type safety/inline and
other features require types to be defined in the headers.

Some fields were moved from XxxPrivate structure to class, C++
has accessibility.

Many destructors are defined as protected to forbid the use of
stack, this as these objects uses internal reference counting
to have normal pointers. Maybe in the future pointers like
std::shared_ptr could be used instead.

Reference counting is now implemented very easily using atomic
operations.

Signed-off-by: Frediano Ziglio <fziglio@redhat.com>
2020-05-01 06:58:09 +01:00

599 lines
19 KiB
C++

/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
Copyright (C) 2017 Red Hat, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <common/generated_server_marshallers.h>
#include <common/recorder.h>
#include <spice/stream-device.h>
#include "red-channel-client.h"
#include "stream-channel.h"
#include "reds.h"
#include "common-graphics-channel.h"
#include "display-limits.h"
#include "video-stream.h" // TODO remove, put common stuff
/* we need to inherit from CommonGraphicsChannelClient
* to get buffer handling */
class StreamChannelClient final: public CommonGraphicsChannelClient
{
protected:
~StreamChannelClient();
public:
using CommonGraphicsChannelClient::CommonGraphicsChannelClient;
bool handle_preferred_video_codec_type(SpiceMsgcDisplayPreferredVideoCodecType *msg);
/* current video stream id, <0 if not initialized or
* we are not sending a stream */
int stream_id = -1;
/* Array with SPICE_VIDEO_CODEC_TYPE_ENUM_END elements, with the client
* preference order (index) as value */
GArray *client_preferred_video_codecs;
virtual void on_disconnect() override;
};
struct StreamChannel final: public RedChannel
{
/* current video stream id, <0 if not initialized or
* we are not sending a stream */
int stream_id;
/* size of the current video stream */
unsigned width, height;
StreamQueueStat queue_stat;
/* callback to notify when a stream should be started or stopped */
stream_channel_start_proc start_cb;
void *start_opaque;
/* callback to notify when queue statistics changes */
stream_channel_queue_stat_proc queue_cb;
void *queue_opaque;
};
struct StreamChannelClass {
RedChannelClass parent_class;
};
G_DEFINE_TYPE(StreamChannel, stream_channel, RED_TYPE_CHANNEL)
enum {
RED_PIPE_ITEM_TYPE_SURFACE_CREATE = RED_PIPE_ITEM_TYPE_COMMON_LAST,
RED_PIPE_ITEM_TYPE_SURFACE_DESTROY,
RED_PIPE_ITEM_TYPE_FILL_SURFACE,
RED_PIPE_ITEM_TYPE_STREAM_CREATE,
RED_PIPE_ITEM_TYPE_STREAM_DATA,
RED_PIPE_ITEM_TYPE_STREAM_DESTROY,
RED_PIPE_ITEM_TYPE_STREAM_ACTIVATE_REPORT,
RED_PIPE_ITEM_TYPE_MONITORS_CONFIG,
};
typedef struct StreamCreateItem {
RedPipeItem base;
SpiceMsgDisplayStreamCreate stream_create;
} StreamCreateItem;
typedef struct StreamDataItem {
RedPipeItem base;
StreamChannel *channel;
// NOTE: this must be the last field in the structure
SpiceMsgDisplayStreamData data;
} StreamDataItem;
#define PRIMARY_SURFACE_ID 0
RECORDER(stream_channel_data, 32, "Stream channel data packet");
StreamChannelClient::~StreamChannelClient()
{
g_clear_pointer(&client_preferred_video_codecs, g_array_unref);
}
static void
request_new_stream(StreamChannel *channel, StreamMsgStartStop *start)
{
if (channel->start_cb) {
channel->start_cb(channel->start_opaque, start, channel);
}
}
void
StreamChannelClient::on_disconnect()
{
StreamChannel *channel = STREAM_CHANNEL(get_channel());
// if there are still some client connected keep streaming
// TODO, maybe would be worth sending new codecs if they are better
if (channel->is_connected()) {
return;
}
channel->stream_id = -1;
channel->width = 0;
channel->height = 0;
// send stream stop to device
StreamMsgStartStop stop = { 0, };
request_new_stream(channel, &stop);
}
static StreamChannelClient*
stream_channel_client_new(StreamChannel *channel, RedClient *client, RedStream *stream,
int mig_target, RedChannelCapabilities *caps)
{
auto rcc = new StreamChannelClient(RED_CHANNEL(channel), client, stream, caps);
if (!rcc->init()) {
rcc->unref();
rcc = nullptr;
}
return rcc;
}
static void
fill_base(SpiceMarshaller *m, const StreamChannel *channel)
{
SpiceMsgDisplayBase base;
base.surface_id = PRIMARY_SURFACE_ID;
base.box = (SpiceRect) { 0, 0, channel->width, channel->height };
base.clip = (SpiceClip) { SPICE_CLIP_TYPE_NONE, NULL };
spice_marshall_DisplayBase(m, &base);
}
static void
marshall_monitors_config(RedChannelClient *rcc, StreamChannel *channel, SpiceMarshaller *m)
{
struct {
SpiceMsgDisplayMonitorsConfig config;
SpiceHead head;
} msg = {
{ 1, 1, },
{
// monitor ID. These IDs are allocated per channel starting from 0
0,
PRIMARY_SURFACE_ID,
channel->width, channel->height,
0, 0,
0 // flags
}
};
rcc->init_send_data(SPICE_MSG_DISPLAY_MONITORS_CONFIG);
spice_marshall_msg_display_monitors_config(m, &msg.config);
}
XXX_CAST(RedChannelClient, StreamChannelClient, STREAM_CHANNEL_CLIENT)
static void
stream_channel_send_item(RedChannelClient *rcc, RedPipeItem *pipe_item)
{
SpiceMarshaller *m = rcc->get_marshaller();
StreamChannelClient *client = STREAM_CHANNEL_CLIENT(rcc);
StreamChannel *channel = STREAM_CHANNEL(rcc->get_channel());
switch (pipe_item->type) {
case RED_PIPE_ITEM_TYPE_SURFACE_CREATE: {
rcc->init_send_data(SPICE_MSG_DISPLAY_SURFACE_CREATE);
SpiceMsgSurfaceCreate surface_create = {
PRIMARY_SURFACE_ID,
channel->width, channel->height,
SPICE_SURFACE_FMT_32_xRGB, SPICE_SURFACE_FLAGS_PRIMARY
};
// give an hint to client that we are sending just streaming
// see spice.proto for capability check here
if (rcc->test_remote_cap(SPICE_DISPLAY_CAP_MULTI_CODEC)) {
surface_create.flags |= SPICE_SURFACE_FLAGS_STREAMING_MODE;
}
spice_marshall_msg_display_surface_create(m, &surface_create);
break;
}
case RED_PIPE_ITEM_TYPE_MONITORS_CONFIG:
if (!rcc->test_remote_cap(SPICE_DISPLAY_CAP_MONITORS_CONFIG)) {
return;
}
marshall_monitors_config(rcc, channel, m);
break;
case RED_PIPE_ITEM_TYPE_SURFACE_DESTROY: {
rcc->init_send_data(SPICE_MSG_DISPLAY_SURFACE_DESTROY);
SpiceMsgSurfaceDestroy surface_destroy = { PRIMARY_SURFACE_ID };
spice_marshall_msg_display_surface_destroy(m, &surface_destroy);
break;
}
case RED_PIPE_ITEM_TYPE_FILL_SURFACE: {
rcc->init_send_data(SPICE_MSG_DISPLAY_DRAW_FILL);
fill_base(m, channel);
SpiceFill fill;
fill.brush = (SpiceBrush) { SPICE_BRUSH_TYPE_SOLID, { .color = 0 } };
fill.rop_descriptor = SPICE_ROPD_OP_PUT;
fill.mask = (SpiceQMask) { 0, { 0, 0 }, NULL };
SpiceMarshaller *brush_pat_out, *mask_bitmap_out;
spice_marshall_Fill(m, &fill, &brush_pat_out, &mask_bitmap_out);
break;
}
case RED_PIPE_ITEM_TYPE_STREAM_CREATE: {
StreamCreateItem *item = SPICE_UPCAST(StreamCreateItem, pipe_item);
client->stream_id = item->stream_create.id;
rcc->init_send_data(SPICE_MSG_DISPLAY_STREAM_CREATE);
spice_marshall_msg_display_stream_create(m, &item->stream_create);
break;
}
case RED_PIPE_ITEM_TYPE_STREAM_ACTIVATE_REPORT: {
if (client->stream_id < 0
|| !rcc->test_remote_cap(SPICE_DISPLAY_CAP_STREAM_REPORT)) {
return;
}
SpiceMsgDisplayStreamActivateReport msg;
msg.stream_id = client->stream_id;
msg.unique_id = 1; // TODO useful ?
msg.max_window_size = RED_STREAM_CLIENT_REPORT_WINDOW;
msg.timeout_ms = RED_STREAM_CLIENT_REPORT_TIMEOUT;
rcc->init_send_data(SPICE_MSG_DISPLAY_STREAM_ACTIVATE_REPORT);
spice_marshall_msg_display_stream_activate_report(m, &msg);
break;
}
case RED_PIPE_ITEM_TYPE_STREAM_DATA: {
StreamDataItem *item = SPICE_UPCAST(StreamDataItem, pipe_item);
rcc->init_send_data(SPICE_MSG_DISPLAY_STREAM_DATA);
spice_marshall_msg_display_stream_data(m, &item->data);
red_pipe_item_ref(pipe_item);
spice_marshaller_add_by_ref_full(m, item->data.data, item->data.data_size,
marshaller_unref_pipe_item, pipe_item);
record(stream_channel_data, "Stream data packet size %u mm_time %u",
item->data.data_size, item->data.base.multi_media_time);
break;
}
case RED_PIPE_ITEM_TYPE_STREAM_DESTROY: {
if (client->stream_id < 0) {
return;
}
SpiceMsgDisplayStreamDestroy stream_destroy = { client->stream_id };
rcc->init_send_data(SPICE_MSG_DISPLAY_STREAM_DESTROY);
spice_marshall_msg_display_stream_destroy(m, &stream_destroy);
client->stream_id = -1;
break;
}
default:
spice_error("invalid pipe item type");
}
rcc->begin_send_message();
}
static bool
handle_message(RedChannelClient *rcc, uint16_t type, uint32_t size, void *msg)
{
StreamChannelClient *client = STREAM_CHANNEL_CLIENT(rcc);
switch (type) {
case SPICE_MSGC_DISPLAY_INIT:
case SPICE_MSGC_DISPLAY_PREFERRED_COMPRESSION:
return true;
case SPICE_MSGC_DISPLAY_STREAM_REPORT:
/* TODO these will help tune the streaming reducing/increasing quality */
return true;
case SPICE_MSGC_DISPLAY_GL_DRAW_DONE:
/* client should not send this message */
return false;
case SPICE_MSGC_DISPLAY_PREFERRED_VIDEO_CODEC_TYPE:
return client->handle_preferred_video_codec_type(
(SpiceMsgcDisplayPreferredVideoCodecType *)msg);
default:
return RedChannelClient::handle_message(rcc, type, size, msg);
}
}
StreamChannel*
stream_channel_new(RedsState *server, uint32_t id)
{
return (StreamChannel*) g_object_new(TYPE_STREAM_CHANNEL,
"spice-server", server,
"core-interface", reds_get_core_interface(server),
"channel-type", SPICE_CHANNEL_DISPLAY,
// TODO this id should be after all qxl devices
"id", id,
"handle-acks", TRUE, // TODO sure ??
NULL);
}
#define MAX_SUPPORTED_CODECS SPICE_VIDEO_CODEC_TYPE_ENUM_END
// find common codecs supported by all clients
static uint8_t
stream_channel_get_supported_codecs(StreamChannel *channel, uint8_t *out_codecs)
{
RedChannelClient *rcc;
int codec;
static const uint16_t codec2cap[] = {
0, // invalid
SPICE_DISPLAY_CAP_CODEC_MJPEG,
SPICE_DISPLAY_CAP_CODEC_VP8,
SPICE_DISPLAY_CAP_CODEC_H264,
SPICE_DISPLAY_CAP_CODEC_VP9,
SPICE_DISPLAY_CAP_CODEC_H265,
};
bool supported[SPICE_N_ELEMENTS(codec2cap)];
for (codec = 0; codec < SPICE_N_ELEMENTS(codec2cap); ++codec) {
supported[codec] = true;
}
FOREACH_CLIENT(channel, rcc) {
for (codec = 1; codec < SPICE_N_ELEMENTS(codec2cap); ++codec) {
// if do not support codec delete from list
if (!rcc->test_remote_cap(codec2cap[codec])) {
supported[codec] = false;
}
}
}
// surely mjpeg is supported
supported[SPICE_VIDEO_CODEC_TYPE_MJPEG] = true;
int num = 0;
for (codec = 1; codec < SPICE_N_ELEMENTS(codec2cap); ++codec) {
if (supported[codec]) {
out_codecs[num++] = codec;
}
}
return num;
}
bool
StreamChannelClient::handle_preferred_video_codec_type(SpiceMsgcDisplayPreferredVideoCodecType *msg)
{
if (msg->num_of_codecs == 0) {
return true;
}
g_clear_pointer(&client_preferred_video_codecs, g_array_unref);
client_preferred_video_codecs = video_stream_parse_preferred_codecs(msg);
return true;
}
static void
stream_channel_connect(RedChannel *red_channel, RedClient *red_client, RedStream *stream,
int migration, RedChannelCapabilities *caps)
{
StreamChannel *channel = STREAM_CHANNEL(red_channel);
StreamChannelClient *client;
struct {
StreamMsgStartStop base;
uint8_t codecs_buffer[MAX_SUPPORTED_CODECS];
} start_msg;
StreamMsgStartStop *const start = &start_msg.base;
spice_return_if_fail(stream != NULL);
client = stream_channel_client_new(channel, red_client, stream, migration, caps);
if (client == NULL) {
return;
}
// request new stream
start->num_codecs = stream_channel_get_supported_codecs(channel, start->codecs);
// send in any case, even if list is not changed
// notify device about changes
request_new_stream(channel, start);
// see guest_set_client_capabilities
RedChannelClient *rcc = client;
rcc->push_set_ack();
// TODO what should happen on migration, dcc return if on migration wait ??
rcc->ack_zero_messages_window();
// "emulate" dcc_start
rcc->pipe_add_empty_msg(SPICE_MSG_DISPLAY_INVAL_ALL_PALETTES);
// only if "surface"
if (channel->width == 0 || channel->height == 0) {
return;
}
// pass proper data
rcc->pipe_add_type(RED_PIPE_ITEM_TYPE_SURFACE_CREATE);
rcc->pipe_add_type(RED_PIPE_ITEM_TYPE_MONITORS_CONFIG);
// surface data
rcc->pipe_add_type(RED_PIPE_ITEM_TYPE_FILL_SURFACE);
// TODO monitor configs ??
rcc->pipe_add_empty_msg(SPICE_MSG_DISPLAY_MARK);
}
static void
stream_channel_constructed(GObject *object)
{
RedChannel *red_channel = RED_CHANNEL(object);
RedsState *reds = red_channel->get_server();
G_OBJECT_CLASS(stream_channel_parent_class)->constructed(object);
red_channel->set_cap(SPICE_DISPLAY_CAP_MONITORS_CONFIG);
red_channel->set_cap(SPICE_DISPLAY_CAP_STREAM_REPORT);
red_channel->set_cap(SPICE_DISPLAY_CAP_PREF_VIDEO_CODEC_TYPE);
reds_register_channel(reds, red_channel);
}
static void
stream_channel_class_init(StreamChannelClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
RedChannelClass *channel_class = RED_CHANNEL_CLASS(klass);
object_class->constructed = stream_channel_constructed;
channel_class->parser = spice_get_client_channel_parser(SPICE_CHANNEL_DISPLAY, NULL);
channel_class->handle_message = handle_message;
channel_class->send_item = stream_channel_send_item;
channel_class->connect = stream_channel_connect;
}
static void
stream_channel_init(StreamChannel *channel)
{
channel->stream_id = -1;
channel->width = 0;
channel->height = 0;
}
void
stream_channel_change_format(StreamChannel *channel, const StreamMsgFormat *fmt)
{
// send destroy old stream
channel->pipes_add_type(RED_PIPE_ITEM_TYPE_STREAM_DESTROY);
// send new create surface if required
if (channel->width != fmt->width || channel->height != fmt->height) {
if (channel->width != 0 && channel->height != 0) {
channel->pipes_add_type(RED_PIPE_ITEM_TYPE_SURFACE_DESTROY);
}
channel->width = fmt->width;
channel->height = fmt->height;
channel->pipes_add_type(RED_PIPE_ITEM_TYPE_SURFACE_CREATE);
channel->pipes_add_type(RED_PIPE_ITEM_TYPE_MONITORS_CONFIG);
// TODO monitors config ??
channel->pipes_add_empty_msg(SPICE_MSG_DISPLAY_MARK);
}
// allocate a new stream id
channel->stream_id = (channel->stream_id + 1) % NUM_STREAMS;
// send create stream
StreamCreateItem *item = g_new0(StreamCreateItem, 1);
red_pipe_item_init(&item->base, RED_PIPE_ITEM_TYPE_STREAM_CREATE);
item->stream_create.id = channel->stream_id;
item->stream_create.flags = SPICE_STREAM_FLAGS_TOP_DOWN;
item->stream_create.codec_type = fmt->codec;
item->stream_create.stream_width = fmt->width;
item->stream_create.stream_height = fmt->height;
item->stream_create.src_width = fmt->width;
item->stream_create.src_height = fmt->height;
item->stream_create.dest = (SpiceRect) { 0, 0, fmt->width, fmt->height };
item->stream_create.clip = (SpiceClip) { SPICE_CLIP_TYPE_NONE, NULL };
channel->pipes_add(&item->base);
// activate stream report if possible
channel->pipes_add_type(RED_PIPE_ITEM_TYPE_STREAM_ACTIVATE_REPORT);
}
static inline void
stream_channel_update_queue_stat(StreamChannel *channel,
int32_t num_diff, int32_t size_diff)
{
channel->queue_stat.num_items += num_diff;
channel->queue_stat.size += size_diff;
if (channel->queue_cb) {
channel->queue_cb(channel->queue_opaque, &channel->queue_stat, channel);
}
}
static void
data_item_free(RedPipeItem *base)
{
StreamDataItem *pipe_item = SPICE_UPCAST(StreamDataItem, base);
stream_channel_update_queue_stat(pipe_item->channel, -1, -pipe_item->data.data_size);
g_free(pipe_item);
}
void
stream_channel_send_data(StreamChannel *channel, const void *data, size_t size, uint32_t mm_time)
{
if (channel->stream_id < 0) {
// this condition can happen if the guest didn't handle
// the format stop that we send so think the stream is still
// started
return;
}
StreamDataItem *item = (StreamDataItem*) g_malloc(sizeof(*item) + size);
red_pipe_item_init_full(&item->base, RED_PIPE_ITEM_TYPE_STREAM_DATA,
data_item_free);
item->data.base.id = channel->stream_id;
item->data.base.multi_media_time = mm_time;
item->data.data_size = size;
item->channel = channel;
stream_channel_update_queue_stat(channel, 1, size);
// TODO try to optimize avoiding the copy
memcpy(item->data.data, data, size);
channel->pipes_add(&item->base);
}
void
stream_channel_register_start_cb(StreamChannel *channel,
stream_channel_start_proc cb, void *opaque)
{
channel->start_cb = cb;
channel->start_opaque = opaque;
}
void
stream_channel_register_queue_stat_cb(StreamChannel *channel,
stream_channel_queue_stat_proc cb, void *opaque)
{
channel->queue_cb = cb;
channel->queue_opaque = opaque;
}
void
stream_channel_reset(StreamChannel *channel)
{
struct {
StreamMsgStartStop base;
uint8_t codecs_buffer[MAX_SUPPORTED_CODECS];
} start_msg;
StreamMsgStartStop *const start = &start_msg.base;
// send destroy old stream
channel->pipes_add_type(RED_PIPE_ITEM_TYPE_STREAM_DESTROY);
// destroy display surface
if (channel->width != 0 && channel->height != 0) {
channel->pipes_add_type(RED_PIPE_ITEM_TYPE_SURFACE_DESTROY);
}
channel->stream_id = -1;
channel->width = 0;
channel->height = 0;
if (!channel->is_connected()) {
return;
}
// try to request a new stream, this should start a new stream
// if the guest is connected to the device and a client is already connected
start->num_codecs = stream_channel_get_supported_codecs(channel, start->codecs);
// send in any case, even if list is not changed
// notify device about changes
request_new_stream(channel, start);
}