Skip to content

Commit

Permalink
#2051: add ability to record video streams
Browse files Browse the repository at this point in the history
git-svn-id: https://xpra.org/svn/Xpra/trunk@21056 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Nov 20, 2018
1 parent 55fc219 commit 06e91ff
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 24 deletions.
20 changes: 13 additions & 7 deletions src/xpra/server/window/window_source.py
Expand Up @@ -121,6 +121,7 @@ def __init__(self,

self.init_vars()

self.start_time = monotonic_time()
self.ui_thread = threading.current_thread()

self.record_congestion_event = record_congestion_event #callback for send latency problems
Expand Down Expand Up @@ -472,17 +473,22 @@ def get_info(self):
ma = self.mapped_at
if ma:
info["mapped-at"] = ma
now = monotonic_time()
cutoff = now-5
lde = [x for x in tuple(self.statistics.last_damage_events) if x[0]>=cutoff]
dfps = 0
if lde:
dfps = len(lde) // 5
info["damage.fps"] = dfps
info["damage.fps"] = self.get_damage_fps()
if self.pixel_format:
info["pixel-format"] = self.pixel_format
return info

def get_damage_fps(self):
now = monotonic_time()
cutoff = now-5
lde = tuple(x[0] for x in tuple(self.statistics.last_damage_events) if x[0]>=cutoff)
fps = 0
if len(lde)>=2:
elapsed = now-min(lde)
if elapsed>0:
fps = len(lde) // elapsed
return fps

def get_quality_speed_info(self):
info = {}
def add_list_info(prefix, v, vinfo):
Expand Down
68 changes: 51 additions & 17 deletions src/xpra/server/window/window_video_source.py
Expand Up @@ -79,6 +79,7 @@
SCROLL_ENCODING = envbool("XPRA_SCROLL_ENCODING", True)
SCROLL_MIN_PERCENT = max(1, min(100, envint("XPRA_SCROLL_MIN_PERCENT", 30)))

SAVE_VIDEO_STREAMS = envbool("XPRA_SAVE_VIDEO_STREAMS", False)
SAVE_VIDEO_FRAMES = os.environ.get("XPRA_SAVE_VIDEO_FRAMES")
if SAVE_VIDEO_FRAMES not in ("png", "jpeg", None):
log.warn("Warning: invalid value for 'XPRA_SAVE_VIDEO_FRAMES'")
Expand Down Expand Up @@ -106,6 +107,7 @@ def __init__(self, *args):
self.supports_video_b_frames = self.encoding_options.strlistget("video_b_frames", [])
self.video_max_size = self.encoding_options.intlistget("video_max_size", (8192, 8192), 2, 2)
self.video_subregion = VideoSubregion(self.timeout_add, self.source_remove, self.refresh_subregion, self.auto_refresh_delay)
self.video_stream_file = None

def init_encoders(self):
WindowSource.init_encoders(self)
Expand Down Expand Up @@ -321,6 +323,17 @@ def ve_clean(self, ve):
if self.supports_eos and self._video_encoder==ve:
log("sending eos for wid %i", self.wid)
self.queue_packet(("eos", self.wid))
if SAVE_VIDEO_STREAMS:
self.close_video_stream_file()

def close_video_stream_file(self):
vsf = self.video_stream_file
if vsf:
self.video_stream_file = None
try:
vsf.close()
except:
log.error("Error closing video stream file", exc_info=True)

def ui_cleanup(self):
WindowSource.ui_cleanup(self)
Expand Down Expand Up @@ -1212,7 +1225,8 @@ def add_scores(info, csc_spec, enc_in_format):
client_score_delta = self.encoding_options.get("%s.score-delta" % encoding, 0)
score_data = get_pipeline_score(enc_in_format, csc_spec, encoder_spec, width, height, scaling,
target_q, min_q, target_s, min_s,
self._csc_encoder, self._video_encoder, client_score_delta)
self._csc_encoder, self._video_encoder,
client_score_delta)
if score_data:
scores.append(score_data)
if not FORCE_CSC or src_format==FORCE_CSC_MODE:
Expand Down Expand Up @@ -1247,6 +1261,28 @@ def csc_equiv(self, csc_mode):
"BGRX" : "YUV444P"}.get(csc_mode, csc_mode)


def get_video_fps(self, width, height):
mvsub = self.matches_video_subregion(width, height)
vs = self.video_subregion
if vs and mvsub:
#matches the video subregion,
#for which we have the fps already:
return self.video_subregion.fps
return self.do_get_video_fps(width, height)

def do_get_video_fps(self, width, height):
now = monotonic_time()
#calculate full frames per second (measured in pixels vs window size):
stime = now-5 #only look at the last 5 seconds max
lde = tuple((t,w,h) for t,_,_,w,h in tuple(self.statistics.last_damage_events) if t>stime)
if len(lde)>=10:
#the first event's first element is the oldest event time:
otime = lde[0][0]
if now>otime:
pixels = sum(w*h for _,w,h in lde)
return int(float(pixels)/(width*height)/(now - otime))
return 0

def calculate_scaling(self, width, height, max_w=4096, max_h=4096):
if width==0 or height==0:
return (1, 1)
Expand Down Expand Up @@ -1296,22 +1332,8 @@ def get_min_required_scaling(default_value=(1, 1)):
else:
#use heuristics to choose the best scaling ratio:
mvsub = self.matches_video_subregion(width, height)
vs = self.video_subregion
video = (bool(mvsub) and self.subregion_is_video()) or self.content_type=="video"
def getfps():
if vs and mvsub:
return self.video_subregion.fps
#calculate full frames per second (measured in pixels vs window size):
stime = now-5 #only look at the last 5 seconds max
lde = [x for x in tuple(self.statistics.last_damage_events) if x[0]>stime]
if len(lde)>=10:
#the first event's first element is the oldest event time:
otime = lde[0][0]
if now>otime:
pixels = sum(w*h for _,_,_,w,h in lde)
return int(pixels/(width*height)/(now - otime))
return 0
ffps = getfps()
ffps = self.get_video_fps(width, height)

if self.scaling_control is None:
#None==auto mode, derive from quality and speed only:
Expand Down Expand Up @@ -1601,7 +1623,7 @@ def setup_pipeline_option(self, width, height, src_format,
videolog("setup_pipeline: csc=%s, video encoder=%s, info: %s, setup took %.2fms",
csce, ve, ve.get_info(), (enc_end-enc_start)*1000.0)
scalinglog("setup_pipeline: scaling=%s, encoder_scaling=%s", scaling, encoder_scaling)
return True
return True

def get_video_encoder_options(self, encoding, width, height):
#tweaks for "real" video:
Expand Down Expand Up @@ -1956,6 +1978,15 @@ def do_video_encode(self, encoding, image, options):
#as it must have received a non-video frame already
client_options["paint"] = False

if frame==0 and SAVE_VIDEO_STREAMS:
self.close_video_stream_file()
stream_filename = "window-%i-%.1f.%s" % (self.wid, monotonic_time()-self.start_time, ve.get_encoding())
self.video_stream_file = open(stream_filename, "wb")
log.info("saving new %s stream for window %i to %s", ve.get_encoding(), self.wid, stream_filename)
if self.video_stream_file:
self.video_stream_file.write(data)
self.video_stream_file.flush()

#tell the client which colour subsampling we used:
#(note: see csc_equiv!)
client_options["csc"] = self.csc_equiv(csc)
Expand Down Expand Up @@ -2045,6 +2076,9 @@ def do_flush_video_encoder(self):
if not data:
videolog("do_flush_video_encoder: %s no data: %s", flush_data, v)
return
if self.video_stream_file:
self.video_stream_file.write(data)
self.video_stream_file.flush()
client_options["csc"] = self.csc_equiv(csc)
if frame<self.start_video_frame:
client_options["paint"] = False
Expand Down

0 comments on commit 06e91ff

Please sign in to comment.