xpra icon
Bug tracker and wiki

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


Ticket #1178: centos-sound-full.patch

File centos-sound-full.patch, 16.1 KB (added by Antoine Martin, 5 years ago)

patch to apply the change in full to the v0.17.x branch

  • src/xpra/sound/sink.py

     
    11#!/usr/bin/env python
    22# This file is part of Xpra.
    3 # Copyright (C) 2010-2015 Antoine Martin <antoine@devloop.org.uk>
     3# Copyright (C) 2010-2016 Antoine Martin <antoine@devloop.org.uk>
    44# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
    55# later version. See the file COPYING for details.
    66
     
    1212from xpra.gtk_common.gobject_util import one_arg_signal, gobject
    1313from xpra.sound.gstreamer_util import plugin_str, get_decoder_parser, get_queue_time, normv, get_codecs, get_default_sink, get_sink_plugins, \
    1414                                        MP3, CODEC_ORDER, gst, QUEUE_LEAK, GST_QUEUE_NO_LEAK, MS_TO_NS, DEFAULT_SINK_PLUGIN_OPTIONS
     15from xpra.gtk_common.gobject_compat import import_glib
    1516
    1617from xpra.scripts.config import InitExit
    1718from xpra.util import csv
     
    2021log = Logger("sound")
    2122gstlog = Logger("gstreamer")
    2223
     24glib = import_glib()
    2325
     26
    2427SINKS = get_sink_plugins()
    2528DEFAULT_SINK = get_default_sink()
    2629
     
    3740                               },
    3841                          }
    3942
    40 QUEUE_SILENT = 0
     43QUEUE_SILENT = os.environ.get("XPRA_QUEUE_SILENT", "0")=="1"
    4144QUEUE_TIME = get_queue_time(450)
    4245
     46UNMUTE_DELAY = int(os.environ.get("XPRA_UNMUTE_DELAY", "1000"))
    4347GRACE_PERIOD = int(os.environ.get("XPRA_SOUND_GRACE_PERIOD", "2000"))
    4448#percentage: from 0 for no margin, to 200% which triples the buffer target
    4549MARGIN = max(0, min(200, int(os.environ.get("XPRA_SOUND_MARGIN", "50"))))
     50#how high we push up the min-level to prevent underruns:
     51UNDERRUN_MIN_LEVEL = max(0, int(os.environ.get("XPRA_SOUND_UNDERRUN_MIN_LEVEL", "50")))
    4652
    4753
    4854GST_FORMAT_BYTES = 2
     
    7884        self.volume = None
    7985        self.src    = None
    8086        self.queue  = None
     87        self.normal_volume = volume
     88        self.target_volume = volume
     89        self.volume_timer = 0
    8190        self.overruns = 0
    8291        self.underruns = 0
    8392        self.overrun_events = deque(maxlen=100)
    8493        self.queue_state = "starting"
     94        self.last_data = None
    8595        self.last_underrun = 0
    8696        self.last_overrun = 0
    8797        self.last_max_update = time.time()
     
    101111        pipeline_els.append(decoder_str)
    102112        pipeline_els.append("audioconvert")
    103113        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")
    114115        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]))
    117123        sink_attributes = SINK_SHARED_DEFAULT_ATTRIBUTES.copy()
    118124        from xpra.sound.gstreamer_util import gst_major_version, get_gst_version
    119125        #anything older than this may cause problems (ie: centos 6.x)
     
    133139        self.volume = self.pipeline.get_by_name("volume")
    134140        self.src    = self.pipeline.get_by_name("src")
    135141        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)
    141165
    142166    def __repr__(self):
    143167        return "SoundSink('%s' - %s)" % (self.pipeline_str, self.state)
    144168
    145169    def cleanup(self):
     170        if self.volume_timer!=0:
     171            glib.source_remove(self.volume_timer)
     172            self.volume_timer = 0
    146173        SoundPipeline.cleanup(self)
    147174        self.sink_type = ""
    148175        self.src = None
    149176
     177    def start(self):
     178        SoundPipeline.start(self)
     179        self.timeout_add(UNMUTE_DELAY, self.start_adjust_volume)
    150180
    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):
    152205        self.queue_state = "pushing"
    153206        self.emit_info()
    154207        return True
    155208
    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")
    157220        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"
    158227        self.set_min_level()
    159228        self.set_max_level()
    160229        self.emit_info()
    161230        return True
    162231
    163     def queue_underrun(self, *args):
     232    def queue_underrun0(self, *args):
    164233        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()
    165261        if self.queue_state=="starting" or 1000*(now-self.start_time)<GRACE_PERIOD:
    166262            gstlog("ignoring underrun during startup")
    167263            return 1
     264        gstlog("queue_underrun1")
    168265        self.queue_state = "underrun"
    169266        if now-self.last_underrun>2:
    170267            self.last_underrun = now
     
    182279            return maxl-minl
    183280        return 0
    184281
     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
    185309    def set_min_level(self):
    186310        if not self.level_lock.acquire(False):
    187311            return
     
    192316                #from 100% down to 0% in 2 seconds after underrun:
    193317                now = time.time()
    194318                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)
    197324                if cmtt!=mtt:
    198325                    self.queue.set_property("min-threshold-time", mtt*MS_TO_NS)
    199326                    log("set_min_level min-threshold-time=%s", mtt)
     
    208335            now = time.time()
    209336            log("set_max_level lrange=%3i, last_max_update=%is", lrange, int(now-self.last_max_update))
    210337            #more than one second since last update and we have a range:
    211             if now-self.last_max_update>1 and lrange>0 and self.queue:
     338            if now-self.last_max_update>1 and self.queue:
    212339                cmst = self.queue.get_property("max-size-time")//MS_TO_NS
    213340                #overruns in the last minute:
    214341                olm = len([x for x in list(self.overrun_events) if now-x<60])
     
    229356        finally:
    230357            self.level_lock.release()
    231358
    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 1
    237         clt = self.queue.get_property("current-level-time")//MS_TO_NS
    238         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 = now
    245             self.set_max_level()
    246             self.overrun_events.append(now)
    247         self.overruns += 1
    248         return 1
    249359
    250360    def eos(self):
    251361        gstlog("eos()")
     
    267377                             "pct"          : min(QUEUE_TIME, clt)*100//qmax,
    268378                             "overruns"     : self.overruns,
    269379                             "underruns"    : self.underruns,
    270                              "state"        : self.queue_state
     380                             "state"        : self.queue_state,
    271381                             }
    272382        return info
    273383
    274     def add_data(self, data, metadata=None):
     384    def can_push_buffer(self):
    275385        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():
    277395            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")
    280416            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):
    281433        #having a timestamp causes problems with the queue and overruns:
    282434        log("add_data(%s bytes, %s) queue_state=%s", len(data), metadata, self.queue_state)
    283435        buf = gst.new_buffer(data)
     
    299451                clt = self.queue.get_property("current-level-time")//MS_TO_NS
    300452                log("pushed %5i bytes, new buffer level: %3ims, queue state=%s", len(data), clt, self.queue_state)
    301453                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
    306456
    307457    def push_buffer(self, buf):
    308458        #buf.size = size
     
    326476def main():
    327477    from xpra.platform import program_context
    328478    with program_context("Sound-Record"):
    329         from xpra.gtk_common.gobject_compat import import_glib
    330         glib = import_glib()
    331479        args = sys.argv
    332480        log.enable_debug()
    333481        import os.path
     
    366514        #force no leak since we push all the data at once
    367515        global QUEUE_LEAK, QUEUE_SILENT
    368516        QUEUE_LEAK = GST_QUEUE_NO_LEAK
    369         QUEUE_SILENT = 1
     517        QUEUE_SILENT = True
    370518        ss = SoundSink(codecs=codecs)
    371519        def eos(*args):
    372520            print("eos")