public:it:spice:codec-agent-trans

差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

后一修订版
前一修订版
public:it:spice:codec-agent-trans [2022/02/28 10:36] – 创建 oakfirepublic:it:spice:codec-agent-trans [2022/06/29 17:46] (当前版本) – [3.2 流程详解] oakfire
行 5: 行 5:
  
   * {{:public:it:spice:spice-stream-agent.png |}}   * {{:public:it:spice:spice-stream-agent.png |}}
 +
   * 数据传输通过 StreamDev,StreamDev 是  char_device 的一种,虚拟化上是 **virtio** spiceport类型设备;   * 数据传输通过 StreamDev,StreamDev 是  char_device 的一种,虚拟化上是 **virtio** spiceport类型设备;
   * guest 与 server 之间的协议(stream-device.h)是额外自定添加的;不影响 server 与 client 之间的协议   * guest 与 server 之间的协议(stream-device.h)是额外自定添加的;不影响 server 与 client 之间的协议
行 34: 行 35:
   * 视频重定向如果直接借用这种方式来传输流,需要修改客户端对新增 DisplayChannel 的操作,魔改协议,得不偿失   * 视频重定向如果直接借用这种方式来传输流,需要修改客户端对新增 DisplayChannel 的操作,魔改协议,得不偿失
   * 问题:是否可以直接把数据插入到主 DisplayChannel 的 视频流里?  有点复杂   * 问题:是否可以直接把数据插入到主 DisplayChannel 的 视频流里?  有点复杂
 +==== 1.2 webdav ====
 +  * {{:public:it:spice:webdav.png |}}
 + 
 +  * guest 与 server 之间的传输方式是相同的,都是  spiceport  virtio 设备
 +  * server 与 client 之间传输新增了一个通道类型 WebDAVChannel,但 spice-protocol 里 只新增了通道类型标识 SPICE_CHANNEL_WEBDAV,没有新增其它消息 ;再看 WebDAVChannel 的声明如下:<code c>
 +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 {
 +};
 +</code>可以看到  WebDAVChannel 协议是继承 PortChannel 通道协议,而且完全没有新增元素
 +
 +  * 查看 server 代码,是能自动识别创建任意名称的 PortChannel 通道:<code c>
 +
 +    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);
 +        }
 +    }
 +</code>
 +
 +  * 那么传输路径与其复用WebDAVChannel 不如直接创建新名称的 PortChannel 
 +===== 2 传输方案 =====
 +
 +==== 2.1 方案一 ====
 +直接使用 PortChannel 协议进行视频重定向流传输路径
 +
 +  * {{:public:it:spice:video-redirect-path.png |}}
 +
 +  * 优点:只借用已有通道类型,不需要修改新增 SPICE 协议, guest与client之间的传输数据内容即可自行随意定义
 +  * 优点:不需要修改 spice-server 代码,
 +  * 缺点:spice-gtk 客户端需要和 之前的 demo codec-agent 一样自行解码收到的视频数据,并根据坐标自行画在屏幕,并处理遮盖问题
 +==== 2.2 方案二 ====
 +
 +直接把数据插入到主 DisplayChannel 的 视频流里传输
 +  * 优点:不需要动客户端代码
 +  * 缺点:需要修改 spice-server 代码,介入到主  DisplayChannel 对视频流的处理(涉及到播放区域与视频流编号),复杂度未能估算
 +  * 缺点:相比方案一,遮盖计算从client 转移到了 server
 +  * 缺点:如果不修改 server 与 client 之间的协议,只能传输符合 SPICE 协议格式的数据, 比如 视频流编码格式必须一致
 +==== 2.3 问题 ====
 +
 +  * virtio 的传输速率是否足够快? 足够
 +  * virtio 是否足够稳定?
 +  * portchannel 是否有足够稳定的用例?
 +===== 3 PortChannel 详解 =====
 +
 +
 +==== 3.1 验证 ====
 +
 +在试验时发现 spice-gtk 里的工具  spicy.c 已经有对特定名称为 org.spice.spicy 的 spiceport 的 传输测试代码,所以测试 portchannel 传输就比较简单了:
 +
 +  * guest Windows10虚拟机添加名为  org.spice.spicy 的  spiceport 通道设备:<code xml>
 +
 +<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>
 +</code>
 +
 +  * 用 VS 编译个测试串口程序, 编译为  TestPortChannel.exe, 对应串口名为 ''\\.\Global\org.spice.spicy'' <code c++>
 +
 +#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;
 +    }
 +}
 +</code>
 +
 +
 +  * 在 client 端(Ubuntu 18.04) 安装 ''sudo apt install spice-client-gtk'',  启动  ''spicy'' 并连接
 +  * 在 guest 使用管理员权限执行 TestPortChannel.exe, 此时  spicy 日志显示对应  portchannel 打开并收到了 hello troila!
 +  * 在 spicy 随意输入十几个字母,guest 端 TestPortChannel.exe 日志显示收到对应字母,并关闭串口<code bash>
 +
 +open spice port successfully
 +write successfully
 +read 1
 +...
 +read content: hello guest!!!
 +close handle ret:1
 +</code>
 +
 +  * spicy 显示 对应 portchannel 已关闭<code bash>
 +
 +** Message: 10:53:14.947: main channel: opened
 +port 0x5561c6fcd270 org.spice.spicy: opened
 +hello troila!
 +port 0x5561c6fcd270 org.spice.spicy: closed
 +</code> 双向传输验证完毕
 +
 +
 +==== 3.2 流程详解 ====
 +
 +=== 3.2.1  spice-gtk 对 port-channel 的处理流程 ===
 +
 +<uml>
 +@startuml spice-gtk-port-channel
 +
 +skinparam sequenceMessageAlign center
 +skinparam shadowing false
 +
 +header spice-gtk 中 port-channel 的处理流程 V0.1.0 by weiyongjiu
 +hide footbox
 +participant MainChannel
 +participant PortChannel
 +participant Spicy
 +==port通道创建==
 +[-> MainChannel: SPICE_MSG_MAIN_CHANNELS_LIST  
 +activate MainChannel
 +create PortChannel
 +MainChannel -> PortChannel: g_object_new() \n SPICE_CHANNEL_PORT  
 +deactivate MainChannel
 +
 +PortChannel -> Spicy: signal "channel-new"
 +activate PortChannel
 +activate Spicy
 +Spicy -> Spicy: channel_new() 进行初始化
 +deactivate Spicy
 +deactivate PortChannel
 +
 +==数据传输==
 +
 +[-> PortChannel: SPICE_MSG_SPICEVMC_DATA 
 +activate PortChannel
 +PortChannel -> PortChannel: port_handle_msg()
 +PortChannel -> Spicy++: signal "port-data"
 +
 +Spicy -> Spicy --: port_data() 输出数据到 stdin
 +deactivate PortChannel
 +
 +@enduml
 + </uml>
 +
 +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.1646015802.txt.gz
  • 最后更改: 2022/02/28 10:36
  • oakfire