OpenBMC开发之obmc-ikvm代码架构

打印 上一主题 下一主题

主题 1706|帖子 1706|积分 5118

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
1. obmc-ikmv 文件

创建了 ikvm::Args 和 ikvm::Manager 两个实例,然后运行管理器 manager.run()
  1. int main(int argc, char* argv[])
  2. {
  3.     // 解析命令行参数
  4.     ikvm::Args args(argc, argv);
  5.     // 创建管理器实例,传入参数
  6.     ikvm::Manager manager(args);
  7.     // 运行管理器
  8.     manager.run();
  9.     // 主函数返回值,表示程序正常退出
  10.     return 0;
  11. }
复制代码
2. ikvm_args 文件

2.1 头文件

界说了 struct CommandLine、frameRate、subsampling、udcName、videoPath、calcFrameCRC、commandLine 等几个变量
  1. namespace ikvm
  2. {
  3. class Args
  4. {
  5.   public:
  6.     struct CommandLine
  7.     {
  8.         CommandLine(int c, char** v) : argc(c), argv(v) {}
  9.         int argc;
  10.         char** argv;
  11.     };
  12.   Args(int argc, char* argv[]);
  13.   private:
  14.     void printUsage();
  15.     int frameRate;
  16.     /* @brief Desired subsampling (0: 444, 1: 420) */
  17.     int subsampling;
  18.     /* @brief Path to the USB keyboard device */
  19.     std::string keyboardPath;
  20.     /* @brief Path to the USB mouse device */
  21.     std::string pointerPath;
  22.     /* @brief Name of UDC */
  23.     std::string udcName;
  24.     /* @brief Path to the V4L2 video device */
  25.     std::string videoPath;
  26.     /* @brief Identical frames detection */
  27.     bool calcFrameCRC;
  28.     /* @brief Original command line arguments passed to the application */
  29.     CommandLine commandLine;
  30. };
  31. } // namespace ikvm
复制代码
2.2 源文件

mian 函数中创建对象时:ikvm::Args args(argc, argv)
frameRate = 30,subsampling = 0,calcFrameCRC = false,argc 和 argv 存入 struct commandLine
struct option 为标准结构体界说,4 个数据界说分别为:长选项名称、是否必要参数、标志、短选项名称
getopt_long 的用法:分标志是否即是 0 两种环境,可见下面代码解释,再详细参考 AI 代码解释
  1. Args::Args(int argc, char* argv[]) :
  2.     frameRate(30), subsampling(0), calcFrameCRC{false}, commandLine(argc, argv)
  3. {
  4.     int option;
  5.     const char* opts = "f:s:h:k:p:u:v:c";
  6.     struct option lopts[] = {           // 长选项名称、是否需要参数、标志、短选项名称
  7.         {"frameRate", 1, 0, 'f'},       // 设置帧率的选项
  8.         {"subsampling", 1, 0, 's'},     // 设置子采样的选项:1 = YUV420, 0 = YUV444
  9.         {"help", 0, 0, 'h'},            // 帮助选项
  10.         {"keyboard", 1, 0, 'k'},        // 指定键盘设备路径的选项:/dev/hidg0
  11.         {"mouse", 1, 0, 'p'},           // 指定鼠标设备路径的选项:/dev/hidg1
  12.         {"udcName", 1, 0, 'u'},         // 指定UDC名称的选项:1e6a0000.usb-vhub:pX
  13.         {"videoDevice", 1, 0, 'v'},     // 指定视频设备路径的选项:/dev/video0
  14.         {"calcCRC", 0, 0, 'c'},         // 启用CRC计算的选项
  15.         {0, 0, 0, 0}};                  // 选项结束
  16.     // /usr/bin/obmc-ikvm -v /dev/video0 -k /dev/hidg0 -p /dev/hidg1
  17.     // 当标志Buff[2]=0时,option返回值为Buff【3】的值,当flag=1时,option返回值为Buff【2】的值
  18.     while ((option = getopt_long(argc, argv, opts, lopts, NULL)) != -1)
  19.     {
  20.         switch (option)
  21.         {
  22.             case 'f': //optarg是getopt_long函数解析到的参数值,是 <getopt.h> 头文件中定义的一个 extern char *optarg 变量
  23.                 frameRate = (int)strtol(optarg, NULL, 0);  // 解析帧率
  24.                 if (frameRate < 0 || frameRate > 60)       // 验证帧率范围
  25.                     frameRate = 30;                        // 如果超出范围则重置为默认值
  26.                 break;
  27.             case 's':
  28.                 subsampling = (int)strtol(optarg, NULL, 0);  // 解析子采样
  29.                 if (subsampling < 0 || subsampling > 1)      // 验证子采样范围
  30.                     subsampling = 0;                         // 如果超出范围则重置为默认值
  31.                 break;
  32.             case 'h':
  33.                 printUsage();  // 打印使用信息
  34.                 exit(0);       // 打印帮助信息后退出程序
  35.             case 'k':
  36.                 keyboardPath = std::string(optarg);  // 设置键盘设备路径
  37.                 break;
  38.             case 'p':
  39.                 pointerPath = std::string(optarg);   // 设置鼠标设备路径
  40.                 break;
  41.             case 'u':
  42.                 udcName = std::string(optarg);       // 设置UDC名称
  43.                 break;
  44.             case 'v':
  45.                 videoPath = std::string(optarg);     // 设置视频设备路径
  46.                 break;
  47.             case 'c':
  48.                 calcFrameCRC = true;                 // 启用CRC计算
  49.                 break;
  50.         }
  51.     }
  52. }
复制代码
3. ikvm_manager 文件

3.1 头文件

界说的几个变量和 **input、video、server **对象如下所示,必要注意的地方有:
explicit Manager(const Args& args) 关键字,用于进步代码安全性,防止隐式转换,主要应用场景为单参数构造函数中,例如:
Args args;
Manager m(args); //表现调用构造函数,精确
**Manager m = args; **//隐式调用构造函数,错误,详细来说,如果一个构造函数只有一个参数,编译器大概会自动将该参数范例隐式转换为当前类的范例
  1. namespace ikvm
  2. {
  3. class Manager
  4. {
  5.   public:
  6.     explicit Manager(const Args& args);
  7.     ~Manager() = default;
  8.     Manager(const Manager&) = default;
  9.     Manager& operator=(const Manager&) = default;
  10.     Manager(Manager&&) = default;
  11.     Manager& operator=(Manager&&) = default;
  12.     /* @brief Begins operation of the VNC server */
  13.     void run();
  14.   private:
  15.     static void serverThread(Manager* manager);
  16.     /* @brief Notifies thread waiters that RFB operations are complete */
  17.     void setServerDone();
  18.     /* @brief Notifies thread waiters that video operations are complete */
  19.     void setVideoDone();
  20.     /* @brief Blocks until RFB operations complete */
  21.     void waitServer();
  22.     /* @brief Blocks until video operations are complete */
  23.     void waitVideo();
  24.     bool continueExecuting;
  25.     /* @brief Boolean to indicate that RFB operations are complete */
  26.     bool serverDone;
  27.     /* @brief Boolean to indicate that video operations are complete */
  28.     bool videoDone;
  29.     /* @brief Input object */
  30.     Input input;
  31.     /* @brief Video object */
  32.     Video video;
  33.     /* @brief RFB server object */
  34.     Server server;
  35.     /* @brief Condition variable to enable waiting for thread completion */
  36.     std::condition_variable sync;
  37.     /* @brief Mutex for waiting on condition variable safely */
  38.     std::mutex lock;
  39. };
  40. } // namespace ikvm
复制代码
3.2 源文件

创建 Manager 对象:input、video、server 对象初始化
continueExecuting = true,serverDone = false,videoDone = true
假设实行: ExecStart=/usr/bin/obmc-ikvm -v /dev/video0 -k /dev/hidg0 -p /dev/hidg1
getKeyboardPath = /dev/hidg0
getPointerPath = /dev/hidg1
getVideoPath = /dev/video0
  1. Manager::Manager(const Args& args) :
  2.     continueExecuting(true), serverDone(false), videoDone(true),
  3.     input(args.getKeyboardPath(), args.getPointerPath(), args.getUdcName()),
  4.     video(args.getVideoPath(), input, args.getFrameRate(),
  5.           args.getSubsampling()),
  6.     server(args, input, video)
  7. {}
复制代码
实行管理器 Manager.run()
continueExecuting 初始化时赋值 true
server.wantsFrame()来源于 ikvm_server.hpp 中的成员函数inline bool wantsFrame() const{return server->clientHead;},只要有 kvm 会话,该值就不为 NULL
  1. void Manager::run()
  2. {
  3.     // 启动服务器线程,执行 serverThread 函数
  4.     std::thread run(serverThread, this);
  5.     // 主循环,持续运行直到 continueExecuting 为 false
  6.     while (continueExecuting)
  7.     {
  8.         // 检查服务器是否需要新的帧
  9.         if (server.wantsFrame())
  10.         {
  11.             // 启动视频设备
  12.             video.start();
  13.             // 获取视频帧
  14.             video.getFrame();
  15.             // 发送帧到服务器
  16.             server.sendFrame();
  17.         }
  18.         else
  19.         {
  20.             // 如果不需要帧,则停止视频设备
  21.             video.stop();
  22.         }
  23.         // 检查视频是否需要调整分辨率
  24.         if (video.needsResize())
  25.         {
  26.             // 等待服务器线程完成当前操作
  27.             waitServer();
  28.             // 标记视频操作未完成
  29.             videoDone = false;
  30.             // 调整视频分辨率
  31.             video.resize();
  32.             // 调整服务器分辨率
  33.             server.resize();
  34.             // 标记视频操作完成,并通知等待的线程
  35.             setVideoDone();
  36.         }
  37.         else
  38.         {
  39.             // 标记视频操作完成,并通知等待的线程
  40.             setVideoDone();
  41.             // 等待服务器线程完成当前操作
  42.             waitServer();
  43.         }
  44.     }
  45.     // 等待服务器线程结束
  46.     run.join();
  47. }
复制代码
**创建线程 serverThread(),实行 ****server.run()**函数,该 run 函数比力紧张,是调用 libvncserver 库rfbProcessEvents()处理函数的开始
  1. void Manager::serverThread(Manager* manager)
  2. {
  3.     while (manager->continueExecuting)
  4.     {
  5.         manager->server.run();
  6.         manager->setServerDone();
  7.         manager->waitVideo();
  8.     }
  9. }
复制代码
4. ikvm_server 文件

4.1 头文件

Input& input;Video& video;是引用范例的成员变量,通过server(args, input, video)传递
  1. namespace ikvm
  2. {
  3. class Server
  4. {
  5.   public:
  6.     struct ClientData
  7.     {
  8.         ClientData(int s, Input* i) : skipFrame(s), input(i), last_crc{-1}
  9.         {
  10.             needUpdate = false;
  11.         }
  12.         int skipFrame;
  13.         Input* input;
  14.         bool needUpdate;
  15.         int64_t last_crc;
  16.     };
  17.     Server(const Args& args, Input& i, Video& v);
  18.     ~Server();
  19.     Server(const Server&) = default;
  20.     Server& operator=(const Server&) = default;
  21.     Server(Server&&) = default;
  22.     Server& operator=(Server&&) = default;
  23.     void resize();
  24.     void run();
  25.     void sendFrame();
  26.     inline bool wantsFrame() const
  27.     {
  28.         return server->clientHead;
  29.     }
  30.     inline const Video& getVideo() const
  31.     {
  32.         return video;
  33.     }
  34.   private:
  35.     static void clientFramebufferUpdateRequest(
  36.         rfbClientPtr cl, rfbFramebufferUpdateRequestMsg* furMsg);
  37.     static void clientGone(rfbClientPtr cl);
  38.     static enum rfbNewClientAction newClient(rfbClientPtr cl);
  39.     void doResize();
  40.     bool pendingResize;
  41.     int frameCounter;
  42.     unsigned int numClients;
  43.     long int processTime;
  44.     rfbScreenInfoPtr server;
  45.     Input& input;                  //创建引用,引用ikvm_manager.hpp中的定义
  46.     Video& video;                  //创建引用,引用ikvm_manager.hpp中的定义
  47.     std::vector<char> framebuffer;
  48.     bool calcFrameCRC;
  49.     static constexpr int cursorWidth = 20;
  50.     static constexpr int cursorHeight = 20;
  51. };
  52. } // namespace ikvm
复制代码
4.2 源文件

ikvm_manager.hpp 中创建对象 Server,ikvm_manager.cpp 中 Manager 对象构造的时间初始化 server(args, input, video)
分辨率调整 pendingResize = false,帧数 frameCounter = 0, 会话数 numClients = 0
通过 rfbScreenInfoPtr server = rfbGetScreen( )获取紧张的屏幕缓冲结构体,该结构体是唯一的,在创建 Server 对象时创建,后面会通过 Server::run()函数中的rfbProcessEvents()与 libvncserver 创建关联,此处关系可详见<<OpenBMC开发之obmc-ikvm与libvncserver的连理关系>>
  1. Server::Server(const Args& args, Input& i, Video& v) :
  2.     pendingResize(false), frameCounter(0), numClients(0), input(i), video(v)
  3. {
  4.     std::string ip("localhost");
  5.     const Args::CommandLine& commandLine = args.getCommandLine();
  6.     int argc = commandLine.argc;
  7.     // ikvm_server.hpp: rfbScreenInfoPtr server
  8.     server = rfbGetScreen(&argc, commandLine.argv, video.getWidth(),
  9.                           video.getHeight(), Video::bitsPerSample,
  10.                           Video::samplesPerPixel, Video::bytesPerPixel);
  11.     if (!server)
  12.     {
  13.         log<level::ERR>("Failed to get VNC screen due to invalid arguments");
  14.         elog<InvalidArgument>(
  15.             xyz::openbmc_project::Common::InvalidArgument::ARGUMENT_NAME(""),
  16.             xyz::openbmc_project::Common::InvalidArgument::ARGUMENT_VALUE(""));
  17.     }
  18.     framebuffer.resize(
  19.         video.getHeight() * video.getWidth() * Video::bytesPerPixel, 0);
  20.     server->screenData = this;                    //Server对象
  21.     server->desktopName = "OpenBMC IKVM";
  22.     server->frameBuffer = framebuffer.data();     //数据缓冲区
  23.     server->newClientHook = newClient;            //创建新连接时的回调函数,下面详细分析
  24.     server->cursor = rfbMakeXCursor(cursorWidth, cursorHeight, (char*)cursor,
  25.                                     (char*)cursorMask);
  26.     server->cursor->xhot = 1;
  27.     server->cursor->yhot = 1;
  28.     rfbStringToAddr(&ip[0], &server->listenInterface);
  29.     rfbInitServer(server);
  30.     rfbMarkRectAsModified(server, 0, 0, video.getWidth(), video.getHeight());
  31.     server->kbdAddEvent = Input::keyEvent;
  32.     server->ptrAddEvent = Input::pointerEvent;
  33.     processTime = (1000000 / video.getFrameRate()) - 100;
  34.     calcFrameCRC = args.getCalcFrameCRC();
  35. }
复制代码
newClient 回调函数处理:input.connect/disconnect 意味着开源框架当有会话时,配置 usbgadget hid 设备使能键鼠,无会话时,断开 hid udc,关闭虚拟鼠标功能
  1. enum rfbNewClientAction Server::newClient(rfbClientPtr cl)
  2. {
  3.     Server* server = (Server*)cl->screen->screenData;
  4.     cl->clientData =
  5.         new ClientData(server->video.getFrameRate(), &server->input);           //创建ClientData结构体并赋值到cl->clientData
  6.     cl->clientGoneHook = clientGone;                                            //定义关闭连接回调函数:释放clientData,0会话时input->disconnect()
  7.     cl->clientFramebufferUpdateRequestHook = clientFramebufferUpdateRequest;    //数据帧缓冲区请求回调函数:cd->needUpdate = true
  8.     if (!server->numClients++)                                                  //仅仅在创建第一个会话时,执行input.connect()
  9.     {
  10.         server->input.connect();
  11.         server->pendingResize = false;
  12.         server->frameCounter = 0;
  13.     }
  14.     return RFB_CLIENT_ACCEPT;
  15. }
复制代码
5. ikvm_video 文件

5.1 头文件

界说了 video 关键的几个参数,例如帧率frameRate、宽width、高height、采样率subSampling 等
界说了数据缓冲区std::vector buffers,Buffer 是一个结构体,内里有四个元素,分别为:void* data,bool queued,size_t payload,size_t size
std::vector 是是 C++ 标准库中的一个动态数组容器,它可以或许存储一系列相同范例的元素,并支持动态调整大小。代码中是在 Video::resize()函数中通过buffers.resize(req.count)动态调整大小
  1. namespace ikvm
  2. {
  3. class Video
  4. {
  5.   public:
  6.     Video(const std::string& p, Input& input, int fr = 30, int sub = 0);
  7.     char* getData();
  8.     void getFrame();
  9.     bool needsResize();
  10.     void resize();
  11.     void start();
  12.     void stop();
  13.     void restart()
  14.     {
  15.         stop();
  16.         start();
  17.     }
  18.     inline int getFrameRate() const
  19.     {
  20.         return frameRate;
  21.     }
  22.     inline size_t getFrameSize() const
  23.     {
  24.         return buffers[lastFrameIndex].payload;
  25.     }
  26.     inline size_t getHeight() const
  27.     {
  28.         return height;
  29.     }
  30.     inline uint32_t getPixelformat() const
  31.     {
  32.         return pixelformat;
  33.     }
  34.     inline size_t getWidth() const
  35.     {
  36.         return width;
  37.     }
  38.     inline int getSubsampling() const
  39.     {
  40.         return subSampling;
  41.     }
  42.     inline void setSubsampling(int _sub)
  43.     {
  44.         subSampling = _sub;
  45.     }
  46.     static const int bitsPerSample;
  47.     static const int bytesPerPixel;
  48.     static const int samplesPerPixel;
  49.   private:
  50.     struct Buffer
  51.     {
  52.         Buffer() : data(nullptr), queued(false), payload(0), size(0) {}
  53.         void* data;
  54.         bool queued;
  55.         size_t payload;
  56.         size_t size;
  57.     };
  58.     bool resizeAfterOpen;
  59.     bool timingsError;
  60.     int fd;
  61.     int frameRate;
  62.     int lastFrameIndex;
  63.     size_t height;
  64.     size_t width;
  65.     int subSampling;
  66.     Input& input;
  67.     const std::string path;
  68.     std::vector<Buffer> buffers;
  69.     uint32_t pixelformat;
  70. };
  71. } // namespace ikvm
复制代码
5.2源文件

创建 Video 对象,初始化赋值:分辨率 800*600、采样率subSampling = args.getSubsampling()初始化值为 0、path = /dev/video0
界说了几个关键函数:Video::getData()、Video::getFrame()、bool Video::needsResize()、void Video::resize()、void Video::start()、void Video::stop()
  1. Video::Video(const std::string& p, Input& input, int fr, int sub) :
  2.     resizeAfterOpen(false), timingsError(false), fd(-1), frameRate(fr),
  3.     lastFrameIndex(-1), height(600), width(800), subSampling(sub), input(input),
  4.     path(p), pixelformat(V4L2_PIX_FMT_JPEG)
  5. {}
复制代码
Video::start(),必要注意的几点是:
初次实行时 ,完全实行全部代码,创建全局句柄 fd,后续再次实行会通过判断 fd 来判断/dev/video0 是否打开,如打开就无需重复设定
然后,主要实行以下操作:叫醒输入设备、设置帧率、设置采样方式 YUV420/444、调整视频流大小、分辨率调整
  1. /**
  2. * @brief 启动视频设备并进行初始化
  3. *
  4. * 本函数负责打开视频设备,查询并设置设备的参数,如分辨率和帧率等。
  5. * 它还负责调整视频流的格式和参数,以满足应用程序的需求。
  6. */
  7. void Video::start()
  8. {
  9.     int rc;
  10.     size_t oldHeight = height;
  11.     size_t oldWidth = width;
  12.     v4l2_capability cap;
  13.     v4l2_format fmt;
  14.     v4l2_streamparm sparm;
  15.     v4l2_control ctrl;
  16.     // 如果文件描述符fd大于等于0,则表明设备已经打开,直接返回
  17.     if (fd >= 0)
  18.     {
  19.         return;
  20.     }
  21.     // 发送唤醒数据包以激活输入设备
  22.     input.sendWakeupPacket();
  23.     // 打开视频设备文件
  24.     fd = open(path.c_str(), O_RDWR);
  25.     if (fd < 0)
  26.     {
  27.         // 如果打开设备失败,记录错误日志并抛出异常
  28.         log<level::ERR>("Failed to open video device",
  29.                         entry("PATH=%s", path.c_str()),
  30.                         entry("ERROR=%s", strerror(errno)));
  31.         elog<Open>(
  32.             xyz::openbmc_project::Common::File::Open::ERRNO(errno),
  33.             xyz::openbmc_project::Common::File::Open::PATH(path.c_str()));
  34.     }
  35.     // 查询视频设备的能力
  36.     memset(&cap, 0, sizeof(v4l2_capability));
  37.     rc = ioctl(fd, VIDIOC_QUERYCAP, &cap);
  38.     if (rc < 0)
  39.     {
  40.         // 如果查询设备能力失败,记录错误日志并抛出异常
  41.         log<level::ERR>("Failed to query video device capabilities",
  42.                         entry("ERROR=%s", strerror(errno)));
  43.         elog<ReadFailure>(
  44.             xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_ERRNO(
  45.                 errno),
  46.             xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_DEVICE_PATH(path.c_str()));
  47.     }
  48.     // 检查视频设备是否支持视频捕获和流式传输
  49.     if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) ||
  50.         !(cap.capabilities & V4L2_CAP_STREAMING))
  51.     {
  52.         // 如果不支持,记录错误日志并抛出异常
  53.         log<level::ERR>("Video device doesn't support this application");
  54.         elog<Open>(
  55.             xyz::openbmc_project::Common::File::Open::ERRNO(errno),
  56.             xyz::openbmc_project::Common::File::Open::PATH(path.c_str()));
  57.     }
  58.     // 查询视频设备的格式
  59.     memset(&fmt, 0, sizeof(v4l2_format));
  60.     fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  61.     rc = ioctl(fd, VIDIOC_G_FMT, &fmt);
  62.     if (rc < 0)
  63.     {
  64.         // 如果查询设备格式失败,记录错误日志并抛出异常
  65.         log<level::ERR>("Failed to query video device format",
  66.                         entry("ERROR=%s", strerror(errno)));
  67.         elog<ReadFailure>(
  68.             xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_ERRNO(
  69.                 errno),
  70.             xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_DEVICE_PATH(path.c_str()));
  71.     }
  72.     // 设置视频设备的帧率参数
  73.     memset(&sparm, 0, sizeof(v4l2_streamparm));
  74.     sparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  75.     sparm.parm.capture.timeperframe.numerator = 1;
  76.     sparm.parm.capture.timeperframe.denominator = frameRate;
  77.     rc = ioctl(fd, VIDIOC_S_PARM, &sparm);
  78.     if (rc < 0)
  79.     {
  80.         // 如果设置帧率失败,记录警告日志
  81.         log<level::WARNING>("Failed to set video device frame rate",
  82.                             entry("ERROR=%s", strerror(errno)));
  83.     }
  84.     // 设置视频设备的JPEG色度抽样方式
  85.     ctrl.id = V4L2_CID_JPEG_CHROMA_SUBSAMPLING;
  86.     ctrl.value = subSampling ? V4L2_JPEG_CHROMA_SUBSAMPLING_420
  87.                              : V4L2_JPEG_CHROMA_SUBSAMPLING_444;
  88.     rc = ioctl(fd, VIDIOC_S_CTRL, &ctrl);
  89.     if (rc < 0)
  90.     {
  91.         // 如果设置JPEG色度抽样失败,记录警告日志
  92.         log<level::WARNING>("Failed to set video jpeg subsampling",
  93.                             entry("ERROR=%s", strerror(errno)));
  94.     }
  95.     // 更新视频设备的分辨率和像素格式
  96.     height = fmt.fmt.pix.height;
  97.     width = fmt.fmt.pix.width;
  98.     pixelformat = fmt.fmt.pix.pixelformat;
  99.     // 检查是否支持像素格式
  100.     if (pixelformat != V4L2_PIX_FMT_RGB24 && pixelformat != V4L2_PIX_FMT_JPEG)
  101.     {
  102.         // 如果不支持,记录错误日志
  103.         log<level::ERR>("Pixel Format not supported",
  104.                         entry("PIXELFORMAT=%d", pixelformat));
  105.     }
  106.     // 调整视频流的大小
  107.     resize();
  108.     // 检查分辨率是否发生变化
  109.     if (oldHeight != height || oldWidth != width)
  110.     {
  111.         resizeAfterOpen = true;
  112.     }
  113. }
复制代码
munmap 用于取消之前通过 mmap 系统调用创建的内存映射。它会将指定的内存区域从进程的地址空间中移除
  1. // 停止视频流并释放相关资源
  2. void Video::stop()
  3. {
  4.     int rc;
  5.     unsigned int i;
  6.     v4l2_buf_type type(V4L2_BUF_TYPE_VIDEO_CAPTURE);
  7.     // 如果文件描述符fd小于0,则不执行停止操作,直接返回
  8.     if (fd < 0)
  9.     {
  10.         return;
  11.     }
  12.     // 重置最后帧的索引
  13.     lastFrameIndex = -1;
  14.     // 停止视频流的捕获
  15.     rc = ioctl(fd, VIDIOC_STREAMOFF, &type);
  16.     if (rc)
  17.     {
  18.         // 如果停止失败,记录错误日志
  19.         log<level::ERR>("Failed to stop streaming",
  20.                         entry("ERROR=%s", strerror(errno)));
  21.     }
  22.     // 释放所有缓冲区资源
  23.     for (i = 0; i < buffers.size(); ++i)
  24.     {
  25.         // 如果缓冲区已分配内存,则释放内存并重置相关标志
  26.         if (buffers[i].data)
  27.         {
  28.             munmap(buffers[i].data, buffers[i].size);
  29.             buffers[i].data = nullptr;
  30.             buffers[i].queued = false;
  31.         }
  32.     }
  33.     // 关闭文件描述符
  34.     close(fd);
  35.     fd = -1;
  36. }
复制代码
Video::getFrame()获取底层 Driver 数据存入数据缓冲区std::vector buffers 中,该函数主要实行以下操作

  • 查抄设备文件描述符有效性
  • 设置文件描述符聚集和超时时间
  • 切换非阻塞模式制止永世阻塞
  • 轮询设备获取有效帧数据
  • 处理获取到的视频缓冲区 (VIDIOC_DQBUF:获取数据,以便发送到前端表现
  • 重新列队未使用的缓冲区 (VIDIOC_QBUF: 重排缓冲区逻辑,加入视频采集队列
这里必要注意两个特别紧张的地方:
IOCTRL:VIDIOC_DQBUF


  • 作用 :从视频设备的队列中取出一个已经处理完毕的缓冲区,以便应用步伐可以访问其中的数据
  • 使用场景 :通常在视频捕获时,应用步伐必要从队列中取出已经填充了数据的缓冲区,以便处理或表现这些数据。
IOCTRL:VIDIOC_QBUF


  • 作用 :将缓冲区加入视频设备的队列中,以便设备可以开始处理该缓冲区中的数据
  • 使用场景 :通常在视频捕获或输出时,应用步伐必要将缓冲区加入队列,以便设备可以填充数据(捕获)或发送数据(输出)。
暂时切换到非阻塞模式的目标 :
代码起首使用 fcntl(fd, F_SETFL, fd_flags | O_NONBLOCK) 将文件描述符 fd 设置为非阻塞模式 。 如许做的主要目标是为了共同 select 函数实现一个超机遇制。select 函数会等待文件描述符可读(有数据到来)或超时。如果文件描述符处于阻塞模式,而且没有数据到来,select 会一直阻塞下去,这正是代码解释中提到的“防止视频信号丢失时驱动步伐永世阻塞”的环境。
恢复为阻塞模式的原因:
在 select 调用竣事后,代码立纵然用 fcntl(fd, F_SETFL, fd_flags) 将文件描述符 fd 恢复到之前保存的原始标志 fd_flags。 在暂时修改文件描述符的属性后,应该将其恢复到原始状态,以制止对步伐的其他部分产生不测的影响
**总而言之,**代码暂时将文件描述符设置为非阻塞模式是为了在 select 调用中实现超机遇制,防止步伐在没有视频信号时永世阻塞。在 select 调用竣事后,为了保持文件描述符的原始行为,并制止对其他潜在的操作产生影响,代码会将其恢复到之前的阻塞模式(或其他原始模式)。
  1. void Video::getFrame()
  2. {
  3.     int rc(0); // 定义返回码变量,用于存储函数调用的结果
  4.     int fd_flags; // 定义文件描述符标志变量,用于保存原始的文件描述符状态
  5.     v4l2_buffer buf; // 定义V4L2缓冲区结构体变量,用于存储从设备获取的视频帧信息
  6.     fd_set fds; // 定义文件描述符集合变量,用于select系统调用,监控文件描述符的可读性
  7.     timeval tv; // 定义时间结构体变量,用于select系统调用,设置超时时间
  8.     // 设备有效性检查
  9.     if (fd < 0)
  10.     {
  11.         return; // 如果文件描述符无效(小于0),则直接返回,表示无法获取视频帧
  12.     }
  13.     // 初始化文件描述符集合
  14.     FD_ZERO(&fds); // 清空文件描述符集合,确保集合中没有任何文件描述符
  15.     FD_SET(fd, &fds); // 将视频设备的文件描述符添加到集合中,select将监控这个文件描述符
  16.     // 设置select超时时间为1秒
  17.     tv.tv_sec = 1; // 设置秒部分为1秒
  18.     tv.tv_usec = 0; // 设置微秒部分为0,总共超时时间为1秒
  19.     // 初始化V4L2缓冲区结构
  20.     memset(&buf, 0, sizeof(v4l2_buffer)); // 将缓冲区结构体清零,确保所有成员都被初始化为0
  21.     buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 设置缓冲区类型为视频捕获
  22.     buf.memory = V4L2_MEMORY_MMAP; // 设置缓冲区内存映射模式为MMAP(内存映射)
  23.     /* 切换非阻塞模式(关键安全措施):
  24.      * 防止视频信号丢失时驱动程序永久阻塞
  25.      * 保留原始文件描述符标志用于后续恢复 */
  26.     fd_flags = fcntl(fd, F_GETFL); // 获取当前文件描述符的标志(包括阻塞/非阻塞等状态)
  27.     fcntl(fd, F_SETFL, fd_flags | O_NONBLOCK); // 设置文件描述符为非阻塞模式,即使没有数据可读,read/ioctl等操作也会立即返回,避免程序永久等待
  28.     // 等待设备数据就绪(最长等待1秒)
  29.     rc = select(fd + 1, &fds, NULL, NULL, &tv); // 使用select系统调用等待视频设备文件描述符变为可读状态。fd + 1是需要监控的最大文件描述符加1,中间两个NULL表示不监控写和异常情况,tv是超时时间。
  30.     if (rc > 0) // 如果select返回值大于0,表示在超时时间内有文件描述符就绪(这里是视频设备)
  31.     {
  32.         // 循环处理所有就绪缓冲区
  33.         do
  34.         {
  35.             // 从队列中取出缓冲区
  36.             rc = ioctl(fd, VIDIOC_DQBUF, &buf); // 使用ioctl命令VIDIOC_DQBUF从驱动程序的捕获队列中取出一个已经填充好数据的缓冲区。buf中会包含帧数据的信息。
  37.             if (rc >= 0) // 如果ioctl返回值大于等于0,表示成功取出一个缓冲区
  38.             {
  39.                 buffers[buf.index].queued = false; // 将该缓冲区标记为未排队,表示它正在被处理
  40.                 // 成功获取有效帧处理
  41.                 if (!(buf.flags & V4L2_BUF_FLAG_ERROR)) // 检查缓冲区标志,如果未设置错误标志,则认为这是一个有效的视频帧
  42.                 {
  43.                     lastFrameIndex = buf.index; // 记录最后一个成功获取的帧的索引
  44.                     buffers[lastFrameIndex].payload = buf.bytesused; // 记录该帧的实际数据大小(有效载荷)
  45.                     break; // 获取到有效帧后,跳出当前的do-while循环,因为我们只需要一个最新的有效帧
  46.                 }
  47.                 else    // 错误帧处理
  48.                 {
  49.                     buffers[buf.index].payload = 0; // 如果是错误帧,则将有效载荷大小设置为0
  50.                 }
  51.             }
  52.         } while (rc >= 0); // 持续循环,直到ioctl(VIDIOC_DQBUF)返回错误(表示队列中没有更多已填充的缓冲区)
  53.     }
  54.     // 恢复原始阻塞模式设置
  55.     fcntl(fd, F_SETFL, fd_flags); // 将文件描述符恢复到之前保存的阻塞模式
  56.     /* 缓冲区重新排队逻辑:
  57.      * 1. 跳过当前使用的最后一帧缓冲区
  58.      * 2. 将所有未排队的缓冲区重新加入采集队列
  59.      * 3. 维持环形缓冲区的持续运转 */
  60.     for (unsigned int i = 0; i < buffers.size(); ++i) // 遍历所有已分配的缓冲区
  61.     {
  62.         if (i == (unsigned int)lastFrameIndex) // 如果当前遍历的缓冲区索引是刚刚使用的最后一帧的索引,则跳过,避免重复排队
  63.         {
  64.             continue;
  65.         }
  66.         if (!buffers[i].queued) // 如果当前缓冲区没有被排队(queued标志为false),表示它需要被重新放入采集队列
  67.         {
  68.             // 重新初始化缓冲区结构
  69.             memset(&buf, 0, sizeof(v4l2_buffer)); // 再次清空缓冲区结构体
  70.             buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 设置缓冲区类型为视频捕获
  71.             buf.memory = V4L2_MEMORY_MMAP; // 设置缓冲区内存映射模式为MMAP
  72.             buf.index = i; // 设置缓冲区的索引
  73.             // 将缓冲区加入采集队列
  74.             rc = ioctl(fd, VIDIOC_QBUF, &buf); // 使用ioctl命令VIDIOC_QBUF将缓冲区重新放入驱动程序的捕获队列中,等待驱动程序填充新的视频数据
  75.             if (rc) // 如果ioctl返回值不为0,表示排队操作失败
  76.             {
  77.                 log<level::ERR>("Failed to queue buffer", // 记录错误日志
  78.                                     entry("ERROR=%s", strerror(errno))); // 包含具体的错误信息(通过strerror获取)
  79.             }
  80.             else // 如果排队操作成功
  81.             {
  82.                 buffers[i].queued = true; // 将该缓冲区标记为已排队
  83.             }
  84.         }
  85.     }
  86. }
复制代码
必要特别注意的一点是:当 i== lastFrameIndex 时,buffers.queued 保持不变,意味着不加入采集队列,而 Video::getData()读取的数据正是此帧数据
buffers[lastFrameIndex].data 该数据会通过 Server::sendFrame()发送到 KVM 前端进行渲染表现
buffers[buf.index].queued = false 暂不加入底层采集队列,必要应用层处理的帧数据
buffers[buf.index].queued = true 加入底层采集队列,准备获取底层 Video 数据帧
  1. char* Video::getData()
  2. {
  3.     if (lastFrameIndex >= 0)
  4.     {
  5.         return (char*)buffers[lastFrameIndex].data;
  6.     }
  7.     return nullptr;
  8. }
复制代码
6. ikvm_input 文件

6.1 头文件

界说了键鼠 HID 操作句柄keyboardFd、pointerFd 等几个紧张变量
  1. namespace ikvm
  2. {
  3. class Input
  4. {
  5.   public:
  6.     Input(const std::string& kbdPath, const std::string& ptrPath,
  7.           const std::string& udc);
  8.     void connect();
  9.     void disconnect();
  10.     static void keyEvent(rfbBool down, rfbKeySym key, rfbClientPtr cl);
  11.     static void pointerEvent(int buttonMask, int x, int y, rfbClientPtr cl);
  12.     void sendWakeupPacket();
  13.   private:
  14.     static constexpr int NUM_MODIFIER_BITS = 4;
  15.     static constexpr int KEY_REPORT_LENGTH = 8;
  16.     static constexpr int PTR_REPORT_LENGTH = 6;
  17.     static constexpr const char* hidUdcPath =
  18.         "/sys/kernel/config/usb_gadget/obmc_hid/UDC";
  19.     static constexpr const char* usbVirtualHubPath =
  20.         "/sys/bus/platform/devices/1e6a0000.usb-vhub";
  21.     static constexpr int HID_REPORT_RETRY_MAX = 5;
  22.     static uint8_t keyToMod(rfbKeySym key);
  23.     static uint8_t keyToScancode(rfbKeySym key);
  24.     bool writeKeyboard(const uint8_t* report);
  25.     void writePointer(const uint8_t* report);
  26.     int keyboardFd;
  27.     int pointerFd;
  28.     uint8_t keyboardReport[KEY_REPORT_LENGTH];
  29.     uint8_t pointerReport[PTR_REPORT_LENGTH];
  30.     std::string keyboardPath;
  31.     std::string pointerPath;
  32.     std::string udcName;
  33.     std::map<int, int> keysDown;
  34.     std::ofstream hidUdcStream;
  35.     std::mutex keyMutex;
  36.     std::mutex ptrMutex;
  37. };
  38. } // namespace ikvm
复制代码
6.2 源文件

在 ikvm_manager.cpp 文件中创建了 Input 对象,详细的构造函数如下所示:
对象中界说了一个 std:fstream hidUdcStream 的范例对象,用于文件输出操作
  1. Input::Input(const std::string& kbdPath, const std::string& ptrPath,
  2.              const std::string& udc) :
  3.     keyboardFd(-1),
  4.     pointerFd(-1), keyboardReport{0}, pointerReport{0}, keyboardPath(kbdPath),
  5.     pointerPath(ptrPath), udcName(udc)
  6. {
  7.     hidUdcStream.exceptions(std::ofstream::failbit | std::ofstream::badbit);
  8.     hidUdcStream.open(hidUdcPath, std::ios::out | std::ios::app);
  9. }
复制代码
别的,界说了几个紧张的函数,其中keyEvent 和pointerEvent 分别在创建Server 对象的时间通过回调函数的方式注册给 libvncserver。
rfbScreenInfoPtr rfbScreen->kbdAddEvent = Input::keyEvent;
rfbScreenInfoPtr rfbScreen->kbdAddEvent = Input::pointerEvent;
末了必要注意的一点是,keyEvent 还涉及键值的转换,即 KVM 前端捕获到的本地键盘或虚拟键盘键值 ASCII 通过 WSS 发送到这里,然后转换成标准的 8 字节 HID 数据,终极才通过 USB 发送到 Host 端。
  1. void Input::connect();          //使能HID虚拟设备:echo "1e6a0000.usb-vhub:p6">/sys/kernel/config/usb_gadget/obmc_hid/UDC
  2. void Input::disconnect();       //断开HID虚拟设备:echo " ">/sys/kernel/config/usb_gadget/obmc_hid/UDC
  3. void Input::keyEvent(rfbBool down, rfbKeySym key, rfbClientPtr cl);
  4. void Input::pointerEvent(int buttonMask, int x, int y, rfbClientPtr cl);
  5. bool Input::writeKeyboard(const uint8_t* report);  //键值发送函数
  6. void Input::writePointer(const uint8_t* report);   //鼠标发送函数
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

前进之路

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表