public:it:spice:codec-agent-trans

SPICE 视频重定向的传输路径选择

  • 数据传输通过 StreamDev,StreamDev 是  char_device 的一种,虚拟化上是 virtio spiceport类型设备;
  • guest 与 server 之间的协议(stream-device.h)是额外自定添加的;不影响 server 与 client 之间的协议
  • 在 server 与 client 之间没有增加新类型通道,stream-channel 实现的通道类型是 DisplayChannel 
    StreamChannel*
    stream_channel_new(RedsState *server, uint32_t id)
    {
        return 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,
                            "migration-flags", 0,
                            "handle-acks", TRUE, // TODO sure ??
                            NULL);
    }
  • 在 client 端,收到通道列表时就会额外创建一个屏幕来对应这个 DisplayChannel,  表现为有两个屏幕显示
  • 发送创建 SPICE_MSG_DISPLAY_SURFACE_CREATE 消息时,标识 SPICE_SURFACE_FLAGS_STREAMING_MODE 来表示全屏流显示
    // give an hint to client that we are sending just streaming
    // see spice.proto for capability check here
    if (red_channel_client_test_remote_cap(rcc, SPICE_DISPLAY_CAP_MULTI_CODEC)) {
    surface_create.flags |= SPICE_SURFACE_FLAGS_STREAMING_MODE;
    }
  • server 与 client 之间传输视频流,  SPICE_MSG_DISPLAY_STREAM_DATA
  • 视频重定向如果直接借用这种方式来传输流,需要修改客户端对新增 DisplayChannel 的操作,魔改协议,得不偿失
  • 问题:是否可以直接把数据插入到主 DisplayChannel 的 视频流里?  有点复杂

 

  • guest 与 server 之间的传输方式是相同的,都是  spiceport  virtio 设备
  • server 与 client 之间传输新增了一个通道类型 WebDAVChannel,但 spice-protocol 里 只新增了通道类型标识 SPICE_CHANNEL_WEBDAV,没有新增其它消息 ;再看 WebDAVChannel 的声明如下:
    channel PortChannel : SpicevmcChannel {
     client:
        message {
            uint8 event;
        } @declare event = 201;
     server:
        message {
            uint32 name_size;
            uint8 *name[name_size] @zero_terminated @marshall @nonnull;
            uint8 opened;
        } @declare init = 201;
        message {
            uint8 event;
        } @declare event;
    };
     
    channel WebDAVChannel : PortChannel {
    };

    可以看到  WebDAVChannel 协议是继承 PortChannel 通道协议,而且完全没有新增元素

  • 查看 server 代码,是能自动识别创建任意名称的 PortChannel 通道:
        else if (strcmp(char_device->subtype, SUBTYPE_PORT) == 0) {
            if (strcmp(char_device->portname, "org.spice-space.webdav.0") == 0) {
                dev_state = spicevmc_device_connect(reds, char_device, SPICE_CHANNEL_WEBDAV);
            } else if (strcmp(char_device->portname, "org.spice-space.stream.0") == 0) {
                dev_state = RED_CHAR_DEVICE(stream_device_connect(reds, char_device));
            } else {
                dev_state = spicevmc_device_connect(reds, char_device, SPICE_CHANNEL_PORT);
            }
        }
  • 那么传输路径与其复用WebDAVChannel 不如直接创建新名称的 PortChannel 

直接使用 PortChannel 协议进行视频重定向流传输路径

  • 优点:只借用已有通道类型,不需要修改新增 SPICE 协议, guest与client之间的传输数据内容即可自行随意定义
  • 优点:不需要修改 spice-server 代码,
  • 缺点:spice-gtk 客户端需要和 之前的 demo codec-agent 一样自行解码收到的视频数据,并根据坐标自行画在屏幕,并处理遮盖问题

直接把数据插入到主 DisplayChannel 的 视频流里传输

  • 优点:不需要动客户端代码
  • 缺点:需要修改 spice-server 代码,介入到主  DisplayChannel 对视频流的处理(涉及到播放区域与视频流编号),复杂度未能估算
  • 缺点:相比方案一,遮盖计算从client 转移到了 server
  • 缺点:如果不修改 server 与 client 之间的协议,只能传输符合 SPICE 协议格式的数据, 比如 视频流编码格式必须一致
  • virtio 的传输速率是否足够快? 足够
  • virtio 是否足够稳定?
  • portchannel 是否有足够稳定的用例?

在试验时发现 spice-gtk 里的工具  spicy.c 已经有对特定名称为 org.spice.spicy 的 spiceport 的 传输测试代码,所以测试 portchannel 传输就比较简单了:

  • guest Windows10虚拟机添加名为  org.spice.spicy 的  spiceport 通道设备:
    <channel type="spiceport">
      <source channel="org.spice.spicy"/>
      <target type="virtio" name="org.spice.spicy" state="disconnected"/>
      <alias name="channel2"/>
      <address type="virtio-serial" controller="0" bus="0" port="3"/>
    </channel>
  • 用 VS 编译个测试串口程序, 编译为  TestPortChannel.exe, 对应串口名为 \\.\Global\org.spice.spicy
    #include <iostream>
    #include <windows.h>
    //#define SPICE_PORT_NAME  L"\\\\.\\Global\\com.troila.newbee.0"
    #define SPICE_PORT_NAME_SPICY L"\\\\.\\Global\\org.spice.spicy"
     
    int main()
    {
        HANDLE port_handle = ::CreateFile(SPICE_PORT_NAME_SPICY,
            GENERIC_WRITE | GENERIC_READ,
            0,
            NULL,
            OPEN_EXISTING,
            0,//FILE_FLAG_OVERLAPPED,
            NULL);
        if (port_handle == INVALID_HANDLE_VALUE) {
            std::cout << "open spice port failed! err:" << ::GetLastError() << std::endl;
        }else {
            std::cout << "open spice port successfully" << std::endl;
            char write_buf[] = "hello troila!\n";
            const DWORD buf_size = sizeof(write_buf);
            char read_buf[buf_size] = {0};
            DWORD size = 0;
            DWORD totalsize = 0;
            BOOL ret = ::WriteFile(port_handle, (LPCVOID)write_buf, buf_size, &size, NULL);
            if (ret) {
                std::cout << "write successfully" << std::endl;
            }
            else {
                std::cout << "write failed! err:" << ::GetLastError() << std::endl;
                goto end;
            }
            size = 0; 
            while (totalsize < buf_size) {
                ret = ::ReadFile(port_handle, read_buf+totalsize, buf_size-totalsize, &size, NULL);
     
                if (!ret)
                {
                    std::cout << "write failed! err:" << ::GetLastError() << std::endl;
                    goto end;
                }
     
                totalsize += size;
                std::cout << "read " << size << std::endl;
            }
            std::cout << "read content: " << read_buf << std::endl;
     end:
            ret = ::CloseHandle(port_handle);
            std::cout << "close handle ret:" << ret << std::endl;
        }
    }
  • 在 client 端(Ubuntu 18.04) 安装 sudo apt install spice-client-gtk,  启动  spicy 并连接
  • 在 guest 使用管理员权限执行 TestPortChannel.exe, 此时  spicy 日志显示对应  portchannel 打开并收到了 hello troila!
  • 在 spicy 随意输入十几个字母,guest 端 TestPortChannel.exe 日志显示收到对应字母,并关闭串口
    open spice port successfully
    write successfully
    read 1
    ...
    read content: hello guest!!!
    close handle ret:1
  • spicy 显示 对应 portchannel 已关闭
    ** Message: 10:53:14.947: main channel: opened
    port 0x5561c6fcd270 org.spice.spicy: opened
    hello troila!
    port 0x5561c6fcd270 org.spice.spicy: closed

    双向传输验证完毕

3.2.1  spice-gtk 对 port-channel 的处理流程

MainChannelSpicyport通道创建SPICE_MSG_MAIN_CHANNELS_LISTg_object_new()SPICE_CHANNEL_PORTPortChannelsignal "channel-new"channel_new() 进行初始化数据传输SPICE_MSG_SPICEVMC_DATAport_handle_msg()signal "port-data"port_data() 输出数据到 stdinspice-gtk 中 port-channel 的处理流程 V0.1.0 by weiyongjiu

PortChannel 对象构造过程:

  • PortChannel 继承 SpiceChannel, 首先构造 spice_channel_class_init()
    • spice_channel_constructed()
    • spice_session_channel_new()
    • g_signal_emit(session, signals[SPICE_SESSION_CHANNEL_NEW], 0, channel);
    • SPICE_SESSION_CHANNEL_NEW “channel-new”
  • PortChannel 自身构造函数 spice_port_channel_class_init()
    • channel_set_handlers() 设置以下消息对应的处理函数 
      • SPICE_MSG_PORT_INIT
      • SPICE_MSG_PORT_EVENT
      • SPICE_MSG_SPICEVMC_DATA
    • 消息对应的处理函数 port_handle_msg()
    • g_coroutine_signal_emit() SPICE_PORT_DATA “port-data”

3.3 client 端 port-channel 的使用方式

  • spice-gtk 实现了一个 SpicePortChannel,  详细文档 https://www.spice-space.org/api/spice-gtk/SpicePortChannel.html
  • 增加对 glib 信号 SPICE_SESSION_CHANNEL_NEW channel-new的处理函数,按类型与通道名来获知特定名字 portchannel 的建立,并进行初始化
  • 初始化时增加对该通道信号 port-event,port-data 的处理函数, 
  • 响应 port-event 用于获取通道打开关闭事件
  • 响应 port-data 来接收数据
  • 使用函数 spice_port_write_async,spice_port_write_finish 来发送数据  
  • public/it/spice/codec-agent-trans.txt
  • 最后更改: 2022/06/29 17:46
  • oakfire