diff --git a/src/xpra/codecs/enc_x264/encoder.pyx b/src/xpra/codecs/enc_x264/encoder.pyx index bd54af314c..9c496f9d4a 100644 --- a/src/xpra/codecs/enc_x264/encoder.pyx +++ b/src/xpra/codecs/enc_x264/encoder.pyx @@ -67,6 +67,10 @@ cdef extern from "x264.h": int X264_CSP_BGRA int X264_CSP_RGB + int X264_RC_CQP + int X264_RC_CRF + int X264_RC_ABR + int X264_B_ADAPT_NONE int X264_B_ADAPT_FAST int X264_B_ADAPT_TRELLIS @@ -288,6 +292,12 @@ ADAPT_TYPES = { X264_B_ADAPT_TRELLIS : "TRELLIS", } +RC_TYPES = { + X264_RC_CQP : "CQP", + X264_RC_CRF : "CRF", + X264_RC_ABR : "ABR", + } + SLICE_TYPES = { X264_TYPE_AUTO : "auto", X264_TYPE_IDR : "IDR", @@ -473,6 +483,7 @@ cdef class Encoder: cdef int b_frames cdef int delayed_frames cdef int export_nals + cdef unsigned long bandwidth_limit cdef unsigned long long bytes_in cdef unsigned long long bytes_out cdef object last_frame_times @@ -507,6 +518,7 @@ cdef class Encoder: self.last_frame_times = deque(maxlen=200) self.time = 0 self.first_frame_timestamp = 0 + self.bandwidth_limit = options.intget("bandwidth-limit", 0) self.profile = self._get_profile(options, self.src_format) self.export_nals = options.intget("h264.export-nals", 0) if self.profile is not None and self.profile not in cs_info[2]: @@ -562,6 +574,16 @@ cdef class Encoder: param.b_open_gop = 1 #allow open gop #param.b_opencl = self.opencl param.i_bframe = self.b_frames + self.bandwidth_limit = 2*1000*1000 + if self.bandwidth_limit>0 and self.bandwidth_limit<=5*1000*1000: + #CBR mode: + param.rc.i_rc_method = X264_RC_ABR + param.rc.i_bitrate = self.bandwidth_limit//1024 + param.rc.i_vbv_max_bitrate = 2*self.bandwidth_limit//1024 + param.rc.i_vbv_buffer_size = self.bandwidth_limit//1024 + param.rc.f_vbv_buffer_init = 1 + else: + param.rc.i_rc_method = X264_RC_CRF param.rc.i_lookahead = min(param.rc.i_lookahead, self.b_frames-1) param.b_vfr_input = 0 if not self.b_frames: @@ -643,6 +665,7 @@ cdef class Encoder: "version" : get_version(), "frame-types" : self.frame_types, "delayed" : self.delayed_frames, + "bandwidth-limit" : self.bandwidth_limit, }) cdef x264_param_t param x264_encoder_parameters(self.context, ¶m) @@ -679,15 +702,9 @@ cdef class Encoder: cdef get_param_info(self, x264_param_t *param): return { - "me" : { - "type" : ME_TYPES.get(param.analyse.i_me_method, param.analyse.i_me_method), - "me-range" : param.analyse.i_me_range, - "mv-range" : param.analyse.i_mv_range, - "weighted-pred" : param.analyse.i_weighted_pred, - }, + "me" : self.get_analyse_info(param), + "rc" : self.get_rc_info(param), "vfr-input" : bool(param.b_vfr_input), - "lookahead" : param.rc.i_lookahead, - "mb-tree" : bool(param.rc.b_mb_tree), "bframe-adaptive" : ADAPT_TYPES.get(param.i_bframe_adaptive, param.i_bframe_adaptive), "open-gop" : bool(param.b_open_gop), "bluray-compat" : bool(param.b_bluray_compat), @@ -700,6 +717,34 @@ cdef class Encoder: "sliced-threads" : bool(param.b_sliced_threads), } + cdef get_analyse_info(self, x264_param_t *param): + return { + "type" : ME_TYPES.get(param.analyse.i_me_method, param.analyse.i_me_method), + "me-range" : param.analyse.i_me_range, + "mv-range" : param.analyse.i_mv_range, + "mv-range-thread" : param.analyse.i_mv_range_thread, + "subpel_refine" : param.analyse.i_subpel_refine, + "weighted-pred" : param.analyse.i_weighted_pred, + } + + cdef get_rc_info(self, x264_param_t *param): + return { + "rc-method" : RC_TYPES.get(param.rc.i_rc_method, param.rc.i_rc_method), + "qp_constant" : param.rc.i_qp_constant, + "qp_min" : param.rc.i_qp_min, + "qp_max" : param.rc.i_qp_max, + "qp_step" : param.rc.i_qp_step, + "bitrate" : param.rc.i_bitrate, + "vbv_max_bitrate" : param.rc.i_vbv_max_bitrate, + "vbv_buffer_size" : param.rc.i_vbv_buffer_size, + "vbv_buffer_init" : param.rc.f_vbv_buffer_init, + "vbv_max_bitrate" : param.rc.i_vbv_max_bitrate, + + "mb-tree" : bool(param.rc.b_mb_tree), + "lookahead" : param.rc.i_lookahead, + } + + def __repr__(self): if self.src_format is None: return "x264_encoder(uninitialized)" diff --git a/src/xpra/codecs/vpx/encoder.pyx b/src/xpra/codecs/vpx/encoder.pyx index 30d529b52c..32875425d7 100644 --- a/src/xpra/codecs/vpx/encoder.pyx +++ b/src/xpra/codecs/vpx/encoder.pyx @@ -344,6 +344,7 @@ cdef class Encoder: cdef int width cdef int height cdef int max_threads + cdef unsigned long bandwidth_limit cdef double initial_bitrate_per_pixel cdef object encoding cdef object src_format @@ -370,6 +371,7 @@ cdef class Encoder: self.height = height self.speed = speed self.quality = quality + self.bandwidth_limit = options.get("bandwidth-limit", 0) self.lossless = 0 self.frames = 0 self.last_frame_times = deque(maxlen=200) @@ -461,8 +463,12 @@ cdef class Encoder: cdef update_cfg(self): self.cfg.rc_undershoot_pct = 100 self.cfg.rc_overshoot_pct = 100 - self.cfg.rc_target_bitrate = max(16, min(15000, int(self.width * self.height * self.initial_bitrate_per_pixel))) - log("update_cfg() bitrate(%i,%i,%.3f)=%i", self.width, self.height, self.initial_bitrate_per_pixel, self.cfg.rc_target_bitrate) + bitrate_kbps = int(self.width * self.height * self.initial_bitrate_per_pixel) + if self.bandwidth_limit>0: + #vpx bitrate values are in Kbps: + bitrate_kbps = min(self.bandwidth_limit//1024, bitrate_kbps) + self.cfg.rc_target_bitrate = max(16, min(15000, bitrate_kbps)) + log("update_cfg() bitrate(%i,%i,%.3f,%i)=%iKbps", self.width, self.height, self.initial_bitrate_per_pixel, self.bandwidth_limit, bitrate_kbps) self.cfg.g_threads = self.max_threads self.cfg.rc_min_quantizer = MAX(0, MIN(63, int((80-self.quality) * 0.63))) self.cfg.rc_max_quantizer = MAX(self.cfg.rc_min_quantizer, MIN(63, int((100-self.quality) * 0.63))) @@ -483,6 +489,7 @@ cdef class Encoder: "encoding" : self.encoding, "src_format": self.src_format, "max_threads": self.max_threads, + "bandwidth-limit" : self.bandwidth_limit, }) #calculate fps: cdef unsigned int f = 0 @@ -564,6 +571,10 @@ cdef class Encoder: assert object_as_buffer(pixels[i], &pic_buf, &pic_buf_len)==0 pic_in[i] = pic_buf strides[i] = istrides[i] + cdef unsigned long bandwidth_limit = options.get("bandwidth-limit", self.bandwidth_limit) + if bandwidth_limit!=self.bandwidth_limit: + self.bandwidth_limit = bandwidth_limit + self.update_cfg() if speed>=0: self.set_encoding_speed(speed) if quality>=0: diff --git a/src/xpra/server/window/window_video_source.py b/src/xpra/server/window/window_video_source.py index bf46a35f6e..d464e4f96c 100644 --- a/src/xpra/server/window/window_video_source.py +++ b/src/xpra/server/window/window_video_source.py @@ -1565,6 +1565,10 @@ def setup_pipeline_option(self, width, height, src_format, def get_video_encoder_options(self, encoding, width, height): #tweaks for "real" video: opts = {} + if not self._fixed_quality and not self._fixed_speed and self._fixed_min_quality<50: + #only allow bandwidth to drive video encoders + #when we don't have strict quality or speed requirements: + opts["bandwidth-limit"] = self.bandwidth_limit if self.matches_video_subregion(width, height) and self.subregion_is_video() and (monotonic_time()-self.last_scroll_time)>5: opts.update({ "content-type" : "video",