1# -*- coding: utf-8 -*- 2# This file is part of Xpra. 3# Copyright (C) 2016-2021 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 7#cython: wraparound=False 8 9import time 10import os 11 12from xpra.log import Logger 13log = Logger("webcam") 14 15from xpra.os_util import path_permission_info 16from xpra.util import print_nested_dict 17from xpra.codecs.image_wrapper import ImageWrapper 18from xpra.codecs.codec_constants import get_subsampling_divs 19from xpra.buffers.membuf cimport memalign #pylint: disable=syntax-error 20 21 22from libc.stdint cimport uint32_t, uint8_t 23from libc.stdlib cimport free 24from libc.string cimport memset, memcpy 25 26 27cdef extern from "Python.h": 28 int PyObject_GetBuffer(object obj, Py_buffer *view, int flags) 29 void PyBuffer_Release(Py_buffer *view) 30 int PyBUF_ANY_CONTIGUOUS 31 32cdef extern from "sys/ioctl.h": 33 int ioctl(int fd, unsigned long request, ...) 34 35include "constants.pxi" 36 37cdef extern from "./video.h": 38 int V4L2_FIELD_NONE 39 int V4L2_FIELD_TOP 40 int V4L2_FIELD_BOTTOM 41 int V4L2_FIELD_INTERLACED 42 int V4L2_FIELD_SEQ_TB 43 int V4L2_FIELD_SEQ_BT 44 int V4L2_FIELD_ALTERNATE 45 #int V4L2_FIELD_INTERLACED_TB 46 #int V4L2_FIELD_INTERLACED_BT 47 int V4L2_COLORSPACE_SRGB 48 int V4L2_COLORSPACE_470_SYSTEM_M 49 int V4L2_COLORSPACE_470_SYSTEM_BG 50 int V4L2_COLORSPACE_SMPTE170M 51 int V4L2_COLORSPACE_SMPTE240M 52 int V4L2_COLORSPACE_REC709 53 54 int V4L2_PIX_FMT_GREY 55 int V4L2_PIX_FMT_YUV422P 56 int V4L2_PIX_FMT_YUV420 57 int V4L2_PIX_FMT_YVU420 58 int V4L2_PIX_FMT_YUYV 59 int V4L2_PIX_FMT_UYVY 60 int V4L2_PIX_FMT_YUV410 61 int V4L2_PIX_FMT_YUV411P 62 int V4L2_PIX_FMT_BGR24 63 int V4L2_PIX_FMT_RGB24 64 int V4L2_PIX_FMT_BGR32 65 int V4L2_PIX_FMT_RGB32 66 int V4L2_PIX_FMT_NV12 67 int V4L2_PIX_FMT_NV21 68 #int V4L2_PIX_FMT_H264 69 #int V4L2_PIX_FMT_MPEG4 70 int VIDIOC_QUERYCAP 71 int VIDIOC_G_FMT 72 int VIDIOC_S_FMT 73 int V4L2_BUF_TYPE_VIDEO_OUTPUT 74 75 #define v4l2_fourcc(a,b,c,d)\ 76 # (((__u32)(a)<<0)|((__u32)(b)<<8)|((__u32)(c)<<16)|((__u32)(d)<<24)) 77 int v4l2_fourcc(unsigned char a, unsigned char b, unsigned char c, unsigned char d) 78 79 IF ENABLE_DEVICE_CAPS: 80 cdef struct v4l2_capability: 81 uint8_t driver[16] 82 uint8_t card[32] 83 uint8_t bus_info[32] 84 uint32_t version 85 uint32_t capabilities 86 uint32_t device_caps 87 uint32_t reserved[3] 88 ELSE: 89 cdef struct v4l2_capability: #redefined without device_caps! 90 uint8_t driver[16] 91 uint8_t card[32] 92 uint8_t bus_info[32] 93 uint32_t version 94 uint32_t capabilities 95 uint32_t reserved[3] 96 97 cdef struct v4l2_pix_format: 98 uint32_t width 99 uint32_t height 100 uint32_t pixelformat 101 uint32_t field # enum v4l2_field */ 102 uint32_t bytesperline # for padding, zero if unused */ 103 uint32_t sizeimage 104 uint32_t colorspace # enum v4l2_colorspace */ 105 uint32_t priv # private data, depends on pixelformat */ 106 uint32_t flags # format flags (V4L2_PIX_FMT_FLAG_*) */ 107 #uint32_t ycbcr_enc # enum v4l2_ycbcr_encoding */ 108 #uint32_t quantization # enum v4l2_quantization */ 109 #uint32_t xfer_func # enum v4l2_xfer_func */ 110 111 cdef struct v4l2_pix_format_mplane: 112 pass 113 cdef struct v4l2_window: 114 pass 115 cdef struct v4l2_vbi_format: 116 pass 117 cdef struct v4l2_sliced_vbi_format: 118 pass 119 cdef struct v4l2_sdr_format: 120 pass 121 122 cdef union v4l2_format_fmt: 123 v4l2_pix_format pix #V4L2_BUF_TYPE_VIDEO_CAPTURE 124 v4l2_pix_format_mplane pix_mp #V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE 125 v4l2_window win #V4L2_BUF_TYPE_VIDEO_OVERLAY 126 v4l2_vbi_format vbi #V4L2_BUF_TYPE_VBI_CAPTURE 127 v4l2_sliced_vbi_format sliced #V4L2_BUF_TYPE_SLICED_VBI_CAPTURE 128 v4l2_sdr_format sdr #V4L2_BUF_TYPE_SDR_CAPTURE 129 uint8_t raw_data[200] #user-defined 130 131 cdef struct v4l2_format: 132 uint32_t type 133 v4l2_format_fmt fmt 134 135 136#these fields are defined in the v4l2 headers, 137#but they may not all be defined, and probably aren't on some platforms (ie: NetBSD) 138#so we duplicate the definition here: 139V4L2_CAP_VIDEO_CAPTURE = 0x00000001 140V4L2_CAP_VIDEO_CAPTURE_MPLANE = 0x00001000 141V4L2_CAP_VIDEO_OUTPUT = 0x00000002 142V4L2_CAP_VIDEO_OUTPUT_MPLANE = 0x00002000 143V4L2_CAP_VIDEO_M2M = 0x00004000 144V4L2_CAP_VIDEO_M2M_MPLANE = 0x00008000 145V4L2_CAP_VIDEO_OVERLAY = 0x00000004 146V4L2_CAP_VBI_CAPTURE = 0x00000010 147V4L2_CAP_VBI_OUTPUT = 0x00000020 148V4L2_CAP_SLICED_VBI_CAPTURE = 0x00000040 149V4L2_CAP_SLICED_VBI_OUTPUT = 0x00000080 150V4L2_CAP_RDS_CAPTURE = 0x00000100 151V4L2_CAP_VIDEO_OUTPUT_OVERLAY = 0x00000200 152V4L2_CAP_HW_FREQ_SEEK = 0x00000400 153V4L2_CAP_RDS_OUTPUT = 0x00000800 154V4L2_CAP_TUNER = 0x00010000 155V4L2_CAP_AUDIO = 0x00020000 156V4L2_CAP_RADIO = 0x00040000 157V4L2_CAP_MODULATOR = 0x00080000 158V4L2_CAP_SDR_CAPTURE = 0x00100000 159V4L2_CAP_EXT_PIX_FORMAT = 0x00200000 160V4L2_CAP_SDR_OUTPUT = 0x00400000 161V4L2_CAP_READWRITE = 0x01000000 162V4L2_CAP_ASYNCIO = 0x02000000 163V4L2_CAP_STREAMING = 0x04000000 164V4L2_CAP_DEVICE_CAPS = 0x80000000 165 166V4L2_CAPS = { 167 V4L2_CAP_VIDEO_CAPTURE : "VIDEO_CAPTURE", 168 V4L2_CAP_VIDEO_CAPTURE_MPLANE : "VIDEO_CAPTURE_MPLANE", 169 V4L2_CAP_VIDEO_OUTPUT : "VIDEO_OUTPUT", 170 V4L2_CAP_VIDEO_OUTPUT_MPLANE : "VIDEO_OUTPUT_MPLANE", 171 V4L2_CAP_VIDEO_M2M : "VIDEO_M2M", 172 V4L2_CAP_VIDEO_M2M_MPLANE : "VIDEO_M2M_MPLANE", 173 V4L2_CAP_VIDEO_OVERLAY : "VIDEO_OVERLAY", 174 V4L2_CAP_VBI_CAPTURE : "VBI_CAPTURE", 175 V4L2_CAP_VBI_OUTPUT : "VBI_OUTPUT", 176 V4L2_CAP_SLICED_VBI_CAPTURE : "SLICED_VBI_CAPTURE", 177 V4L2_CAP_SLICED_VBI_OUTPUT : "SLICED_VBI_OUTPUT", 178 V4L2_CAP_RDS_CAPTURE : "RDS_CAPTURE", 179 V4L2_CAP_VIDEO_OUTPUT_OVERLAY : "VIDEO_OUTPUT_OVERLAY", 180 V4L2_CAP_HW_FREQ_SEEK : "HW_FREQ_SEEK", 181 V4L2_CAP_RDS_OUTPUT : "RDS_OUTPUT", 182 V4L2_CAP_TUNER : "TUNER", 183 V4L2_CAP_AUDIO : "AUDIO", 184 V4L2_CAP_RADIO : "RADIO", 185 V4L2_CAP_MODULATOR : "MODULATOR", 186 V4L2_CAP_SDR_CAPTURE : "SDR_CAPTURE", 187 V4L2_CAP_EXT_PIX_FORMAT : "EXT_PIX_FORMAT", 188 V4L2_CAP_SDR_OUTPUT : "SDR_OUTPUT", 189 V4L2_CAP_READWRITE : "READWRITE", 190 V4L2_CAP_ASYNCIO : "ASYNCIO", 191 V4L2_CAP_STREAMING : "STREAMING", 192 V4L2_CAP_DEVICE_CAPS : "DEVICE_CAPS", 193 } 194 195 196FIELD_STR = { 197 V4L2_FIELD_NONE : "None", 198 V4L2_FIELD_TOP : "Top", 199 V4L2_FIELD_BOTTOM : "Bottom", 200 V4L2_FIELD_INTERLACED : "Interlaced", 201 V4L2_FIELD_SEQ_TB : "SEQ TB", 202 V4L2_FIELD_SEQ_BT : "SEQ BT", 203 V4L2_FIELD_ALTERNATE : "ALTERNATE", 204 #V4L2_FIELD_INTERLACED_TB : "INTERLACED TB", 205 #V4L2_FIELD_INTERLACED_BT : "INTERLACED BT", 206} 207COLORSPACE_STR = { 208 V4L2_COLORSPACE_SRGB : "SRGB", 209 V4L2_COLORSPACE_470_SYSTEM_M : "470_SYSTEM_M", 210 V4L2_COLORSPACE_470_SYSTEM_BG : "470_SYSTEM_BG", 211 V4L2_COLORSPACE_SMPTE170M : "SMPTE170M", 212 V4L2_COLORSPACE_SMPTE240M : "SMPTE240M", 213 V4L2_COLORSPACE_REC709 : "REC709", 214} 215 216cdef int V4L2_PIX_FMT_H264 = v4l2_fourcc(b'H', b'2', b'6', b'4') 217cdef int V4L2_PIX_FMT_MPEG4 = v4l2_fourcc(b'M', b'P', b'G', b'4') 218 219FORMAT_STR = { 220 V4L2_PIX_FMT_GREY : "GREY", 221 V4L2_PIX_FMT_YUV422P : "YUV422P", 222 V4L2_PIX_FMT_YUV420 : "YUV420P", 223 V4L2_PIX_FMT_YVU420 : "YVU420P", 224 V4L2_PIX_FMT_YUYV : "YUYV", 225 V4L2_PIX_FMT_UYVY : "UYVY", 226 V4L2_PIX_FMT_YUV410 : "YUV410P", 227 V4L2_PIX_FMT_YUV411P : "YUV411P", 228 V4L2_PIX_FMT_BGR24 : "BGR", 229 V4L2_PIX_FMT_RGB24 : "RGB", 230 V4L2_PIX_FMT_BGR32 : "BGRX", 231 V4L2_PIX_FMT_RGB32 : "RGBX", 232 V4L2_PIX_FMT_NV12 : "NV12", 233 V4L2_PIX_FMT_NV21 : "NV21", 234 V4L2_PIX_FMT_H264 : "H264", 235 V4L2_PIX_FMT_MPEG4 : "MPEG4", 236} 237PIX_FMT = {} 238for k,v in FORMAT_STR.items(): 239 PIX_FMT[v] = k 240 241 242log("v4l2.pusher init") 243print_nested_dict({ 244 "FIELD_STR" : FIELD_STR, 245 "COLORSPACE_STR" : COLORSPACE_STR, 246 "FORMAT_STR" : dict((hex(k),v) for k,v in FORMAT_STR.items()), 247 }, print_fn=log.debug) 248 249 250def query_video_device(device="/dev/video0"): 251 cdef v4l2_capability vid_caps 252 try: 253 log("v4l2 using device %s", device) 254 with open(device, "wb") as f: 255 r = ioctl(f.fileno(), VIDIOC_QUERYCAP, &vid_caps) 256 log("ioctl(%s, VIDIOC_QUERYCAP, %#x)=%s", device, <unsigned long> &vid_caps, r) 257 if r<0: 258 return {} 259 info = { 260 "driver" : vid_caps.driver, 261 "card" : vid_caps.card, 262 "bus_info" : vid_caps.bus_info, 263 "version" : vid_caps.version, 264 "capabilities" : [v for k,v in V4L2_CAPS.items() if vid_caps.capabilities & k], 265 } 266 IF ENABLE_DEVICE_CAPS: 267 info["device_caps"] = [v for k,v in V4L2_CAPS.items() if vid_caps.device_caps & k] 268 return dict((k,v) for k,v in info.items() if v) 269 except Exception as e: 270 log("query_video_device(%s)", device, exc_info=True) 271 log.error("Error: failed to query device '%s':", device) 272 log.error(" %s", e) 273 for x in path_permission_info(device, "device"): 274 log.error(" %s", x) 275 return {} 276 277 278def get_version(): 279 return (1, 0) 280 281def get_type(): 282 return "v4l2" 283 284def get_info(): 285 global COLORSPACES, MAX_WIDTH, MAX_HEIGHT 286 return { 287 "version" : get_version(), 288 } 289 290def get_input_colorspaces(): 291 return ["YUV420P"] #,"YUV422P" 292 293 294cdef class Pusher: 295 cdef unsigned long frames 296 cdef unsigned int width 297 cdef unsigned int height 298 cdef unsigned int rowstride 299 cdef size_t framesize 300 cdef object src_format 301 cdef object device 302 cdef object device_name 303 304 cdef object __weakref__ 305 306 def init_context(self, int width, int height, int rowstride, src_format, device): #@DuplicatedSignature 307 assert src_format in get_input_colorspaces(), "invalid source format '%s', must be one of %s" % (src_format, get_input_colorspaces()) 308 self.width = width 309 self.height = height 310 self.rowstride = rowstride 311 self.src_format = src_format 312 self.frames = 0 313 self.init_device(device) 314 315 cdef init_device(self, device): 316 cdef v4l2_capability vid_caps 317 cdef v4l2_format vid_format 318 self.device_name = device or os.environ.get("XPRA_VIDEO_DEVICE", "/dev/video1") 319 log("v4l2 using device %s", self.device_name) 320 self.device = open(self.device_name, "w+b", 0) 321 r = ioctl(self.device.fileno(), VIDIOC_QUERYCAP, &vid_caps) 322 log("ioctl(%s, VIDIOC_QUERYCAP, %#x)=%s", self.device_name, <unsigned long> &vid_caps, r) 323 assert r>=0, "VIDIOC_QUERYCAP ioctl failed on %s" % self.device_name 324 memset(&vid_format, 0, sizeof(vid_format)) 325 r = ioctl(self.device.fileno(), VIDIOC_G_FMT, &vid_format) 326 log("ioctl(%s, VIDIOC_G_FMT, %#x)=%s", self.device_name, <unsigned long> &vid_format, r) 327 if r>=0: 328 log("current device capture format:") 329 self.show_vid_format(&vid_format) 330 assert self.src_format in PIX_FMT, "unknown pixel format %s" % self.src_format 331 cdef int pixel_format = PIX_FMT[self.src_format] 332 divs = get_subsampling_divs(self.src_format) #ie: YUV420P -> (1, 1), (2, 2), (2, 2) 333 self.framesize = 0 334 for xdiv, ydiv in divs: 335 self.framesize += self.rowstride//xdiv*(self.height//ydiv) 336 vid_format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT 337 vid_format.fmt.pix.width = self.width 338 vid_format.fmt.pix.height = self.height 339 vid_format.fmt.pix.bytesperline = self.rowstride 340 vid_format.fmt.pix.pixelformat = pixel_format 341 vid_format.fmt.pix.sizeimage = self.framesize 342 vid_format.fmt.pix.field = V4L2_FIELD_NONE 343 #vid_format.fmt.pix.n_v4l_planes = 3 344 vid_format.fmt.pix.colorspace = V4L2_COLORSPACE_SRGB 345 #vid_format.fmt.pix.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT 346 #vid_format.fmt.pix.quantization = V4L2_QUANTIZATION_DEFAULT 347 #vid_format.fmt.pix.xfer_func = V4L2_XFER_FUNC_DEFAULT 348 r = ioctl(self.device.fileno(), VIDIOC_S_FMT, &vid_format) 349 log("ioctl(%s, VIDIOC_S_FMT, %#x)=%s", self.device_name, <unsigned long> &vid_format, r) 350 assert r>=0, "VIDIOC_S_FMT ioctl failed on %s" % self.device_name 351 self.show_vid_format(&vid_format) 352 self.width = vid_format.fmt.pix.width 353 self.height = vid_format.fmt.pix.height 354 self.rowstride = vid_format.fmt.pix.bytesperline 355 parsed_pixel_format = self.parse_pixel_format(&vid_format) 356 log("parsed pixel format(%s)=%s", vid_format.fmt.pix.pixelformat, parsed_pixel_format) 357 self.src_format = self.get_equiv_format(parsed_pixel_format) 358 log("internal format(%s)=%s", parsed_pixel_format, self.src_format) 359 #assert self.src_format in get_input_colorspaces(), "invalid pixel format used: %s" % self.src_format 360 361 362 def get_equiv_format(self, fmt): 363 return {"YU12" : "YUV420P", "YV12" : "YVU420P", "GREY" : "YUV420P"}.get(fmt, fmt) 364 365 cdef parse_pixel_format(self, v4l2_format *vid_format): 366 if vid_format.fmt.pix.pixelformat==0: 367 return "" 368 return "".join([chr((vid_format.fmt.pix.pixelformat//(2**(8*x))) % 256) for x in range(4)]) 369 370 cdef show_vid_format(self, v4l2_format *vid_format): 371 log("vid_format.type = %i", vid_format.type) 372 log("vid_format.fmt.pix.width = %i", vid_format.fmt.pix.width) 373 log("vid_format.fmt.pix.height = %i", vid_format.fmt.pix.height) 374 parsed_pixel_format = self.parse_pixel_format(vid_format) 375 equiv = self.get_equiv_format(parsed_pixel_format) 376 log("vid_format.fmt.pix.pixelformat = %s = %s (for %#x)", parsed_pixel_format or "unset", equiv or "unset", vid_format.fmt.pix.pixelformat) 377 log("vid_format.fmt.pix.sizeimage = %i", vid_format.fmt.pix.sizeimage) 378 log("vid_format.fmt.pix.field = %s (%i)", FIELD_STR.get(vid_format.fmt.pix.field, vid_format.fmt.pix.field), vid_format.fmt.pix.field) 379 log("vid_format.fmt.pix.bytesperline = %i", vid_format.fmt.pix.bytesperline) 380 log("vid_format.fmt.pix.colorspace = %s (%i)", COLORSPACE_STR.get(vid_format.fmt.pix.colorspace, vid_format.fmt.pix.colorspace), vid_format.fmt.pix.colorspace) 381 #log("vid_format.fmt.pix.ycbcr_enc = %s (%i)", YCBCR_ENC_STR.get(vid_format.fmt.pix.ycbcr_enc, vid_format.fmt.pix.ycbcr_enc), vid_format.fmt.pix.ycbcr_enc) 382 #log("vid_format.fmt.pix.quantization = %s (%i)", QUANTIZATION_STR.get(vid_format.fmt.pix.quantization, vid_format.fmt.pix.quantization), vid_format.fmt.pix.quantization) 383 #log("vid_format.fmt.pix.xfer_func = %s (%i)", XFER_FUNC_STR.get(vid_format.fmt.pix.xfer_func, vid_format.fmt.pix.xfer_func), vid_format.fmt.pix.xfer_func) 384 385 386 def clean(self): #@DuplicatedSignature 387 self.width = 0 388 self.height = 0 389 self.rowstride = 0 390 self.src_format = "" 391 self.frames = 0 392 self.framesize = 0 393 d = self.device 394 if d: 395 self.device = None 396 d.close() 397 398 def get_info(self) -> dict: #@DuplicatedSignature 399 info = get_info() 400 info.update({ 401 "frames" : int(self.frames), 402 "width" : self.width, 403 "height" : self.height, 404 "src_format": self.src_format, 405 "device" : self.device_name, 406 }) 407 return info 408 409 def __repr__(self): 410 if self.src_format is None: 411 return "v4l2.Pusher(uninitialized)" 412 return "v4l2.Pusher(%s:%s - %sx%s)" % (self.device_name, self.src_format, self.width, self.height) 413 414 def is_closed(self): 415 return not bool(self.src_format) 416 417 def __dealloc__(self): 418 self.clean() 419 420 def get_width(self): 421 return self.width 422 423 def get_height(self): 424 return self.height 425 426 def get_type(self): #@DuplicatedSignature 427 return "v4l2" 428 429 def get_src_format(self): 430 return self.src_format 431 432 433 def push_image(self, image): 434 cdef int i 435 divs = get_subsampling_divs(self.src_format) #ie: YUV420P -> (1, 1), (2, 2), (2, 2) 436 437 iplanes = image.get_planes() 438 assert iplanes==ImageWrapper.PLANAR_3, "invalid input format: %s planes" % iplanes 439 assert image.get_width()>=self.width, "invalid image width: %s (minimum is %s)" % (image.get_width(), self.width) 440 assert image.get_height()>=self.height, "invalid image height: %s (minimum is %s)" % (image.get_height(), self.height) 441 planes = image.get_pixels() 442 assert planes, "failed to get pixels from %s" % image 443 input_strides = image.get_rowstride() 444 445 #validate rowstrides: 446 for i in range(3): 447 stride = self.rowstride//divs[i][0] 448 assert input_strides[i]==stride, "invalid stride for plane %s: %s but expected %i" % (i, input_strides[i], stride) 449 450 #allocate temporary buffer we use for writing to the device: 451 cdef size_t l = self.framesize + self.rowstride 452 cdef uint8_t* buf = <uint8_t*> memalign(l) 453 assert buf!=NULL, "failed to allocate temporary output buffer" 454 455 cdef Py_buffer py_buf[3] 456 for i in range(3): 457 if PyObject_GetBuffer(planes[i], &py_buf[i], PyBUF_ANY_CONTIGUOUS): 458 raise Exception("failed to read pixel data from %s" % type(planes[i])) 459 min_len = input_strides[i]*(image.get_height()//divs[i][1]) 460 assert py_buf.len>=min_len, "buffer for Y plane is too small: %s bytes, expected at least %s" % (py_buf.len, min_len) 461 462 cdef unsigned char *Ybuf = <unsigned char *> py_buf[0].buf 463 cdef unsigned char *Ubuf = <unsigned char *> py_buf[1].buf 464 cdef unsigned char *Vbuf = <unsigned char *> py_buf[2].buf 465 cdef unsigned int Ystride = input_strides[0] 466 cdef unsigned int Ustride = input_strides[1] 467 cdef unsigned int Vstride = input_strides[2] 468 cdef unsigned int Yhdiv = divs[0][1] 469 cdef unsigned int Uhdiv = divs[1][1] 470 cdef unsigned int Vhdiv = divs[2][1] 471 472 cdef size_t s 473 try: 474 with nogil: 475 memset(buf, 0, l) 476 s = Ystride*(self.height//Yhdiv) 477 memcpy(buf, Ybuf, s) 478 i = s 479 s = Ustride*(self.height//Uhdiv) 480 memcpy(buf+i, Ubuf, s) 481 i += s 482 s = Vstride*(self.height//Vhdiv) 483 memcpy(buf+i, Vbuf, s) 484 for i in range(3): 485 PyBuffer_Release(&py_buf[i]) 486 self.device.write(buf[:self.framesize]) 487 self.device.flush() 488 finally: 489 free(buf) 490