xpra icon
Bug tracker and wiki

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


Ticket #849: sound-autotune-queue.patch

File sound-autotune-queue.patch, 7.7 KB (added by Antoine Martin, 6 years ago)

experimenting with auto-tuning the queue to minimize overruns and underruns whilst keeping the overall delay low

  • xpra/sound/sink.py

     
    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
    7 import sys, os, time
     7import sys, os
    88
    99from xpra.sound.sound_pipeline import SoundPipeline, gobject, one_arg_signal
    1010from xpra.sound.pulseaudio_util import has_pa
     
    4646    DEFAULT_SINK = SINKS[0]
    4747QUEUE_SILENT = 0
    4848QUEUE_TIME = get_queue_time(450)
    49 QUEUE_MIN_TIME = get_queue_time(QUEUE_TIME//10//MS_TO_NS, "MIN")
    50 assert QUEUE_MIN_TIME<=QUEUE_TIME
    5149
    52 VARIABLE_MIN_QUEUE = os.environ.get("XPRA_VARIABLE_MIN_QUEUE", "1")=="1"
    53 
    54 
    5550GST_FORMAT_BUFFERS = 4
    5651
    5752def sink_has_device_attribute(sink):
     
    9489        pipeline_els.append("volume name=volume volume=%s" % volume)
    9590        queue_el = ["queue",
    9691                    "name=queue",
    97                     "min-threshold-time=%s" % QUEUE_MIN_TIME,
     92                    "min-threshold-time=0",
    9893                    "max-size-buffers=0",
    9994                    "max-size-bytes=0",
    10095                    "max-size-time=%s" % QUEUE_TIME,
     
    131126    def queue_pushing(self, *args):
    132127        ltime = self.queue.get_property("current-level-time")/MS_TO_NS
    133128        log("queue pushing: level=%i", ltime)
     129        self.check_levels()
    134130        self.queue_state = "pushing"
    135         self.emit_info()
     131        #self.emit_info()
     132        return 0
    136133
    137134    def queue_running(self, *args):
    138135        ltime = self.queue.get_property("current-level-time")/MS_TO_NS
    139136        log("queue running: level=%s", ltime)
    140         if self.queue_state=="underrun" and VARIABLE_MIN_QUEUE:
     137        self.check_levels()
     138        self.queue_state = "running"
     139        return 0
     140
     141    def check_levels(self):
     142        if True:
     143            return
     144        if self.queue_state=="underrun":
    141145            #lift min time restrictions:
    142146            self.queue.set_property("min-threshold-time", 0)
    143147        elif self.queue_state == "overrun":
    144148            clt = self.queue.get_property("current-level-time")
    145149            qpct = min(QUEUE_TIME, clt)*100//QUEUE_TIME
    146             log("resetting max-size-time back to %ims (level=%ims, %i%%)", QUEUE_TIME//MS_TO_NS, clt//MS_TO_NS, qpct)
     150            log.info("resetting max-size-time back to %ims (level=%ims, %i%%)", QUEUE_TIME//MS_TO_NS, clt//MS_TO_NS, qpct)
    147151            self.queue.set_property("max-size-time", QUEUE_TIME)
    148152        self.queue_state = "running"
    149153        self.emit_info()
    150154
     155
    151156    def queue_underrun(self, *args):
     157        if self.queue_state=="underrun" or self.queue_state=="starting":
     158            return
    152159        ltime = self.queue.get_property("current-level-time")/MS_TO_NS
    153         log("queue underrun: level=%i", ltime)
    154         if self.queue_state!="underrun" and VARIABLE_MIN_QUEUE:
    155             #lift min time restrictions:
    156             self.queue.set_property("min-threshold-time", QUEUE_MIN_TIME)
     160        log.info("queue underrun: level=%i, previous state=%s", ltime, self.queue_state)
    157161        self.queue_state = "underrun"
    158         self.emit_info()
     162        MIN_QUEUE = QUEUE_TIME//2
     163        mts = self.queue.get_property("min-threshold-time")
     164        if mts==MIN_QUEUE:
     165            return 0
     166        #set min time restrictions to fill up queue:
     167        log.info("increasing the min-threshold-time to %ims", MIN_QUEUE//MS_TO_NS)
     168        self.queue.set_property("min-threshold-time", MIN_QUEUE)
     169        def restore():
     170            if self.queue.get_property("min-threshold-time")==0:
     171                log.info("min-threshold-time already restored!")
     172                return False
     173            ltime = self.queue.get_property("current-level-time")//MS_TO_NS
     174            if ltime==0:
     175                log.info("not restored! (still underrun: %ims)", ltime)
     176                return True
     177            log.info("resetting the min-threshold-time back to %ims", 0)
     178            self.queue.set_property("min-threshold-time", 0)
     179            self.queue_state = "running"
     180            return False
     181        import glib
     182        glib.timeout_add(1000, restore)
     183        #self.emit_info()
     184        return 0
    159185
    160186    def queue_overrun(self, *args):
     187        if self.queue_state=="overrun":
     188            return
    161189        ltime = self.queue.get_property("current-level-time")//MS_TO_NS
    162         pqs = self.queue_state
     190        log("queue overrun: level=%i, previous state=%s", ltime, self.queue_state)
    163191        self.queue_state = "overrun"
    164         #no overruns for the first 2 seconds:
    165         if ltime<QUEUE_TIME//MS_TO_NS//2*75//100:
    166             elapsed = time.time()-self.start_time
    167             log("queue overrun ignored: level=%i, elapsed time=%.1f", ltime, elapsed)
    168             return
    169         log("queue overrun: level=%i, previous state=%s", ltime//MS_TO_NS, pqs)
    170         if pqs!="overrun":
    171             log("halving the max-size-time to %ims", QUEUE_TIME//2//MS_TO_NS)
    172             self.queue.set_property("max-size-time", QUEUE_TIME//2)
    173             self.overruns += 1
    174             self.emit("overrun", ltime)
    175             self.emit_info()
     192        REDUCED_QT = QUEUE_TIME//2
     193        #empty the queue by reducing its max size:
     194        log.info("queue overrun: halving the max-size-time to %ims", REDUCED_QT//MS_TO_NS)
     195        self.queue.set_property("max-size-time", REDUCED_QT)
     196        self.overruns += 1
     197        self.emit("overrun", ltime)
     198        def restore():
     199            if self.queue.get_property("max-size-time")==QUEUE_TIME:
     200                log.info("max-size-time already restored!")
     201                return False
     202            ltime = self.queue.get_property("current-level-time")//MS_TO_NS
     203            if ltime>=REDUCED_QT:
     204                log.info("max-size-time not restored! (still overrun: %ims)", ltime)
     205                return True
     206            log.info("raising the max-size-time back to %ims", QUEUE_TIME//MS_TO_NS)
     207            self.queue.set_property("max-size-time", QUEUE_TIME)
     208            self.queue_state = "running"
     209            return False
     210        import glib
     211        glib.timeout_add(1000, restore)
     212        #self.emit_info()
     213        return 0
    176214
    177215    def eos(self):
    178216        log("eos()")
     
    179217        if self.src:
    180218            self.src.emit('end-of-stream')
    181219        self.cleanup()
     220        return 0
    182221
    183222    def get_info(self):
    184223        info = SoundPipeline.get_info(self)
     
    186225            clt = self.queue.get_property("current-level-time")
    187226            updict(info, "queue", {
    188227                "time"          : QUEUE_TIME//MS_TO_NS,
    189                 "min_time"      : QUEUE_MIN_TIME//MS_TO_NS,
    190228                "used_pct"      : min(QUEUE_TIME, clt)*100//QUEUE_TIME,
    191229                "used"          : clt//MS_TO_NS,
    192230                "overruns"      : self.overruns,
     
    197235        if not self.src:
    198236            log("add_data(..) dropped")
    199237            return
     238        if self.queue_state=="overrun":
     239            log.info("add_data(..) queue in overrun, data dropped")
     240            return
    200241        #having a timestamp causes problems with the queue and overruns:
    201242        if "timestamp" in metadata:
    202243            del metadata["timestamp"]
  • xpra/sound/src.py

     
    1616from xpra.log import Logger
    1717log = Logger("sound")
    1818
    19 APPSINK = os.environ.get("XPRA_SOURCE_APPSINK", "appsink name=sink emit-signals=true max-buffers=10 drop=true sync=false async=false qos=false")
     19APPSINK = os.environ.get("XPRA_SOURCE_APPSINK", "appsink name=sink emit-signals=true max-buffers=10 drop=true sync=true async=false qos=false")
    2020
    2121
    2222class SoundSource(SoundPipeline):