xpra icon
Bug tracker and wiki

This bug tracker and wiki are being discontinued
please use https://github.com/Xpra-org/xpra instead.


Ticket #229: codec.pyx

File codec.pyx, 11.9 KB (added by Antoine Martin, 9 years ago)

codec with lots of debugging to try to figure out where/why it crashes on win32

Line 
1# This file is part of Parti.
2# Copyright (C) 2012 Antoine Martin <antoine@devloop.org.uk>
3# Parti 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
6import os
7from libc.stdlib cimport free
8
9DEFAULT_INITIAL_QUALITY = 70
10ALL_PROFILES = ["baseline", "main", "high", "high10", "high422", "high444"]
11I420_PROFILES = ALL_PROFILES[:]
12I422_PROFILES = ["high422", "high444"]
13I444_PROFILES = ["high444"]
14DEFAULT_I420_PROFILE = "baseline"
15DEFAULT_I422_PROFILE = "high422"
16DEFAULT_I444_PROFILE = "high444"
17DEFAULT_I422_MIN_QUALITY = 80
18DEFAULT_I444_MIN_QUALITY = 90
19
20cdef extern from "string.h":
21    void * memcpy ( void * destination, void * source, size_t num )
22    void * memset ( void * ptr, int value, size_t num )
23
24cdef extern from *:
25    ctypedef unsigned long size_t
26
27cdef extern from "Python.h":
28    ctypedef int Py_ssize_t
29    ctypedef object PyObject
30    ctypedef void** const_void_pp "const void**"
31    int PyObject_AsReadBuffer(object obj, void ** buffer, Py_ssize_t * buffer_len) except -1
32
33ctypedef unsigned char uint8_t
34ctypedef void x264lib_ctx
35ctypedef void x264_picture_t
36cdef extern from "x264lib.h":
37    void* xmemalign(size_t size)
38    void xmemfree(void* ptr)
39
40    x264lib_ctx* init_encoder(int width, int height, int initial_quality, int supports_csc_option,
41                              int I422_min_quality, int I444_min_quality,
42                              char *i420_profile, char *i422_profile, char *i444_profile)
43    void clean_encoder(x264lib_ctx *context)
44    x264_picture_t* csc_image_rgb2yuv(x264lib_ctx *ctx, uint8_t *input, int stride)
45    int compress_image(x264lib_ctx *ctx, x264_picture_t *pic_in, uint8_t **out, int *outsz, int quality_override) nogil
46    int get_encoder_pixel_format(x264lib_ctx *ctx)
47    int get_encoder_quality(x264lib_ctx *ctx)
48    int get_pixel_format(int csc_format)
49
50    x264lib_ctx* init_decoder(int width, int height, int csc_fmt)
51    void set_decoder_csc_format(x264lib_ctx *context, int csc_fmt)
52    void clean_decoder(x264lib_ctx *context)
53    int decompress_image(x264lib_ctx *context, uint8_t *input, int size, uint8_t *(*out)[3], int (*outstride)[3]) nogil
54    int csc_image_yuv2rgb(x264lib_ctx *ctx, uint8_t *input[3], int stride[3], uint8_t **out, int *outsz, int *outstride) nogil
55    void set_encoding_speed(x264lib_ctx *context, int pct)
56    void set_encoding_quality(x264lib_ctx *context, int pct)
57
58
59""" common superclass for Decoder and Encoder """
60cdef class xcoder:
61    cdef x264lib_ctx *context
62    cdef int width
63    cdef int height
64
65    def init(self, width, height):
66        self.width = width
67        self.height = height
68
69    def __dealloc__(self):
70        self.clean()
71
72    def get_width(self):
73        return self.width
74
75    def get_height(self):
76        return self.height
77
78    def get_type(self):
79        return  "x264"
80
81cdef class RGBImage:
82    cdef uint8_t *data
83    cdef int size
84    cdef int rowstride
85
86    cdef init(self, uint8_t *data, int size, int rowstride):
87        self.data = data
88        self.size = size
89        self.rowstride = rowstride
90
91    cdef free(self):
92        assert self.data!=NULL
93        xmemfree(self.data)
94        self.data = NULL
95
96    def get_data(self):
97        return (<char *>self.data)[:self.size]
98
99    def get_size(self):
100        return self.size
101
102    def get_rowstride(self):
103        return self.rowstride
104
105    def __dealloc__(self):                  #@DuplicatedSignature
106        self.free()
107
108
109cdef class Decoder(xcoder):
110
111    def init_context(self, width, height, options):
112        self.init(width, height)
113        csc_fmt = options.get("csc_pixel_format", -1)
114        self.context = init_decoder(width, height, csc_fmt)
115
116    def clean(self):
117        if self.context!=NULL:
118            clean_decoder(self.context)
119            self.context = NULL
120
121    def decompress_image_to_yuv(self, input, options):
122        cdef uint8_t *dout[3]
123        cdef int outsize
124        cdef int outstrides[3]
125        cdef unsigned char * padded_buf = NULL
126        cdef unsigned char * buf = NULL
127        cdef Py_ssize_t buf_len = 0
128        assert self.context!=NULL
129        print("decompress_image_to_yuv start")
130        PyObject_AsReadBuffer(input, <const_void_pp> &buf, &buf_len)
131        padded_buf = <unsigned char *> xmemalign(buf_len+32)
132        if padded_buf==NULL:
133            return 1, [0, 0, 0], ["", "", ""]
134        memcpy(padded_buf, buf, buf_len)
135        memset(padded_buf+buf_len, 0, 32)
136        set_decoder_csc_format(self.context, int(options.get("csc_pixel_format", -1)))
137        cdef int i = 0
138        print("decompress_image_to_yuv before nogil")
139        with nogil:
140            i = decompress_image(self.context, buf, buf_len, &dout, &outstrides)
141        xmemfree(padded_buf)
142        print("decompress_image_to_yuv before xmemfree, i=%s" % i)
143        if i!=0:
144            return i, [0, 0, 0], ["", "", ""]
145        if dout[0]==NULL or dout[1]==NULL or dout[2]==NULL:
146            print("invalid NULL data pointers!")
147            return 2, [0, 0, 0], ["", "", ""]
148        print("decompress_image_to_yuv before doutv copy")
149        print("outstrides: %s, height=%s" % ((outstrides[0], outstrides[1], outstrides[2]), self.height))
150        cdef unsigned long lp = 0
151        channels = [0,1,2]
152        for pct in 50, 60, 63, 64, 65, 100:
153            outs = []
154            strides = []
155            h = self.height
156            print("div pct=%s, height=%s" % (pct, self.height))
157            for c in 0,1,2:
158                stride = outstrides[c]
159                #size = h*s
160                size = int(h*stride*pct/100)
161                lp = <unsigned long> dout[c]
162                print("%s: pointer: %s" % (c, lp))
163                print("%s: stride=%s, size=%s" % (c, stride, size))
164                #m = s*10
165                #outv = (<char *>dout[c])[:m] + '\0'*s*(h-10)
166                if c in channels:
167                    out = (<char *>dout[c])[:size]
168                else:
169                    out = '\0'*size
170                print("%s: len=%s" % (c, len(out)))
171                outs.append(out)
172                strides.append(stride)
173        print("decompress_image_to_yuv end")
174        print("decompress_image_to_yuv strides=%s, out len=%s" % (strides, [len(x) for x in outs]))
175        return  i, strides, outs
176
177    def get_pixel_format(self, csc_pixel_format):
178        return get_pixel_format(csc_pixel_format)
179
180    def decompress_image_to_rgb(self, input, options):
181        cdef uint8_t *yuvplanes[3]
182        cdef uint8_t *dout
183        cdef int outsize                        #@DuplicatedSignature
184        cdef int yuvstrides[3]
185        cdef int outstride
186        cdef unsigned char * padded_buf = NULL  #@DuplicatedSignature
187        cdef unsigned char * buf = NULL         #@DuplicatedSignature
188        cdef Py_ssize_t buf_len = 0             #@DuplicatedSignature
189        cdef int i = 0                          #@DuplicatedSignature
190        assert self.context!=NULL
191        PyObject_AsReadBuffer(input, <const_void_pp> &buf, &buf_len)
192        padded_buf = <unsigned char *> xmemalign(buf_len+32)
193        if padded_buf==NULL:
194            return 100, None
195        memcpy(padded_buf, buf, buf_len)
196        memset(padded_buf+buf_len, 0, 32)
197        set_decoder_csc_format(self.context, int(options.get("csc_pixel_format", -1)))
198        with nogil:
199            i = decompress_image(self.context, padded_buf, buf_len, &yuvplanes, &yuvstrides)
200            if i==0:
201                i = csc_image_yuv2rgb(self.context, yuvplanes, yuvstrides, &dout, &outsize, &outstride)
202        xmemfree(padded_buf)
203        if i!=0:
204            return i, None
205        rgb_image = RGBImage()
206        rgb_image.init(dout, outsize, outstride)
207        return  i, rgb_image
208
209
210cdef class Encoder(xcoder):
211    cdef int frames
212    cdef int supports_options
213
214    def _get_profile(self, options, csc_mode, default_value, valid_options):
215        #try the environment as a default, fallback to hardcoded default:
216        profile = os.environ.get("XPRA_X264_%s_PROFILE" % csc_mode, default_value)
217        #now see if the client has requested a different value:
218        profile = options.get("x264.%s.profile" % csc_mode, profile)
219        if profile not in valid_options:
220            print("invalid %s profile: %s" % (csc_mode, profile))
221            return default_value
222        return profile
223
224    def _get_min_quality(self, options, csc_mode, default_value):
225        #try the environment as a default, fallback to hardcoded default:
226        min_quality = int(os.environ.get("XPRA_X264_%s_MIN_QUALITY" % csc_mode, default_value))
227        #now see if the client has requested a different value:
228        min_quality = options.get("x264.%s.min_quality" % csc_mode, min_quality)
229        #enforce valid range:
230        return min(100, max(-1, min_quality))
231
232    def init_context(self, width, height, options):    #@DuplicatedSignature
233        self.init(width, height)
234        self.frames = 0
235        self.supports_options = int(options.get("encoding_client_options", False))
236        I420_profile = self._get_profile(options, "I420", DEFAULT_I420_PROFILE, I420_PROFILES)
237        I422_profile = self._get_profile(options, "I422", DEFAULT_I422_PROFILE, I422_PROFILES)
238        I444_profile = self._get_profile(options, "I444", DEFAULT_I444_PROFILE, I444_PROFILES)
239        I422_min_quality = self._get_min_quality(options, "I422", DEFAULT_I422_MIN_QUALITY)
240        I444_min_quality = self._get_min_quality(options, "I444", DEFAULT_I444_MIN_QUALITY)
241        if I422_min_quality>I444_min_quality:
242            print("ignoring nonsensical I422 vs i444 thresholds: %s vs %s" % (I422_min_quality, I444_min_quality))
243            I422_min_quality = -1
244            I444_min_quality = -1
245        initial_quality = min(100, max(0, options.get("initial_quality", DEFAULT_INITIAL_QUALITY)))
246        self.context = init_encoder(width, height, initial_quality, int(self.supports_options),
247                                    int(I422_min_quality), int(I444_min_quality),
248                                    I420_profile, I422_profile, I444_profile)
249
250    def clean(self):                        #@DuplicatedSignature
251        if self.context!=NULL:
252            clean_encoder(self.context)
253            self.context = NULL
254
255    def get_client_options(self, options):
256        client_options = {
257                "csc_pixel_format" : get_encoder_pixel_format(self.context),
258                "frame" : self.frames
259                }
260        if "quality" in options:
261            #quality was overriden via options:
262            client_options["quality"] = options["quality"]
263        else:
264
265            #current quality settings:
266            client_options["quality"] = get_encoder_quality(self.context)
267        return  client_options
268
269    def compress_image(self, input, rowstride, options):
270        cdef x264_picture_t *pic_in = NULL
271        cdef uint8_t *pic_buf = NULL
272        cdef Py_ssize_t pic_buf_len = 0
273        cdef int quality_override = options.get("quality", -1)
274        assert self.context!=NULL
275        #colourspace conversion with gil held:
276        PyObject_AsReadBuffer(input, <const_void_pp> &pic_buf, &pic_buf_len)
277        pic_in = csc_image_rgb2yuv(self.context, pic_buf, rowstride)
278        assert pic_in!=NULL, "colourspace conversion failed"
279        return self.do_compress_image(pic_in, quality_override)
280
281    cdef do_compress_image(self, x264_picture_t *pic_in, int quality_override):
282        #actual compression (no gil):
283        cdef int i
284        cdef uint8_t *cout
285        cdef int coutsz
286        with nogil:
287            i = compress_image(self.context, pic_in, &cout, &coutsz, quality_override)
288        if i!=0:
289            return i, 0, ""
290        coutv = (<char *>cout)[:coutsz]
291        self.frames += 1
292        return  i, coutsz, coutv
293
294    def set_encoding_speed(self, pct):
295        ipct = int(pct)
296        assert ipct>=0 and ipct<=100, "invalid percentage: %s" % ipct
297        set_encoding_speed(self.context, ipct)
298
299    def set_encoding_quality(self, pct):
300        ipct = int(pct)
301        assert ipct>=0 and ipct<=100, "invalid percentage: %s" % ipct
302        set_encoding_quality(self.context, ipct)