xpra icon
Bug tracker and wiki

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


Ticket #733: video-lockless.patch

File video-lockless.patch, 32.6 KB (added by Antoine Martin, 6 years ago)

work in progress patch which does everything in the encode thread and removes locking

  • xpra/server/window_video_source.py

     
    77import os
    88import time
    99import operator
    10 from threading import Lock
    1110
    1211from xpra.net.compression import Compressed
    1312from xpra.codecs.codec_constants import get_avutil_enum_from_colorspace, get_subsampling_divs, \
     
    8988        nv_common = (set(self.server_core_encodings) & set(self.core_encodings)) - set(self.video_encodings)
    9089        self.non_video_encodings = [x for x in PREFERED_ENCODING_ORDER if x in nv_common]
    9190
     91        #those two instances should only ever be modified or accessed from the encode thread:
    9292        self._csc_encoder = None
    9393        self._video_encoder = None
    9494        self._last_pipeline_check = 0
    95         self._lock = Lock()               #to ensure we serialize access to the encoder and its internals
    9695
    9796    def __repr__(self):
    9897        return "WindowVideoSource(%s : %s)" % (self.wid, self.window_dimensions)
     
    113112
    114113        self.last_pipeline_params = None
    115114        self.last_pipeline_scores = []
     115        self.last_pipeline_time = 0
    116116
    117117        self.uses_swscale = False
    118118        self.uses_csc_atoms = False
     
    214214        """
    215215        if self._csc_encoder is None and self._video_encoder is None:
    216216            return
    217         with self._lock:
    218             self.do_csc_encoder_cleanup()
    219             self.do_video_encoder_cleanup()
     217        self.csc_encoder_clean()
     218        self.video_encoder_clean()
    220219
    221     def do_csc_encoder_cleanup(self):
    222         #MUST be called with video lock held!
    223         if self._csc_encoder is None:
     220    def csc_encoder_clean(self):
     221        """ Calls self._csc_encoder.clean() from the encode thread """
     222        csc_encoder = self._csc_encoder
     223        self._csc_encoder = None
     224        if csc_encoder is None:
    224225            return
    225         self._csc_encoder.clean()
    226         self._csc_encoder = None
     226        def csc_encoder_clean():
     227            csc_encoder.clean()
     228        self.queue_damage(csc_encoder_clean)
    227229
    228     def do_video_encoder_cleanup(self):
    229         #MUST be called with video lock held!
    230         if self._video_encoder is None:
     230    def video_encoder_clean(self):
     231        """ Calls self._video_encoder.clean() from the encode thread """
     232        video_encoder = self._video_encoder
     233        self._video_encoder = None
     234        if video_encoder is None:
    231235            return
    232         self._video_encoder.clean()
    233         self._video_encoder = None
     236        def video_encoder_clean():
     237            video_encoder.clean()
     238        self.queue_damage(video_encoder_clean)
    234239
    235240
    236241    def parse_csc_modes(self, csc_modes, full_csc_modes):
     
    576581        dw = w - (w & self.width_mask)
    577582        dh = h - (h & self.height_mask)
    578583        if coding in self.video_encodings and (dw>0 or dh>0):
     584            #FIXME: we assume that rgb24 is always supported (it generally is)
    579585            #no point in using get_best_encoding here, rgb24 wins
    580586            #(as long as the mask is small - and it is)
    581587            if dw>0:
     
    594600            or from the timer that allows to tune the quality and speed.
    595601            (this tuning is done in WindowSource.reconfigure)
    596602            Here we re-evaluate if the pipeline we are currently using
    597             is really the best one, and if not we switch to the best one.
     603            is really the best one, and if not we invalidate it.
    598604            This uses get_video_pipeline_options() to get a list of pipeline
    599605            options with a score for each.
     606
     607            Can be called from any thread.
    600608        """
    601609        WindowSource.update_encoding_options(self, force_reload)
    602610        log("update_encoding_options(%s) csc_encoder=%s, video_encoder=%s", force_reload, self._csc_encoder, self._video_encoder)
     
    614622                self._lossless_threshold_base = min(80, 10+self._current_speed/5)
    615623                self._lossless_threshold_pixel_boost = 90
    616624
    617         if self._video_encoder:
     625        if force_reload:
     626            self.cleanup_codecs()
     627        elif self._video_encoder:
    618628            self.check_pipeline_score(force_reload)
    619629
    620630    def check_pipeline_score(self, force_reload):
    621         if not force_reload:
    622             if time.time()-self._last_pipeline_check<0.5:
    623                 #already checked not long ago
    624                 return
    625             def checknovideo(*info):
    626                 scorelog(*info)
    627                 self.cleanup_codecs()
    628             #do some sanity checks to see if there is any point in setting up / having a video context at all:
    629             if self.encoding not in self.video_encodings:
    630                 return checknovideo("non-video encoding: %s", self.encoding)
    631             if self._sequence<2 or self._damage_cancelled>=float("inf"):
    632                 #too early, or too late!
    633                 return checknovideo("sequence=%s (cancelled=%s)", self._sequence, self._damage_cancelled)
    634             ww, wh = self.window_dimensions
    635             if ww*wh<=MAX_NONVIDEO_PIXELS:
    636                 return checknovideo("not enough pixels: %sx%s", ww, wh)
    637             w = ww & self.width_mask
    638             h = wh & self.height_mask
    639             if w<self.min_w or w>self.max_w or h<self.min_h or h>self.max_h:
    640                 return checknovideo("out of bounds: %sx%s (min %sx%s, max %sx%s)", w, h, self.min_w, self.min_h, self.max_w, self.max_h)
    641             if time.time()-self.statistics.last_resized<0.500:
    642                 return checknovideo("resized just %.1f seconds ago", time.time()-self.statistics.last_resized)
    643         with self._lock:
    644             ve = self._video_encoder
    645             if not ve or ve.is_closed():
    646                 #could have been freed since we got the lock!
    647                 return
    648             if force_reload:
    649                 if self._csc_encoder:
    650                     self.do_csc_encoder_cleanup()
    651                 self.do_video_encoder_cleanup()
    652                 return
     631        """
     632            Calculate pipeline scores using get_video_pipeline_options(),
     633            and schedule the cleanup of the current video pipeline elements
     634            using csc_encoder_clean() / video_encoder_clean()
     635            if those are no longer the best options.
    653636
    654             pixel_format = None
    655             if self._csc_encoder:
    656                 pixel_format = self._csc_encoder.get_src_format()
    657             else:
    658                 pixel_format = ve.get_src_format()
    659             width, height = self.window_dimensions
     637            Can be called from any thread.
     638        """
     639        if time.time()-self._last_pipeline_check<0.5:
     640            #already checked not long ago
     641            return
     642        def checknovideo(*info):
     643            #for whatever reason, we shouldn't be using a video encoding,
     644            #get_best_encoding() should ensure we don't end up with one
     645            #it duplicates some of these same checks
     646            scorelog(*info)
     647            self.cleanup_codecs()
     648        #do some sanity checks to see if there is any point in finding a suitable video encoding pipeline:
     649        if self.encoding not in self.video_encodings:
     650            return checknovideo("non-video encoding: %s", self.encoding)
     651        if self._sequence<2 or self._damage_cancelled>=float("inf"):
     652            #too early, or too late!
     653            return checknovideo("sequence=%s (cancelled=%s)", self._sequence, self._damage_cancelled)
     654        ww, wh = self.window_dimensions
     655        w = ww & self.width_mask
     656        h = wh & self.height_mask
     657        if w<self.min_w or w>self.max_w or h<self.min_h or h>self.max_h:
     658            return checknovideo("out of bounds: %sx%s (min %sx%s, max %sx%s)", w, h, self.min_w, self.min_h, self.max_w, self.max_h)
     659        if time.time()-self.statistics.last_resized<0.500:
     660            return checknovideo("resized just %.1f seconds ago", time.time()-self.statistics.last_resized)
    660661
    661             scores = self.get_video_pipeline_options(ve.get_encoding(), width, height, pixel_format)
    662             if len(scores)==0:
    663                 log("check_pipeline_score(%s) no pipeline options found!")
    664                 return
     662        #must copy reference to those objects because of threading races:
     663        ve = self._video_encoder
     664        csce = self._csc_encoder
     665        if ve is None or csce is None or ve.is_closed() or csce.is_closed():
     666            #already being closed?
     667            return
    665668
    666             log("check_pipeline_score(%s) best=%s", force_reload, scores[0])
    667             _, _, _, csc_width, csc_height, csc_spec, enc_in_format, _, enc_width, enc_height, encoder_spec = scores[0]
    668             if self._csc_encoder:
    669                 if csc_spec is None or \
    670                    type(self._csc_encoder)!=csc_spec.codec_class or \
    671                    self._csc_encoder.get_dst_format()!=enc_in_format or \
    672                    self._csc_encoder.get_src_width()!=csc_width or \
    673                    self._csc_encoder.get_src_height()!=csc_height:
    674                     log("check_pipeline_score(%s) found better csc encoder: %s", force_reload, scores[0])
    675                     self.do_csc_encoder_cleanup()
    676             if type(self._video_encoder)!=encoder_spec.codec_class or \
    677                self._video_encoder.get_src_format()!=enc_in_format or \
    678                self._video_encoder.get_width()!=enc_width or \
    679                self._video_encoder.get_height()!=enc_height:
    680                 log("check_pipeline_score(%s) found better video encoder: %s", force_reload, scores[0])
    681                 self.do_video_encoder_cleanup()
     669        pixel_format = None
     670        if csce:
     671            pixel_format = csce.get_src_format()
     672        else:
     673            pixel_format = ve.get_src_format()
     674        if not pixel_format:
     675            #probably got cleaned up already
     676            return
     677           
     678        width, height = self.window_dimensions
     679        scores = self.get_video_pipeline_options(ve.get_encoding(), width, height, pixel_format, force_reload)
     680        if len(scores)==0:
     681            log("check_pipeline_score() no pipeline options found!")
     682            return
    682683
    683             if self._video_encoder is None:
    684                 self.setup_pipeline(scores, width, height, pixel_format)
    685             else:
    686                 self._video_encoder.set_encoding_speed(self._current_speed)
    687                 self._video_encoder.set_encoding_quality(self._current_quality)
     684        log("check_pipeline_score() best=%s", scores[0])
     685        _, _, _, csc_width, csc_height, csc_spec, enc_in_format, _, enc_width, enc_height, encoder_spec = scores[0]
     686        if csce:
     687            if csc_spec is None or \
     688               type(csce)!=csc_spec.codec_class or \
     689               csce.get_dst_format()!=enc_in_format or \
     690               csce.get_src_width()!=csc_width or \
     691               csce.get_src_height()!=csc_height:
     692                log("check_pipeline_score() found a better csc encoder: %s", scores[0])
     693                self.csc_encoder_clean()
     694        if type(ve)!=encoder_spec.codec_class or \
     695           ve.get_src_format()!=enc_in_format or \
     696           ve.get_width()!=enc_width or \
     697           ve.get_height()!=enc_height:
     698            log("check_pipeline_score() found a better video encoder: %s", scores[0])
     699            self.video_encoder_clean()
     700
     701        #TODO: cache scores!
    688702        self._last_pipeline_check = time.time()
    689703
    690704
    691     def get_video_pipeline_options(self, encoding, width, height, src_format):
     705    def get_video_pipeline_options(self, encoding, width, height, src_format, force_refresh=False):
    692706        """
    693707            Given a picture format (width, height and src pixel format),
    694708            we find all the pipeline options that will allow us to compress
     
    698712            using csc encoders to convert to an intermediary format.
    699713            Each solution is rated and we return all of them in descending
    700714            score (best solution comes first).
     715            Because this function is expensive to call, we cache the results.
     716            This allows it to run more often from the timer thread.
    701717        """
     718        if not force_refresh and (time.time()-self.last_pipeline_time<1) and self.last_pipeline_params and self.last_pipeline_params==(encoding, width, height, src_format):
     719            #keep existing scores
     720            return self.last_pipeline_scores
     721       
    702722        #these are the CSC modes the client can handle for this encoding:
    703723        #we must check that the output csc mode for each encoder is one of those
    704724        supported_csc_modes = self.full_csc_modes.get(encoding, self.csc_modes)
     
    739759                        add_scores("via %s (%s)" % (out_csc, actual_csc), csc_spec, out_csc)
    740760        s = sorted(scores, key=lambda x : -x[0])
    741761        scorelog("get_video_pipeline_options%s scores=%s", (encoding, width, height, src_format), s)
     762        self.last_pipeline_params = (encoding, width, height, src_format)
     763        self.last_pipeline_scores = s
     764        self.last_pipeline_time = time.time()
    742765        return s
    743766
    744767    def csc_equiv(self, csc_mode):
     
    9851008        """
    9861009            Checks that the current pipeline is still valid
    9871010            for the given input. If not, close it and make a new one.
     1011
     1012            Runs in the 'encode' thread.
    9881013        """
    9891014        #must be called with video lock held!
    9901015        if self.do_check_pipeline(encoding, width, height, src_format):
     
    9921017
    9931018        #cleanup existing one if needed:
    9941019        if self._csc_encoder:
    995             self.do_csc_encoder_cleanup()
     1020            self._csc_encoder.clean()
     1021            self._csc_encoder = None
    9961022        if self._video_encoder:
    997             self.do_video_encoder_cleanup()
     1023            self._video_encoder.clean()
     1024            self._video_encoder = None
    9981025        #and make a new one:
    999         self.last_pipeline_params = encoding, width, height, src_format
    1000         self.last_pipeline_scores = self.get_video_pipeline_options(encoding, width, height, src_format)
    1001         return self.setup_pipeline(self.last_pipeline_scores, width, height, src_format)
     1026        scores = self.get_video_pipeline_options(encoding, width, height, src_format)
     1027        return self.setup_pipeline(scores, width, height, src_format)
    10021028
    10031029    def do_check_pipeline(self, encoding, width, height, src_format):
    10041030        """
    10051031            Checks that the current pipeline is still valid
    1006             for the given input. If not, close it and make a new one.
     1032            for the given input.
     1033
     1034            Runs in the 'encode' thread.
    10071035        """
    1008         #must be called with video lock held!
    1009         if self._video_encoder is None:
     1036        #use aliases, not because of threading (we are in the encode thread anyway)
     1037        #but to make the code less dense:
     1038        ve = self._video_encoder
     1039        csce = self._csc_encoder
     1040        if ve is None:
    10101041            return False
    10111042
    1012         if self._csc_encoder:
     1043        if csce:
    10131044            csc_width = width & self.width_mask
    10141045            csc_height = height & self.height_mask
    1015             if self._csc_encoder.get_src_format()!=src_format:
     1046            if csce.get_src_format()!=src_format:
    10161047                log("check_pipeline csc: switching source format from %s to %s",
    1017                                             self._csc_encoder.get_src_format(), src_format)
     1048                                    csce.get_src_format(), src_format)
    10181049                return False
    1019             elif self._csc_encoder.get_src_width()!=csc_width or self._csc_encoder.get_src_height()!=csc_height:
     1050            elif csce.get_src_width()!=csc_width or csce.get_src_height()!=csc_height:
    10201051                log("check_pipeline csc: window dimensions have changed from %sx%s to %sx%s, csc info=%s",
    1021                                             self._csc_encoder.get_src_width(), self._csc_encoder.get_src_height(), csc_width, csc_height, self._csc_encoder.get_info())
     1052                                    csce.get_src_width(), csce.get_src_height(), csc_width, csc_height, csce.get_info())
    10221053                return False
    1023             elif self._csc_encoder.get_dst_format()!=self._video_encoder.get_src_format():
     1054            elif csce.get_dst_format()!=ve.get_src_format():
    10241055                log.warn("check_pipeline csc: intermediate format mismatch: %s vs %s, csc info=%s",
    1025                                             self._csc_encoder.get_dst_format(), self._video_encoder.get_src_format(), self._csc_encoder.get_info())
     1056                                    csce.get_dst_format(), ve.get_src_format(), csce.get_info())
    10261057                return False
    10271058
    10281059            #encoder will take its input from csc:
    1029             encoder_src_width = self._csc_encoder.get_dst_width()
    1030             encoder_src_height = self._csc_encoder.get_dst_height()
     1060            encoder_src_width = csce.get_dst_width()
     1061            encoder_src_height = csce.get_dst_height()
    10311062        else:
    10321063            #direct to video encoder without csc:
    10331064            encoder_src_width = width & self.width_mask
    10341065            encoder_src_height = height & self.height_mask
    10351066
    1036             if self._video_encoder.get_src_format()!=src_format:
     1067            if ve.get_src_format()!=src_format:
    10371068                log("check_pipeline video: invalid source format %s, expected %s",
    1038                                                 self._video_encoder.get_src_format(), src_format)
     1069                                                ve.get_src_format(), src_format)
    10391070                return False
    10401071
    1041         if self._video_encoder.get_encoding()!=encoding:
     1072        if ve.get_encoding()!=encoding:
    10421073            log("check_pipeline video: invalid encoding %s, expected %s",
    1043                                             self._video_encoder.get_encoding(), encoding)
     1074                                            ve.get_encoding(), encoding)
    10441075            return False
    1045         elif self._video_encoder.get_width()!=encoder_src_width or self._video_encoder.get_height()!=encoder_src_height:
     1076        elif ve.get_width()!=encoder_src_width or ve.get_height()!=encoder_src_height:
    10461077            log("check_pipeline video: window dimensions have changed from %sx%s to %sx%s",
    1047                                             self._video_encoder.get_width(), self._video_encoder.get_height(), encoder_src_width, encoder_src_height)
     1078                                            ve.get_width(), ve.get_height(), encoder_src_width, encoder_src_height)
    10481079            return False
    10491080        return True
    10501081
     
    10531084        """
    10541085            Given a list of pipeline options ordered by their score
    10551086            and an input format (width, height and source pixel format),
    1056             we try to create a working pipeline, trying each option
    1057             until one succeeds.
     1087            we try to create a working video pipeline (csc + encoder),
     1088            trying each option until one succeeds.
     1089            (some may not be suitable because of scaling?)
     1090
     1091            Runs in the 'encode' thread.
    10581092        """
    10591093        assert width>0 and height>0, "invalid dimensions: %sx%s" % (width, height)
    10601094        start = time.time()
     
    10611095        log("setup_pipeline%s", (scores, width, height, src_format))
    10621096        for option in scores:
    10631097            try:
    1064                 _, scaling, _, csc_width, csc_height, csc_spec, enc_in_format, encoder_scaling, enc_width, enc_height, encoder_spec = option
    10651098                log("setup_pipeline: trying %s", option)
    1066                 speed = self._current_speed
    1067                 quality = self._current_quality
    1068                 min_w = 1
    1069                 min_h = 1
    1070                 max_w = 16384
    1071                 max_h = 16384
    1072                 if csc_spec:
    1073                     #TODO: no need to OR encoder mask if we are scaling...
    1074                     width_mask = csc_spec.width_mask & encoder_spec.width_mask
    1075                     height_mask = csc_spec.height_mask & encoder_spec.height_mask
    1076                     min_w = max(min_w, csc_spec.min_w)
    1077                     min_h = max(min_h, csc_spec.min_h)
    1078                     max_w = min(max_w, csc_spec.max_w)
    1079                     max_h = min(max_h, csc_spec.max_h)
    1080                     #csc speed is not very important compared to encoding speed,
    1081                     #so make sure it never degrades quality
    1082                     csc_speed = min(speed, 100-quality/2.0)
    1083                     csc_start = time.time()
    1084                     self._csc_encoder = csc_spec.make_instance()
    1085                     self._csc_encoder.init_context(csc_width, csc_height, src_format,
    1086                                                    enc_width, enc_height, enc_in_format, csc_speed)
    1087                     csc_end = time.time()
    1088                     log("setup_pipeline: csc=%s, info=%s, setup took %.2fms",
    1089                           self._csc_encoder, self._csc_encoder.get_info(), (csc_end-csc_start)*1000.0)
     1099                if self.setup_pipeline_option(width, height, src_format, *option):
     1100                    #success!
     1101                    return True
    10901102                else:
    1091                     #use the encoder's mask directly since that's all we have to worry about!
    1092                     width_mask = encoder_spec.width_mask
    1093                     height_mask = encoder_spec.height_mask
    1094                     #restrict limits:
    1095                     min_w = max(min_w, encoder_spec.min_w)
    1096                     min_h = max(min_h, encoder_spec.min_h)
    1097                     max_w = min(max_w, encoder_spec.max_w)
    1098                     max_h = min(max_h, encoder_spec.max_h)
    1099                     if encoder_scaling!=(1,1) and not encoder_spec.can_scale:
    1100                         log("scaling is now enabled, so skipping %s", encoder_spec)
    1101                         continue
    1102                 if width<=0 or height<=0:
    1103                     #log.warn("skipping invalid dimensions..")
     1103                    #skip cleanup below
    11041104                    continue
    1105                 enc_start = time.time()
    1106                 #FIXME: filter dst_formats to only contain formats the encoder knows about?
    1107                 dst_formats = self.full_csc_modes.get(encoder_spec.encoding, self.csc_modes)
    1108                 self._video_encoder = encoder_spec.make_instance()
    1109                 self._video_encoder.init_context(enc_width, enc_height, enc_in_format, dst_formats, encoder_spec.encoding, quality, speed, encoder_scaling, self.encoding_options)
    1110                 #record new actual limits:
    1111                 self.actual_scaling = scaling
    1112                 self.width_mask = width_mask
    1113                 self.height_mask = height_mask
    1114                 self.min_w = min_w
    1115                 self.min_h = min_h
    1116                 self.max_w = max_w
    1117                 self.max_h = max_h
    1118                 enc_end = time.time()
    1119                 log("setup_pipeline: video encoder=%s, info: %s, setup took %.2fms",
    1120                         self._video_encoder, self._video_encoder.get_info(), (enc_end-enc_start)*1000.0)
    1121                 scalinglog("setup_pipeline: scaling=%s, encoder_scaling=%s", scaling, encoder_scaling)
    1122                 return  True
    11231105            except TransientCodecException as e:
    11241106                log.warn("setup_pipeline failed for %s: %s", option, e)
    1125                 self.cleanup_codecs()
    11261107            except:
    11271108                log.warn("setup_pipeline failed for %s", option, exc_info=True)
    1128                 self.cleanup_codecs()
     1109            #we're here because an exception occurred, cleanup before trying again:
     1110            if self._csc_encoder:
     1111                self._csc_encoder.clean()
     1112            if self._video_encoder:
     1113                self._video_encoder.clean()
    11291114        end = time.time()
    1130         log("setup_pipeline(..) failed! took %.2fms", (end-start)*1000.0)
     1115        log.error("setup_pipeline(..) failed! took %.2fms", (end-start)*1000.0)
    11311116        return False
    11321117
     1118    def setup_pipeline_option(self, width, height, src_format,
     1119                    #pipeline option:
     1120                      _score, scaling, _csc_scaling, csc_width, csc_height, csc_spec,
     1121                      enc_in_format, encoder_scaling, enc_width, enc_height, encoder_spec):
     1122        speed = self._current_speed
     1123        quality = self._current_quality
     1124        min_w = 1
     1125        min_h = 1
     1126        max_w = 16384
     1127        max_h = 16384
     1128        if csc_spec:
     1129            #TODO: no need to OR encoder mask if we are scaling...
     1130            width_mask = csc_spec.width_mask & encoder_spec.width_mask
     1131            height_mask = csc_spec.height_mask & encoder_spec.height_mask
     1132            min_w = max(min_w, csc_spec.min_w)
     1133            min_h = max(min_h, csc_spec.min_h)
     1134            max_w = min(max_w, csc_spec.max_w)
     1135            max_h = min(max_h, csc_spec.max_h)
     1136            #csc speed is not very important compared to encoding speed,
     1137            #so make sure it never degrades quality
     1138            csc_speed = min(speed, 100-quality/2.0)
     1139            csc_start = time.time()
     1140            csce = csc_spec.make_instance()
     1141            csce.init_context(csc_width, csc_height, src_format,
     1142                                   enc_width, enc_height, enc_in_format, csc_speed)
     1143            csc_end = time.time()
     1144            self._csc_encoder = csce
     1145            log("setup_pipeline: csc=%s, info=%s, setup took %.2fms",
     1146                  csce, csce.get_info(), (csc_end-csc_start)*1000.0)
     1147        else:
     1148            #use the encoder's mask directly since that's all we have to worry about!
     1149            width_mask = encoder_spec.width_mask
     1150            height_mask = encoder_spec.height_mask
     1151            #restrict limits:
     1152            min_w = max(min_w, encoder_spec.min_w)
     1153            min_h = max(min_h, encoder_spec.min_h)
     1154            max_w = min(max_w, encoder_spec.max_w)
     1155            max_h = min(max_h, encoder_spec.max_h)
     1156            if encoder_scaling!=(1,1) and not encoder_spec.can_scale:
     1157                log("scaling is now enabled, so skipping %s", encoder_spec)
     1158                return False
     1159        enc_start = time.time()
     1160        #FIXME: filter dst_formats to only contain formats the encoder knows about?
     1161        dst_formats = self.full_csc_modes.get(encoder_spec.encoding, self.csc_modes)
     1162        ve = encoder_spec.make_instance()
     1163        ve.init_context(enc_width, enc_height, enc_in_format, dst_formats, encoder_spec.encoding, quality, speed, encoder_scaling, self.encoding_options)
     1164        #record new actual limits:
     1165        self.actual_scaling = scaling
     1166        self.width_mask = width_mask
     1167        self.height_mask = height_mask
     1168        self.min_w = min_w
     1169        self.min_h = min_h
     1170        self.max_w = max_w
     1171        self.max_h = max_h
     1172        enc_end = time.time()
     1173        self._video_encoder = ve
     1174        log("setup_pipeline: video encoder=%s, info: %s, setup took %.2fms",
     1175                ve, ve.get_info(), (enc_end-enc_start)*1000.0)
     1176        scalinglog("setup_pipeline: scaling=%s, encoder_scaling=%s", scaling, encoder_scaling)
     1177        return  True
    11331178
     1179
    11341180    def video_encode(self, encoding, image, options):
    11351181        """
    11361182            This method is used by make_data_packet to encode frames using video encoders.
     
    11371183            Video encoders only deal with fixed dimensions,
    11381184            so we must clean and reinitialize the encoder if the window dimensions
    11391185            has changed.
    1140             Since this runs in the non-UI 'encode' thread, we must
    1141             use the '_lock' to prevent races.
     1186
     1187            Runs in the 'encode' thread.
    11421188        """
    11431189        log("video_encode%s", (encoding, image, options))
    11441190        x, y, w, h = image.get_geometry()[:4]
    11451191        assert self.supports_video_subregion or (x==0 and y==0), "invalid position: %s,%s" % (x,y)
    11461192        src_format = image.get_pixel_format()
    1147         with self._lock:
    1148             if not self.check_pipeline(encoding, w, h, src_format):
    1149                 #find one that is not video:
    1150                 fallback_encodings = set(self._encoders.keys()) - set(self.video_encodings) - set(["mmap"])
    1151                 log.error("BUG: failed to setup a video pipeline for %s encoding with source format %s, will fallback to: %s", encoding, src_format, fallback_encodings)
    1152                 assert len(fallback_encodings)>0
    1153                 fallback_encoding = [x for x in PREFERED_ENCODING_ORDER if x in fallback_encodings][0]
    1154                 return self._encoders[fallback_encoding](fallback_encoding, image, options)
    11551193
    1156             #dw and dh are the edges we don't handle here
    1157             width = w & self.width_mask
    1158             height = h & self.height_mask
    1159             log("video_encode%s image size: %sx%s, encoder/csc size: %sx%s", (encoding, image, options), w, h, width, height)
     1194        if not self.check_pipeline(encoding, w, h, src_format):
     1195            #find one that is not video:
     1196            fallback_encodings = set(self._encoders.keys()) - set(self.video_encodings) - set(["mmap"])
     1197            log.error("BUG: failed to setup a video pipeline for %s encoding with source format %s, will fallback to: %s", encoding, src_format, fallback_encodings)
     1198            assert len(fallback_encodings)>0
     1199            fallback_encoding = [x for x in PREFERED_ENCODING_ORDER if x in fallback_encodings][0]
     1200            return self._encoders[fallback_encoding](fallback_encoding, image, options)
     1201        assert self._video_encoder
    11601202
    1161             csc_image, csc, enc_width, enc_height = self.csc_image(image, width, height)
     1203        #dw and dh are the edges we don't handle here
     1204        width = w & self.width_mask
     1205        height = h & self.height_mask
     1206        log("video_encode%s image size: %sx%s, encoder/csc size: %sx%s", (encoding, image, options), w, h, width, height)
    11621207
    1163             start = time.time()
    1164             ret = self._video_encoder.compress_image(csc_image, options)
    1165             if ret is None:
    1166                 log.error("video_encode: ouch, %s compression failed", encoding)
    1167                 return None
    1168             data, client_options = ret
    1169             end = time.time()
     1208        csc_image, csc, enc_width, enc_height = self.csc_image(image, width, height)
    11701209
    1171             self.free_image_wrapper(csc_image)
    1172             del csc_image
     1210        start = time.time()
     1211        ret = self._video_encoder.compress_image(csc_image, options)
     1212        if ret is None:
     1213            log.error("video_encode: ouch, %s compression failed", encoding)
     1214            return None
     1215        data, client_options = ret
     1216        end = time.time()
    11731217
    1174             #tell the client which colour subsampling we used:
    1175             #(note: see csc_equiv!)
    1176             if self.uses_csc_atoms:
    1177                 client_options["csc"] = self.csc_equiv(csc)
    1178             else:
    1179                 #ugly hack: expose internal ffmpeg/libav constant
    1180                 #for old versions without the "csc_atoms" feature:
    1181                 client_options["csc_pixel_format"] = get_avutil_enum_from_colorspace(csc)
    1182             #tell the client about scaling (the size of the encoded picture):
    1183             #(unless the video encoder has already done so):
    1184             if self._csc_encoder and ("scaled_size" not in client_options) and (enc_width!=width or enc_height!=height):
    1185                 client_options["scaled_size"] = enc_width, enc_height
    1186             log("video_encode encoder: %s %sx%s result is %s bytes (%.1f MPixels/s), client options=%s",
    1187                                 encoding, enc_width, enc_height, len(data), (enc_width*enc_height/(end-start+0.000001)/1024.0/1024.0), client_options)
    1188             return self._video_encoder.get_encoding(), Compressed(encoding, data), client_options, width, height, 0, 24
     1218        self.free_image_wrapper(csc_image)
     1219        del csc_image
    11891220
     1221        #tell the client which colour subsampling we used:
     1222        #(note: see csc_equiv!)
     1223        if self.uses_csc_atoms:
     1224            client_options["csc"] = self.csc_equiv(csc)
     1225        else:
     1226            #ugly hack: expose internal ffmpeg/libav constant
     1227            #for old versions without the "csc_atoms" feature:
     1228            client_options["csc_pixel_format"] = get_avutil_enum_from_colorspace(csc)
     1229        #tell the client about scaling (the size of the encoded picture):
     1230        #(unless the video encoder has already done so):
     1231        if self._csc_encoder and ("scaled_size" not in client_options) and (enc_width!=width or enc_height!=height):
     1232            client_options["scaled_size"] = enc_width, enc_height
     1233        log("video_encode encoder: %s %sx%s result is %s bytes (%.1f MPixels/s), client options=%s",
     1234                            encoding, enc_width, enc_height, len(data), (enc_width*enc_height/(end-start+0.000001)/1024.0/1024.0), client_options)
     1235        return self._video_encoder.get_encoding(), Compressed(encoding, data), client_options, width, height, 0, 24
     1236
    11901237    def csc_image(self, image, width, height):
    11911238        """
    11921239            Takes a source image and converts it
     
    11941241            If there are no csc_encoders (because the video
    11951242            encoder can process the source format directly)
    11961243            then the image is returned unchanged.
     1244
     1245            Runs in the 'encode' thread.
    11971246        """
    11981247        if self._csc_encoder is None:
    11991248            #no csc step!