Hide keyboard shortcuts

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. 

6 

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 

11 

12log = Logger("webcam") 

13 

14MAX_WEBCAM_DEVICES = envint("XPRA_MAX_WEBCAM_DEVICES", 1) 

15 

16 

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 

28 

29 

30""" 

31Handle webcam forwarding. 

32""" 

33class WebcamMixin(StubSourceMixin): 

34 

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 

47 

48 def __init__(self): 

49 self.webcam_enabled = False 

50 self.webcam_device = None 

51 self.webcam_encodings = [] 

52 

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) 

59 

60 def init_state(self): 

61 #for each webcam device_id, the actual device used 

62 self.webcam_forwarding_devices = {} 

63 

64 def cleanup(self): 

65 self.stop_all_virtual_webcams() 

66 

67 

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 } 

75 

76 

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() 

89 

90 

91 def send_webcam_ack(self, device, frame, *args): 

92 self.send_async("webcam-ack", device, frame, *args) 

93 

94 def send_webcam_stop(self, device, message): 

95 self.send_async("webcam-stop", device, message) 

96 

97 

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 

146 

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) 

151 

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) 

163 

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