简易屏幕共享工具-基于WebSocket

前面写了两个简单的屏幕共享工具,不过那只是为了验证通过截屏的方式是否可行,因为通常手动截屏的频率很低,而对于视频来说它的帧率要求就很高了,至少要一秒30帧率左右。所以,经过实际的截屏工具验证,我了解了几个Python截屏库的特点和限制。例如,多数截屏库都不支持很高的截屏速度,并且截屏是典型的 CPU 密集任务(我尝试使用多线程截屏,发现速度更慢了,之后有时间我也会把这一点整理成文章发出来)。
所以,我的初始的想法其实是基于 WebSocket 来实现的。现在,就让我们对先前的代码进行重构,采用 WebSocket 来传输图片数据。不过我这里没有使用到它的双向传输的特性,只是将原来 HTTP 传输的图片换成通过 WebSocket 来传输了。不过这里后续还有很多东西可以开发,如果有时间的话,也可以基于这个做一些有趣的东西。

演示

我这个笔记本的性能可能不太行,我只要打开视频帧率就降低了很多,哈哈。

截取播放B站视频

在这里插入图片描述

截取摄像头画面

这样甚至可以远程共享画面了,如果两个人都布置一个,就可以各自看到对话了(不过没有声音,且效率低下,可能也就只能在局域网使用),不过这样它是相当于从服务器的地方获取的数据,而我们平时使用的视频通话工具都是从客户端获取的数据。

而且页面越多,帧率越低,这里可能要优化一下或者它就是这么累赘,只能个人使用。

在这里插入图片描述

说明

注意,这里的测试环境是 Windows,因为有些库依赖于 Windows 提供的特性,所以需要在 Windows 上运行它。在程序启动时,它会开启一个截屏的线程,不断去获取屏幕的截屏,如果有用户访问,它就会通过 WebSocket 连接,把获取的屏幕截图数据传送给前端,前端的逻辑就是将它绘制在 canvas 上,并添加帧率显示。

注意:这部分前端的代码是 AI 生成,对于验证小功能来说,AI 真是太完美了。而且,其实这整个部分都可以让 AI 来做,但是具体是哪些的组合还是自己选择的,毕竟每个人的偏好和技术栈不同。

代码

所有的代码都在这里了,大概60行代码,我把前端压缩成一行代码了。如果要运行代码先要安装 flask, flask_sock, pillow, dxcam 库。

import time
from flask import Flask
from flask_sock import Sock
from io import BytesIO
from PIL import Image
import dxcam

# 创建应用
app = Flask(__name__)
sockets = Sock(app)
# 整个应用只创建一个即可
camera = dxcam.create(device_idx=0, output_idx=1)  # output_idx 0 是第一块屏幕,1 是第二块屏幕
camera.start(target_fps=60, video_mode=True)
JPEG_QUALITY = 80 # 默认的jpeg图片质量,越高需要的计算量越大,同时越清晰


# 直接前后端写一起了,这个只是一个演示的demo
INDEX_HTML = """
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Screen Sharing</title> <style> body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #f0f0f0; } canvas { border: 1px solid black; } </style> </head> <body> <canvas id="screenCanvas" width="960" height="540"></canvas> <script> const canvas = document.getElementById('screenCanvas'); const ctx = canvas.getContext('2d'); const ws = new WebSocket('ws://'+window.location.host+'/remote_desktop'); ws.onopen = () => { console.log("WebSocket connected"); }; ws.onerror = (event) => { console.error("WebSocket error:", event); }; let lastFrameTime = performance.now(); let frameCount = 0; let fps = 0; ws.onmessage = (event) => { const image = new Image(); image.src = URL.createObjectURL(event.data); image.onload = () => { ctx.drawImage(image, 0, 0, canvas.width, canvas.height); frameCount++; const now = performance.now(); const elapsed = now - lastFrameTime; if (elapsed >= 1000) { fps = frameCount / (elapsed / 1000); frameCount = 0; lastFrameTime = now; } displayFPS(); }; }; function displayFPS() { ctx.font = '30px Arial'; ctx.fillStyle = 'red'; ctx.fillText(`FPS: ${Math.round(fps)}`, 50, 50); } </script> </body> </html>
"""

@app.route('/', methods=['GET'])
def index():
    """简单的前端"""
    return INDEX_HTML


@sockets.route('/remote_desktop')
def get_desktop(ws):
    """
    获取一帧图片,并发送给前端
    """
    buffer = BytesIO()
    fps = 0    # 计算后端生成的帧率
    frame_count = 0
    last_frame_time = time.perf_counter()
    while True:
        reset_buffer(buffer)  # 每次重置buffer,方便复用
        img_data = get_frame(buffer)
        frame_count += 1
        elapsed = time.perf_counter() - last_frame_time
        if elapsed >= 1:
            fps = frame_count // elapsed
            last_frame_time = time.perf_counter()
            frame_count = 0
            print("backend real frame fps: ", fps)
        ws.send(img_data)


def get_frame(buffer):
    """
    获取一帧,然后处理成jpeg并返回二进制数据
    """
    image_array = camera.get_latest_frame()
    Image.fromarray(image_array).save(buffer, format="JPEG", quality=JPEG_QUALITY)
    return buffer.getvalue()


def reset_buffer(buffer):
    # 重置buffer,方便复用
    buffer.truncate(0)
    buffer.seek(0)


if __name__ == '__main__':    
    print("remote desktop server starting...")
    app.run("0.0.0.0", 9000)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/946769.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

yakit-靶场-高级前端加解密与验签实战(for嵌套纯享版)

高级前端加解密与验签实战 一、前端验证签名&#xff08;验签&#xff09;表单&#xff1a;HMAC-SHA256 使用hmac-sha256的十六进制key值可以加密 与页面加密后的值相同 热加载&#xff1a; encryptData func(p) { //sha256key值key codec.DecodeHex("313233343132333…

嵌入式入门Day35

网络编程 Day2 套接字socket基于TCP通信的流程服务器端客户端TCP通信API 基于UDP通信的流程服务器端客户端 作业 套接字socket socket套接字本质是一个特殊的文件&#xff0c;在原始的Linux中&#xff0c;它和管道&#xff0c;消息队列&#xff0c;共享内存&#xff0c;信号等…

模仿微信小程序wx.showModal自定义弹窗,内容可以修改

实现以下弹框样式功能 1.在components新建一个文件showModel.wpy作为组件&#xff0c;复制下面代码 <style lang"less" scoped> .bg_model {display: flex;justify-content: center;align-items: center;// 弹框背景.bg_hui {width: 100%;height: 100%;posi…

如何在 Ubuntu 22.04 上安装并开始使用 RabbitMQ

简介 消息代理是中间应用程序&#xff0c;在不同服务之间提供可靠和稳定的通信方面发挥着关键作用。它们可以将传入的请求存储在队列中&#xff0c;并逐个提供给接收服务。通过以这种方式解耦服务&#xff0c;你可以使其更具可扩展性和性能。 RabbitMQ 是一种流行的开源消息代…

分布式系统架构6:链路追踪

这是小卷对分布式系统架构学习的第6篇文章&#xff0c;关于链路追踪&#xff0c;之前写过traceId的相关内容&#xff1a;https://juejin.cn/post/7135611432808218661&#xff0c;不过之前写的太浅了&#xff0c;且不成系统&#xff0c;只是简单的理解&#xff0c;今天来捋一下…

python opencv的orb特征检测(Oriented FAST and Rotated BRIEF)

官方文档&#xff1a;https://docs.opencv.org/4.10.0/d1/d89/tutorial_py_orb.html SIFT/SURF/ORB对比 https://www.bilibili.com/video/BV1Yw411S7hH?spm_id_from333.788.player.switch&vd_source26bb43d70f463acac2b0cce092be2eaa&p80 ORB代码 import numpy a…

蓝桥杯JAVA刷题--001

文章目录 题目需求2.代码3.总结 题目需求 2.代码 class Solution {public String convertDateToBinary(String date) {if (date null || date.length() ! 10 || date.charAt(4) ! - || date.charAt(7) ! -) {throw new IllegalArgumentException("输入的日期格式不正确&…

WebRTC的线程事件处理

1. 不同平台下处理事件的API&#xff1a; Linux系统下&#xff0c;处理事件的API是epoll或者select&#xff1b;Windows系统下&#xff0c;处理事件的API是WSAEventSelect&#xff0c;完全端口&#xff1b;Mac系统下&#xff0c;kqueue 2. WebRTC下的事件处理类&#xff1a; …

zentao ubuntu上安装

#下载ZenTaoPMS-21.2-zbox_amd64.tar.gz&#xff08;https://www.zentao.net/downloads.html&#xff09; https://dl.zentao.net/zentao/21.2/ZenTaoPMS-21.2-zbox_amd64.tar.gzcd /opt tar -zxvf ZenTaoPMS-21.2-zbox_amd64.tar.gz#启动 /opt/zbox/zbox start /opt/zbox/zbox…

LeetCode算法题——有序数组的平方

题目描述 给你一个按非递减顺序排序的整数数组nums&#xff0c;返回每个数字的平方组成的新数组&#xff0c;要求也按非递减顺序排序。 题解 解法一&#xff1a;暴力解法 思路&#xff1a; 该题目可通过暴力解法解决&#xff0c;即利用for循环遍历数组&#xff0c;对数组每…

vue v-for 数据增加页面不刷新

<div style"float:left;border:1px solid red;height:100px;width:600px;"><el-form-item label"多语言配置" style"width:700px;" prop"validTanleHead"><el-input style"width: 180px" placeholder"请…

前端-动画库Lottie 3分钟学会使用

目录 1. Lottie地址 2. 使用html实操 3. 也可以选择其他的语言 1. Lottie地址 LottieFiles: Download Free lightweight animations for website & apps.Effortlessly bring the smallest, free, ready-to-use motion graphics for the web, app, social, and designs.…

汇编环境搭建

学习视频 将MASM所在目录 指定为C盘

Flutter:打包apk,详细图文介绍(一)

困扰了一天&#xff0c;终于能正常打包apk安装了&#xff0c;记录下打包的流程。建议参考我这篇文章时&#xff0c;同时看下官网的构建说明。 官网构建并发布 Android 应用详情 1、AS创建Flutter项目 2、cmd执行命令 生成一个sunluyi.jks的文件&#xff0c;可以自行把sunluyi替…

单个变量a的妙用

一道清华大学复试上机题 问题&#xff1a;为什么只需要定义一个整数变量a&#xff0c;而不是定义一个数组a[]&#xff1f; 回答 在这段代码中&#xff0c;只需要定义一个整数变量 a&#xff0c;而不是一个数组 a[]&#xff0c;是因为程序的逻辑是逐个处理输入的整数并立即输出…

【YOLOv8模型网络结构图理解】

YOLOv8模型网络结构图理解 1 YOLOv8的yaml配置文件2 YOLOv8网络结构2.1 Conv2.2 C3与C2f2.3 SPPF2.4 Upsample2.5 Detect层 1 YOLOv8的yaml配置文件 YOLOv8的配置文件定义了模型的关键参数和结构&#xff0c;包括类别数、模型尺寸、骨干&#xff08;backbone&#xff09;和头部…

手机租赁平台开发助力智能设备租赁新模式

内容概要 手机租赁平台开发&#xff0c;简单说就是让你用得起高大上的智能设备&#xff0c;不管是最新款的手机、平板&#xff0c;还是那些炫酷的智能耳机&#xff0c;这个平台应有尽有。想要体验但又不希望花大钱&#xff1f;那你就找对地方了&#xff01;通过灵活的租赁方案…

「Mac畅玩鸿蒙与硬件48」UI互动应用篇25 - 简易购物车功能实现

本篇教程将带你实现一个简易购物车功能。通过使用接口定义商品结构&#xff0c;我们将创建一个动态购物车&#xff0c;支持商品的添加、移除以及实时总价计算。 关键词 UI互动应用接口定义购物车功能动态计算商品管理列表操作 一、功能说明 简易购物车功能包含以下交互&#…

Datawhale AI冬令营(第二期)动手学AI Agent task2--学Prompt工程,优化Agent效果

目录 如何写好Prompt&#xff1f; 工具包神器1&#xff1a;Prompt框架——CO-STAR 框架 工具包神器2&#xff1a;Prompt结构优化 工具包神器3&#xff1a;引入案例 案例&#xff1a;构建虚拟女友小冰 1. 按照 CO-STAR框架 梳理目标 2. 撰写Prompt 3. 制作对话生成应用&…

SpringBoot整合springmvc

文章目录 1.SpringMVC的自动管理1.1中央转发器1.1.1Spring boot配置多个DispatcherServlet 1.2控制器1.2.1找到启动类的位置1.2.1.1SpringApplication.run()1.2.1.2SpringApplication 构造方法1.2.1.3deduceMainApplicationClass() 1.2.2ComponentScan 注解 1.3视图解析器自动管…