Alvin3y1 commited on
Commit
dc0f259
·
verified ·
1 Parent(s): 67403ce

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +92 -75
app.py CHANGED
@@ -10,23 +10,13 @@ import threading
10
  import numpy as np
11
  import psutil
12
  import ctypes
13
- import traceback
14
  from ctypes import c_int, c_void_p, c_char_p, POINTER, c_ubyte, c_bool
15
  from aiohttp import web
16
-
17
- # CORRECT IMPORTS FOR AIORTC
18
- from aiortc import (
19
- RTCPeerConnection,
20
- RTCSessionDescription,
21
- VideoStreamTrack,
22
- RTCIceServer,
23
- RTCConfiguration
24
- )
25
- from aiortc.rtcconfiguration import RTCIceTransportPolicy, RTCBundlePolicy
26
  from av import VideoFrame
27
 
28
  # ==========================================
29
- # C++ X11 CAPTURE + FAULT TOLERANCE
30
  # ==========================================
31
  CPP_SOURCE = r"""
32
  #include <X11/Xlib.h>
@@ -135,6 +125,8 @@ int init_grabber(int w, int h, const char* display_name) {
135
  XShmAttach(cap.display, &cap.shminfo);
136
  XSync(cap.display, False);
137
 
 
 
138
  cap.sws_ctx = sws_getContext(w, h, AV_PIX_FMT_BGRA,
139
  w, h, AV_PIX_FMT_YUV420P,
140
  SWS_FAST_BILINEAR, NULL, NULL, NULL);
@@ -151,12 +143,14 @@ int init_grabber(int w, int h, const char* display_name) {
151
  int capture_frame() {
152
  if (cap.is_init && cap.display && cap.image) {
153
  last_x_error_code = 0;
 
154
  XShmGetImage(cap.display, cap.root, cap.image, 0, 0, AllPlanes);
155
  return (last_x_error_code == 0);
156
  }
157
  return 0;
158
  }
159
 
 
160
  void convert_to_yuv(void* y, int y_stride, void* u, int u_stride, void* v, int v_stride) {
161
  if (!cap.is_init || !cap.sws_ctx || !cap.image) return;
162
 
@@ -168,10 +162,11 @@ void convert_to_yuv(void* y, int y_stride, void* u, int u_stride, void* v, int v
168
  sws_scale(cap.sws_ctx, srcSlice, srcStride, 0, cap.height, dst, dstStride);
169
  }
170
 
 
171
  void move_mouse(int x, int y) {
172
  if (!cap.is_init || !cap.input_display) return;
173
  XTestFakeMotionEvent(cap.input_display, -1, x, y, CurrentTime);
174
- XFlush(cap.input_display);
175
  }
176
 
177
  void mouse_button(int button, int is_down) {
@@ -205,6 +200,7 @@ def compile_cpp():
205
  with open("xcapture.cpp", "w") as f:
206
  f.write(CPP_SOURCE)
207
 
 
208
  cmd = [
209
  "g++", "-O3", "-march=native", "-ffast-math", "-flto", "-shared", "-fPIC",
210
  "-o", LIB_PATH, "xcapture.cpp",
@@ -221,6 +217,7 @@ compile_cpp()
221
  # Load C++ Library
222
  try:
223
  xlib = ctypes.CDLL(LIB_PATH)
 
224
  xlib.init_grabber.argtypes = [c_int, c_int, c_char_p]
225
  xlib.init_grabber.restype = c_int
226
  xlib.capture_frame.argtypes = []
@@ -235,6 +232,7 @@ try:
235
  xlib.mouse_button.restype = None
236
  xlib.key_send.argtypes = [c_char_p, c_int]
237
  xlib.key_send.restype = None
 
238
  USE_CSHM = True
239
  except Exception as e:
240
  print(f"Library load failed: {e}")
@@ -249,8 +247,14 @@ DEFAULT_WIDTH = 1280
249
  DEFAULT_HEIGHT = 720
250
 
251
  logging.basicConfig(level=logging.WARNING)
 
 
252
  video_executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
 
 
 
253
  video_lock = threading.Lock()
 
254
 
255
  config = {
256
  "width": DEFAULT_WIDTH,
@@ -261,6 +265,7 @@ class InputManager:
261
  def __init__(self):
262
  self.scroll_accum = 0
263
 
 
264
  def mouse_move(self, x, y):
265
  if USE_CSHM: xlib.move_mouse(x, y)
266
 
@@ -300,7 +305,7 @@ def start_system():
300
  "-ac", "-noreset", "-nolisten", "tcp"
301
  ], stderr=subprocess.DEVNULL)
302
 
303
- time.sleep(1)
304
  set_resolution(DEFAULT_WIDTH, DEFAULT_HEIGHT)
305
 
306
  if shutil.which("matchbox-window-manager"):
@@ -311,6 +316,7 @@ def start_system():
311
  def maintain_antigravity():
312
  while True:
313
  try:
 
314
  running = False
315
  for p in psutil.process_iter(['name']):
316
  if p.info['name'] == "antigravity":
@@ -319,12 +325,13 @@ def maintain_antigravity():
319
  if not running:
320
  subprocess.Popen(["antigravity"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
321
  except: pass
322
- time.sleep(5)
323
 
324
  def set_resolution(w, h):
325
  with video_lock:
326
  if w == config["width"] and h == config["height"]:
327
  return
 
328
  try:
329
  if w % 2 != 0: w += 1
330
  if h % 2 != 0: h += 1
@@ -332,10 +339,14 @@ def set_resolution(w, h):
332
  if h > MAX_HEIGHT: h = MAX_HEIGHT
333
 
334
  mode_name = f"M_{w}_{h}"
 
 
 
335
  subprocess.call(["xrandr", "--newmode", mode_name, f"{60*w*h/1000000:.2f}",
336
  str(w), str(w+40), str(w+80), str(w+160),
337
  str(h), str(h+3), str(h+10), str(h+16),
338
  "-hsync", "+vsync"], stderr=subprocess.DEVNULL)
 
339
  subprocess.call(["xrandr", "--addmode", "screen", mode_name], stderr=subprocess.DEVNULL)
340
  subprocess.call(["xrandr", "--output", "screen", "--mode", mode_name], stderr=subprocess.DEVNULL)
341
 
@@ -355,6 +366,8 @@ class VirtualScreenTrack(VideoStreamTrack):
355
 
356
  def _produce_frame(self, w, h):
357
  if not USE_CSHM: return None
 
 
358
  with video_lock:
359
  try:
360
  if w != self.last_w or h != self.last_h:
@@ -363,25 +376,39 @@ class VirtualScreenTrack(VideoStreamTrack):
363
  self.last_w = w
364
  self.last_h = h
365
 
366
- if xlib.capture_frame() == 0: return None
 
 
367
 
 
368
  frame = VideoFrame(width=w, height=h, format="yuv420p")
 
 
 
369
  xlib.convert_to_yuv(
370
  c_void_p(int(frame.planes[0].buffer_ptr)), frame.planes[0].line_size,
371
  c_void_p(int(frame.planes[1].buffer_ptr)), frame.planes[1].line_size,
372
  c_void_p(int(frame.planes[2].buffer_ptr)), frame.planes[2].line_size
373
  )
374
  return frame
375
- except: return None
 
376
 
377
  async def recv(self):
378
  pts, time_base = await self.next_timestamp()
 
379
  w, h = config["width"], config["height"]
380
 
381
  frame = None
382
  if USE_CSHM:
 
 
 
 
383
  try:
384
- frame = await asyncio.get_event_loop().run_in_executor(video_executor, self._produce_frame, w, h)
 
 
385
  except: pass
386
 
387
  if frame is None:
@@ -395,63 +422,49 @@ class VirtualScreenTrack(VideoStreamTrack):
395
 
396
  async def offer(request):
397
  try:
398
- try:
399
- params = await request.json()
400
- offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"])
401
- except Exception as e:
402
- return web.Response(status=400, text="Invalid JSON")
403
-
404
- ice_servers = [RTCIceServer(
405
- urls=["turns:turn.cloudflare.com:443?transport=tcp"],
406
- username="g08abe68c81a07f098bb5f0914549bb32440e5aad0b216c7fba2b61e76fd62c6",
407
- credential="aed1a10dd10eba9401ad9d99e5c66036d8a970eab5ba8e6dc9845ab57c771a7d"
408
- )]
409
-
410
- # Using correct Enums imported from rtcconfiguration
411
- pc = RTCPeerConnection(RTCConfiguration(
412
- iceServers=ice_servers,
413
- iceTransportPolicy=RTCIceTransportPolicy.relay,
414
- bundlePolicy=RTCBundlePolicy.maxBundle
415
- ))
416
-
417
- pcs.add(pc)
418
-
419
- @pc.on("connectionstatechange")
420
- async def on_state():
421
- if pc.connectionState in ["failed", "closed"]:
422
- await pc.close()
423
- pcs.discard(pc)
424
-
425
- @pc.on("datachannel")
426
- def on_dc(channel):
427
- @channel.on("message")
428
- async def on_message(message):
429
- await process_input(message)
430
-
431
- pc.addTrack(VirtualScreenTrack())
432
-
433
- await pc.setRemoteDescription(offer)
434
- answer = await pc.createAnswer()
435
-
436
- sdp_lines = answer.sdp.splitlines()
437
- new_sdp = []
438
- for line in sdp_lines:
439
- new_sdp.append(line)
440
- if line.startswith("m=video"):
441
- new_sdp.append("b=AS:4000")
442
- new_sdp.append("b=TIAS:4000000")
443
- answer.sdp = "\r\n".join(new_sdp)
444
-
445
- await pc.setLocalDescription(answer)
446
-
447
- return web.Response(content_type="application/json", text=json.dumps({
448
- "sdp": pc.localDescription.sdp,
449
- "type": pc.localDescription.type
450
- }), headers={"Access-Control-Allow-Origin": "*"})
451
-
452
- except Exception as e:
453
- traceback.print_exc()
454
- return web.Response(status=500, text=f"Server Error: {str(e)}")
455
 
456
  def map_key(key):
457
  if not key: return None
@@ -465,6 +478,7 @@ async def handle_debounced_resize(w, h):
465
  if resize_timer: resize_timer.cancel()
466
 
467
  async def task():
 
468
  await asyncio.sleep(0.1)
469
  await asyncio.get_event_loop().run_in_executor(video_executor, set_resolution, w, h)
470
 
@@ -472,8 +486,11 @@ async def handle_debounced_resize(w, h):
472
 
473
  async def process_input(data):
474
  try:
 
475
  msg = json.loads(data)
476
  t = msg.get("type")
 
 
477
  if t == "mousemove":
478
  w, h = config["width"], config["height"]
479
  input_manager.mouse_move(int(msg["x"] * w), int(msg["y"] * h))
 
10
  import numpy as np
11
  import psutil
12
  import ctypes
 
13
  from ctypes import c_int, c_void_p, c_char_p, POINTER, c_ubyte, c_bool
14
  from aiohttp import web
15
+ from aiortc import RTCPeerConnection, RTCSessionDescription, VideoStreamTrack, RTCIceServer, RTCConfiguration
 
 
 
 
 
 
 
 
 
16
  from av import VideoFrame
17
 
18
  # ==========================================
19
+ # C++ X11 CAPTURE + FAULT TOLERANCE + DUAL CHANNEL
20
  # ==========================================
21
  CPP_SOURCE = r"""
22
  #include <X11/Xlib.h>
 
125
  XShmAttach(cap.display, &cap.shminfo);
126
  XSync(cap.display, False);
127
 
128
+ // SWS_FAST_BILINEAR is good, SWS_POINT is faster but blocky.
129
+ // Using BILINEAR for balance.
130
  cap.sws_ctx = sws_getContext(w, h, AV_PIX_FMT_BGRA,
131
  w, h, AV_PIX_FMT_YUV420P,
132
  SWS_FAST_BILINEAR, NULL, NULL, NULL);
 
143
  int capture_frame() {
144
  if (cap.is_init && cap.display && cap.image) {
145
  last_x_error_code = 0;
146
+ // This blocks only the video thread
147
  XShmGetImage(cap.display, cap.root, cap.image, 0, 0, AllPlanes);
148
  return (last_x_error_code == 0);
149
  }
150
  return 0;
151
  }
152
 
153
+ // Optimized pointer math happens inside sws_scale
154
  void convert_to_yuv(void* y, int y_stride, void* u, int u_stride, void* v, int v_stride) {
155
  if (!cap.is_init || !cap.sws_ctx || !cap.image) return;
156
 
 
162
  sws_scale(cap.sws_ctx, srcSlice, srcStride, 0, cap.height, dst, dstStride);
163
  }
164
 
165
+ // INPUT FUNCTIONS USE SEPARATE DISPLAY CONNECTION
166
  void move_mouse(int x, int y) {
167
  if (!cap.is_init || !cap.input_display) return;
168
  XTestFakeMotionEvent(cap.input_display, -1, x, y, CurrentTime);
169
+ XFlush(cap.input_display); // Flush only input stream
170
  }
171
 
172
  void mouse_button(int button, int is_down) {
 
200
  with open("xcapture.cpp", "w") as f:
201
  f.write(CPP_SOURCE)
202
 
203
+ # ADDED: -march=native -ffast-math -flto for CPU optimization
204
  cmd = [
205
  "g++", "-O3", "-march=native", "-ffast-math", "-flto", "-shared", "-fPIC",
206
  "-o", LIB_PATH, "xcapture.cpp",
 
217
  # Load C++ Library
218
  try:
219
  xlib = ctypes.CDLL(LIB_PATH)
220
+
221
  xlib.init_grabber.argtypes = [c_int, c_int, c_char_p]
222
  xlib.init_grabber.restype = c_int
223
  xlib.capture_frame.argtypes = []
 
232
  xlib.mouse_button.restype = None
233
  xlib.key_send.argtypes = [c_char_p, c_int]
234
  xlib.key_send.restype = None
235
+
236
  USE_CSHM = True
237
  except Exception as e:
238
  print(f"Library load failed: {e}")
 
247
  DEFAULT_HEIGHT = 720
248
 
249
  logging.basicConfig(level=logging.WARNING)
250
+
251
+ # Dedicated thread for video capture
252
  video_executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
253
+
254
+ # LOCKS
255
+ # video_lock: Protects XShm, resizing, and video display connection
256
  video_lock = threading.Lock()
257
+ # Input does NOT use a lock anymore because it uses a separate X11 connection in C++
258
 
259
  config = {
260
  "width": DEFAULT_WIDTH,
 
265
  def __init__(self):
266
  self.scroll_accum = 0
267
 
268
+ # No locking needed here, C++ handles separate connection
269
  def mouse_move(self, x, y):
270
  if USE_CSHM: xlib.move_mouse(x, y)
271
 
 
305
  "-ac", "-noreset", "-nolisten", "tcp"
306
  ], stderr=subprocess.DEVNULL)
307
 
308
+ time.sleep(1) # Reduced sleep
309
  set_resolution(DEFAULT_WIDTH, DEFAULT_HEIGHT)
310
 
311
  if shutil.which("matchbox-window-manager"):
 
316
  def maintain_antigravity():
317
  while True:
318
  try:
319
+ # Check optimization: Avoid list parsing overhead
320
  running = False
321
  for p in psutil.process_iter(['name']):
322
  if p.info['name'] == "antigravity":
 
325
  if not running:
326
  subprocess.Popen(["antigravity"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
327
  except: pass
328
+ time.sleep(5) # Increased sleep to save CPU
329
 
330
  def set_resolution(w, h):
331
  with video_lock:
332
  if w == config["width"] and h == config["height"]:
333
  return
334
+
335
  try:
336
  if w % 2 != 0: w += 1
337
  if h % 2 != 0: h += 1
 
339
  if h > MAX_HEIGHT: h = MAX_HEIGHT
340
 
341
  mode_name = f"M_{w}_{h}"
342
+
343
+ # Combine xrandr calls to reduce process fork overhead?
344
+ # Xrandr is CLI, safer to keep separate but fast.
345
  subprocess.call(["xrandr", "--newmode", mode_name, f"{60*w*h/1000000:.2f}",
346
  str(w), str(w+40), str(w+80), str(w+160),
347
  str(h), str(h+3), str(h+10), str(h+16),
348
  "-hsync", "+vsync"], stderr=subprocess.DEVNULL)
349
+
350
  subprocess.call(["xrandr", "--addmode", "screen", mode_name], stderr=subprocess.DEVNULL)
351
  subprocess.call(["xrandr", "--output", "screen", "--mode", mode_name], stderr=subprocess.DEVNULL)
352
 
 
366
 
367
  def _produce_frame(self, w, h):
368
  if not USE_CSHM: return None
369
+
370
+ # Only lock the video part. Input continues in parallel.
371
  with video_lock:
372
  try:
373
  if w != self.last_w or h != self.last_h:
 
376
  self.last_w = w
377
  self.last_h = h
378
 
379
+ # 1. Capture (Copy X11 -> Shared Memory)
380
+ if xlib.capture_frame() == 0:
381
+ return None
382
 
383
+ # 2. Allocate Frame (Python overhead, but required for aiortc)
384
  frame = VideoFrame(width=w, height=h, format="yuv420p")
385
+
386
+ # 3. Convert (Shared Memory -> Frame Buffer)
387
+ # Using direct address integer for speed
388
  xlib.convert_to_yuv(
389
  c_void_p(int(frame.planes[0].buffer_ptr)), frame.planes[0].line_size,
390
  c_void_p(int(frame.planes[1].buffer_ptr)), frame.planes[1].line_size,
391
  c_void_p(int(frame.planes[2].buffer_ptr)), frame.planes[2].line_size
392
  )
393
  return frame
394
+ except:
395
+ return None
396
 
397
  async def recv(self):
398
  pts, time_base = await self.next_timestamp()
399
+
400
  w, h = config["width"], config["height"]
401
 
402
  frame = None
403
  if USE_CSHM:
404
+ # Offload heavy C++ work to thread.
405
+ # While this runs, the Main Thread can process "process_input" (Mouse/Keys)
406
+ # because we are not holding the Global Interpreter Lock (ctypes releases it)
407
+ # and we are not holding a global "input lock".
408
  try:
409
+ frame = await asyncio.get_event_loop().run_in_executor(
410
+ video_executor, self._produce_frame, w, h
411
+ )
412
  except: pass
413
 
414
  if frame is None:
 
422
 
423
  async def offer(request):
424
  try:
425
+ params = await request.json()
426
+ offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"])
427
+ except: return web.Response(status=400)
428
+
429
+ ice_servers = [RTCIceServer(urls=["stun:stun.l.google.com:19302"])]
430
+ pc = RTCPeerConnection(RTCConfiguration(iceServers=ice_servers))
431
+ pcs.add(pc)
432
+
433
+ @pc.on("connectionstatechange")
434
+ async def on_state():
435
+ if pc.connectionState in ["failed", "closed"]:
436
+ await pc.close()
437
+ pcs.discard(pc)
438
+
439
+ @pc.on("datachannel")
440
+ def on_dc(channel):
441
+ @channel.on("message")
442
+ async def on_message(message):
443
+ # Direct call to input processing
444
+ await process_input(message)
445
+
446
+ pc.addTrack(VirtualScreenTrack())
447
+
448
+ await pc.setRemoteDescription(offer)
449
+ answer = await pc.createAnswer()
450
+
451
+ # FORCE HIGHER BITRATE (4Mbps)
452
+ # Injecting bandwidth info into SDP before setting local description
453
+ sdp_lines = answer.sdp.splitlines()
454
+ new_sdp = []
455
+ for line in sdp_lines:
456
+ new_sdp.append(line)
457
+ if line.startswith("m=video"):
458
+ new_sdp.append("b=AS:4000")
459
+ new_sdp.append("b=TIAS:4000000")
460
+ answer.sdp = "\r\n".join(new_sdp)
461
+
462
+ await pc.setLocalDescription(answer)
463
+
464
+ return web.Response(content_type="application/json", text=json.dumps({
465
+ "sdp": pc.localDescription.sdp,
466
+ "type": pc.localDescription.type
467
+ }), headers={"Access-Control-Allow-Origin": "*"})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
 
469
  def map_key(key):
470
  if not key: return None
 
478
  if resize_timer: resize_timer.cancel()
479
 
480
  async def task():
481
+ # REDUCED DELAY FROM 0.5 to 0.1 FOR FASTER RESIZING
482
  await asyncio.sleep(0.1)
483
  await asyncio.get_event_loop().run_in_executor(video_executor, set_resolution, w, h)
484
 
 
486
 
487
  async def process_input(data):
488
  try:
489
+ # Optimized parsing
490
  msg = json.loads(data)
491
  t = msg.get("type")
492
+
493
+ # Immediate dispatch
494
  if t == "mousemove":
495
  w, h = config["width"], config["height"]
496
  input_manager.mouse_move(int(msg["x"] * w), int(msg["y"] * h))