Python基于WebSocket实现简易屏幕共享工具

 更新时间:2025年01月03日 09:08:49   作者:CrazyDragon_King  
这篇文章主要为大家详细介绍了Python如何基于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)

方法补充

除了上文的方法,小编为大家整理了其他两个实现屏幕共享的方法,希望对大家有所帮助

方法一:50 行代码实现简易屏幕共享工具

思路

它的思路很简单,程序不断的截屏然后发送给浏览器去显示,效果也还凑合吧。这里最重要的就是 multipart/x-mixed-replace; boundary=frame 这个首部的使用了,好几年前还在大学时,我买了一个树莓派+摄像头,使用一个开源的软件叫 motion,搭建了一个局域网的视频 监控。这个 motion 的网页端就是使用该首部实现的,因为我当时很好奇就去了解了它。

代码

如果要运行代码需要安装对应的依赖:

pip install flask, mss, PIL
import time
from io import BytesIO
from flask import Flask, Response
import mss
from PIL import Image, ImageDraw, ImageFont

app = Flask(__name__)

@app.route('/monitor', methods=['GET'])
def monitor():
    """获取屏幕数据并返回"""
    def gen_img():
        with mss.mss() as sct:
            # 获取屏幕的全部区域
            screen  = sct.monitors[1]
            buffer = BytesIO()
            fps = 0  # 帧率
            frame_count = 0 # 计算帧率,这是服务端生成的速率,到了客户端还会更低
            start = time.time()
            while True:
                # 抓取屏幕并返回
                screenshot = sct.grab(screen)                                                                           
                # 将截图数据转换为 JPEG 格式的二进制数据
                img = Image.frombytes("RGB", screenshot.size, screenshot.bgra, "raw", "BGRX")
                if fps > 0:
                    img = add_fps(img, fps)
                img.save(buffer, format="JPEG")
                jpeg_data = buffer.getvalue()
                yield (b'--frame\r\n' +
                    b'Content-Type: image/jpeg\r\n\r\n' + jpeg_data + b'\r\n')
                # 重置buffer,方便复用
                buffer.seek(0)
                buffer.truncate(0)
                frame_count += 1
                elapsed = time.time() - start
                if elapsed >= 1:
                    fps = frame_count // elapsed
                    frame_count = 0
                    start = time.time()
    return Response(gen_img(), mimetype="multipart/x-mixed-replace; boundary=frame")

def add_fps(img, fps):
    """在图片上添加fps"""
    draw = ImageDraw.Draw(img)
    draw.text((100, 100), f"fps: {fps}", (255, 0, 0), ImageFont.truetype("arial.ttf", 100))
    return img

if __name__ == '__main__':
    print("monitoring...")
    app.run("127.0.0.1", 9000)

方法二:简易共享屏幕工具改进版

改进

1.降低分辨率

方法一那个测试的帧率低,最大的原因是电脑的分辨率有点高了,它是2.5K屏幕,所以这次换成了 1920*1080 的屏幕来测试,效果就好很多了,基本都在 50 帧左右。

2.更换截屏库

更换了一个速度更快的截屏库,这个截屏库的速度很快,比方法一那个快多了,但是反应到实际的帧率上就低了,所以还是后续其他的操作太过于耗时,但是暂时也没有想到啥优化的好方法。

3.降低图片的质量

JPEG 格式的图片是可以选择压缩质量的,适当的调低质量可以提高处理的速度。

代码

from flask import Flask, Response
from io import BytesIO
from PIL import Image, ImageDraw, ImageFont
import time
import dxcam

app = Flask(__name__)
# 整个应用只创建一个即可
camera = dxcam.create(device_idx=0, output_idx=1)  # output_idx 0 是第一块屏幕,1 是第二块屏幕
camera.start(target_fps=60, video_mode=True)

@app.route('/monitor', methods=['GET'])
def monitor():
    """获取屏幕数据并返回"""
    def gen_img():
        # 获取屏幕的全部区域
        fps = 0  # 帧率
        frame_count = 0 # 计算帧率,这是服务端生成的速率,到了客户端还会更低
        start_time = time.perf_counter()
        buffer = BytesIO()
        while True:
            reset_buffer(buffer)  # 每次重置buffer,方便复用
            t1 = time.perf_counter()
            yield get_frame(buffer, fps)
            print("get frame fps: ", 1.0 / (time.perf_counter() - t1))
            frame_count += 1
            elapsed_time = time.perf_counter() - start_time
            if elapsed_time >= 1:
                fps = frame_count // elapsed_time
                frame_count = 0
                start_time = time.perf_counter()
                print("real frame fps: ", fps)
    return Response(gen_img(), mimetype="multipart/x-mixed-replace; boundary=frame")

def get_frame(buffer, fps):
    """
    获取一帧,然后处理成jpeg并返回二进制数据
    """
    image_array = camera.get_latest_frame()
    img = Image.fromarray(image_array)
    if fps > 0:
        img = add_fps(img, fps)
    img.save(buffer, format="JPEG", quality=20)
    return (b'--frame\r\n' +
            b'Content-Type: image/jpeg\r\n\r\n' + buffer.getvalue() + b'\r\n')


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


def add_fps(img, fps):
    """在图片上添加fps"""
    draw = ImageDraw.Draw(img)
    draw.text((100, 100), f"fps: {fps}", (255, 0, 0), ImageFont.truetype("arial.ttf", 100))
    return img


if __name__ == '__main__':    
    print("monitoring...")
    app.run("127.0.0.1", 9000)

到此这篇关于Python基于WebSocket实现简易屏幕共享工具的文章就介绍到这了,更多相关Python WebSocket屏幕共享内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Python代码模拟CPU工作原理

    Python代码模拟CPU工作原理

    Python代码来实现一个最简单的CPU。用代码模拟大的部件,使大家从原理上理解CPU工作。使它可编程,支持加减法运算、读写内存、无条件跳转、条件跳转的功能。
    2023-01-01
  • Python中数据清洗与处理的常用方法小结

    Python中数据清洗与处理的常用方法小结

    数据清洗与处理是数据分析的重要步骤,确保数据的准确性和一致性,这篇文章为大家整理了Python中一些常用的数据清洗与处理方法,需要的可以参考下
    2025-02-02
  • python 统计list中各个元素出现的次数的几种方法

    python 统计list中各个元素出现的次数的几种方法

    这篇文章主要介绍了python 统计list中各个元素出现的次数的几种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-02-02
  • python基础之类方法和静态方法

    python基础之类方法和静态方法

    这篇文章主要介绍了python类方法和静态方法,实例分析了Python中返回一个返回值与多个返回值的方法,需要的朋友可以参考下
    2021-10-10
  • python实现的二叉树算法和kmp算法实例

    python实现的二叉树算法和kmp算法实例

    最近重温数据结构,又用python,所以就用python重新写了数据结构的一些东西,以下是二叉树的python写法
    2014-04-04
  • 为2021年的第一场雪锦上添花:用matplotlib绘制雪花和雪景

    为2021年的第一场雪锦上添花:用matplotlib绘制雪花和雪景

    这篇文章主要介绍了为2021年的第一场雪锦上添花:用matplotlib绘制雪花和雪景,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • Python实现修改PDF文件内部属性值

    Python实现修改PDF文件内部属性值

    部分 PDF 生成时会自动嵌入一些隐藏属性,比如创建软件版本,电脑用户名,修改记录等,下面小编就来和大家简单讲讲如何使用Python修改PDF文件内部属性值吧
    2025-07-07
  • 学习python之编写简单简单连接数据库并执行查询操作

    学习python之编写简单简单连接数据库并执行查询操作

    这篇文章主要介绍了学习python之编写简单简单连接数据库并执行查询操作,需要的朋友可以参考下
    2016-02-02
  • Python线程同步的实现代码

    Python线程同步的实现代码

    本文介绍了threading 模块提供的线程同步原语包括:Lock、RLock、Condition、Event、Semaphore等对象。对大家的学习具有一定的参考学习价值,需要的朋友可以参考下
    2018-10-10
  • 简单分析python的类变量、实例变量

    简单分析python的类变量、实例变量

    在本篇文章中小编给大家整理的是关于python类变量、实例变量的知识点内容,有需要的朋友们可以学习下。
    2019-08-08

最新评论