1# This file is part of Xpra.
2# Copyright (C) 2012-2021 Antoine Martin <antoine@xpra.org>
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#cython: wraparound=False
7
8import os
9from time import monotonic
10
11from xpra.log import Logger
12log = Logger("encoder", "x264")
13
14from xpra.util import envint, envbool, csv, typedict, AtomicInteger
15from xpra.os_util import bytestostr, strtobytes
16from xpra.codecs.codec_constants import video_spec
17from collections import deque
18
19from libc.string cimport memset
20from libc.stdint cimport int64_t, uint64_t, uint8_t, uintptr_t
21
22
23MAX_DELAYED_FRAMES = envint("XPRA_X264_MAX_DELAYED_FRAMES", 4)
24THREADS = envint("XPRA_X264_THREADS", min(4, max(1, os.cpu_count()//2)))
25MIN_SLICED_THREADS_SPEED = envint("XPRA_X264_SLICED_THREADS", 60)
26LOGGING = os.environ.get("XPRA_X264_LOGGING", "WARNING")
27PROFILE = os.environ.get("XPRA_X264_PROFILE")
28SUPPORT_24BPP = envbool("XPRA_X264_SUPPORT_24BPP")
29SUPPORT_30BPP = envbool("XPRA_X264_SUPPORT_30BPP", True)
30TUNE = os.environ.get("XPRA_X264_TUNE")
31LOG_NALS = envbool("XPRA_X264_LOG_NALS")
32SAVE_TO_FILE = os.environ.get("XPRA_SAVE_TO_FILE")
33BLANK_VIDEO = envbool("XPRA_X264_BLANK_VIDEO")
34
35FAST_DECODE_MIN_SPEED = envint("XPRA_FAST_DECODE_MIN_SPEED", 70)
36
37cdef extern from "Python.h":
38    int PyObject_GetBuffer(object obj, Py_buffer *view, int flags)
39    void PyBuffer_Release(Py_buffer *view)
40    int PyBUF_ANY_CONTIGUOUS
41
42cdef extern from "string.h":
43    int vsnprintf(char * s, size_t n, const char * format, va_list arg)
44
45cdef extern from "stdarg.h":
46    ctypedef struct va_list:
47        pass
48    ctypedef struct fake_type:
49        pass
50    void va_start(va_list, void* arg)
51    void* va_arg(va_list, fake_type)
52    void va_end(va_list)
53    fake_type int_type "int"
54
55cdef extern from "x264.h":
56    int X264_KEYINT_MAX_INFINITE
57
58    int X264_BUILD
59
60    int X264_LOG_DEBUG
61    int X264_LOG_INFO
62    int X264_LOG_WARNING
63    int X264_LOG_ERROR
64
65    int X264_CSP_I420
66    int X264_CSP_I422
67    int X264_CSP_I444
68    int X264_CSP_BGR
69    int X264_CSP_BGRA
70    int X264_CSP_RGB
71    int X264_CSP_NV12
72    int X264_CSP_V210
73    int X264_CSP_HIGH_DEPTH
74
75    int X264_RC_CQP
76    int X264_RC_CRF
77    int X264_RC_ABR
78
79    int X264_B_ADAPT_NONE
80    int X264_B_ADAPT_FAST
81    int X264_B_ADAPT_TRELLIS
82
83    #enum
84    int X264_ME_DIA
85    int X264_ME_HEX
86    int X264_ME_UMH
87    int X264_ME_ESA
88    int X264_ME_TESA
89
90    #enum nal_unit_type_e
91    int NAL_UNKNOWN
92    int NAL_SLICE
93    int NAL_SLICE_DPA
94    int NAL_SLICE_DPB
95    int NAL_SLICE_DPC
96    int NAL_SLICE_IDR
97    int NAL_SEI
98    int NAL_SPS
99    int NAL_PPS
100    int NAL_AUD
101    int NAL_FILLER
102
103    #enum nal_priority_e
104    int NAL_PRIORITY_DISPOSABLE
105    int NAL_PRIORITY_LOW
106    int NAL_PRIORITY_HIGH
107    int NAL_PRIORITY_HIGHEST
108
109    #frame type
110    int X264_TYPE_AUTO              # Let x264 choose the right type
111    int X264_TYPE_KEYFRAME
112    int X264_TYPE_IDR
113    int X264_TYPE_I
114    int X264_TYPE_P
115    int X264_TYPE_BREF
116    int X264_TYPE_B
117
118    int X264_WEIGHTP_NONE
119
120    const char * const *x264_preset_names
121
122    ctypedef struct rc:
123        int         i_rc_method
124        int         i_qp_constant       #0 to (51 + 6*(x264_bit_depth-8)). 0=lossless
125        int         i_qp_min            #min allowed QP value
126        int         i_qp_max            #max allowed QP value
127        int         i_qp_step           #max QP step between frames
128
129        int         i_bitrate
130        float       f_rf_constant       #1pass VBR, nominal QP
131        float       f_rf_constant_max   #In CRF mode, maximum CRF as caused by VBV
132        float       f_rate_tolerance
133        int         i_vbv_max_bitrate
134        int         i_vbv_buffer_size
135        float       f_vbv_buffer_init   #<=1: fraction of buffer_size. >1: kbit
136        float       f_ip_factor
137        float       f_pb_factor
138
139        int         i_aq_mode           #psy adaptive QP. (X264_AQ_*)
140        float       f_aq_strength
141        int         b_mb_tree           #Macroblock-tree ratecontrol
142        int         i_lookahead
143
144        # 2pass
145        int         b_stat_write        #Enable stat writing in psz_stat_out
146        char        *psz_stat_out       #output filename (in UTF-8) of the 2pass stats file
147        int         b_stat_read         #Read stat from psz_stat_in and use it
148        char        *psz_stat_in        #input filename (in UTF-8) of the 2pass stats file
149
150        # 2pass params (same as ffmpeg ones)
151        float       f_qcompress         #0.0 => cbr, 1.0 => constant qp
152        float       f_qblur             #temporally blur quants
153        float       f_complexity_blur   #temporally blur complexity
154        #x264_zone_t *zones              #ratecontrol overrides
155        int         i_zones             #number of zone_t's
156        char        *psz_zones          #alternate method of specifying zones
157
158    ctypedef struct analyse:
159        int         i_me_method         # motion estimation algorithm to use (X264_ME_*)
160        int         i_me_range          # integer pixel motion estimation search range (from predicted mv) */
161        int         i_mv_range          # maximum length of a mv (in pixels). -1 = auto, based on level */
162        int         i_mv_range_thread   # minimum space between threads. -1 = auto, based on number of threads. */
163        int         i_subpel_refine     # subpixel motion estimation quality */
164        int         i_weighted_pred     # weighting for P-frames
165        int         b_weighted_bipred   # implicit weighting for B-frames
166
167    ctypedef struct x264_param_t:
168        unsigned int cpu
169        int i_threads           #encode multiple frames in parallel
170        int i_lookahead_threads #multiple threads for lookahead analysis
171        int b_sliced_threads    #Whether to use slice-based threading
172        int b_deterministic     #whether to allow non-deterministic optimizations when threaded
173        int b_cpu_independent   #force canonical behavior rather than cpu-dependent optimal algorithms
174        int i_sync_lookahead    #threaded lookahead buffer
175
176        int i_width
177        int i_height
178        int i_csp               #CSP of encoded bitstream
179        int i_bitdepth
180        int i_level_idc
181        int i_frame_total       #number of frames to encode if known, else 0
182
183        int i_log_level
184        void* pf_log
185
186        #Bitstream parameters
187        int i_frame_reference   #Maximum number of reference frames
188        int i_dpb_size          #Force a DPB size larger than that implied by B-frames and reference frames
189                                #Useful in combination with interactive error resilience.
190        int i_keyint_max        #Force an IDR keyframe at this interval
191        int i_keyint_min        #Scenecuts closer together than this are coded as I, not IDR.
192        int i_scenecut_threshold#how aggressively to insert extra I frames
193        int b_intra_refresh     #Whether or not to use periodic intra refresh instead of IDR frames.
194
195        int i_bframe            #how many b-frame between 2 references pictures
196        int i_bframe_adaptive
197        int i_bframe_bias
198        int i_bframe_pyramid    #Keep some B-frames as references: 0=off, 1=strict hierarchical, 2=normal
199        int b_open_gop
200        int b_bluray_compat
201        #older x264 builds do not support this:
202        #int b_opencl            #use OpenCL when available
203
204        int b_cabac
205        int b_deblocking_filter
206        int b_interlaced
207        int b_constrained_intra
208
209        rc  rc                  #rate control
210        analyse analyse
211
212        int b_vfr_input         #VFR input. If 1, use timebase and timestamps for ratecontrol purposes. If 0, use fps only
213
214    ctypedef struct x264_t:
215        pass
216    ctypedef struct x264_nal_t:
217        int i_ref_idc
218        int i_type
219        int b_long_startcode
220        int i_first_mb
221        int i_last_mb
222        int i_payload
223        uint8_t *p_payload
224    ctypedef struct x264_image_t:
225        int i_csp           #Colorspace
226        int i_plane         #Number of image planes
227        int i_stride[4]     #Strides for each plane
228        uint8_t *plane[4]   #Pointers to each plane
229    ctypedef struct x264_image_properties_t:
230        pass
231    ctypedef struct x264_hrd_t:
232        pass
233    ctypedef struct x264_sei_t:
234        pass
235    ctypedef struct x264_picture_t:
236        int i_type          #In: force picture type (if not auto)
237        int i_qpplus1       #In: force quantizer for != X264_QP_AUTO
238        int i_pic_struct    #In: pic_struct, for pulldown/doubling/etc...used only if b_pic_struct=1.
239                            #use pic_struct_e for pic_struct inputs
240                            #Out: pic_struct element associated with frame
241        int b_keyframe      #Out: whether this frame is a keyframe.  Important when using modes that result in
242                            #SEI recovery points being used instead of IDR frames.
243        int64_t i_pts       #In: user pts, Out: pts of encoded picture (user)
244                            #Out: frame dts. When the pts of the first frame is close to zero,
245                            #initial frames may have a negative dts which must be dealt with by any muxer
246        x264_param_t *param #In: custom encoding parameters to be set from this frame forwards (..)
247        x264_image_t img    #In: raw image data
248                            #Out: Out: reconstructed image data
249        x264_image_properties_t prop    #In: optional information to modify encoder decisions for this frame
250                            #Out: information about the encoded frame */
251        x264_hrd_t hrd_timing   #Out: HRD timing information. Output only when i_nal_hrd is set.
252        x264_sei_t extra_sei#In: arbitrary user SEI (e.g subtitles, AFDs)
253        void *opaque        #private user data. copied from input to output frames.
254
255    void x264_picture_init(x264_picture_t *pic) nogil
256
257    int x264_param_default_preset(x264_param_t *param, const char *preset, const char *tune)
258    int x264_param_apply_profile(x264_param_t *param, const char *profile)
259    void x264_encoder_parameters(x264_t *context, x264_param_t *param)
260    int x264_encoder_reconfig(x264_t *context, x264_param_t *param)
261
262    x264_t *x264_encoder_open(x264_param_t *param)
263    void x264_encoder_close(x264_t *context)
264
265    int x264_encoder_encode(x264_t *context, x264_nal_t **pp_nal, int *pi_nal, x264_picture_t *pic_in, x264_picture_t *pic_out ) nogil
266    int x264_encoder_delayed_frames(x264_t *)
267    int x264_encoder_maximum_delayed_frames(x264_t *h)
268
269
270cdef set_f_rf(x264_param_t *param, float q):
271    param.rc.f_rf_constant = q
272
273cdef const char * const *get_preset_names():
274    return x264_preset_names;
275
276
277#the x264 quality option ranges from 0 (best) to 51 (lowest)
278cdef float get_x264_quality(int pct, char *profile):
279    if pct>=100 and profile:
280        #easier to compare as python strings:
281        pyiprofile = bytestostr(profile)
282        pycprofile = bytestostr(PROFILE_HIGH444_PREDICTIVE)
283        if pycprofile==pyiprofile:
284            return 0.0
285    return <float> (50.0 - (min(100, max(0, pct)) * 49.0 / 100.0))
286
287ADAPT_TYPES = {
288               X264_B_ADAPT_NONE        : "NONE",
289               X264_B_ADAPT_FAST        : "FAST",
290               X264_B_ADAPT_TRELLIS     : "TRELLIS",
291               }
292
293RC_TYPES = {
294    X264_RC_CQP : "CQP",
295    X264_RC_CRF : "CRF",
296    X264_RC_ABR : "ABR",
297    }
298
299SLICE_TYPES = {
300    X264_TYPE_AUTO  : "auto",
301    X264_TYPE_IDR   : "IDR",
302    X264_TYPE_I     : "I",
303    X264_TYPE_P     : "P",
304    X264_TYPE_BREF  : "BREF",
305    X264_TYPE_B     : "B",
306    X264_TYPE_KEYFRAME  : "KEYFRAME",
307    }
308
309ME_TYPES = {
310    X264_ME_DIA     : "DIA",
311    X264_ME_HEX     : "HEX",
312    X264_ME_UMH     : "UMH",
313    X264_ME_ESA     : "ESA",
314    X264_ME_TESA    : "TESA",
315    }
316
317NAL_TYPES = {
318    NAL_UNKNOWN     : "unknown",
319    NAL_SLICE       : "slice",
320    NAL_SLICE_DPA   : "slice-dpa",
321    NAL_SLICE_DPB   : "slice-dpb",
322    NAL_SLICE_DPC   : "slice-dpc",
323    NAL_SLICE_IDR   : "slice-idr",
324    NAL_SEI         : "sei",
325    NAL_SPS         : "sps",
326    NAL_PPS         : "pps",
327    NAL_AUD         : "aud",
328    NAL_FILLER      : "filler",
329    }
330
331NAL_PRIORITIES = {
332    NAL_PRIORITY_DISPOSABLE : "disposable",
333    NAL_PRIORITY_LOW        : "low",
334    NAL_PRIORITY_HIGH       : "high",
335    NAL_PRIORITY_HIGHEST    : "highest",
336    }
337
338
339cdef unsigned char *PROFILE_BASELINE = "baseline"
340cdef unsigned char *PROFILE_MAIN     = "main"
341cdef unsigned char *PROFILE_HIGH     = "high"
342cdef unsigned char *PROFILE_HIGH10   = "high10"
343cdef unsigned char *PROFILE_HIGH422  = "high422"
344cdef unsigned char *PROFILE_HIGH444_PREDICTIVE = "high444"
345I420_PROFILES = [PROFILE_BASELINE, PROFILE_MAIN, PROFILE_HIGH, PROFILE_HIGH10, PROFILE_HIGH422, PROFILE_HIGH444_PREDICTIVE]
346I422_PROFILES = [PROFILE_HIGH422, PROFILE_HIGH444_PREDICTIVE]
347I444_PROFILES = [PROFILE_HIGH444_PREDICTIVE]
348RGB_PROFILES = [PROFILE_HIGH444_PREDICTIVE]
349
350COLORSPACE_FORMATS = {
351    "YUV420P"   : (X264_CSP_I420,    PROFILE_HIGH,                  I420_PROFILES),
352    "YUV422P"   : (X264_CSP_I422,    PROFILE_HIGH422,               I422_PROFILES),
353    "YUV444P"   : (X264_CSP_I444,    PROFILE_HIGH444_PREDICTIVE,    I444_PROFILES),
354    "BGRA"      : (X264_CSP_BGRA,    PROFILE_HIGH444_PREDICTIVE,    RGB_PROFILES),
355    "BGRX"      : (X264_CSP_BGRA,    PROFILE_HIGH444_PREDICTIVE,    RGB_PROFILES),
356    }
357if SUPPORT_24BPP:
358    COLORSPACE_FORMATS.update({
359        "BGR"       : (X264_CSP_BGR,     PROFILE_HIGH444_PREDICTIVE,    RGB_PROFILES),
360        "RGB"       : (X264_CSP_RGB,     PROFILE_HIGH444_PREDICTIVE,    RGB_PROFILES),
361        })
362
363COLORSPACES = {
364    "YUV420P"   : "YUV420P",
365    "YUV422P"   : "YUV422P",
366    "YUV444P"   : "YUV444P",
367    "BGRA"      : "BGRA",
368    "BGRX"      : "BGRX",
369    }
370if SUPPORT_30BPP:
371    COLORSPACE_FORMATS["BGR48"] = (X264_CSP_BGR | X264_CSP_HIGH_DEPTH,    PROFILE_HIGH444_PREDICTIVE,    RGB_PROFILES)
372    COLORSPACES["BGR48"] = "GBRP10"
373if SUPPORT_24BPP:
374    COLORSPACES.update({
375        "BGR"       : "BGR",
376        "RGB"       : "RGB",
377        })
378
379
380def init_module():
381    log("enc_x264.init_module()")
382
383def cleanup_module():
384    log("enc_x264.cleanup_module()")
385
386def get_version():
387    return (X264_BUILD, )
388
389def get_type():
390    return "x264"
391
392generation = AtomicInteger()
393def get_info():
394    global COLORSPACES, MAX_WIDTH, MAX_HEIGHT
395    return {
396        "version"   : get_version(),
397        "max-size"  : (MAX_WIDTH, MAX_HEIGHT),
398        "generation": generation.get(),
399        "formats"   : tuple(COLORSPACES.keys()),
400        }
401
402def get_encodings():
403    return ("h264", )
404
405def get_input_colorspaces(encoding):
406    assert encoding in get_encodings()
407    return tuple(COLORSPACES.keys())
408
409def get_output_colorspaces(encoding, input_colorspace):
410    assert encoding in get_encodings()
411    assert input_colorspace in COLORSPACES
412    return (COLORSPACES[input_colorspace],)
413
414#actual limits (which we cannot reach because we hit OOM):
415#MAX_WIDTH, MAX_HEIGHT = 16384, 16384
416MAX_WIDTH, MAX_HEIGHT = 8192, 4096
417
418def get_spec(encoding, colorspace):
419    assert encoding in get_encodings(), "invalid encoding: %s (must be one of %s" % (encoding, get_encodings())
420    assert colorspace in COLORSPACES, "invalid colorspace: %s (must be one of %s)" % (colorspace, COLORSPACES.keys())
421    #we can handle high quality and any speed
422    #setup cost is moderate (about 10ms)
423    has_lossless_mode = colorspace in ("YUV444P", "BGR", "BGRA", "BGRX", "RGB")
424    return video_spec(encoding=encoding, input_colorspace=colorspace, output_colorspaces=(COLORSPACES[colorspace],),
425                      has_lossless_mode=has_lossless_mode,
426                      codec_class=Encoder, codec_type=get_type(),
427                      quality=60+40*int(has_lossless_mode), speed=60,
428                      size_efficiency=60,
429                      setup_cost=20, width_mask=0xFFFE, height_mask=0xFFFE, max_w=MAX_WIDTH, max_h=MAX_HEIGHT)
430
431
432#maps a log level to one of our logger functions:
433LOGGERS = {
434           X264_LOG_ERROR   : log.error,
435           X264_LOG_WARNING : log.warn,
436           X264_LOG_INFO    : log.info,
437           X264_LOG_DEBUG   : log.debug,
438           }
439
440#maps a log level string to the actual constant:
441LOG_LEVEL = {
442             "ERROR"    : X264_LOG_ERROR,
443             "WARNING"  : X264_LOG_WARNING,
444             "WARN"     : X264_LOG_WARNING,
445             "INFO"     : X264_LOG_INFO,
446             #getting segfaults with "DEBUG" level logging...
447             #so this is currently disabled
448             #"DEBUG"    : X264_LOG_DEBUG,
449             }.get(LOGGING.upper(), X264_LOG_WARNING)
450
451
452#the static logging function we want x264 to use:
453cdef void X264_log(void *p_unused, int level, const char *psz_fmt, va_list arg) with gil:
454    cdef char buffer[256]
455    cdef int r = vsnprintf(buffer, 256, psz_fmt, arg)
456    if r<0:
457        log.error("X264_log: vsnprintf returned %s on format string '%s'", r, psz_fmt)
458        return
459    s = bytestostr(buffer[:r]).rstrip("\n\r")
460    logger = LOGGERS.get(level, log.info)
461    logger("X264: %r", s)
462
463
464cdef class Encoder:
465    cdef unsigned long frames
466    cdef x264_t *context
467    cdef unsigned int width
468    cdef unsigned int height
469    cdef unsigned int fast_decode
470    #cdef int opencl
471    cdef object src_format
472    cdef object csc_format
473    cdef object content_type
474    cdef object profile
475    cdef object tune
476    cdef double time
477    cdef int colorspace
478    cdef int preset
479    cdef int quality
480    cdef int speed
481    cdef int b_frames
482    cdef int max_delayed
483    cdef int delayed_frames
484    cdef int export_nals
485    cdef unsigned long bandwidth_limit
486    cdef unsigned long long bytes_in
487    cdef unsigned long long bytes_out
488    cdef object last_frame_times
489    cdef object file
490    cdef object frame_types
491    cdef object blank_buffer
492    cdef uint64_t first_frame_timestamp
493    cdef uint8_t ready
494
495    cdef object __weakref__
496
497    def init_context(self, encoding, unsigned int width, unsigned int height, src_format, options:typedict=None):
498        log("enc_x264.init_context%s", (width, height, src_format, encoding, options))
499        options = options or typedict()
500        global COLORSPACE_FORMATS, generation
501        cs_info = COLORSPACE_FORMATS.get(src_format)
502        assert cs_info is not None, "invalid source format: %s, must be one of: %s" % (src_format, COLORSPACE_FORMATS.keys())
503        assert encoding=="h264", "invalid encoding: %s" % encoding
504        assert options.get("scaled-width", width)==width, "x264 encoder does not handle scaling"
505        assert options.get("scaled-height", height)==height, "x264 encoder does not handle scaling"
506        self.width = width
507        self.height = height
508        self.quality = options.intget("quality", 50)
509        self.speed = options.intget("speed", 50)
510        #self.opencl = USE_OPENCL and width>=32 and height>=32
511        self.content_type = options.strget("content-type", "unknown")      #ie: "video"
512        self.b_frames = options.intget("b-frames", 0)
513        self.fast_decode = options.boolget("h264.fast-decode", False)
514        self.max_delayed = options.intget("max-delayed", MAX_DELAYED_FRAMES) * int(not self.fast_decode) * int(self.b_frames)
515        self.preset = self.get_preset_for_speed(self.speed)
516        self.src_format = src_format
517        self.csc_format = COLORSPACES[src_format]
518        self.colorspace = cs_info[0]
519        self.frames = 0
520        self.frame_types = {}
521        self.last_frame_times = deque(maxlen=200)
522        self.time = 0
523        self.first_frame_timestamp = 0
524        self.bandwidth_limit = options.intget("bandwidth-limit", 0)
525        self.profile = self._get_profile(options, self.src_format)
526        self.export_nals = options.intget("h264.export-nals", 0)
527        if self.profile is not None and self.profile not in cs_info[2]:
528            log.warn("Warning: '%s' is not a valid profile for %s", bytestostr(self.profile), src_format)
529            log.warn(" must be one of: %s", csv([bytestostr(x) for x in cs_info[2]]))
530            self.profile = None
531        if self.profile is None:
532            self.profile = cs_info[1]
533            log("using default profile=%s", bytestostr(self.profile))
534        else:
535            log("using profile=%s", bytestostr(self.profile))
536        self.init_encoder(options)
537        gen = generation.increase()
538        if SAVE_TO_FILE is not None:
539            filename = SAVE_TO_FILE+"x264-"+str(gen)+".%s" % encoding
540            self.file = open(filename, 'wb')
541            log.info("saving %s stream to %s", encoding, filename)
542        if BLANK_VIDEO:
543            self.blank_buffer = b"\0" * (self.width * self.height * 4)
544        self.ready = 1
545
546    def is_ready(self):
547        return bool(self.ready)
548
549    def get_tune(self):
550        log("x264: get_tune() TUNE=%s, fast_decode=%s, content_type=%s", TUNE, self.fast_decode, self.content_type)
551        if TUNE:
552            return TUNE
553        tunes = []
554        if self.content_type.find("video")>=0:
555            tunes.append(b"film")
556        elif self.content_type.find("text")>=0:
557            tunes.append(b"grain")
558            tunes.append(b"zerolatency")
559        else:
560            tunes.append(b"zerolatency")
561        if self.fast_decode:
562            tunes.append(b"fastdecode")
563        return b",".join(tunes)
564
565    cdef init_encoder(self, options:typedict):
566        cdef x264_param_t param
567        cdef const char *preset = get_preset_names()[self.preset]
568        self.tune = self.get_tune()
569        x264_param_default_preset(&param, strtobytes(preset), strtobytes(self.tune))
570        x264_param_apply_profile(&param, self.profile)
571        self.tune_param(&param, options)
572
573        self.context = x264_encoder_open(&param)
574        cdef int maxd = x264_encoder_maximum_delayed_frames(self.context)
575        log("x264 context=%#x, %7s %4ix%-4i quality=%i, speed=%i, content_type=%s", <uintptr_t> self.context, self.src_format, self.width, self.height, self.quality, self.speed, self.content_type)
576        log("x264 maximum_delayed_frames=%i", maxd)
577        log("x264 params: %s", self.get_param_info(&param))
578        assert self.context!=NULL,  "context initialization failed for format %s" % self.src_format
579
580    cdef tune_param(self, x264_param_t *param, options:typedict):
581        param.i_lookahead_threads = 0
582        if MIN_SLICED_THREADS_SPEED>0 and self.speed>=MIN_SLICED_THREADS_SPEED and not self.fast_decode:
583            param.b_sliced_threads = 1
584            param.i_threads = THREADS
585        else:
586            #cap i_threads since i_thread_frames will be set to i_threads
587            param.i_threads = min(self.max_delayed, THREADS)
588        #we never lose frames or use seeking, so no need for regular I-frames:
589        param.i_keyint_max = X264_KEYINT_MAX_INFINITE
590        #we don't want IDR frames either:
591        param.i_keyint_min = X264_KEYINT_MAX_INFINITE
592        param.b_intra_refresh = 0   #no intra refresh
593        param.b_open_gop = 1        #allow open gop
594        #param.b_opencl = self.opencl
595        param.i_bframe = self.b_frames
596        if self.bandwidth_limit>0 and self.bandwidth_limit<=5*1000*1000:
597            #CBR mode:
598            param.rc.i_rc_method = X264_RC_ABR
599            param.rc.i_bitrate = self.bandwidth_limit//1024
600            param.rc.i_vbv_max_bitrate = 2*self.bandwidth_limit//1024
601            param.rc.i_vbv_buffer_size = self.bandwidth_limit//1024
602            param.rc.f_vbv_buffer_init = 1
603        else:
604            param.rc.i_rc_method = X264_RC_CRF
605        param.rc.i_lookahead = min(param.rc.i_lookahead, self.b_frames-1)
606        param.b_vfr_input = 0
607        if not self.b_frames:
608            param.i_sync_lookahead = 0
609            param.rc.b_mb_tree = 0
610        else:
611            param.i_sync_lookahead = max(0, min(self.max_delayed-param.i_threads-param.rc.i_lookahead, param.i_sync_lookahead))
612            #don't use TRELLIS, which uses too many delayed frames:
613            if param.i_bframe_adaptive==X264_B_ADAPT_TRELLIS:
614                param.i_bframe_adaptive = X264_B_ADAPT_FAST
615        if self.content_type!="unknown" and self.content_type.find("video")<0:
616            #specifically told this is not video,
617            #so use a simple motion search:
618            param.analyse.i_me_method = X264_ME_DIA
619        set_f_rf(param, get_x264_quality(self.quality, self.profile))
620        #client can tune these options:
621        param.b_open_gop = options.boolget("h264.open-gop", param.b_open_gop)
622        param.b_deblocking_filter = not self.fast_decode and options.boolget("h264.deblocking-filter", param.b_deblocking_filter)
623        param.b_cabac = not self.fast_decode and options.boolget("h264.cabac", param.b_cabac)
624        param.b_bluray_compat = options.boolget("h264.bluray-compat", param.b_bluray_compat)
625        if self.fast_decode:
626            param.analyse.b_weighted_bipred = 0
627            param.analyse.i_weighted_pred = X264_WEIGHTP_NONE
628        #input format:
629        param.i_width = self.width
630        param.i_height = self.height
631        param.i_csp = self.colorspace
632        if (self.colorspace & X264_CSP_HIGH_DEPTH)>0:
633            param.i_bitdepth = 10
634        else:
635            param.i_bitdepth = 8
636        #logging hook:
637        param.pf_log = <void *> X264_log
638        param.i_log_level = LOG_LEVEL
639
640
641    def clean(self):
642        log("x264 close context %#x", <uintptr_t> self.context)
643        cdef x264_t *context = self.context
644        if context!=NULL:
645            self.context = NULL
646            x264_encoder_close(context)
647        self.frames = 0
648        self.width = 0
649        self.height = 0
650        self.fast_decode = 0
651        self.src_format = ""
652        self.content_type = None
653        self.profile = None
654        self.time = 0
655        self.colorspace = 0
656        self.preset = 0
657        self.quality = 0
658        self.speed = 0
659        self.bytes_in = 0
660        self.bytes_out = 0
661        self.last_frame_times = []
662        self.first_frame_timestamp = 0
663        f = self.file
664        if f:
665            self.file = None
666            f.close()
667
668
669    def get_info(self) -> dict:
670        cdef double pps
671        if self.profile is None:
672            return {}
673        info = get_info()
674        info.update({
675            "profile"       : bytestostr(self.profile),
676            "preset"        : get_preset_names()[self.preset],
677            "fast-decode"   : bool(self.fast_decode),
678            "max-delayed"   : self.max_delayed,
679            "b-frames"      : self.b_frames,
680            "tune"          : self.tune or "",
681            "frames"        : int(self.frames),
682            "width"         : self.width,
683            "height"        : self.height,
684            #"opencl"        : bool(self.opencl),
685            "speed"         : self.speed,
686            "quality"       : self.quality,
687            "lossless"      : self.quality==100,
688            "src_format"    : self.src_format,
689            "csc_format"    : self.csc_format,
690            "content-type"  : self.content_type,
691            "version"       : get_version(),
692            "frame-types"   : self.frame_types,
693            "delayed"       : self.delayed_frames,
694            "bandwidth-limit" : int(self.bandwidth_limit),
695            })
696        cdef x264_param_t param
697        x264_encoder_parameters(self.context, &param)
698        info["params"] = self.get_param_info(&param)
699        if self.bytes_in>0 and self.bytes_out>0:
700            info.update({
701                "bytes_in"  : int(self.bytes_in),
702                "bytes_out" : int(self.bytes_out),
703                "ratio_pct" : int(100.0 * self.bytes_out / self.bytes_in),
704                })
705        if self.frames>0 and self.time>0:
706            pps = self.width * self.height * self.frames / self.time
707            info.update({
708                "total_time_ms"     : int(self.time*1000.0),
709                "pixels_per_second" : int(pps),
710                })
711        #calculate fps:
712        cdef unsigned int f = 0
713        cdef double now = monotonic()
714        cdef double last_time = now
715        cdef double cut_off = now-10.0
716        cdef double ms_per_frame = 0
717        for start,end in tuple(self.last_frame_times):
718            if end>cut_off:
719                f += 1
720                last_time = min(last_time, end)
721                ms_per_frame += (end-start)
722        if f>0 and last_time<now:
723            info.update({
724                "fps"           : int(0.5+f/(now-last_time)),
725                "ms_per_frame"  : int(1000.0*ms_per_frame/f),
726                })
727        return info
728
729    cdef get_param_info(self, x264_param_t *param):
730        return {
731            "me"                : self.get_analyse_info(param),
732            "rc"                : self.get_rc_info(param),
733            "vfr-input"         : bool(param.b_vfr_input),
734            "bframe-adaptive"   :  ADAPT_TYPES.get(param.i_bframe_adaptive, param.i_bframe_adaptive),
735            "open-gop"          : bool(param.b_open_gop),
736            "bluray-compat"     : bool(param.b_bluray_compat),
737            "cabac"             : bool(param.b_cabac),
738            "deblocking-filter" : bool(param.b_deblocking_filter),
739            "intra-refresh"     : bool(param.b_intra_refresh),
740            "interlaced"        : bool(param.b_interlaced),
741            "constrained_intra" : bool(param.b_constrained_intra),
742            "threads"           : {0 : "auto"}.get(param.i_threads, param.i_threads),
743            "sliced-threads"    : bool(param.b_sliced_threads),
744            }
745
746    cdef get_analyse_info(self, x264_param_t *param):
747        return {
748                "type"              : ME_TYPES.get(param.analyse.i_me_method, param.analyse.i_me_method),
749                "me-range"          : param.analyse.i_me_range,
750                "mv-range"          : param.analyse.i_mv_range,
751                "mv-range-thread"   : param.analyse.i_mv_range_thread,
752                "subpel_refine"     : param.analyse.i_subpel_refine,
753                "weighted-pred"     : param.analyse.i_weighted_pred,
754                }
755
756    cdef get_rc_info(self, x264_param_t *param):
757        return {
758            "rc-method"         : RC_TYPES.get(param.rc.i_rc_method, param.rc.i_rc_method),
759            "qp_constant"       : param.rc.i_qp_constant,
760            "qp_min"            : param.rc.i_qp_min,
761            "qp_max"            : param.rc.i_qp_max,
762            "qp_step"           : param.rc.i_qp_step,
763            "bitrate"           : param.rc.i_bitrate,
764            "vbv_max_bitrate"   : param.rc.i_vbv_max_bitrate,
765            "vbv_buffer_size"   : param.rc.i_vbv_buffer_size,
766            #"vbv_buffer_init"   : param.rc.f_vbv_buffer_init, #can't have floats with bencoder
767            "vbv_max_bitrate"   : param.rc.i_vbv_max_bitrate,
768
769            "mb-tree"           : bool(param.rc.b_mb_tree),
770            "lookahead"         : param.rc.i_lookahead,
771            }
772
773
774    def __repr__(self):
775        if self.src_format is None:
776            return "x264_encoder(uninitialized)"
777        return "x264_encoder(%s - %sx%s)" % (self.src_format, self.width, self.height)
778
779    def is_closed(self):
780        return self.context==NULL
781
782    def get_encoding(self):
783        return "h264"
784
785    def __dealloc__(self):
786        self.clean()
787
788    def get_width(self):
789        return self.width
790
791    def get_height(self):
792        return self.height
793
794    def get_type(self):
795        return  "x264"
796
797    def get_src_format(self):
798        return self.src_format
799
800    cdef _get_profile(self, options, csc_mode):
801        #use the environment as default if present:
802        profile = os.environ.get("XPRA_X264_%s_PROFILE" % csc_mode, PROFILE)
803        #now see if the client has requested a different value:
804        profile = options.strget("h264.%s.profile" % csc_mode, profile)
805        if profile is None:
806            profile = options.strget("h264.profile", profile)
807        if profile is None:
808            return None
809        return strtobytes(profile)
810
811
812    def compress_image(self, image, options=None):
813        cdef x264_picture_t pic_in
814        cdef int i
815
816        assert image.get_width()>=self.width
817        assert image.get_height()>=self.height
818        assert image.get_pixel_format()==self.src_format, "expected %s but got %s" % (self.src_format, image.get_pixel_format())
819
820        if self.first_frame_timestamp==0:
821            self.first_frame_timestamp = image.get_timestamp()
822
823        options = typedict(options or {})
824        content_type = options.strget("content-type", self.content_type)
825        b_frames = options.intget("b-frames", 0)
826        if content_type!=self.content_type or self.b_frames!=b_frames:
827            #some options have changed:
828            log("compress_image: reconfig b-frames=%s, content_type=%s (from %s, %s)", b_frames, content_type, self.b_frames, self.content_type)
829            self.content_type = content_type
830            self.b_frames = b_frames
831            self.reconfig_tune()
832
833        cdef int speed = options.intget("speed", 50)
834        if speed>=0:
835            self.set_encoding_speed(speed)
836        else:
837            speed = self.speed
838        cdef int quality = options.intget("quality", -1)
839        if quality>=0:
840            self.set_encoding_quality(quality)
841        else:
842            quality = self.quality
843
844        if BLANK_VIDEO:
845            if image.get_planes()==3:
846                pixels = (self.blank_buffer, self.blank_buffer, self.blank_buffer)
847            else:
848                pixels = self.blank_buffer
849        else:
850            pixels = image.get_pixels()
851        assert pixels, "failed to get pixels from %s" % image
852        istrides = image.get_rowstride()
853
854        x264_picture_init(&pic_in)
855        cdef Py_buffer py_buf[3]
856        for i in range(3):
857            pic_in.img.plane[i] = NULL
858            pic_in.img.i_stride[i] = 0
859            memset(&py_buf[i], 0, sizeof(Py_buffer))
860
861        try:
862            if self.src_format.find("RGB")>=0 or self.src_format.find("BGR")>=0:
863                assert len(pixels)>0
864                assert istrides>0
865                if PyObject_GetBuffer(pixels, &py_buf[0], PyBUF_ANY_CONTIGUOUS):
866                    raise Exception("failed to read pixel data from %s" % type(pixels))
867                pic_in.img.plane[0] = <uint8_t*> py_buf.buf
868                pic_in.img.i_stride[0] = istrides
869                self.bytes_in += py_buf.len
870                pic_in.img.i_plane = 1
871            else:
872                assert len(pixels)==3, "image pixels does not have 3 planes! (found %s)" % len(pixels)
873                assert len(istrides)==3, "image strides does not have 3 values! (found %s)" % len(istrides)
874                for i in range(3):
875                    if PyObject_GetBuffer(pixels[i], &py_buf[i], PyBUF_ANY_CONTIGUOUS):
876                        raise Exception("failed to read pixel data from %s" % type(pixels[i]))
877                    pic_in.img.plane[i] = <uint8_t*> py_buf[i].buf
878                    pic_in.img.i_stride[i] = istrides[i]
879                    self.bytes_in += py_buf[i].len
880                pic_in.img.i_plane = 3
881            pic_in.img.i_csp = self.colorspace
882            pic_in.i_pts = image.get_timestamp()-self.first_frame_timestamp
883            return self.do_compress_image(&pic_in, quality, speed)
884        finally:
885            for i in range(3):
886                if py_buf[i].buf:
887                    PyBuffer_Release(&py_buf[i])
888
889    cdef do_compress_image(self, x264_picture_t *pic_in, int quality=-1, int speed=-1):
890        cdef x264_nal_t *nals = NULL
891        cdef int i_nals = 0
892        cdef x264_picture_t pic_out
893        assert self.context!=NULL
894        cdef double start = monotonic()
895
896        cdef int frame_size = 0
897        with nogil:
898            x264_picture_init(&pic_out)
899            frame_size = x264_encoder_encode(self.context, &nals, &i_nals, pic_in, &pic_out)
900        if frame_size < 0:
901            log.error("x264 encoding error: frame_size is invalid!")
902            return None
903        self.delayed_frames = x264_encoder_delayed_frames(self.context)
904        if i_nals==0:
905            if self.delayed_frames>0:
906                log("x264 encode %i delayed frames after %i", self.delayed_frames, self.frames)
907                return None, {
908                    "delayed" : self.delayed_frames,
909                    "frame"   : self.frames,
910                    }
911            raise Exception("x264_encoder_encode produced no data (frame=%i, frame-size=%i, b-frames=%s, delayed-frames=%i)" % (self.frames, frame_size, self.b_frames, self.delayed_frames))
912        slice_type = SLICE_TYPES.get(pic_out.i_type, pic_out.i_type)
913        self.frame_types[slice_type] = self.frame_types.get(slice_type, 0)+1
914        log("x264 encode %7s frame %5i as %4s slice with %i nals, tune=%s, total %7i bytes, keyframe=%-5s, delayed=%i",
915            self.src_format, self.frames, slice_type, i_nals, bytestostr(self.tune), frame_size, bool(pic_out.b_keyframe), self.delayed_frames)
916        bnals = []
917        nal_indexes = []
918        cdef unsigned int index = 0
919        for i in range(i_nals):
920            out = <char *>nals[i].p_payload
921            cdata = out[:nals[i].i_payload]
922            bnals.append(cdata)
923            index += nals[i].i_payload
924            nal_indexes.append(index)
925            if LOG_NALS:
926                log.info(" nal %s priority:%10s, type:%10s, payload=%#x, payload size=%i",
927                         i, NAL_PRIORITIES.get(nals[i].i_ref_idc, nals[i].i_ref_idc), NAL_TYPES.get(nals[i].i_type, nals[i].i_type), <uintptr_t> nals[i].p_payload, nals[i].i_payload)
928        cdata = b"".join(bnals)
929        if len(cdata)!=frame_size:
930            log.warn("Warning: h264 nals do not match frame size")
931            log.warn(" expected %i bytes, but got %i nals and %i bytes", frame_size, len(bnals), len(cdata))
932        self.bytes_out += frame_size
933        #restore speed and quality if we temporarily modified them:
934        if speed>=0:
935            self.set_encoding_speed(self.speed)
936        if quality>=0:
937            self.set_encoding_quality(self.quality)
938        #info for client:
939        client_options = {
940                "frame"     : int(self.frames),
941                "pts"       : int(pic_out.i_pts),
942                #"quality"   : max(0, min(100, quality)),
943                #"speed"     : max(0, min(100, speed)),
944                "csc"       : self.csc_format,
945                }
946        if slice_type!="P":
947            client_options["type"] = slice_type
948        if self.delayed_frames>0:
949            client_options["delayed"] = self.delayed_frames
950        if self.export_nals:
951            client_options["nals"] = nal_indexes
952        #accounting:
953        cdef double end = monotonic()
954        self.time += end-start
955        self.frames += 1
956        self.last_frame_times.append((start, end))
957        assert self.context!=NULL
958        if self.file and frame_size>0:
959            self.file.write(cdata)
960            self.file.flush()
961        return cdata, client_options
962
963    def flush(self, unsigned long frame_no):
964        if self.frames>frame_no or self.context==NULL:
965            return None, {}
966        self.delayed_frames = x264_encoder_delayed_frames(self.context)
967        log("x264 flush(%i) %i delayed frames", frame_no, self.delayed_frames)
968        if self.delayed_frames<=0:
969            return None, {}
970        cdef x264_picture_t pic_out
971        x264_picture_init(&pic_out)
972        return self.do_compress_image(NULL)
973
974
975    def set_encoding_speed(self, int pct):
976        assert pct>=0 and pct<=100, "invalid percentage: %s" % pct
977        assert self.context!=NULL, "context is closed!"
978        cdef x264_param_t param
979        cdef int new_preset = self.get_preset_for_speed(pct)
980        if new_preset == self.preset:
981            return
982        self.speed = pct
983        #retrieve current parameters:
984        x264_encoder_parameters(self.context, &param)
985        #apply new preset:
986        self.tune = self.get_tune()
987        x264_param_default_preset(&param, get_preset_names()[new_preset], self.tune)
988        #ensure quality remains what it was:
989        self.do_reconfig_tune(&param)
990        self.preset = new_preset
991
992    def set_encoding_quality(self, int pct):
993        assert pct>=0 and pct<=100, "invalid percentage: %s" % pct
994        if self.quality==pct:
995            return
996        if abs(self.quality - pct)<=4 and pct!=100 and self.quality!=100:
997            #not enough of a change to bother (but always change to/from 100)
998            return
999        #adjust quality:
1000        self.quality = pct
1001        self.reconfig_tune()
1002
1003    #we choose presets from 1 to 7
1004    #(we exclude placebo)
1005    cdef int get_preset_for_speed(self, int speed):
1006        if self.fast_decode:
1007            speed = max(FAST_DECODE_MIN_SPEED, speed)
1008        if speed > 99:
1009            #only allow "ultrafast" if pct > 99
1010            return 0
1011        return 5 - max(0, min(4, speed // 20))
1012
1013
1014    def reconfig_tune(self):
1015        cdef x264_param_t param
1016        x264_encoder_parameters(self.context, &param)
1017        self.do_reconfig_tune(&param)
1018
1019    cdef do_reconfig_tune(self, x264_param_t *param):
1020        assert self.context!=NULL, "context is closed!"
1021        #adjust quality:
1022        set_f_rf(param, get_x264_quality(self.quality, self.profile))
1023        self.tune_param(param, typedict())
1024        #apply it:
1025        if x264_encoder_reconfig(self.context, param)!=0:
1026            raise Exception("x264_encoder_reconfig failed")
1027
1028
1029def selftest(full=False):
1030    log("enc_x264 selftest: %s", get_info())
1031    global SAVE_TO_FILE
1032    from xpra.codecs.codec_checks import testencoder, get_encoder_max_sizes
1033    from xpra.codecs.enc_x264 import encoder
1034    temp = SAVE_TO_FILE
1035    try:
1036        SAVE_TO_FILE = None
1037        assert testencoder(encoder, full)
1038        #this is expensive, so don't run it unless "full" is set:
1039        if full:
1040            global MAX_WIDTH, MAX_HEIGHT
1041            maxw, maxh = get_encoder_max_sizes(encoder)
1042            assert maxw>=MAX_WIDTH and maxh>=MAX_HEIGHT, "%s is limited to %ix%i and not %ix%i" % (encoder, maxw, maxh, MAX_WIDTH, MAX_HEIGHT)
1043            MAX_WIDTH, MAX_HEIGHT = maxw, maxh
1044            log.info("%s max dimensions: %ix%i", encoder, MAX_WIDTH, MAX_HEIGHT)
1045    finally:
1046        SAVE_TO_FILE = temp
1047