Skip to content

Commit

Permalink
#504: delegated encoding:
Browse files Browse the repository at this point in the history
* add boost score to codec_spec so we can favour the new proxy encoder
* VideoHelper is no longer a singleton, so we can add encoders to it (and csc if we wanted to) for each client. Use this to add the proxy encoder when requested.
* proxy encoder which just forwards the RGB pixel data with added metadata (scaling, etc)
* proxy server sends the codec specs for the encodings it wants to handle itself (for now, use "h264" via both "x264" and "nvenc" to make it easier to test)
* proxy server detects RGB proxy encoder frames, creates an encoder when required and uses it to encode them. Also keeps track of "lost-window" to free encoder context.

git-svn-id: https://xpra.org/svn/Xpra/trunk@5286 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Jan 28, 2014
1 parent dc4b4d4 commit 8c31054
Show file tree
Hide file tree
Showing 6 changed files with 323 additions and 20 deletions.
14 changes: 13 additions & 1 deletion src/xpra/codecs/codec_constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# coding=utf8
# This file is part of Xpra.
# Copyright (C) 2012, 2013 Antoine Martin <[email protected]>
# Copyright (C) 2012-2014 Antoine Martin <[email protected]>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.

Expand Down Expand Up @@ -55,6 +55,7 @@ def __init__(self, codec_class, codec_type="", encoding=None,
setup_cost=50, cpu_cost=100, gpu_cost=0,
min_w=1, min_h=1, max_w=4*1024, max_h=4*1024,
can_scale=False,
score_boost=0,
width_mask=0xFFFF, height_mask=0xFFFF):
self.codec_class = codec_class #ie: xpra.codecs.enc_x264.encoder.Encoder
self.codec_type = codec_type #ie: "nvenc"
Expand All @@ -63,6 +64,7 @@ def __init__(self, codec_class, codec_type="", encoding=None,
self.setup_cost = setup_cost
self.cpu_cost = cpu_cost
self.gpu_cost = gpu_cost
self.score_boost = score_boost
self.min_w = min_w
self.min_h = min_h
self.max_w = max_w
Expand All @@ -72,6 +74,16 @@ def __init__(self, codec_class, codec_type="", encoding=None,
self.can_scale = can_scale
self.encoding = encoding #ie: "h264"

def to_dict(self):
d = {}
for k in ("codec_class", "codec_type", "quality", "speed",
"setup_cost", "cpu_cost", "gpu_cost", "score_boost",
"min_w", "min_h", "max_w", "max_h",
"width_mask", "height_mask",
"can_scale", "encoding"):
d[k] = getattr(self, k)
return d

def get_runtime_factor(self):
#a cost multiplier that some encoder may want to override
#1.0 means no change:
Expand Down
4 changes: 4 additions & 0 deletions src/xpra/codecs/enc_proxy/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# This file is part of Xpra.
# Copyright (C) 2014 Antoine Martin <[email protected]>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.
140 changes: 140 additions & 0 deletions src/xpra/codecs/enc_proxy/encoder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# This file is part of Xpra.
# Copyright (C) 2014 Antoine Martin <[email protected]>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.

import time

from xpra.log import Logger, debug_if_env
log = Logger()
debug = debug_if_env(log, "XPRA_PROXYVIDEO_DEBUG")
error = log.error

from xpra.codecs.image_wrapper import ImageWrapper
from xpra.deque import maxdeque


def get_version():
return (0, 1)

def get_type():
return "proxy"

def get_info():
return {"version" : get_version()}

def get_encodings():
return ["proxy"]

def init_module():
#nothing to do!
pass


class Encoder(object):
"""
This is a "fake" encoder which just forwards
the raw pixels and the metadata that goes with it.
"""

def init_context(self, width, height, src_format, encoding, quality, speed, scaling, options):
self.encoding = encoding
self.width = width
self.height = height
self.quality = quality
self.speed = speed
self.scaling = scaling
self.src_format = src_format
self.last_frame_times = maxdeque(200)
self.frames = 0
self.time = 0

def get_info(self): #@DuplicatedSignature
info = get_info()
if self.src_format is None:
return info
info.update({"frames" : self.frames,
"width" : self.width,
"height" : self.height,
"speed" : self.speed,
"quality" : self.quality,
"src_format": self.src_format,
"version" : get_version()})
if self.scaling!=(1,1):
info["scaling"] = self.scaling
#calculate fps:
now = time.time()
last_time = now
cut_off = now-10.0
f = 0
for v in list(self.last_frame_times):
if v>cut_off:
f += 1
last_time = min(last_time, v)
if f>0 and last_time<now:
info["fps"] = int(f/(now-last_time))
return info

def __str__(self):
if self.src_format is None:
return "proxy_encoder(uninitialized)"
return "proxy_encoder(%s - %sx%s)" % (self.src_format, self.width, self.height)

def is_closed(self):
return self.src_format is None

def get_encoding(self):
return self.encoding

def get_width(self):
return self.width

def get_height(self):
return self.height

def get_type(self): #@DuplicatedSignature
return "proxy"

def get_src_format(self):
return self.src_format

def clean(self): #@DuplicatedSignature
self.width = 0
self.height = 0
self.quality = 0
self.speed = 0
self.src_format = None

def get_client_options(self, image, options):
options = {
"proxy" : True,
"frame" : self.frames,
#pass-through encoder options:
"options" : options,
#redundant metadata:
#"width" : image.get_width(),
#"height" : image.get_height(),
"rowstride" : image.get_rowstride(),
"depth" : image.get_depth(),
"rgb_format": image.get_pixel_format(),
}
if self.scaling!=(1,1):
options["scaling"] = self.scaling
return options

def compress_image(self, image, options={}):
debug("compress_image(%s, %s)", image, options)
#pass the pixels as they are
assert image.get_planes()==ImageWrapper.PACKED, "invalid number of planes: %s" % image.get_planes()
pixels = str(image.get_pixels())
self.frames += 1
self.last_frame_times.append(time.time())
client_options = self.get_client_options(image, options)
debug("compress_image(%s, %s) returning %s bytes and options=%s", image, options, len(pixels), client_options)
return pixels, client_options

def set_encoding_speed(self, pct):
self.speed = min(100, max(0, pct))

def set_encoding_quality(self, pct):
self.quality = min(100, max(0, pct))
31 changes: 23 additions & 8 deletions src/xpra/codecs/video_helper.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# coding=utf8
# This file is part of Xpra.
# Copyright (C) 2013 Antoine Martin <[email protected]>
# Copyright (C) 2013, 2014 Antoine Martin <[email protected]>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.

Expand All @@ -17,15 +17,21 @@
class VideoHelper(object):

def __init__(self):
global singleton
assert singleton is None
self._video_encoder_specs = {}
self._csc_encoder_specs = {}
#bits needed to ensure we can initialize just once
#even when called from multiple threads:
self._initialized = False
self._lock = Lock()

def clone(self):
assert self._initialized
clone = VideoHelper()
clone._video_encoder_specs = self._video_encoder_specs.copy()
clone._csc_encoder_specs = self._csc_encoder_specs.copy()
clone._initialized = True
return clone

def get_info(self):
d = {}
for in_csc, specs in self._csc_encoder_specs.items():
Expand All @@ -52,6 +58,9 @@ def may_init(self):
finally:
self._lock.release()

def get_encodings(self):
return self._video_encoder_specs.keys()

def get_encoder_specs(self, encoding):
return self._video_encoder_specs.get(encoding, [])

Expand Down Expand Up @@ -93,10 +102,13 @@ def init_video_encoder_option(self, encoder_name):
encodings = encoder_module.get_encodings()
debug("init_video_encoder_option(%s) %s encodings=%s", encoder_module, encoder_type, encodings)
for encoding in encodings:
encoder_specs = self._video_encoder_specs.setdefault(encoding, {})
for colorspace in colorspaces:
spec = encoder_module.get_spec(encoding, colorspace)
encoder_specs.setdefault(colorspace, []).append(spec)
self.add_encoder_spec(encoding, colorspace, spec)

def add_encoder_spec(self, encoding, colorspace, spec):
self._video_encoder_specs.setdefault(encoding, {}).setdefault(colorspace, []).append(spec)


def init_csc_options(self):
try:
Expand Down Expand Up @@ -137,13 +149,16 @@ def init_csc_option(self, csc_name):
return
in_cscs = csc_module.get_input_colorspaces()
for in_csc in in_cscs:
csc_specs = self._csc_encoder_specs.setdefault(in_csc, [])
out_cscs = csc_module.get_output_colorspaces(in_csc)
debug("init_csc_option(..) %s.get_output_colorspaces(%s)=%s", csc_module.get_type(), in_csc, out_cscs)
for out_csc in out_cscs:
spec = csc_module.get_spec(in_csc, out_csc)
item = out_csc, spec
csc_specs.append(item)
self.add_csc_spec(in_csc, out_csc, spec)

def add_csc_spec(self, in_csc, out_csc, spec):
item = out_csc, spec
self._csc_encoder_specs.setdefault(in_csc, []).append(item)


singleton = VideoHelper()
def getVideoHelper():
Expand Down
Loading

0 comments on commit 8c31054

Please sign in to comment.