Ticket #1178: centos-sound-full.patch
File centos-sound-full.patch, 16.1 KB (added by , 5 years ago) |
---|
-
src/xpra/sound/sink.py
1 1 #!/usr/bin/env python 2 2 # This file is part of Xpra. 3 # Copyright (C) 2010-201 5Antoine Martin <antoine@devloop.org.uk>3 # Copyright (C) 2010-2016 Antoine Martin <antoine@devloop.org.uk> 4 4 # Xpra is released under the terms of the GNU GPL v2, or, at your option, any 5 5 # later version. See the file COPYING for details. 6 6 … … 12 12 from xpra.gtk_common.gobject_util import one_arg_signal, gobject 13 13 from xpra.sound.gstreamer_util import plugin_str, get_decoder_parser, get_queue_time, normv, get_codecs, get_default_sink, get_sink_plugins, \ 14 14 MP3, CODEC_ORDER, gst, QUEUE_LEAK, GST_QUEUE_NO_LEAK, MS_TO_NS, DEFAULT_SINK_PLUGIN_OPTIONS 15 from xpra.gtk_common.gobject_compat import import_glib 15 16 16 17 from xpra.scripts.config import InitExit 17 18 from xpra.util import csv … … 20 21 log = Logger("sound") 21 22 gstlog = Logger("gstreamer") 22 23 24 glib = import_glib() 23 25 26 24 27 SINKS = get_sink_plugins() 25 28 DEFAULT_SINK = get_default_sink() 26 29 … … 37 40 }, 38 41 } 39 42 40 QUEUE_SILENT = 043 QUEUE_SILENT = os.environ.get("XPRA_QUEUE_SILENT", "0")=="1" 41 44 QUEUE_TIME = get_queue_time(450) 42 45 46 UNMUTE_DELAY = int(os.environ.get("XPRA_UNMUTE_DELAY", "1000")) 43 47 GRACE_PERIOD = int(os.environ.get("XPRA_SOUND_GRACE_PERIOD", "2000")) 44 48 #percentage: from 0 for no margin, to 200% which triples the buffer target 45 49 MARGIN = max(0, min(200, int(os.environ.get("XPRA_SOUND_MARGIN", "50")))) 50 #how high we push up the min-level to prevent underruns: 51 UNDERRUN_MIN_LEVEL = max(0, int(os.environ.get("XPRA_SOUND_UNDERRUN_MIN_LEVEL", "50"))) 46 52 47 53 48 54 GST_FORMAT_BYTES = 2 … … 78 84 self.volume = None 79 85 self.src = None 80 86 self.queue = None 87 self.normal_volume = volume 88 self.target_volume = volume 89 self.volume_timer = 0 81 90 self.overruns = 0 82 91 self.underruns = 0 83 92 self.overrun_events = deque(maxlen=100) 84 93 self.queue_state = "starting" 94 self.last_data = None 85 95 self.last_underrun = 0 86 96 self.last_overrun = 0 87 97 self.last_max_update = time.time() … … 101 111 pipeline_els.append(decoder_str) 102 112 pipeline_els.append("audioconvert") 103 113 pipeline_els.append("audioresample") 104 pipeline_els.append("volume name=volume volume=%s" % volume) 105 queue_el = ["queue", 106 "name=queue", 107 "min-threshold-time=0", 108 "max-size-buffers=0", 109 "max-size-bytes=0", 110 "max-size-time=%s" % QUEUE_TIME, 111 "leaky=%s" % QUEUE_LEAK] 112 if QUEUE_SILENT: 113 queue_el.append("silent=%s" % QUEUE_SILENT) 114 pipeline_els.append("volume name=volume volume=0") 114 115 if QUEUE_TIME>0: 115 #pipeline_els.append("audiorate") 116 pipeline_els.append(" ".join(queue_el)) 116 pipeline_els.append(" ".join(["queue", 117 "name=queue", 118 "min-threshold-time=0", 119 "max-size-buffers=0", 120 "max-size-bytes=0", 121 "max-size-time=%s" % QUEUE_TIME, 122 "leaky=%s" % QUEUE_LEAK])) 117 123 sink_attributes = SINK_SHARED_DEFAULT_ATTRIBUTES.copy() 118 124 from xpra.sound.gstreamer_util import gst_major_version, get_gst_version 119 125 #anything older than this may cause problems (ie: centos 6.x) … … 133 139 self.volume = self.pipeline.get_by_name("volume") 134 140 self.src = self.pipeline.get_by_name("src") 135 141 self.queue = self.pipeline.get_by_name("queue") 136 if QUEUE_SILENT==0 and self.queue: 137 self.queue.connect("overrun", self.queue_overrun) 138 self.queue.connect("underrun", self.queue_underrun) 139 self.queue.connect("running", self.queue_running) 140 self.queue.connect("pushing", self.queue_pushing) 142 if get_gst_version()<(1, ): 143 self.add_data = self.add_data0 144 else: 145 self.add_data = self.add_data1 146 if self.queue: 147 if not QUEUE_SILENT: 148 if get_gst_version()<(1, ): 149 self.queue.connect("overrun", self.queue_overrun0) 150 self.queue.connect("underrun", self.queue_underrun0) 151 self.queue.connect("running", self.queue_running0) 152 self.queue.connect("pushing", self.queue_pushing0) 153 else: 154 self.queue.connect("overrun", self.queue_overrun1) 155 self.queue.connect("underrun", self.queue_underrun1) 156 self.queue.connect("running", self.queue_running1) 157 self.queue.connect("pushing", self.queue_pushing1) 158 else: 159 #older versions may not have the "silent" attribute, 160 #in which case we will emit the signals for nothing 161 try: 162 self.queue.set_property("silent", False) 163 except Exception as e: 164 log("cannot silence the queue %s: %s", self.queue, e) 141 165 142 166 def __repr__(self): 143 167 return "SoundSink('%s' - %s)" % (self.pipeline_str, self.state) 144 168 145 169 def cleanup(self): 170 if self.volume_timer!=0: 171 glib.source_remove(self.volume_timer) 172 self.volume_timer = 0 146 173 SoundPipeline.cleanup(self) 147 174 self.sink_type = "" 148 175 self.src = None 149 176 177 def start(self): 178 SoundPipeline.start(self) 179 self.timeout_add(UNMUTE_DELAY, self.start_adjust_volume) 150 180 151 def queue_pushing(self, *args): 181 def start_adjust_volume(self, interval=100): 182 if self.volume_timer!=0: 183 glib.source_remove(self.volume_timer) 184 self.volume_timer = self.timeout_add(interval, self.adjust_volume) 185 return False 186 187 188 def adjust_volume(self): 189 if not self.volume: 190 self.volume_timer = 0 191 return False 192 cv = self.volume.get_property("volume") 193 delta = self.target_volume-cv 194 from math import sqrt, copysign 195 change = copysign(sqrt(abs(delta)), delta)/15.0 196 gstlog("adjust_volume current volume=%.2f, change=%.2f", cv, change) 197 self.volume.set_property("volume", max(0, cv+change)) 198 if abs(delta)<0.01: 199 self.volume_timer = 0 200 return False 201 return True 202 203 204 def _queue_pushing(self, *args): 152 205 self.queue_state = "pushing" 153 206 self.emit_info() 154 207 return True 155 208 156 def queue_running(self, *args): 209 def queue_pushing0(self, *args): 210 gstlog("queue_pushing0") 211 return self._queue_pushing() 212 213 def queue_pushing1(self, *args): 214 gstlog("queue_pushing1") 215 return self._queue_pushing() 216 217 218 def queue_running0(self, *args): 219 gstlog("queue_running") 157 220 self.queue_state = "running" 221 self.emit_info() 222 return True 223 224 def queue_running1(self, *args): 225 gstlog("queue_running") 226 self.queue_state = "running" 158 227 self.set_min_level() 159 228 self.set_max_level() 160 229 self.emit_info() 161 230 return True 162 231 163 def queue_underrun (self, *args):232 def queue_underrun0(self, *args): 164 233 now = time.time() 234 gstlog("queue_underrun0") 235 self.queue_state = "underrun" 236 self.last_underrun = now 237 clt = self.queue.get_property("current-level-time")//MS_TO_NS 238 mintt = self.queue.get_property("min-threshold-time")//MS_TO_NS 239 gstlog("underrun: clt=%s mintt=%s state=%s", clt, mintt, self.state) 240 if clt==0 and mintt==0 and self.state in ("running", "active"): 241 if self.last_data: 242 self.add_data(self.last_data) 243 #this is going to cause scratchy sound, 244 #temporarily lower the volume: 245 def fadeout(): 246 gstlog("fadeout") 247 self.target_volume = 0.0 248 self.start_adjust_volume(1) 249 def fadein(): 250 gstlog("fadein") 251 self.target_volume = self.normal_volume 252 self.start_adjust_volume(10) 253 fadeout() 254 glib.timeout_add(300, fadein) 255 return 1 256 self.emit_info() 257 return 1 258 259 def queue_underrun1(self, *args): 260 now = time.time() 165 261 if self.queue_state=="starting" or 1000*(now-self.start_time)<GRACE_PERIOD: 166 262 gstlog("ignoring underrun during startup") 167 263 return 1 264 gstlog("queue_underrun1") 168 265 self.queue_state = "underrun" 169 266 if now-self.last_underrun>2: 170 267 self.last_underrun = now … … 182 279 return maxl-minl 183 280 return 0 184 281 282 def queue_overrun0(self, *args): 283 clt = self.queue.get_property("current-level-time")//MS_TO_NS 284 log("queue_overrun0 level=%ims", clt) 285 now = time.time() 286 self.last_overrun = now 287 self.overrun_events.append(now) 288 self.overruns += 1 289 return 1 290 291 def queue_overrun1(self, *args): 292 now = time.time() 293 if self.queue_state=="starting" or 1000*(now-self.start_time)<GRACE_PERIOD: 294 gstlog("ignoring overrun during startup") 295 return 1 296 clt = self.queue.get_property("current-level-time")//MS_TO_NS 297 log("overrun level=%ims", clt) 298 now = time.time() 299 #grace period of recording overruns: 300 #(because when we record an overrun, we lower the max-time, 301 # which causes more overruns!) 302 if self.last_overrun is None or now-self.last_overrun>2: 303 self.last_overrun = now 304 self.set_max_level() 305 self.overrun_events.append(now) 306 self.overruns += 1 307 return 1 308 185 309 def set_min_level(self): 186 310 if not self.level_lock.acquire(False): 187 311 return … … 192 316 #from 100% down to 0% in 2 seconds after underrun: 193 317 now = time.time() 194 318 pct = max(0, int((self.last_underrun+2-now)*50)) 195 mtt = min(50, pct*max(50, lrange)//200) 196 log("set_min_level pct=%2i, cmtt=%3i, mtt=%3i", pct, cmtt, mtt) 319 #cannot go higher than mst-50: 320 mst = self.queue.get_property("max-size-time") 321 mrange = max(lrange+100, 150) 322 mtt = min(mst-50, pct*max(UNDERRUN_MIN_LEVEL, mrange)//200) 323 log("set_min_level pct=%2i, cmtt=%3i, mtt=%3i, lrange=%s (UNDERRUN_MIN_LEVEL=%s)", pct, cmtt, mtt, lrange, UNDERRUN_MIN_LEVEL) 197 324 if cmtt!=mtt: 198 325 self.queue.set_property("min-threshold-time", mtt*MS_TO_NS) 199 326 log("set_min_level min-threshold-time=%s", mtt) … … 208 335 now = time.time() 209 336 log("set_max_level lrange=%3i, last_max_update=%is", lrange, int(now-self.last_max_update)) 210 337 #more than one second since last update and we have a range: 211 if now-self.last_max_update>1 and lrange>0 andself.queue:338 if now-self.last_max_update>1 and self.queue: 212 339 cmst = self.queue.get_property("max-size-time")//MS_TO_NS 213 340 #overruns in the last minute: 214 341 olm = len([x for x in list(self.overrun_events) if now-x<60]) … … 229 356 finally: 230 357 self.level_lock.release() 231 358 232 def queue_overrun(self, *args):233 now = time.time()234 if self.queue_state=="starting" or 1000*(now-self.start_time)<GRACE_PERIOD:235 gstlog("ignoring overrun during startup")236 return 1237 clt = self.queue.get_property("current-level-time")//MS_TO_NS238 log("overrun level=%ims", clt)239 now = time.time()240 #grace period of recording overruns:241 #(because when we record an overrun, we lower the max-time,242 # which causes more overruns!)243 if self.last_overrun is None or now-self.last_overrun>2:244 self.last_overrun = now245 self.set_max_level()246 self.overrun_events.append(now)247 self.overruns += 1248 return 1249 359 250 360 def eos(self): 251 361 gstlog("eos()") … … 267 377 "pct" : min(QUEUE_TIME, clt)*100//qmax, 268 378 "overruns" : self.overruns, 269 379 "underruns" : self.underruns, 270 "state" : self.queue_state 380 "state" : self.queue_state, 271 381 } 272 382 return info 273 383 274 def add_data(self, data, metadata=None):384 def can_push_buffer(self): 275 385 if not self.src: 276 log("add_data(..) dropped, no source") 386 log("no source, dropping buffer") 387 return False 388 if self.state in ("stopped", "error"): 389 log("pipeline is %s, dropping buffer", self.state) 390 return False 391 return True 392 393 def add_data0(self, data, metadata=None): 394 if not self.can_push_buffer(): 277 395 return 278 if self.state=="stopped": 279 log("add_data(..) dropped, pipeline is stopped") 396 self.last_data = data 397 now = time.time() 398 clt = self.queue.get_property("current-level-time")//MS_TO_NS 399 delta = QUEUE_TIME//MS_TO_NS-clt 400 gstlog("add_data current-level-time=%s, QUEUE_TIME=%s, delta=%s", clt, QUEUE_TIME//MS_TO_NS, delta) 401 def fade(): 402 #this is going to cause scratchy sound, 403 #temporarily lower the volume: 404 def fadeout(): 405 gstlog("fadeout") 406 self.target_volume = 0.0 407 self.start_adjust_volume(10) 408 def fadein(): 409 gstlog("fadein") 410 self.target_volume = self.normal_volume 411 self.start_adjust_volume(10) 412 glib.timeout_add(max(0, clt-100), fadeout) 413 glib.timeout_add(clt+300, fadein) 414 if now-self.last_overrun<QUEUE_TIME//MS_TO_NS//2//1000: 415 gstlog("dropping sample to try to stop overrun") 280 416 return 417 if delta<50: 418 gstlog("dropping sample to try to avoid overrun") 419 return 420 self.do_add_data(data, metadata) 421 self.emit_info() 422 423 def add_data1(self, data, metadata=None): 424 if not self.can_push_buffer(): 425 return 426 self.do_add_data(data, metadata) 427 if self.queue_state=="pushing": 428 self.set_min_level() 429 self.set_max_level() 430 self.emit_info() 431 432 def do_add_data(self, data, metadata=None): 281 433 #having a timestamp causes problems with the queue and overruns: 282 434 log("add_data(%s bytes, %s) queue_state=%s", len(data), metadata, self.queue_state) 283 435 buf = gst.new_buffer(data) … … 299 451 clt = self.queue.get_property("current-level-time")//MS_TO_NS 300 452 log("pushed %5i bytes, new buffer level: %3ims, queue state=%s", len(data), clt, self.queue_state) 301 453 self.levels.append((time.time(), clt)) 302 if self.queue_state=="pushing": 303 self.set_min_level() 304 self.set_max_level() 305 self.emit_info() 454 return True 455 return False 306 456 307 457 def push_buffer(self, buf): 308 458 #buf.size = size … … 326 476 def main(): 327 477 from xpra.platform import program_context 328 478 with program_context("Sound-Record"): 329 from xpra.gtk_common.gobject_compat import import_glib330 glib = import_glib()331 479 args = sys.argv 332 480 log.enable_debug() 333 481 import os.path … … 366 514 #force no leak since we push all the data at once 367 515 global QUEUE_LEAK, QUEUE_SILENT 368 516 QUEUE_LEAK = GST_QUEUE_NO_LEAK 369 QUEUE_SILENT = 1517 QUEUE_SILENT = True 370 518 ss = SoundSink(codecs=codecs) 371 519 def eos(*args): 372 520 print("eos")