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