我对基于 python 的屏幕共享脚本集感兴趣。我不关心编写低级套接字代码。我最近发现了一个有趣的消息代理/服务器,称为 mosquitto (https://mosquitto.org/) 简而言之,您可以连接到服务器并订阅主题。当代理收到有关您订阅的主题的消息时,它将向您发送该消息。
这是连接到 mosquitto 代理的两个脚本。一个脚本监听屏幕抓取请求。另一个脚本请求屏幕抓取并显示它们。
这些脚本依赖图像处理模块来完成繁重的工作
过程是
- 客户端请求屏幕
- 通知服务器有关于屏幕抓取主题的消息
- 服务器使用 mss 抓取屏幕
- 服务器将屏幕转换为 numpy
- server base 64 对压缩的 pickle numpy 图像进行编码
- 如果可能,服务器会使用最后一张图片进行增量处理
- 服务器将 base 64 字符串发布到屏幕抓取主题
- 通知客户端屏幕抓取主题有消息
- 客户端逆向处理
- 客户端显示屏幕
- 客户端返回第 1 步
使用命令行消息退出服务器
C:\Program Files\mosquitto>mosquitto_pub.exe -h "127.0.0.1" -t "server/quit" -m "0"
此实现使用增量刷新。它使用 numpy 对当前和
最后一屏。这确实增加了压缩比。它表明,许多可能对某台机器上正在发生的事情的实时流感兴趣的客户可以使用和连接一个异地服务器。这些脚本绝对不是生产质量,只能作为 POC。
脚本 1 - 服务器
import paho.mqtt.client as mqtt
import time
import uuid
import cv2
import mss
from mss.tools import zlib
import numpy
import base64
import io
import pickle
monitor = 0 # all monitors
quit = False
capture = False
def on_connect(client, userdata, flags, rc):
print("Connected flags " + str(flags) + " ,result code=" + str(rc))
def on_disconnect(client, userdata, flags, rc):
print("Disconnected flags " + str(flags) + " ,result code=" + str(rc))
def on_message(client, userdata, message):
global quit
global capture
global last_image
if message.topic == "server/size":
with mss.mss() as sct:
sct_img = sct.grab(sct.monitors[monitor])
size = sct_img.size
client.publish("client/size", str(size.width) + "|" + str(size.height))
if message.topic == "server/update/first":
with mss.mss() as sct:
b64img = BuildPayload(False)
client.publish("client/update/first", b64img)
if message.topic == "server/update/next":
with mss.mss() as sct:
b64img = BuildPayload()
client.publish("client/update/next", b64img)
if message.topic == "server/quit":
quit = True
def BuildPayload(NextFrame = True):
global last_image
with mss.mss() as sct:
sct_img = sct.grab(sct.monitors[monitor])
image = numpy.array(sct_img)
if NextFrame == True:
# subsequent image - delta that brings much better compression ratio as unchanged RGBA quads will XOR to 0,0,0,0
xor_image = image ^ last_image
b64img = base64.b64encode(zlib.compress(pickle.dumps(xor_image), 9))
else:
# first image - less compression than delta
b64img = base64.b64encode(zlib.compress(pickle.dumps(image), 9))
print("Source Image Size=" + str(len(sct_img.rgb)))
last_image = image
print("Compressed Image Size=" + str(len(b64img)) + " bytes")
return b64img
myid = str(uuid.uuid4()) + str(time.time())
print("Client Id = " + myid)
client = mqtt.Client(myid, False)
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.on_message = on_message
try:
client.connect("127.0.0.1")
client.loop_start()
client.subscribe("server/size")
client.subscribe("server/update/first")
client.subscribe("server/update/next")
client.subscribe("server/quit")
while not quit:
time.sleep(5)
continue
client.publish("client/quit")
time.sleep(5)
client.loop_stop()
client.disconnect()
except:
print("Could not connect to the Mosquito server")
脚本 2 - 客户端
import paho.mqtt.client as mqtt
import time
import uuid
import cv2
import mss
from mss.tools import zlib
import numpy
import base64
import io
import pickle
quit = False
size = False
capture = False
width = 0
height = 0
last_image = None
first = False
def on_connect(client, userdata, flags, rc):
print("Connected flags " + str(flags) + " ,result code=" + str(rc))
def on_message(client, userdata, message):
global quit
global size
global capture
global width
global height
global last_image
global first
if message.topic == "client/size":
if width == 0 and height == 0:
strsize = message.payload.decode("utf-8")
strlist = strsize.split("|")
width = int(strlist[0])
height = int(strlist[1])
size = True
if message.topic == "client/update/first":
# stay synchronized with other connected clients
if size == True:
DecodeAndShowPayload(message, False)
first = True
if message.topic == "client/update/next":
# stay synchronized with other connected clients
if size == True and first == True:
DecodeAndShowPayload(message)
if message.topic == "client/quit":
quit = True
def DecodeAndShowPayload(message, NextFrame = True):
global last_image
global capture
global quit
if NextFrame == True:
# subsequent image - delta that brings much better compression ratio as unchanged RGBA quads will XOR to 0,0,0,0
xor_image = pickle.loads(zlib.decompress(base64.b64decode(message.payload.decode("utf-8")), 15, 65535))
image = last_image ^ xor_image
else:
# first image - less compression than delta
image = pickle.loads(zlib.decompress(base64.b64decode(message.payload.decode("utf-8")), 15, 65535))
last_image = image
cv2.imshow("Server", image)
if cv2.waitKeyEx(25) == 113:
quit = True
capture = False
myid = str(uuid.uuid4()) + str(time.time())
print("Client Id = " + myid)
client = mqtt.Client(myid, False)
client.on_connect = on_connect
client.on_message = on_message
try:
client.connect("127.0.0.1")
client.loop_start()
client.subscribe("client/size")
client.subscribe("client/update/first")
client.subscribe("client/update/next")
client.subscribe("client/quit")
# ask once and retain in case client starts before server
asksize = False
while not size:
if not asksize:
client.publish("server/size", "1", 0, True)
asksize = True
time.sleep(1)
first_image = True
while not quit:
if capture == False:
capture = True
if first_image:
client.publish("server/update/first")
first_image = False
else:
client.publish("server/update/next")
time.sleep(.1)
cv2.destroyAllWindows()
client.loop_stop()
client.disconnect()
except:
print("Could not connect to the Mosquito server")
显示压缩的示例输出
例如:源为 18,662,400 字节(3 个屏幕)
压缩图像小至 35,588 字节,即 524 比 1