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(¶m, strtobytes(preset), strtobytes(self.tune)) 570 x264_param_apply_profile(¶m, self.profile) 571 self.tune_param(¶m, options) 572 573 self.context = x264_encoder_open(¶m) 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(¶m)) 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, ¶m) 698 info["params"] = self.get_param_info(¶m) 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, ¶m) 985 #apply new preset: 986 self.tune = self.get_tune() 987 x264_param_default_preset(¶m, get_preset_names()[new_preset], self.tune) 988 #ensure quality remains what it was: 989 self.do_reconfig_tune(¶m) 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, ¶m) 1017 self.do_reconfig_tune(¶m) 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