Ticket #1030: webcam-forwarding.patch
File webcam-forwarding.patch, 35.4 KB (added by , 3 years ago) |
---|
-
etc/xpra/xpra.conf.in
205 205 206 206 207 207 ################################################################################ 208 # Webcam forwarding 209 # webcam = auto 210 # webcam = no 211 # webcam = /dev/video0 212 webcam = auto 213 214 215 ################################################################################ 208 216 # Network Connection 209 217 210 218 # Enable shared memory transfers: -
setup.py
144 144 pillow_ENABLED = DEFAULT 145 145 webp_ENABLED = DEFAULT and pkg_config_ok("--atleast-version=0.4", "libwebp", fallback=WIN32) 146 146 vpx_ENABLED = DEFAULT and pkg_config_ok("--atleast-version=1.3", "vpx", fallback=WIN32) 147 v4l2_ENABLED = DEFAULT and (not WIN32 and not OSX) 147 148 #ffmpeg 2 onwards: 148 149 dec_avcodec2_ENABLED = DEFAULT and pkg_config_ok("--atleast-version=56", "libavcodec", fallback=WIN32) 149 150 # some version strings I found: … … 960 961 "xpra/codecs/enc_x264/encoder.c", 961 962 "xpra/codecs/enc_x265/encoder.c", 962 963 "xpra/codecs/libav_common/av_log.c", 964 "xpra/codecs/v4l/pusher.c", 963 965 "xpra/codecs/webp/encode.c", 964 966 "xpra/codecs/webp/decode.c", 965 967 "xpra/codecs/dec_avcodec2/decoder.c", … … 2281 2283 ["xpra/codecs/vpx/decoder.pyx"]+membuffers_c, 2282 2284 **vpx_pkgconfig)) 2283 2285 2286 toggle_packages(v4l2_ENABLED, "xpra.codecs.v4l2") 2287 if v4l2_ENABLED: 2288 cython_add(Extension("xpra.codecs.v4l2.pusher", 2289 ["xpra/codecs/v4l2/pusher.pyx"]+membuffers_c)) 2284 2290 2291 2285 2292 toggle_packages(bencode_ENABLED, "xpra.net.bencode") 2286 2293 if cython_bencode_ENABLED: 2287 2294 bencode_pkgconfig = pkgconfig(optimize=not debug_ENABLED) -
xpra/client/ui_client_base.py
34 34 avsynclog = Logger("av-sync") 35 35 clipboardlog = Logger("clipboard") 36 36 scalinglog = Logger("scaling") 37 webcamlog = Logger("webcam") 37 38 38 39 39 40 from xpra import __version__ as XPRA_VERSION … … 53 54 from xpra.net import compression, packet_encoding 54 55 from xpra.child_reaper import reaper_cleanup 55 56 from xpra.make_thread import make_thread 56 from xpra.os_util import Queue, platform_name, get_machine_id, get_user_uuid, bytestostr57 from xpra.os_util import BytesIOClass, Queue, platform_name, get_machine_id, get_user_uuid, bytestostr 57 58 from xpra.util import nonl, std, iround, AtomicInteger, log_screen_sizes, typedict, updict, csv, engs, CLIENT_EXIT 58 59 from xpra.version_util import get_version_info_full, get_platform_info 59 60 try: … … 166 167 self.core_encodings = None 167 168 self.encoding = None 168 169 170 #webcam: 171 self.webcam_option = False 172 self.webcam_forwarding = False 173 self.webcam_device = None 174 self.webcam_device_no = -1 175 self.server_supports_webcam = False 176 self.server_virtual_video_devices = 0 177 169 178 #sound: 170 179 self.sound_source_plugin = None 171 180 self.speaker_allowed = False … … 298 307 self.mmap_group = opts.mmap_group 299 308 self.shadow_fullscreen = opts.shadow_fullscreen 300 309 310 self.webcam_option = opts.webcam.lower() 311 self.webcam_forwarding = self.webcam_option not in FALSE_OPTIONS 312 self.server_supports_webcam = False 313 self.server_virtual_video_devices = 0 314 if self.webcam_forwarding: 315 try: 316 import cv2 317 from PIL import Image 318 assert cv2 and Image 319 except ImportError as e: 320 webcamlog.warn("Warning: failed to import opencv:") 321 webcamlog.warn(" %s", e) 322 webcamlog.warn(" webcam forwarding is disabled") 323 self.webcam_forwarding = False 324 webcamlog("webcam forwarding: %s", self.webcam_option) 325 301 326 self.sound_properties = typedict() 302 327 self.speaker_allowed = sound_option(opts.speaker) in ("on", "off") 303 328 self.microphone_allowed = sound_option(opts.microphone) in ("on", "off") … … 545 570 log.error("error on %s cleanup", type(x), exc_info=True) 546 571 #the protocol has been closed, it is now safe to close all the windows: 547 572 #(cleaner and needed when we run embedded in the client launcher) 573 self.stop_sending_webcam() 548 574 self.destroy_all_windows() 549 575 self.clean_mmap() 550 576 reaper_cleanup() … … 1766 1792 if modifier_keycodes: 1767 1793 self.keyboard_helper.set_modifier_mappings(modifier_keycodes) 1768 1794 1795 #webcam 1796 self.server_supports_webcam = c.boolget("webcam") 1797 self.server_virtual_video_devices = c.intget("virtual-video-devices") 1798 webcamlog("webcam server support: %s (%i devices)", self.server_supports_webcam, self.server_virtual_video_devices) 1799 if self.webcam_forwarding and self.webcam_option=="on" and self.server_supports_webcam and self.server_virtual_video_devices>0: 1800 self.start_sending_webcam() 1801 1769 1802 #sound: 1770 1803 self.server_pulseaudio_id = c.strget("sound.pulseaudio.id") 1771 1804 self.server_pulseaudio_server = c.strget("sound.pulseaudio.server") … … 1940 1973 log.warn("received invalid control command from server: %s", command) 1941 1974 1942 1975 1976 def start_sending_webcam(self): 1977 self.do_start_sending_webcam(self.webcam_option) 1978 1979 def do_start_sending_webcam(self, device_str): 1980 webcamlog("start_sending_webcam(%s)", device_str) 1981 assert self.server_supports_webcam 1982 device = 0 1983 if device_str=="auto": 1984 if os.name=="posix": 1985 try: 1986 from xpra.platform.xposix.webcam_util import get_virtual_video_devices, get_all_video_devices 1987 virt_devices = get_virtual_video_devices() 1988 non_virtual = [x.replace("/dev/video", "") for x in get_all_video_devices() if x not in virt_devices] 1989 webcamlog("found %s non-virtual video devices: %s", len(non_virtual), non_virtual) 1990 for x in non_virtual: 1991 try: 1992 device = int(x) 1993 break 1994 except: 1995 pass 1996 except ImportError as e: 1997 webcamlog("no webcam_util: %s", e) 1998 else: 1999 try: 2000 device = int(device_str) 2001 except: 2002 p = device_str.find("video") 2003 if p>=0: 2004 try: 2005 device = int(device_str[p:]) 2006 except: 2007 device = 0 2008 import cv2 2009 webcamlog("start_sending_webcam(%s) device=%i", device_str, device) 2010 self.webcam_frame_no = 0 2011 self.webcam_device_no = device 2012 self.webcam_device = cv2.VideoCapture(device) #0 -> /dev/video0 2013 try: 2014 #test capture: 2015 ret, frame = self.webcam_device.read() 2016 webcamlog("test capture using %s: %s, %s", self.webcam_device, ret, frame is not None) 2017 assert ret and frame is not None, "no data" 2018 assert frame.ndim==3, "unexpected number of dimensions: %s" % frame.ndim 2019 w, h, Bpp = frame.shape 2020 assert Bpp==3, "unexpected number of bytes per pixel: %s" % Bpp 2021 assert frame.size==w*h*Bpp 2022 self.send("webcam-start", device, w, h) 2023 #FIXME: add timer to stop if we don't get an ack 2024 except Exception as e: 2025 webcamlog.warn("webcam test capture failed: %s", e) 2026 self.stop_sending_webcam() 2027 2028 def stop_sending_webcam(self): 2029 wd = self.webcam_device 2030 webcamlog("stop_sending_webcam() device=%s", wd) 2031 if not wd: 2032 return 2033 self.send("webcam-stop", self.webcam_device_no) 2034 assert self.server_supports_webcam 2035 self.webcam_device = None 2036 self.webcam_device_no = -1 2037 self.webcam_frame_no = 0 2038 try: 2039 wd.release() 2040 except Exception as e: 2041 webcamlog.error("Error closing webcam device %s: %s", wd, e) 2042 2043 def _process_webcam_stop(self, packet): 2044 device_no = packet[1] 2045 if device_no!=self.webcam_device_no: 2046 return 2047 self.stop_sending_webcam() 2048 2049 def _process_webcam_ack(self, packet): 2050 webcamlog("process_webcam_ack: %s", packet) 2051 self.send_webcam_frame() 2052 2053 def send_webcam_frame(self): 2054 assert self.webcam_device_no>=0 and self.webcam_device 2055 try: 2056 import cv2 2057 ret, frame = self.webcam_device.read() 2058 assert ret and frame.ndim==3 2059 w, h, Bpp = frame.shape 2060 assert Bpp==3 and frame.size==w*h*Bpp 2061 webcamlog("send_webcam_frame strides=%s", frame.strides) 2062 rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) 2063 from PIL import Image 2064 image = Image.fromarray(rgb) 2065 png = BytesIOClass() 2066 image.save(png, format="PNG") 2067 data = png.getvalue() 2068 png.close() 2069 frame_no = self.webcam_frame_no 2070 self.webcam_frame_no += 1 2071 self.send("webcam-frame", self.webcam_device_no, frame_no, "png", w, h, compression.Compressed("png", data)) 2072 except Exception as e: 2073 webcamlog.error("Error sending webcam frame: %s", e) 2074 self.stop_sending_webcam() 2075 2076 1943 2077 def start_sending_sound(self): 1944 2078 """ (re)start a sound source and emit client signal """ 1945 2079 soundlog("start_sending_sound()") … … 2779 2913 "rpc-reply": self._process_rpc_reply, 2780 2914 "control" : self._process_control, 2781 2915 "draw": self._process_draw, 2916 "webcam-stop": self._process_webcam_stop, 2917 "webcam-ack": self._process_webcam_ack, 2782 2918 # "clipboard-*" packets are handled by a special case below. 2783 2919 }) 2784 2920 #these handlers can run directly from the network thread: -
xpra/codecs/v4l2/__init__.py
1 # This file is part of Xpra. 2 # Copyright (C) 2016 Antoine Martin <antoine@devloop.org.uk> 3 # Xpra is released under the terms of the GNU GPL v2, or, at your option, any 4 # later version. See the file COPYING for details. -
xpra/codecs/v4l2/pusher.pyx
1 # This file is part of Xpra. 2 # Copyright (C) 2012-2015 Antoine Martin <antoine@devloop.org.uk> 3 # Xpra is released under the terms of the GNU GPL v2, or, at your option, any 4 # later version. See the file COPYING for details. 5 6 import time 7 import os 8 9 from xpra.log import Logger 10 from _dbus_bindings import UInt32 11 log = Logger("webcam") 12 13 from xpra.codecs.image_wrapper import ImageWrapper 14 15 16 from libc.stdint cimport uint32_t, uint8_t 17 18 cdef extern from "../../buffers/memalign.h": 19 void *xmemalign(size_t size) nogil 20 void *memset(void * ptr, int value, size_t num) 21 22 cdef extern from "stdlib.h": 23 void free(void* ptr) 24 25 cdef extern from "string.h": 26 void * memcpy ( void * destination, void * source, size_t num ) 27 28 29 cdef extern from "../../buffers/buffers.h": 30 int object_as_buffer(object obj, const void ** buffer, Py_ssize_t * buffer_len) 31 int get_buffer_api_version() 32 33 34 cdef extern from *: 35 ctypedef unsigned long size_t 36 37 cdef extern from "sys/ioctl.h": 38 int ioctl(int fd, unsigned long request, ...) 39 40 cdef extern from "linux/videodev2.h": 41 int VIDIOC_QUERYCAP 42 int VIDIOC_G_FMT 43 int VIDIOC_S_FMT 44 int V4L2_COLORSPACE_SRGB 45 int V4L2_FIELD_NONE 46 int V4L2_PIX_FMT_YUV420 47 int V4L2_PIX_FMT_YVU420 48 int V4L2_BUF_TYPE_VIDEO_OUTPUT 49 50 cdef struct v4l2_capability: 51 uint8_t driver[16] 52 uint8_t card[32] 53 uint8_t bus_info[32] 54 uint32_t version 55 uint32_t capabilities 56 uint32_t device_caps 57 uint32_t reserved[3] 58 59 cdef struct v4l2_pix_format: 60 uint32_t width 61 uint32_t height 62 uint32_t pixelformat 63 uint32_t field # enum v4l2_field */ 64 uint32_t bytesperline # for padding, zero if unused */ 65 uint32_t sizeimage 66 uint32_t colorspace # enum v4l2_colorspace */ 67 uint32_t priv # private data, depends on pixelformat */ 68 uint32_t flags # format flags (V4L2_PIX_FMT_FLAG_*) */ 69 uint32_t ycbcr_enc # enum v4l2_ycbcr_encoding */ 70 uint32_t quantization # enum v4l2_quantization */ 71 uint32_t xfer_func # enum v4l2_xfer_func */ 72 73 pass 74 cdef struct v4l2_pix_format_mplane: 75 pass 76 cdef struct v4l2_window: 77 pass 78 cdef struct v4l2_vbi_format: 79 pass 80 cdef struct v4l2_sliced_vbi_format: 81 pass 82 cdef struct v4l2_sdr_format: 83 pass 84 85 cdef union v4l2_format_fmt: 86 v4l2_pix_format pix #V4L2_BUF_TYPE_VIDEO_CAPTURE 87 v4l2_pix_format_mplane pix_mp #V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE 88 v4l2_window win #V4L2_BUF_TYPE_VIDEO_OVERLAY 89 v4l2_vbi_format vbi #V4L2_BUF_TYPE_VBI_CAPTURE 90 v4l2_sliced_vbi_format sliced #V4L2_BUF_TYPE_SLICED_VBI_CAPTURE 91 v4l2_sdr_format sdr #V4L2_BUF_TYPE_SDR_CAPTURE 92 uint8_t raw_data[200] #user-defined 93 94 cdef struct v4l2_format: 95 uint32_t type 96 v4l2_format_fmt fmt 97 98 def init_module(): 99 log("v4l2.pusher.init_module()") 100 101 def cleanup_module(): 102 log("v4l2pusher.cleanup_module()") 103 104 def get_version(): 105 return 0 106 107 def get_type(): 108 return "v4l2" 109 110 def get_info(): 111 global COLORSPACES, MAX_WIDTH, MAX_HEIGHT 112 return {"version" : get_version(), 113 "buffer_api": get_buffer_api_version()} 114 115 def get_input_colorspaces(): 116 return ["YUV420P"] 117 118 119 cdef class Pusher: 120 cdef unsigned long frames 121 cdef int width 122 cdef int height 123 cdef int rowstride 124 cdef size_t framesize 125 cdef object src_format 126 cdef object device 127 cdef object device_name 128 129 cdef object __weakref__ 130 131 def init_context(self, int width, int height, int rowstride, src_format, device): #@DuplicatedSignature 132 assert src_format=="YUV420P" 133 self.width = width 134 self.height = height 135 self.rowstride = rowstride 136 self.src_format = src_format 137 self.frames = 0 138 self.framesize = self.height*self.rowstride*3//2 139 self.init_device(device) 140 141 cdef init_device(self, device): 142 cdef v4l2_capability vid_caps 143 cdef v4l2_format vid_format 144 self.device_name = device or os.environ.get("XPRA_VIDEO_DEVICE", "/dev/video1") 145 log("v4l2 using device %s", self.device_name) 146 self.device = open(self.device_name, "wb") 147 r = ioctl(self.device.fileno(), VIDIOC_QUERYCAP, &vid_caps) 148 log("ioctl(%s, VIDIOC_QUERYCAP, %#x)=%s", self.device_name, <unsigned long> &vid_caps, r) 149 assert r!=-1, "VIDIOC_QUERYCAP ioctl failed on %s" % self.device_name 150 memset(&vid_format, 0, sizeof(vid_format)) 151 r = ioctl(self.device.fileno(), VIDIOC_G_FMT, &vid_format) 152 log("ioctl(%s, VIDIOC_G_FMT, %#x)=%s", self.device_name, <unsigned long> &vid_format, r) 153 #assert r!=-1, "VIDIOC_G_FMT ioctl failed on %s" % self.device_name 154 vid_format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT 155 vid_format.fmt.pix.width = self.width 156 vid_format.fmt.pix.height = self.height 157 vid_format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420 #V4L2_PIX_FMT_YVU420 158 vid_format.fmt.pix.sizeimage = self.framesize 159 vid_format.fmt.pix.field = V4L2_FIELD_NONE 160 vid_format.fmt.pix.bytesperline = self.rowstride 161 vid_format.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB 162 r = ioctl(self.device.fileno(), VIDIOC_S_FMT, &vid_format) 163 log("vid_format.type = %i", vid_format.type) 164 log("vid_format.fmt.pix.width = %i", vid_format.fmt.pix.width) 165 log("vid_format.fmt.pix.height = %i", vid_format.fmt.pix.height) 166 log("vid_format.fmt.pix.pixelformat = %i", vid_format.fmt.pix.pixelformat) 167 log("vid_format.fmt.pix.sizeimage = %i", vid_format.fmt.pix.sizeimage) 168 log("vid_format.fmt.pix.field = %i", vid_format.fmt.pix.field) 169 log("vid_format.fmt.pix.bytesperline = %i", vid_format.fmt.pix.bytesperline) 170 log("vid_format.fmt.pix.colorspace = %i", vid_format.fmt.pix.colorspace) 171 log("ioctl(%s, VIDIOC_S_FMT, %#x)=%s", self.device_name, <unsigned long> &vid_format, r) 172 assert r!=-1, "VIDIOC_S_FMT ioctl failed on %s" % self.device_name 173 174 def clean(self): #@DuplicatedSignature 175 self.width = 0 176 self.height = 0 177 self.rowstride = 0 178 self.src_format = "" 179 self.frames = 0 180 self.framesize = 0 181 182 def get_info(self): #@DuplicatedSignature 183 info = get_info() 184 info.update({"frames" : self.frames, 185 "width" : self.width, 186 "height" : self.height, 187 "src_format": self.src_format}) 188 return info 189 190 def __repr__(self): 191 if self.src_format is None: 192 return "v4l2.Pusher(uninitialized)" 193 return "v4l2.Pusher(%s - %sx%s)" % (self.src_format, self.width, self.height) 194 195 def is_closed(self): 196 return not bool(self.src_format) 197 198 def __dealloc__(self): 199 self.clean() 200 201 def get_width(self): 202 return self.width 203 204 def get_height(self): 205 return self.height 206 207 def get_type(self): #@DuplicatedSignature 208 return "v4l2" 209 210 def get_src_format(self): 211 return self.src_format 212 213 214 def push_image(self, image): 215 cdef unsigned char *Ybuf 216 cdef unsigned char *Ubuf 217 cdef unsigned char *Vbuf 218 cdef unsigned int Ystride, Ustride, Vstride 219 cdef Py_ssize_t buf_len = 0 220 cdef uint8_t* buf 221 222 iplanes = image.get_planes() 223 assert iplanes==ImageWrapper._3_PLANES, "invalid input format: %s planes" % iplanes 224 assert image.get_width()>=self.width, "invalid image width: %s (minimum is %s)" % (image.get_width(), self.width) 225 assert image.get_height()>=self.height, "invalid image height: %s (minimum is %s)" % (image.get_height(), self.height) 226 planes = image.get_pixels() 227 assert planes, "failed to get pixels from %s" % image 228 input_strides = image.get_rowstride() 229 log("push_image(%s) strides=%s", (image), input_strides) 230 Ystride = input_strides[0] 231 Ustride = input_strides[1] 232 Vstride = input_strides[2] 233 assert Ystride==self.rowstride 234 assert Ustride==self.rowstride//2 235 assert Vstride==self.rowstride//2 236 assert object_as_buffer(planes[0], <const void**> &Ybuf, &buf_len)==0, "failed to convert %s to a buffer" % type(planes[0]) 237 assert buf_len>=Ystride*image.get_height(), "buffer for Y plane is too small: %s bytes, expected at least %s" % (buf_len, Ystride*image.get_height()) 238 assert object_as_buffer(planes[1], <const void**> &Ubuf, &buf_len)==0, "failed to convert %s to a buffer" % type(planes[1]) 239 assert buf_len>=Ustride*image.get_height()//2, "buffer for U plane is too small: %s bytes, expected at least %s" % (buf_len, Ustride*image.get_height()/2) 240 assert object_as_buffer(planes[2], <const void**> &Vbuf, &buf_len)==0, "failed to convert %s to a buffer" % type(planes[2]) 241 assert buf_len>=Vstride*image.get_height()//2, "buffer for V plane is too small: %s bytes, expected at least %s" % (buf_len, Vstride*image.get_height()/2) 242 243 buf = <uint8_t*> xmemalign(self.framesize) 244 assert buf!=NULL 245 memcpy(buf, Ybuf, Ystride*self.height) 246 memcpy(buf+Ystride*self.height, Ubuf, Ustride*self.height//2) 247 memcpy(buf+Ystride*self.height+Ustride*self.height//2, Vbuf, Vstride*self.height//2) 248 #memset(buf, 128, self.framesize) 249 self.device.write(buf[:self.framesize]) 250 self.device.flush() 251 free(buf) 252 253 254 def selftest(full=False): 255 pass -
xpra/codecs/v4l2
-
xpra/log.py
Property changes on: xpra/codecs/v4l2 ___________________________________________________________________ Added: svn:ignore ## -0,0 +1,2 ## +pusher.c +pusher.html
198 198 ("x264" , "libx264 encoder"), 199 199 ("x265" , "libx265 encoder"), 200 200 ("webp" , "libwebp encoder and decoder"), 201 ("webcam" , "webcam access"), 201 202 ])), 202 203 ("Pointer", OrderedDict([ 203 204 ("mouse" , "Mouse motion"), -
xpra/platform/xposix/webcam_util.py
1 # This file is part of Xpra. 2 # Copyright (C) 2016 Antoine Martin <antoine@devloop.org.uk> 3 # Xpra is released under the terms of the GNU GPL v2, or, at your option, any 4 # later version. See the file COPYING for details. 5 6 import os 7 8 from xpra.log import Logger 9 log = Logger("client") 10 from xpra.util import engs 11 12 13 def get_virtual_video_devices(): 14 log("get_virtual_video_devices") 15 v4l2_virtual_dir = "/sys/devices/virtual/video4linux" 16 if not os.path.exists(v4l2_virtual_dir) or not os.path.isdir(v4l2_virtual_dir): 17 log.warn("Warning: virtual video directory %s not found", v4l2_virtual_dir) 18 log.warn(" webcam forwarding disabled") 19 return [] 20 contents = os.listdir(v4l2_virtual_dir) 21 devices = [] 22 for f in contents: 23 if not f.startswith("video"): 24 continue 25 try: 26 no = int(f[len("video"):]) 27 assert no>=0 28 except: 29 continue 30 dev_dir = os.path.join(v4l2_virtual_dir, f) 31 if not os.path.isdir(dev_dir): 32 continue 33 dev_name = os.path.join(dev_dir, "name") 34 try: 35 name = open(dev_name).read().replace("\n", "") 36 log("found %s: %s", f, name) 37 except: 38 continue 39 dev_file = "/dev/%s" % f 40 devices.append(dev_file) 41 log("devices: %s", devices) 42 log("found %i virtual video device%s", len(devices), engs(devices)) 43 return devices 44 45 def get_all_video_devices(): 46 contents = os.listdir("/dev") 47 devices = [] 48 for f in contents: 49 if not f.startswith("video"): 50 continue 51 dev_file = "/dev/%s" % f 52 if not os.path.isfile(dev_file): 53 continue 54 try: 55 no = int(f[len("video"):]) 56 assert no>=0 57 except: 58 continue 59 devices.append(f) 60 return devices 61 62 63 def main(): 64 import sys 65 if "-v" in sys.argv or "--verbose" in sys.argv: 66 from xpra.log import add_debug_category 67 add_debug_category("webcam") 68 69 from xpra.platform import program_context 70 with program_context("Webcam Info", "Webcam Info"): 71 devices = get_virtual_video_devices() 72 log.info("Found %i virtual video device%s:", len(devices), engs(devices)) 73 for d in devices: 74 log.info("%s", d) 75 76 if __name__ == "__main__": 77 main() -
xpra/scripts/config.py
316 316 "socket-permissions": str, 317 317 "exec-wrapper" : str, 318 318 "dbus-launch" : str, 319 "webcam" : str, 319 320 #int options: 320 321 "quality" : int, 321 322 "min-quality" : int, … … 460 461 "socket-permissions": "600", 461 462 "exec-wrapper" : "", 462 463 "dbus-launch" : "dbus-launch --close-stderr", 464 "webcam" : "auto", 463 465 "quality" : 0, 464 466 "min-quality" : 30, 465 467 "speed" : 0, -
xpra/scripts/main.py
423 423 group.add_option("--bell", action="store", 424 424 dest="bell", default=defaults.bell, metavar="yes|no", 425 425 help="Forward the system bell. Default: %s." % enabled_str(defaults.bell)) 426 group.add_option("--webcam", action="store", 427 dest="webcam", default=defaults.webcam, 428 help="Webcam forwarding, can be used to specify a device. Default: %s." % defaults.webcam) 426 429 legacy_bool_parse("global-menus") 427 430 group.add_option("--global-menus", action="store", 428 431 dest="global_menus", default=defaults.global_menus, metavar="yes|no", -
xpra/server/server_base.py
26 26 clipboardlog = Logger("clipboard") 27 27 rpclog = Logger("rpc") 28 28 dbuslog = Logger("dbus") 29 webcamlog = Logger("webcam") 29 30 30 31 from xpra.keyboard.mask import DEFAULT_MODIFIER_MEANINGS 31 32 from xpra.server.server_core import ServerCore, get_thread_info … … 32 33 from xpra.server.control_command import ArgsControlCommand, ControlError 33 34 from xpra.simple_stats import to_std_unit 34 35 from xpra.child_reaper import getChildReaper 35 from xpra.os_util import thread, get_hex_uuid, livefds, load_binary_file36 from xpra.os_util import BytesIOClass, thread, get_hex_uuid, livefds, load_binary_file 36 37 from xpra.util import typedict, updict, log_screen_sizes, engs, repr_ellipsized, csv, iround, \ 37 38 SERVER_EXIT, SERVER_ERROR, SERVER_SHUTDOWN, DETACH_REQUEST, NEW_CLIENT, DONE, IDLE_TIMEOUT 38 39 from xpra.net.bytestreams import set_socket_timeout … … 138 139 self.send_pings = False 139 140 self.scaling_control = False 140 141 self.rpc_handlers = {} 142 self.webcam_forwarding = False 143 self.webcam_forwarding_device = None 144 self.virtual_video_devices = 0 141 145 142 146 #sound: 143 147 self.pulseaudio = False … … 231 235 self.notifications_forwarder = None 232 236 self.notifications = opts.notifications 233 237 self.scaling_control = parse_bool_or_int("video-scaling", opts.video_scaling) 238 self.webcam_forwarding = opts.webcam.lower() not in FALSE_OPTIONS 239 if self.webcam_forwarding: 240 self.virtual_video_devices = self.init_virtual_video_devices() 241 if self.virtual_video_devices==0: 242 self.webcam_forwarding = False 234 243 235 244 #sound: 236 245 self.pulseaudio = opts.pulseaudio … … 343 352 printlog("init_printing() printing=%s", self.printing) 344 353 345 354 355 def init_virtual_video_devices(self): 356 webcamlog("init_virtual_video_devices") 357 if os.name!="posix": 358 return 0 359 try: 360 from xpra.codecs.v4l2.pusher import Pusher 361 assert Pusher 362 except ImportError as e: 363 webcamlog.error("Error: failed to import the virtual video module:") 364 webcamlog.error(" %s", e) 365 return 0 366 try: 367 from xpra.platform.xposix.webcam_util import get_virtual_video_devices 368 except ImportError as e: 369 webcamlog.warn("Warning: cannot load webcam components") 370 webcamlog.warn(" %s", e) 371 webcamlog.warn(" webcam forwarding disabled") 372 return 0 373 devices = get_virtual_video_devices() 374 webcamlog.info("found %i virtual video device%s", len(devices), engs(devices)) 375 return len(devices) 376 346 377 def init_uuid(self): 347 378 # Define a server UUID if needed: 348 379 self.uuid = self.get_uuid() … … 564 595 "command_request": self._process_command_request, 565 596 "printers": self._process_printers, 566 597 "send-file": self._process_send_file, 598 "webcam-start": self._process_webcam_start, 599 "webcam-stop": self._process_webcam_stop, 600 "webcam-frame": self._process_webcam_frame, 567 601 } 568 602 self._authenticated_ui_packet_handlers = self._default_packet_handlers.copy() 569 603 self._authenticated_ui_packet_handlers.update({ … … 731 765 getVideoHelper().cleanup() 732 766 reaper_cleanup() 733 767 self.cleanup_pulseaudio() 768 self.stop_virtual_webcam() 734 769 ds = self.dbus_server 735 770 if ds: 736 771 ds.cleanup() … … 1155 1190 "start-new-commands" : self.start_new_commands, 1156 1191 "exit-with-children" : self.exit_with_children, 1157 1192 "av-sync.enabled" : self.av_sync, 1193 "webcam" : self.webcam_forwarding, 1194 "virtual-video-devices" : self.virtual_video_devices, 1158 1195 }) 1159 1196 capabilities.update(self.get_file_transfer_features()) 1160 1197 for x in self.get_server_features(): … … 1738 1775 return {} 1739 1776 return self._clipboard_helper.get_info() 1740 1777 1778 def get_webcam_info(self): 1779 return {"" : self.webcam_forwarding, 1780 "virtual-video-devices" : self.virtual_video_devices} 1781 1741 1782 def do_get_info(self, proto, server_sources=None, window_ids=None): 1742 1783 info = {"server.python.version" : python_platform.python_version()} 1743 1784 … … 1744 1785 def up(prefix, d, suffix=""): 1745 1786 updict(info, prefix, d, suffix) 1746 1787 1788 up("webcam", self.get_webcam_info()) 1747 1789 up("file", self.get_file_transfer_info()) 1748 1790 up("printing", self.get_printing_info()) 1749 1791 up("features", self.get_features_info()) … … 2450 2492 log.info("_process_configure_window(%s, %s)", proto, packet) 2451 2493 2452 2494 2495 def _process_webcam_start(self, proto, packet): 2496 assert self.webcam_forwarding 2497 ss = self._server_sources.get(proto) 2498 if not ss: 2499 webcamlog.warn("Warning: invalid client source for webcam start") 2500 return 2501 device, w, h = packet[1:4] 2502 self.start_virtual_webcam(ss, device, w, h) 2503 2504 def start_virtual_webcam(self, ss, device, w, h): 2505 assert w>0 and h>0 2506 from xpra.platform.xposix.webcam_util import get_virtual_video_devices 2507 devices = get_virtual_video_devices() 2508 if len(devices)==0: 2509 webcamlog.warn("Warning: cannot start webcam forwarding, no virtual devices found") 2510 ss.send_webcam_stop(device) 2511 return 2512 if self.webcam_forwarding_device: 2513 self.stop_virtual_webcam() 2514 device_str = devices[0] 2515 try: 2516 from xpra.codecs.v4l2.pusher import Pusher #@UnresolvedImport 2517 p = Pusher() 2518 p.init_context(w, h, w, "YUV420P", device_str) 2519 self.webcam_forwarding_device = p 2520 webcamlog.info("webcam forwarding using %s", device_str) 2521 #this tell the client to start sending: 2522 ss.send_webcam_ack(device, 0) 2523 except Exception as e: 2524 webcamlog.error("Error setting up webcam forwarding:") 2525 webcamlog.error(" %s", e) 2526 ss.send_webcam_stop(device, str(e)) 2527 2528 def _process_webcam_stop(self, proto, packet): 2529 device, message = packet[1, 2] 2530 webcamlog("stopping webcam device %s: %s", device, message) 2531 if not self.webcam_forwarding_device: 2532 webcamlog.warn("Warning: cannot stop webcam device %s: no such context!", device, message) 2533 return 2534 self.stop_virtual_webcam() 2535 2536 def stop_virtual_webcam(self): 2537 webcamlog("stop_virtual_webcam() webcam_forwarding_device=%s", self.webcam_forwarding_device) 2538 vfd = self.webcam_forwarding_device 2539 if vfd: 2540 self.webcam_forwarding_device = None 2541 vfd.clean() 2542 2543 def _process_webcam_frame(self, proto, packet): 2544 device, frame_no, encoding, w, h, data = packet[1:7] 2545 assert encoding and w and h and data 2546 ss = self._server_sources.get(proto) 2547 if not ss: 2548 webcamlog.warn("Warning: invalid client source for webcam frame") 2549 return 2550 vfd = self.webcam_forwarding_device 2551 if not self.webcam_forwarding_device: 2552 webcamlog.warn("Warning: webcam forwarding is not active, dropping frame") 2553 ss.send_webcam_stop(device, "not started") 2554 return 2555 try: 2556 #only png supported for now! 2557 assert encoding=="png" 2558 from PIL import Image 2559 buf = BytesIOClass(data) 2560 img = Image.open(buf) 2561 pixels = img.tobytes('raw', "BGRX") 2562 from xpra.codecs.image_wrapper import ImageWrapper 2563 #now convert it to YUV: 2564 bgrx_image = ImageWrapper(0, 0, w, h, pixels, "BGRX", 24, w*4, planes=ImageWrapper.PACKED) 2565 from xpra.codecs.csc_cython.colorspace_converter import ColorspaceConverter #@UnresolvedImport 2566 csc = ColorspaceConverter() 2567 csc.init_context(w, h, "BGRX", w, h, "YUV420P") 2568 image = csc.convert_image(bgrx_image) 2569 vfd.push_image(image) 2570 #tell the client all is good: 2571 ss.send_webcam_ack(device, frame_no) 2572 except Exception as e: 2573 webcamlog.error("Error processing webcam frame:") 2574 webcamlog.error(" %s", e) 2575 ss.send_webcam_stop(device, str(e)) 2576 self.stop_virtual_webcam() 2577 2578 2453 2579 def process_clipboard_packet(self, ss, packet): 2454 2580 if not ss: 2455 2581 #protocol has been dropped! -
xpra/server/source.py
1580 1580 self.send("set_deflate", level) 1581 1581 1582 1582 1583 def send_webcam_ack(self, device, frame=0): 1584 self.send("webcam-ack", device, frame) 1585 1586 def send_webcam_stop(self, device, message): 1587 self.send("webcam-stop", device, message) 1588 1589 1583 1590 def set_printers(self, printers, password_file, encryption, encryption_keyfile): 1584 1591 printlog("set_printers(%s, %s, %s, %s) for %s", printers, password_file, encryption, encryption_keyfile, self) 1585 1592 if self.machine_id==get_machine_id() and not ADD_LOCAL_PRINTERS: