Example #1
0
 def calculate_scaling(self, width, height):
     self.actual_scaling = self.scaling
     if not SCALING or not self.video_scaling:
         # not supported by client or disabled by env:
         self.actual_scaling = 1, 1
     elif SCALING_HARDCODED:
         self.actual_scaling = tuple(SCALING_HARDCODED)
         debug("using hardcoded scaling: %s", self.actual_scaling)
     elif self.actual_scaling is None:
         # no scaling window attribute defined, so use heuristics to enable:
         quality = self.get_current_quality()
         speed = self.get_current_speed()
         if width >= 4096 or height >= 4096:
             # most encoders can't deal with that!
             d = 1
             while width / d >= 4096 or height / d >= 4096:
                 d += 1
             self.actual_scaling = 1, d
         elif width * height >= 1024 * 1024 and quality < 30 and speed > 90:
             self.actual_scaling = 2, 3
         elif self.maximized and quality < 50 and speed > 80:
             self.actual_scaling = 2, 3
         elif self.fullscreen and quality < 60 and speed > 70:
             self.actual_scaling = 1, 2
     if self.actual_scaling is None:
         self.actual_scaling = 1, 1
     v, u = self.actual_scaling
     if v / u > 1.0:
         # never upscale before encoding!
         self.actual_scaling = 1, 1
     elif float(v) / float(u) < 0.1:
         # don't downscale more than 10 times! (for each dimension - that's 100 times!)
         self.actual_scaling = 1, 10
     return self.actual_scaling
Example #2
0
 def calculate_scaling(self, width, height):
     self.actual_scaling = self.scaling
     if not SCALING or not self.video_scaling:
         #not supported by client or disabled by env:
         self.actual_scaling = 1, 1
     elif SCALING_HARDCODED:
         self.actual_scaling = tuple(SCALING_HARDCODED)
         debug("using hardcoded scaling: %s", self.actual_scaling)
     elif self.actual_scaling is None:
         #no scaling window attribute defined, so use heuristics to enable:
         quality = self.get_current_quality()
         speed = self.get_current_speed()
         if width >= 4096 or height >= 4096:
             #most encoders can't deal with that!
             d = 1
             while width / d >= 4096 or height / d >= 4096:
                 d += 1
             self.actual_scaling = 1, d
         elif width * height >= 1024 * 1024 and quality < 30 and speed > 90:
             self.actual_scaling = 2, 3
         elif self.maximized and quality < 50 and speed > 80:
             self.actual_scaling = 2, 3
         elif self.fullscreen and quality < 60 and speed > 70:
             self.actual_scaling = 1, 2
     if self.actual_scaling is None:
         self.actual_scaling = 1, 1
     v, u = self.actual_scaling
     if v / u > 1.0:
         #never upscale before encoding!
         self.actual_scaling = 1, 1
     elif float(v) / float(u) < 0.1:
         #don't downscale more than 10 times! (for each dimension - that's 100 times!)
         self.actual_scaling = 1, 10
     return self.actual_scaling
Example #3
0
    def csc_image(self, image, width, height):
        """
            Takes a source image and converts it
            using the current csc_encoder.
            If there are no csc_encoders (because the video
            encoder can process the source format directly)
            then the image is returned unchanged.
        """
        if self._csc_encoder is None:
            # no csc step!
            return image, image.get_pixel_format(), width, height

        start = time.time()
        csc_image = self._csc_encoder.convert_image(image)
        end = time.time()
        # the image comes from the UI server, free it in the UI thread:
        self.idle_add(image.free)
        debug(
            "csc_image(%s, %s, %s) converted to %s in %.1fms (%.1f MPixels/s)",
            image,
            width,
            height,
            csc_image,
            (1000.0 * end - 1000.0 * start),
            (width * height / (end - start + 0.000001) / 1024.0 / 1024.0),
        )
        if not csc_image:
            raise Exception("csc_image: conversion of %s to %s failed" % (image, self._csc_encoder.get_dst_format()))
        assert self._csc_encoder.get_dst_format() == csc_image.get_pixel_format()
        return (
            csc_image,
            self._csc_encoder.get_dst_format(),
            self._csc_encoder.get_dst_width(),
            self._csc_encoder.get_dst_height(),
        )
Example #4
0
    def csc_image(self, image, width, height):
        """
            Takes a source image and converts it
            using the current csc_encoder.
            If there are no csc_encoders (because the video
            encoder can process the source format directly)
            then the image is returned unchanged.
        """
        if self._csc_encoder is None:
            #no csc step!
            return image, image.get_pixel_format(), width, height

        start = time.time()
        csc_image = self._csc_encoder.convert_image(image)
        end = time.time()
        #the image comes from the UI server, free it in the UI thread:
        self.idle_add(image.free)
        debug(
            "csc_image(%s, %s, %s) converted to %s in %.1fms (%.1f MPixels/s)",
            image, width, height, csc_image, (1000.0 * end - 1000.0 * start),
            (width * height / (end - start + 0.000001) / 1024.0 / 1024.0))
        if not csc_image:
            raise Exception("csc_image: conversion of %s to %s failed" %
                            (image, self._csc_encoder.get_dst_format()))
        assert self._csc_encoder.get_dst_format(
        ) == csc_image.get_pixel_format()
        return csc_image, self._csc_encoder.get_dst_format(
        ), self._csc_encoder.get_dst_width(), self._csc_encoder.get_dst_height(
        )
Example #5
0
 def setup_pipeline(self, scores, width, height, src_format):
     """
         Given a list of pipeline options ordered by their score
         and an input format (width, height and source pixel format),
         we try to create a working pipeline, trying each option
         until one succeeds.
     """
     start = time.time()
     debug("setup_pipeline%s", (scores, width, height, src_format))
     for option in scores:
         try:
             _, csc_spec, enc_in_format, encoder_spec = option
             debug("setup_pipeline: trying %s", option)
             speed = self.get_current_speed()
             quality = self.get_current_quality()
             if csc_spec:
                 #TODO: no need to OR encoder mask if we are scaling...
                 self.width_mask = csc_spec.width_mask & encoder_spec.width_mask
                 self.height_mask = csc_spec.height_mask & encoder_spec.height_mask
                 csc_width = width & self.width_mask
                 csc_height = height & self.height_mask
                 enc_width, enc_height = self.get_encoder_dimensions(
                     csc_spec, encoder_spec, csc_width, csc_height)
                 #csc speed is not very important compared to encoding speed,
                 #so make sure it never degrades quality
                 csc_speed = min(speed, 100 - quality / 2.0)
                 csc_start = time.time()
                 self._csc_encoder = csc_spec.codec_class()
                 self._csc_encoder.init_context(csc_width, csc_height,
                                                src_format, enc_width,
                                                enc_height, enc_in_format,
                                                csc_speed)
                 csc_end = time.time()
                 debug("setup_pipeline: csc=%s, info=%s, setup took %.2fms",
                       self._csc_encoder, self._csc_encoder.get_info(),
                       (csc_end - csc_start) * 1000.0)
             else:
                 #use the encoder's mask directly since that's all we have to worry about!
                 self.width_mask = encoder_spec.width_mask
                 self.height_mask = encoder_spec.height_mask
                 enc_width = width & self.width_mask
                 enc_height = height & self.height_mask
             enc_start = time.time()
             self._video_encoder = encoder_spec.codec_class()
             self._video_encoder.init_context(enc_width, enc_height,
                                              enc_in_format, quality, speed,
                                              self.encoding_options)
             enc_end = time.time()
             debug(
                 "setup_pipeline: video encoder=%s, info: %s, setup took %.2fms",
                 self._video_encoder, self._video_encoder.get_info(),
                 (enc_end - enc_start) * 1000.0)
             return True
         except:
             log.warn("setup_pipeline failed for %s", option, exc_info=True)
     end = time.time()
     debug("setup_pipeline(..) failed! took %.2fms", (end - start) * 1000.0)
     return False
Example #6
0
    def get_video_pipeline_options(self, encoding, width, height, src_format):
        """
            Given a picture format (width, height and src pixel format),
            we find all the pipeline options that will allow us to compress
            it using the given encoding.
            First, we try with direct encoders (for those that support the
            source pixel format natively), then we try all the combinations
            using csc encoders to convert to an intermediary format.
            Each solution is rated and we return all of them in descending
            score (best solution comes first).
        """
        encoder_specs = self._video_pipeline_helper.get_encoder_specs(encoding)
        assert len(encoder_specs) > 0, "cannot handle %s encoding!" % encoding
        scores = []

        def add_scores(info, csc_spec, enc_in_format):
            colorspace_specs = encoder_specs.get(enc_in_format)
            #first, add the direct matches (no csc needed) - if any:
            if colorspace_specs:
                #debug("%s encoding from %s: %s", info, pixel_format, colorspace_specs)
                for encoder_spec in colorspace_specs:
                    score = self.get_score(enc_in_format, csc_spec,
                                           encoder_spec, width, height)
                    if score >= 0:
                        item = score, csc_spec, enc_in_format, encoder_spec
                        scores.append(item)

        if src_format in self.csc_modes:
            scaling = self.calculate_scaling(width, height)
            #we can only use direct if not scaling
            #as csc is the step that does the scaling (at present..)
            if scaling == (1, 1):
                add_scores("direct (no csc)", None, src_format)
        #now add those that require a csc step:
        csc_specs = self._video_pipeline_helper.get_csc_specs(src_format)
        if csc_specs:
            #debug("%s can also be converted to %s using %s", pixel_format, [x[0] for x in csc_specs], set(x[1] for x in csc_specs))
            #we have csc module(s) that can get us from pixel_format to out_csc:
            for out_csc, csc_spec in csc_specs:
                if out_csc in self.csc_modes:
                    add_scores("via %s" % out_csc, csc_spec, out_csc)
        s = sorted(scores, key=lambda x: -x[0])
        debug("get_video_pipeline_options%s scores=%s",
              (encoding, width, height, src_format), s)
        return s
Example #7
0
    def get_video_pipeline_options(self, encoding, width, height, src_format):
        """
            Given a picture format (width, height and src pixel format),
            we find all the pipeline options that will allow us to compress
            it using the given encoding.
            First, we try with direct encoders (for those that support the
            source pixel format natively), then we try all the combinations
            using csc encoders to convert to an intermediary format.
            Each solution is rated and we return all of them in descending
            score (best solution comes first).
        """
        encoder_specs = self._video_pipeline_helper.get_encoder_specs(encoding)
        assert len(encoder_specs) > 0, "cannot handle %s encoding!" % encoding
        scores = []

        def add_scores(info, csc_spec, enc_in_format):
            colorspace_specs = encoder_specs.get(enc_in_format)
            # first, add the direct matches (no csc needed) - if any:
            if colorspace_specs:
                # debug("%s encoding from %s: %s", info, pixel_format, colorspace_specs)
                for encoder_spec in colorspace_specs:
                    score = self.get_score(enc_in_format, csc_spec, encoder_spec, width, height)
                    if score >= 0:
                        item = score, csc_spec, enc_in_format, encoder_spec
                        scores.append(item)

        if src_format in self.csc_modes:
            scaling = self.calculate_scaling(width, height)
            # we can only use direct if not scaling
            # as csc is the step that does the scaling (at present..)
            if scaling == (1, 1):
                add_scores("direct (no csc)", None, src_format)
        # now add those that require a csc step:
        csc_specs = self._video_pipeline_helper.get_csc_specs(src_format)
        if csc_specs:
            # debug("%s can also be converted to %s using %s", pixel_format, [x[0] for x in csc_specs], set(x[1] for x in csc_specs))
            # we have csc module(s) that can get us from pixel_format to out_csc:
            for out_csc, csc_spec in csc_specs:
                if out_csc in self.csc_modes:
                    add_scores("via %s" % out_csc, csc_spec, out_csc)
        s = sorted(scores, key=lambda x: -x[0])
        debug("get_video_pipeline_options%s scores=%s", (encoding, width, height, src_format), s)
        return s
Example #8
0
    def check_pipeline(self, encoding, width, height, src_format):
        """
            Checks that the current pipeline is still valid
            for the given input. If not, close it and make a new one.
        """
        debug("check_pipeline%s", (encoding, width, height, src_format))
        # must be called with video lock held!
        if self.do_check_pipeline(encoding, width, height, src_format):
            return True  # OK!

        # cleanup existing one if needed:
        if self._csc_encoder:
            self.do_csc_encoder_cleanup()
        if self._video_encoder:
            self.do_video_encoder_cleanup()
        # and make a new one:
        self.last_pipeline_params = encoding, width, height, src_format
        self.last_pipeline_scores = self.get_video_pipeline_options(encoding, width, height, src_format)
        return self.setup_pipeline(self.last_pipeline_scores, width, height, src_format)
Example #9
0
    def check_pipeline(self, encoding, width, height, src_format):
        """
            Checks that the current pipeline is still valid
            for the given input. If not, close it and make a new one.
        """
        debug("check_pipeline%s", (encoding, width, height, src_format))
        #must be called with video lock held!
        if self.do_check_pipeline(encoding, width, height, src_format):
            return True  #OK!

        #cleanup existing one if needed:
        if self._csc_encoder:
            self.do_csc_encoder_cleanup()
        if self._video_encoder:
            self.do_video_encoder_cleanup()
        #and make a new one:
        self.last_pipeline_params = encoding, width, height, src_format
        self.last_pipeline_scores = self.get_video_pipeline_options(
            encoding, width, height, src_format)
        return self.setup_pipeline(self.last_pipeline_scores, width, height,
                                   src_format)
Example #10
0
    def do_check_pipeline(self, encoding, width, height, src_format):
        """
            Checks that the current pipeline is still valid
            for the given input. If not, close it and make a new one.
        """
        debug("do_check_pipeline%s", (encoding, width, height, src_format))
        #must be called with video lock held!
        if self._video_encoder is None:
            return False

        if self._csc_encoder:
            csc_width = width & self.width_mask
            csc_height = height & self.height_mask
            if self._csc_encoder.get_src_format() != src_format:
                debug(
                    "check_pipeline csc: switching source format from %s to %s",
                    self._csc_encoder.get_src_format(), src_format)
                return False
            elif self._csc_encoder.get_src_width(
            ) != csc_width or self._csc_encoder.get_src_height() != csc_height:
                debug(
                    "check_pipeline csc: window dimensions have changed from %sx%s to %sx%s, csc info=%s",
                    self._csc_encoder.get_src_width(),
                    self._csc_encoder.get_src_height(), csc_width, csc_height,
                    self._csc_encoder.get_info())
                return False
            elif self._csc_encoder.get_dst_format(
            ) != self._video_encoder.get_src_format():
                log.warn(
                    "check_pipeline csc: intermediate format mismatch: %s vs %s, csc info=%s",
                    self._csc_encoder.get_dst_format(),
                    self._video_encoder.get_src_format(),
                    self._csc_encoder.get_info())
                return False

            encoder_src_format = self._csc_encoder.get_dst_format()
            encoder_src_width = self._csc_encoder.get_dst_width()
            encoder_src_height = self._csc_encoder.get_dst_height()
        else:
            #direct to video encoder without csc:
            encoder_src_format = src_format
            encoder_src_width = width & self.width_mask
            encoder_src_height = height & self.height_mask

        if self._video_encoder.get_src_format() != encoder_src_format:
            debug(
                "check_pipeline video: invalid source format %s, expected %s",
                self._video_encoder.get_src_format(), encoder_src_format)
            return False
        elif self._video_encoder.get_type() != encoding:
            debug("check_pipeline video: invalid encoding %s, expected %s",
                  self._video_encoder.get_type(), encoding)
            return False
        elif self._video_encoder.get_width(
        ) != encoder_src_width or self._video_encoder.get_height(
        ) != encoder_src_height:
            debug(
                "check_pipeline video: window dimensions have changed from %sx%s to %sx%s",
                self._video_encoder.get_width(),
                self._video_encoder.get_height(), encoder_src_width,
                encoder_src_height)
            return False
        return True
Example #11
0
    def get_score(self, csc_format, csc_spec, encoder_spec, width, height):
        """
            Given an optional csc step (csc_format and csc_spec), and
            and a required encoding step (encoder_spec and width/height),
            we calculate a score of how well this matches our requirements:
            * our quality target (as per get_currend_quality)
            * our speed target (as per get_current_speed)
            * how expensive it would be to switch to this pipeline option
            Note: we know the current pipeline settings, so the "switching
            cost" will be lower for pipelines that share components with the
            current one.
        """
        #first discard if we cannot handle this size:
        if csc_spec and not csc_spec.can_handle(width, height):
            return -1
        if not encoder_spec.can_handle(width, height):
            return -1
        #debug("get_score%s", (csc_format, csc_spec, encoder_spec,
        #          width, height, min_quality, target_quality, min_speed, target_speed))
        def clamp(v):
            return max(0, min(100, v))

        #evaluate output quality:
        quality = clamp(encoder_spec.quality)
        if csc_format and csc_format in ("YUV420P", "YUV422P", "YUV444P"):
            #account for subsampling (reduces quality):
            y, u, v = get_subsampling_divs(csc_format)
            div = 0.5  #any colourspace convertion will lose at least some quality (due to rounding)
            for div_x, div_y in (y, u, v):
                div += (div_x + div_y) / 2.0 / 3.0
            quality = quality / div
        if csc_spec and csc_spec.quality < 100:
            #csc_spec.quality is the upper limit (up to 100):
            quality *= csc_spec.quality / 100.0
        #score based on how far we are:
        if quality < self.get_min_quality():
            qscore = 0
        else:
            qscore = 100 - abs(quality - self.get_current_quality())

        #score based on speed:
        speed = clamp(encoder_spec.speed)
        if csc_spec:
            speed *= csc_spec.speed / 100.0
        if speed < self.get_min_speed():
            sscore = 0
        else:
            sscore = 100 - abs(speed - self.get_current_speed())

        #score for "edge resistance" via setup cost:
        ecsc_score = 100
        if csc_spec:
            #OR the masks so we have a chance of making it work
            width_mask = csc_spec.width_mask & encoder_spec.width_mask
            height_mask = csc_spec.height_mask & encoder_spec.height_mask
            csc_width = width & width_mask
            csc_height = height & height_mask
            if self._csc_encoder is None or self._csc_encoder.get_dst_format()!=csc_format or \
               type(self._csc_encoder)!=csc_spec.codec_class or \
               self._csc_encoder.get_src_width()!=csc_width or self._csc_encoder.get_src_height()!=csc_height:
                #if we have to change csc, account for new csc setup cost:
                ecsc_score = max(0, 80 - csc_spec.setup_cost * 80.0 / 100.0)
            else:
                ecsc_score = 80
            enc_width, enc_height = self.get_encoder_dimensions(
                csc_spec, encoder_spec, csc_width, csc_height)
        else:
            #not using csc at all!
            ecsc_score = 100
            width_mask = encoder_spec.width_mask
            height_mask = encoder_spec.height_mask
            enc_width = width & width_mask
            enc_height = height & height_mask
        ee_score = 100
        if self._video_encoder is None or type(self._video_encoder)!=encoder_spec.codec_class or \
           self._video_encoder.get_src_format()!=csc_format or \
           self._video_encoder.get_width()!=enc_width or self._video_encoder.get_height()!=enc_height:
            #account for new encoder setup cost:
            ee_score = 100 - encoder_spec.setup_cost
        #edge resistance score: average of csc and encoder score:
        er_score = (ecsc_score + ee_score) / 2.0
        debug("get_score%s %s/%s/%s",
              (csc_format, csc_spec, encoder_spec, width, height), int(qscore),
              int(sscore), int(er_score))
        return int((qscore + sscore + er_score) / 3.0)
Example #12
0
    def video_encode(self, encoding, image, options):
        """
            This method is used by make_data_packet to encode frames using x264 or vpx.
            Video encoders only deal with fixed dimensions,
            so we must clean and reinitialize the encoder if the window dimensions
            has changed.
            Since this runs in the non-UI thread 'data_to_packet', we must
            use the '_lock' to prevent races.
        """
        debug("video_encode%s", (encoding, image, options))
        x, y, w, h = image.get_geometry()[:4]
        assert x == 0 and y == 0, "invalid position: %s,%s" % (x, y)
        src_format = image.get_pixel_format()
        try:
            self._lock.acquire()
            if not self.check_pipeline(encoding, w, h, src_format):
                raise Exception(
                    "failed to setup a video pipeline for %s encoding with source format %s" % (encoding, src_format)
                )

            # dw and dh are the edges we don't handle here
            width = w & self.width_mask
            height = h & self.height_mask
            debug("video_encode%s w-h=%s-%s, width-height=%s-%s", (encoding, image, options), w, h, width, height)

            csc_image, csc, enc_width, enc_height = self.csc_image(image, width, height)

            start = time.time()
            data, client_options = self._video_encoder.compress_image(csc_image, options)
            end = time.time()

            if csc_image is image:
                # no csc step, so the image comes from the UI server
                # and must be freed in the UI thread:
                self.idle_add(csc_image.free)
            else:
                # csc temporary images can be freed at will
                csc_image.free()
            del csc_image

            if data is None:
                log.error("video_encode: ouch, %s compression failed", encoding)
                return None, None, 0
            if self.encoding_client_options:
                # tell the client which pixel encoding we used:
                if self.uses_csc_atoms:
                    client_options["csc"] = csc
                else:
                    # ugly hack: expose internal ffmpeg/libav constant
                    # for old versions without the "csc_atoms" feature:
                    client_options["csc_pixel_format"] = get_avutil_enum_from_colorspace(csc)
                # tell the client about scaling:
                if self._csc_encoder and (enc_width != width or enc_height != height):
                    client_options["scaled_size"] = enc_width, enc_height
            debug(
                "video_encode encoder: %s %sx%s result is %s bytes (%.1f MPixels/s), client options=%s",
                encoding,
                enc_width,
                enc_height,
                len(data),
                (enc_width * enc_height / (end - start + 0.000001) / 1024.0 / 1024.0),
                client_options,
            )
            return Compressed(encoding, data), client_options, width, height, 0, 24
        finally:
            self._lock.release()
Example #13
0
 def setup_pipeline(self, scores, width, height, src_format):
     """
         Given a list of pipeline options ordered by their score
         and an input format (width, height and source pixel format),
         we try to create a working pipeline, trying each option
         until one succeeds.
     """
     start = time.time()
     debug("setup_pipeline%s", (scores, width, height, src_format))
     for option in scores:
         try:
             _, csc_spec, enc_in_format, encoder_spec = option
             debug("setup_pipeline: trying %s", option)
             speed = self.get_current_speed()
             quality = self.get_current_quality()
             if csc_spec:
                 # TODO: no need to OR encoder mask if we are scaling...
                 self.width_mask = csc_spec.width_mask & encoder_spec.width_mask
                 self.height_mask = csc_spec.height_mask & encoder_spec.height_mask
                 csc_width = width & self.width_mask
                 csc_height = height & self.height_mask
                 enc_width, enc_height = self.get_encoder_dimensions(csc_spec, encoder_spec, csc_width, csc_height)
                 # csc speed is not very important compared to encoding speed,
                 # so make sure it never degrades quality
                 csc_speed = min(speed, 100 - quality / 2.0)
                 csc_start = time.time()
                 self._csc_encoder = csc_spec.codec_class()
                 self._csc_encoder.init_context(
                     csc_width, csc_height, src_format, enc_width, enc_height, enc_in_format, csc_speed
                 )
                 csc_end = time.time()
                 debug(
                     "setup_pipeline: csc=%s, info=%s, setup took %.2fms",
                     self._csc_encoder,
                     self._csc_encoder.get_info(),
                     (csc_end - csc_start) * 1000.0,
                 )
             else:
                 # use the encoder's mask directly since that's all we have to worry about!
                 self.width_mask = encoder_spec.width_mask
                 self.height_mask = encoder_spec.height_mask
                 enc_width = width & self.width_mask
                 enc_height = height & self.height_mask
             enc_start = time.time()
             self._video_encoder = encoder_spec.codec_class()
             self._video_encoder.init_context(
                 enc_width, enc_height, enc_in_format, quality, speed, self.encoding_options
             )
             enc_end = time.time()
             debug(
                 "setup_pipeline: video encoder=%s, info: %s, setup took %.2fms",
                 self._video_encoder,
                 self._video_encoder.get_info(),
                 (enc_end - enc_start) * 1000.0,
             )
             return True
         except:
             log.warn("setup_pipeline failed for %s", option, exc_info=True)
     end = time.time()
     debug("setup_pipeline(..) failed! took %.2fms", (end - start) * 1000.0)
     return False
Example #14
0
    def do_check_pipeline(self, encoding, width, height, src_format):
        """
            Checks that the current pipeline is still valid
            for the given input. If not, close it and make a new one.
        """
        debug("do_check_pipeline%s", (encoding, width, height, src_format))
        # must be called with video lock held!
        if self._video_encoder is None:
            return False

        if self._csc_encoder:
            csc_width = width & self.width_mask
            csc_height = height & self.height_mask
            if self._csc_encoder.get_src_format() != src_format:
                debug(
                    "check_pipeline csc: switching source format from %s to %s",
                    self._csc_encoder.get_src_format(),
                    src_format,
                )
                return False
            elif self._csc_encoder.get_src_width() != csc_width or self._csc_encoder.get_src_height() != csc_height:
                debug(
                    "check_pipeline csc: window dimensions have changed from %sx%s to %sx%s, csc info=%s",
                    self._csc_encoder.get_src_width(),
                    self._csc_encoder.get_src_height(),
                    csc_width,
                    csc_height,
                    self._csc_encoder.get_info(),
                )
                return False
            elif self._csc_encoder.get_dst_format() != self._video_encoder.get_src_format():
                log.warn(
                    "check_pipeline csc: intermediate format mismatch: %s vs %s, csc info=%s",
                    self._csc_encoder.get_dst_format(),
                    self._video_encoder.get_src_format(),
                    self._csc_encoder.get_info(),
                )
                return False

            encoder_src_format = self._csc_encoder.get_dst_format()
            encoder_src_width = self._csc_encoder.get_dst_width()
            encoder_src_height = self._csc_encoder.get_dst_height()
        else:
            # direct to video encoder without csc:
            encoder_src_format = src_format
            encoder_src_width = width & self.width_mask
            encoder_src_height = height & self.height_mask

        if self._video_encoder.get_src_format() != encoder_src_format:
            debug(
                "check_pipeline video: invalid source format %s, expected %s",
                self._video_encoder.get_src_format(),
                encoder_src_format,
            )
            return False
        elif self._video_encoder.get_type() != encoding:
            debug("check_pipeline video: invalid encoding %s, expected %s", self._video_encoder.get_type(), encoding)
            return False
        elif (
            self._video_encoder.get_width() != encoder_src_width
            or self._video_encoder.get_height() != encoder_src_height
        ):
            debug(
                "check_pipeline video: window dimensions have changed from %sx%s to %sx%s",
                self._video_encoder.get_width(),
                self._video_encoder.get_height(),
                encoder_src_width,
                encoder_src_height,
            )
            return False
        return True
Example #15
0
    def get_score(self, csc_format, csc_spec, encoder_spec, width, height):
        """
            Given an optional csc step (csc_format and csc_spec), and
            and a required encoding step (encoder_spec and width/height),
            we calculate a score of how well this matches our requirements:
            * our quality target (as per get_currend_quality)
            * our speed target (as per get_current_speed)
            * how expensive it would be to switch to this pipeline option
            Note: we know the current pipeline settings, so the "switching
            cost" will be lower for pipelines that share components with the
            current one.
        """
        # first discard if we cannot handle this size:
        if csc_spec and not csc_spec.can_handle(width, height):
            return -1
        if not encoder_spec.can_handle(width, height):
            return -1
        # debug("get_score%s", (csc_format, csc_spec, encoder_spec,
        #          width, height, min_quality, target_quality, min_speed, target_speed))
        def clamp(v):
            return max(0, min(100, v))

        # evaluate output quality:
        quality = clamp(encoder_spec.quality)
        if csc_format and csc_format in ("YUV420P", "YUV422P", "YUV444P"):
            # account for subsampling (reduces quality):
            y, u, v = get_subsampling_divs(csc_format)
            div = 0.5  # any colourspace convertion will lose at least some quality (due to rounding)
            for div_x, div_y in (y, u, v):
                div += (div_x + div_y) / 2.0 / 3.0
            quality = quality / div
        if csc_spec and csc_spec.quality < 100:
            # csc_spec.quality is the upper limit (up to 100):
            quality *= csc_spec.quality / 100.0
        # score based on how far we are:
        if quality < self.get_min_quality():
            qscore = 0
        else:
            qscore = 100 - abs(quality - self.get_current_quality())

        # score based on speed:
        speed = clamp(encoder_spec.speed)
        if csc_spec:
            speed *= csc_spec.speed / 100.0
        if speed < self.get_min_speed():
            sscore = 0
        else:
            sscore = 100 - abs(speed - self.get_current_speed())

        # score for "edge resistance" via setup cost:
        ecsc_score = 100
        if csc_spec:
            # OR the masks so we have a chance of making it work
            width_mask = csc_spec.width_mask & encoder_spec.width_mask
            height_mask = csc_spec.height_mask & encoder_spec.height_mask
            csc_width = width & width_mask
            csc_height = height & height_mask
            if (
                self._csc_encoder is None
                or self._csc_encoder.get_dst_format() != csc_format
                or type(self._csc_encoder) != csc_spec.codec_class
                or self._csc_encoder.get_src_width() != csc_width
                or self._csc_encoder.get_src_height() != csc_height
            ):
                # if we have to change csc, account for new csc setup cost:
                ecsc_score = max(0, 80 - csc_spec.setup_cost * 80.0 / 100.0)
            else:
                ecsc_score = 80
            enc_width, enc_height = self.get_encoder_dimensions(csc_spec, encoder_spec, csc_width, csc_height)
        else:
            # not using csc at all!
            ecsc_score = 100
            width_mask = encoder_spec.width_mask
            height_mask = encoder_spec.height_mask
            enc_width = width & width_mask
            enc_height = height & height_mask
        ee_score = 100
        if (
            self._video_encoder is None
            or type(self._video_encoder) != encoder_spec.codec_class
            or self._video_encoder.get_src_format() != csc_format
            or self._video_encoder.get_width() != enc_width
            or self._video_encoder.get_height() != enc_height
        ):
            # account for new encoder setup cost:
            ee_score = 100 - encoder_spec.setup_cost
        # edge resistance score: average of csc and encoder score:
        er_score = (ecsc_score + ee_score) / 2.0
        debug(
            "get_score%s %s/%s/%s",
            (csc_format, csc_spec, encoder_spec, width, height),
            int(qscore),
            int(sscore),
            int(er_score),
        )
        return int((qscore + sscore + er_score) / 3.0)
Example #16
0
    def video_encode(self, encoding, image, options):
        """
            This method is used by make_data_packet to encode frames using x264 or vpx.
            Video encoders only deal with fixed dimensions,
            so we must clean and reinitialize the encoder if the window dimensions
            has changed.
            Since this runs in the non-UI thread 'data_to_packet', we must
            use the '_lock' to prevent races.
        """
        debug("video_encode%s", (encoding, image, options))
        x, y, w, h = image.get_geometry()[:4]
        assert x == 0 and y == 0, "invalid position: %s,%s" % (x, y)
        src_format = image.get_pixel_format()
        try:
            self._lock.acquire()
            if not self.check_pipeline(encoding, w, h, src_format):
                raise Exception(
                    "failed to setup a video pipeline for %s encoding with source format %s"
                    % (encoding, src_format))

            #dw and dh are the edges we don't handle here
            width = w & self.width_mask
            height = h & self.height_mask
            debug("video_encode%s w-h=%s-%s, width-height=%s-%s",
                  (encoding, image, options), w, h, width, height)

            csc_image, csc, enc_width, enc_height = self.csc_image(
                image, width, height)

            start = time.time()
            data, client_options = self._video_encoder.compress_image(
                csc_image, options)
            end = time.time()

            if csc_image is image:
                #no csc step, so the image comes from the UI server
                #and must be freed in the UI thread:
                self.idle_add(csc_image.free)
            else:
                #csc temporary images can be freed at will
                csc_image.free()
            del csc_image

            if data is None:
                log.error("video_encode: ouch, %s compression failed",
                          encoding)
                return None, None, 0
            if self.encoding_client_options:
                #tell the client which pixel encoding we used:
                if self.uses_csc_atoms:
                    client_options["csc"] = csc
                else:
                    #ugly hack: expose internal ffmpeg/libav constant
                    #for old versions without the "csc_atoms" feature:
                    client_options[
                        "csc_pixel_format"] = get_avutil_enum_from_colorspace(
                            csc)
                #tell the client about scaling:
                if self._csc_encoder and (enc_width != width
                                          or enc_height != height):
                    client_options["scaled_size"] = enc_width, enc_height
            debug(
                "video_encode encoder: %s %sx%s result is %s bytes (%.1f MPixels/s), client options=%s",
                encoding, enc_width, enc_height, len(data),
                (enc_width * enc_height /
                 (end - start + 0.000001) / 1024.0 / 1024.0), client_options)
            return Compressed(encoding,
                              data), client_options, width, height, 0, 24
        finally:
            self._lock.release()
Example #17
0
    def reconfigure(self, force_reload=False):
        """
            This is called when we want to force a full re-init (force_reload=True)
            or from the timer that allows to tune the quality and speed.
            (this tuning is done in WindowSource.reconfigure)
            Here we re-evaluate if the pipeline we are currently using
            is really the best one, and if not we switch to the best one.
            This uses get_video_pipeline_options() to get a list of pipeline
            options with a score for each.
        """
        debug("reconfigure(%s) csc_encoder=%s, video_encoder=%s", force_reload,
              self._csc_encoder, self._video_encoder)
        WindowSource.reconfigure(self, force_reload)
        if not self._video_encoder:
            return
        try:
            self._lock.acquire()
            ve = self._video_encoder
            if not ve or ve.is_closed():
                #could have been freed since we got the lock!
                return
            if force_reload:
                if self._csc_encoder:
                    self.do_csc_encoder_cleanup()
                self.do_video_encoder_cleanup()
                return

            pixel_format = None
            if self._csc_encoder:
                pixel_format = self._csc_encoder.get_src_format()
            else:
                pixel_format = ve.get_src_format()
            width = ve.get_width()
            height = ve.get_height()
            quality = self.get_current_quality()
            speed = self.get_current_speed()

            scores = self.get_video_pipeline_options(ve.get_type(), width,
                                                     height, pixel_format)
            if len(scores) > 0:
                debug("reconfigure(%s) best=%s", force_reload, scores[0])
                _, csc_spec, enc_in_format, encoder_spec = scores[0]
                if self._csc_encoder:
                    if csc_spec is None or \
                       type(self._csc_encoder)!=csc_spec.codec_class or \
                       self._csc_encoder.get_dst_format()!=enc_in_format:
                        debug("reconfigure(%s) found better csc encoder: %s",
                              force_reload, scores[0])
                        self.do_csc_encoder_cleanup()
                if type(self._video_encoder)!=encoder_spec.codec_class or \
                   self._video_encoder.get_src_format()!=enc_in_format:
                    debug("reconfigure(%s) found better video encoder: %s",
                          force_reload, scores[0])
                    self.do_video_encoder_cleanup()

            if self._video_encoder is None:
                self.setup_pipeline(scores, width, height, pixel_format)

            if self._video_encoder:
                self._video_encoder.set_encoding_speed(speed)
                self._video_encoder.set_encoding_quality(quality)
        finally:
            self._lock.release()
Example #18
0
    def reconfigure(self, force_reload=False):
        """
            This is called when we want to force a full re-init (force_reload=True)
            or from the timer that allows to tune the quality and speed.
            (this tuning is done in WindowSource.reconfigure)
            Here we re-evaluate if the pipeline we are currently using
            is really the best one, and if not we switch to the best one.
            This uses get_video_pipeline_options() to get a list of pipeline
            options with a score for each.
        """
        debug("reconfigure(%s) csc_encoder=%s, video_encoder=%s", force_reload, self._csc_encoder, self._video_encoder)
        WindowSource.reconfigure(self, force_reload)
        if not self._video_encoder:
            return
        try:
            self._lock.acquire()
            ve = self._video_encoder
            if not ve or ve.is_closed():
                # could have been freed since we got the lock!
                return
            if force_reload:
                if self._csc_encoder:
                    self.do_csc_encoder_cleanup()
                self.do_video_encoder_cleanup()
                return

            pixel_format = None
            if self._csc_encoder:
                pixel_format = self._csc_encoder.get_src_format()
            else:
                pixel_format = ve.get_src_format()
            width = ve.get_width()
            height = ve.get_height()
            quality = self.get_current_quality()
            speed = self.get_current_speed()

            scores = self.get_video_pipeline_options(ve.get_type(), width, height, pixel_format)
            if len(scores) > 0:
                debug("reconfigure(%s) best=%s", force_reload, scores[0])
                _, csc_spec, enc_in_format, encoder_spec = scores[0]
                if self._csc_encoder:
                    if (
                        csc_spec is None
                        or type(self._csc_encoder) != csc_spec.codec_class
                        or self._csc_encoder.get_dst_format() != enc_in_format
                    ):
                        debug("reconfigure(%s) found better csc encoder: %s", force_reload, scores[0])
                        self.do_csc_encoder_cleanup()
                if (
                    type(self._video_encoder) != encoder_spec.codec_class
                    or self._video_encoder.get_src_format() != enc_in_format
                ):
                    debug("reconfigure(%s) found better video encoder: %s", force_reload, scores[0])
                    self.do_video_encoder_cleanup()

            if self._video_encoder is None:
                self.setup_pipeline(scores, width, height, pixel_format)

            if self._video_encoder:
                self._video_encoder.set_encoding_speed(speed)
                self._video_encoder.set_encoding_quality(quality)
        finally:
            self._lock.release()