Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/server/source/webcam_mixin.py : 71%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2# This file is part of Xpra.
3# Copyright (C) 2010-2020 Antoine Martin <antoine@xpra.org>
4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
5# later version. See the file COPYING for details.
7from xpra.os_util import POSIX, OSX, bytestostr
8from xpra.util import envint, csv, typedict
9from xpra.server.source.stub_source_mixin import StubSourceMixin
10from xpra.log import Logger
12log = Logger("webcam")
14MAX_WEBCAM_DEVICES = envint("XPRA_MAX_WEBCAM_DEVICES", 1)
17def valid_encodings(args):
18 #ensure that the encodings specified can be validated using HEADERS
19 from xpra.codecs.pillow.decoder import HEADERS
20 encodings = []
21 for x in args:
22 x = bytestostr(x)
23 if x not in HEADERS.values():
24 log.warn("Warning: %s is not supported for webcam forwarding", x)
25 else:
26 encodings.append(x)
27 return encodings
30"""
31Handle webcam forwarding.
32"""
33class WebcamMixin(StubSourceMixin):
35 @classmethod
36 def is_needed(cls, caps : typedict) -> bool:
37 #the 'webcam' capability was only added in v4,
38 #so we have to enable the mixin by default:
39 if not caps.boolget("webcam", True):
40 return False
41 try:
42 from xpra.codecs.pillow.decoder import HEADERS
43 assert HEADERS
44 except ImportError:
45 return False
46 return True
48 def __init__(self):
49 self.webcam_enabled = False
50 self.webcam_device = None
51 self.webcam_encodings = []
53 def init_from(self, _protocol, server):
54 self.webcam_enabled = server.webcam_enabled
55 self.webcam_device = server.webcam_device
56 self.webcam_encodings = valid_encodings(server.webcam_encodings)
57 log("WebcamMixin: enabled=%s, device=%s, encodings=%s",
58 self.webcam_enabled, self.webcam_device, self.webcam_encodings)
60 def init_state(self):
61 #for each webcam device_id, the actual device used
62 self.webcam_forwarding_devices = {}
64 def cleanup(self):
65 self.stop_all_virtual_webcams()
68 def get_info(self) -> dict:
69 return {
70 "webcam" : {
71 "encodings" : self.webcam_encodings,
72 "active-devices" : len(self.webcam_forwarding_devices),
73 }
74 }
77 def get_device_options(self, device_id : int):
78 if not POSIX or OSX or not self.webcam_enabled:
79 return {}
80 if self.webcam_device:
81 #use the device specified:
82 return {
83 0 : {
84 "device" : self.webcam_device,
85 },
86 }
87 from xpra.platform.xposix.webcam import get_virtual_video_devices
88 return get_virtual_video_devices()
91 def send_webcam_ack(self, device, frame, *args):
92 self.send_async("webcam-ack", device, frame, *args)
94 def send_webcam_stop(self, device, message):
95 self.send_async("webcam-stop", device, message)
98 def start_virtual_webcam(self, device_id, w, h):
99 log("start_virtual_webcam%s", (device_id, w, h))
100 assert w>0 and h>0
101 webcam = self.webcam_forwarding_devices.get(device_id)
102 if webcam:
103 log.warn("Warning: virtual webcam device %s already in use,", device_id)
104 log.warn(" stopping it first")
105 self.stop_virtual_webcam(device_id)
106 def fail(msg):
107 log.error("Error: cannot start webcam forwarding")
108 log.error(" %s", msg)
109 self.send_webcam_stop(device_id, msg)
110 if not self.webcam_enabled:
111 fail("webcam forwarding is disabled")
112 return False
113 devices = self.get_device_options(device_id)
114 if not devices:
115 fail("no virtual devices found")
116 return False
117 if len(self.webcam_forwarding_devices)>MAX_WEBCAM_DEVICES:
118 fail("too many virtual devices are already in use: %i" % len(self.webcam_forwarding_devices))
119 return False
120 errs = {}
121 for vid, device_info in devices.items():
122 log("trying device %s: %s", vid, device_info)
123 device_str = device_info.get("device")
124 try:
125 from xpra.codecs.v4l2.pusher import Pusher, get_input_colorspaces #@UnresolvedImport
126 in_cs = get_input_colorspaces()
127 p = Pusher()
128 src_format = in_cs[0]
129 p.init_context(w, h, w, src_format, device_str)
130 self.webcam_forwarding_devices[device_id] = p
131 log.info("webcam forwarding using %s", device_str)
132 #this tell the client to start sending, and the size to use - which may have changed:
133 self.send_webcam_ack(device_id, 0, p.get_width(), p.get_height())
134 return True
135 except Exception as e:
136 log.error("Error: failed to start virtual webcam")
137 log.error(" using device %s: %s", vid, device_info, exc_info=True)
138 errs[device_str] = str(e)
139 del e
140 fail("all devices failed")
141 if len(errs)>1:
142 log.error(" tried %i devices:", len(errs))
143 for device_str, err in errs.items():
144 log.error(" %s : %s", device_str, err)
145 return False
147 def stop_all_virtual_webcams(self):
148 log("stop_all_virtual_webcams() stopping: %s", self.webcam_forwarding_devices)
149 for device_id in tuple(self.webcam_forwarding_devices.keys()):
150 self.stop_virtual_webcam(device_id)
152 def stop_virtual_webcam(self, device_id, message=""):
153 webcam = self.webcam_forwarding_devices.pop(device_id, None)
154 log("stop_virtual_webcam(%s, %s) webcam=%s", device_id, message, webcam)
155 if not webcam:
156 log.warn("Warning: cannot stop webcam device %s: no such context!", device_id)
157 return
158 try:
159 webcam.clean()
160 except Exception as e:
161 log.error("Error stopping virtual webcam device: %s", e)
162 log("%s.clean()", exc_info=True)
164 def process_webcam_frame(self, device_id, frame_no, encoding, w, h, data):
165 webcam = self.webcam_forwarding_devices.get(device_id)
166 log("process_webcam_frame: device %s, frame no %i: %s %ix%i, %i bytes, webcam=%s",
167 device_id, frame_no, encoding, w, h, len(data), webcam)
168 assert encoding and w and h and data
169 if not webcam:
170 log.error("Error: webcam forwarding is not active, dropping frame")
171 self.send_webcam_stop(device_id, "not started")
172 return False
173 try:
174 from xpra.codecs.pillow.decoder import open_only
175 assert encoding in self.webcam_encodings, "invalid encoding specified: %s (must be one of %s)" % (encoding, self.webcam_encodings)
176 rgb_pixel_format = "BGRX" #BGRX
177 img = open_only(data, (encoding,))
178 pixels = img.tobytes('raw', rgb_pixel_format)
179 from xpra.codecs.image_wrapper import ImageWrapper
180 bgrx_image = ImageWrapper(0, 0, w, h, pixels, rgb_pixel_format, 32, w*4, planes=ImageWrapper.PACKED)
181 src_format = webcam.get_src_format()
182 if not src_format:
183 #closed / closing
184 return False
185 #one of those two should be present
186 try:
187 csc_mod = "csc_libyuv"
188 from xpra.codecs.csc_libyuv.colorspace_converter import ( #@UnresolvedImport
189 get_input_colorspaces,
190 get_output_colorspaces,
191 ColorspaceConverter,
192 )
193 except ImportError:
194 self.send_webcam_stop(device_id, "no csc module")
195 return False
196 try:
197 assert rgb_pixel_format in get_input_colorspaces(), "unsupported RGB pixel format %s" % rgb_pixel_format
198 assert src_format in get_output_colorspaces(rgb_pixel_format), "unsupported output colourspace format %s" % src_format
199 except Exception as e:
200 log.error("Error: cannot convert %s to %s using %s:", rgb_pixel_format, src_format, csc_mod)
201 log.error(" input-colorspaces: %s", csv(get_input_colorspaces()))
202 log.error(" output-colorspaces: %s", csv(get_output_colorspaces(rgb_pixel_format)))
203 self.send_webcam_stop(device_id, "csc format error")
204 return False
205 tw = webcam.get_width()
206 th = webcam.get_height()
207 csc = ColorspaceConverter()
208 csc.init_context(w, h, rgb_pixel_format, tw, th, src_format)
209 image = csc.convert_image(bgrx_image)
210 webcam.push_image(image)
211 #tell the client all is good:
212 self.send_webcam_ack(device_id, frame_no)
213 return True
214 except Exception as e:
215 log("error on %ix%i frame %i using encoding %s", w, h, frame_no, encoding, exc_info=True)
216 log.error("Error processing webcam frame:")
217 msg = str(e)
218 if not msg:
219 msg = "unknown error"
220 log.error(" %s error" % webcam, exc_info=True)
221 log.error(" %s", msg)
222 self.send_webcam_stop(device_id, msg)
223 self.stop_virtual_webcam(device_id)
224 return False