xpra icon
Bug tracker and wiki

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


Ticket #362: sound-underrun.patch

File sound-underrun.patch, 13.6 KB (added by Antoine Martin, 8 years ago)

ugly patch which fixes win32 and maybe even osx, but breaks linux.. ffs

  • xpra/client/ui_client_base.py

     
    837837    def start_receiving_sound(self):
    838838        """ ask the server to start sending sound and emit the client signal """
    839839        soundlog("start_receiving_sound() sound sink=%s", self.sound_sink)
    840         if self.sound_sink is not None and self.sound_sink.get_state()=="active":
    841             soundlog("start_receiving_sound: we are already receiving sound!")
     840        if self.sound_sink is not None:
     841            soundlog("start_receiving_sound: we already have a sound sink")
     842            return
    842843        elif not self.server_sound_send:
    843844            log.error("cannot start receiving sound: support not enabled on the server")
    844         else:
    845             #choose a codec:
    846             from xpra.sound.gstreamer_util import CODEC_ORDER
    847             matching_codecs = [x for x in self.server_sound_encoders if x in self.speaker_codecs]
    848             ordered_codecs = [x for x in CODEC_ORDER if x in matching_codecs]
    849             if len(ordered_codecs)==0:
    850                 log.error("no matching codecs between server (%s) and client (%s)", self.server_sound_encoders, self.speaker_codecs)
    851                 return
    852             codec = ordered_codecs[0]
    853             self.speaker_enabled = True
    854             self.emit("speaker-changed")
    855             def sink_ready(*args):
    856                 soundlog("sink_ready(%s) codec=%s", args, codec)
    857                 self.send("sound-control", "start", codec)
    858                 return False
    859             self.on_sink_ready = sink_ready
    860             self.start_sound_sink(codec)
     845            return
     846        #choose a codec:
     847        from xpra.sound.gstreamer_util import CODEC_ORDER
     848        matching_codecs = [x for x in self.server_sound_encoders if x in self.speaker_codecs]
     849        ordered_codecs = [x for x in CODEC_ORDER if x in matching_codecs]
     850        if len(ordered_codecs)==0:
     851            log.error("no matching codecs between server (%s) and client (%s)", self.server_sound_encoders, self.speaker_codecs)
     852            return
     853        codec = ordered_codecs[0]
     854        self.speaker_enabled = True
     855        self.emit("speaker-changed")
     856        def sink_ready(*args):
     857            soundlog("sink_ready(%s) codec=%s", args, codec)
     858            self.send("sound-control", "start", codec)
     859            return False
     860        self.on_sink_ready = sink_ready
     861        self.start_sound_sink(codec)
    861862
    862863    def stop_receiving_sound(self):
    863864        """ ask the server to stop sending sound, toggle flag so we ignore further packets and emit client signal """
     
    920921            soundlog("restart() sound_sink=%s, codec=%s, server_sound_sequence=%s", self.sound_sink, codec, self.server_sound_sequence)
    921922            if self.server_sound_sequence:
    922923                self.send_new_sound_sequence()
     924            self.start_receiving_sound()
    923925            self.sink_restart_pending = False
    924             self.start_receiving_sound()
    925926            return False
    926927        self.timeout_add(200, restart)
    927928
     
    962963            self.sound_sink.stop()
    963964            return
    964965        if self.sound_sink is None:
    965             soundlog("sound data received, creating a sound sink for it")
    966             if not self.start_sound_sink(codec):
    967                 return
     966            soundlog("no sound sink to process sound data, dropping it")
     967            return
    968968        elif self.sound_sink.get_state()=="stopped":
    969969            soundlog("sound data received, sound sink is stopped - starting it")
    970970            self.sound_sink.start()
  • xpra/sound/sink.py

     
    2929GST_QUEUE_NO_LEAK             = 0
    3030GST_QUEUE_LEAK_UPSTREAM       = 1
    3131GST_QUEUE_LEAK_DOWNSTREAM     = 2
     32GST_QUEUE_LEAK_DEFAULT = GST_QUEUE_LEAK_DOWNSTREAM
    3233
    3334MS_TO_NS = 1000000
     35QUEUE_LEAK = int(os.environ.get("XPRA_SOUND_QUEUE_LEAK", GST_QUEUE_LEAK_DEFAULT))
     36if QUEUE_LEAK not in (GST_QUEUE_NO_LEAK, GST_QUEUE_LEAK_UPSTREAM, GST_QUEUE_LEAK_DOWNSTREAM):
     37    log.error("invalid leak option %s", QUEUE_LEAK)
     38    QUEUE_LEAK = GST_QUEUE_LEAK_DEFAULT
    3439QUEUE_TIME = int(os.environ.get("XPRA_SOUND_QUEUE_TIME", "450"))*MS_TO_NS
    35 QUEUE_MIN_TIME = int(os.environ.get("XPRA_SOUND_QUEUE_MIN_TIME", "50"))*MS_TO_NS
     40QUEUE_MIN_TIME = int(os.environ.get("XPRA_SOUND_QUEUE_MIN_TIME", "100"))*MS_TO_NS
    3641QUEUE_TIME = max(0, QUEUE_TIME)
    3742QUEUE_MIN_TIME = max(0, min(QUEUE_TIME, QUEUE_MIN_TIME))
    3843DEFAULT_SINK = os.environ.get("XPRA_SOUND_SINK", DEFAULT_SINK)
     
    4045    log.error("invalid default sound sink: '%s' is not in %s, using %s instead", DEFAULT_SINK, SINKS, SINKS[0])
    4146    DEFAULT_SINK = SINKS[0]
    4247
    43 VOLUME = False
    4448
    45 
    4649def sink_has_device_attribute(sink):
    4750    return sink not in ("autoaudiosink", "jackaudiosink", "directsoundsink")
    4851
     
    6366        self.sink_type = sink_type
    6467        decoder_str = plugin_str(decoder, decoder_options)
    6568        pipeline_els = []
    66         pipeline_els.append("appsrc name=src max-bytes=512")
     69        pipeline_els.append("appsrc name=src max-bytes=1048576 emit-signals=0")
    6770        pipeline_els.append(parser)
    6871        pipeline_els.append(decoder_str)
    69         if VOLUME:
    70             pipeline_els.append("volume name=volume")
    7172        pipeline_els.append("audioconvert")
    7273        pipeline_els.append("audioresample")
    73         if QUEUE_TIME>0:
    74             pipeline_els.append("queue" +
    75                                 " name=queue"+
    76                                 " min-threshold-time=%s" % QUEUE_MIN_TIME+
    77                                 " max-size-time=%s" % QUEUE_TIME+
    78                                 " leaky=%s" % GST_QUEUE_LEAK_DOWNSTREAM)
    79         else:
    80             pipeline_els.append("queue leaky=%s" % GST_QUEUE_LEAK_DOWNSTREAM)
     74        pipeline_els.append("queue" +
     75                            " name=buffer"+
     76                            " min-threshold-time=%s" % QUEUE_MIN_TIME+
     77                            " max-size-buffers=0"+
     78                            " max-size-bytes=0"+
     79                            " max-size-time=%s" % int(QUEUE_TIME/2)+
     80                            " leaky=%s" % GST_QUEUE_NO_LEAK+
     81                            " silent=1")
     82        pipeline_els.append("queue" +
     83                            " name=queue"+
     84                            " max-size-buffers=0"+
     85                            " max-size-bytes=0"+
     86                            " max-size-time=%s" % int(QUEUE_TIME/2)+
     87                            " leaky=%s" % QUEUE_LEAK)
    8188        pipeline_els.append(sink_type)
    8289        self.setup_pipeline_and_bus(pipeline_els)
    83         self.volume = self.pipeline.get_by_name("volume")
    8490        self.src = self.pipeline.get_by_name("src")
    85         self.src.set_property('emit-signals', True)
    8691        self.src.set_property('stream-type', 'stream')
    8792        self.src.set_property('block', False)
    8893        self.src.set_property('format', 4)
    8994        self.src.set_property('is-live', True)
    9095        self.queue = self.pipeline.get_by_name("queue")
     96        self.buffer = self.pipeline.get_by_name("buffer")
    9197        self.overruns = 0
     98        self.last_underrun_toggle = 0
    9299        self.queue.connect("overrun", self.queue_overrun)
    93100        self.queue.connect("underrun", self.queue_underrun)
    94         self.src.connect("need-data", self.need_data)
    95         self.src.connect("enough-data", self.on_enough_data)
     101        self.queue.connect("running", self.queue_running)
    96102
     103    def queue_running(self, *args):
     104        ltime = int(self.queue.get_property("current-level-time")/MS_TO_NS)
     105        debug("sound sink queue running: level=%s", ltime)
     106
    97107    def queue_underrun(self, *args):
    98         ltime = int(self.queue.get_property("current-level-time")/1000000)
    99         debug("sound sink queue underrun: level=%s", ltime)
     108        ltime = int(self.queue.get_property("current-level-time")/MS_TO_NS)
     109        if time.time()-self.start_time<2.0 or ltime>0:
     110            return
     111        btime = int(self.buffer.get_property("min-threshold-time")/MS_TO_NS)
     112        debug("sound sink queue underrun: level=%s, buffer min time=%s", ltime, btime)
     113        now = time.time()
     114        if now-self.last_underrun_toggle>2.0:
     115            if btime>0:
     116                ntime = 0
     117            else:
     118                ntime = QUEUE_MIN_TIME
     119            debug("resetting buffer min-threshold-time to %s", ntime/MS_TO_NS)
     120            self.buffer.set_property("min-threshold-time", ntime)
     121            self.last_underrun_toggle = now
    100122        self.emit("underrun", ltime)
    101123
    102124    def queue_overrun(self, *args):
    103         ltime = self.queue.get_property("current-level-time")
    104         mtime = int(ltime/1000000)
    105         debug("sound sink queue overrun: level=%s", mtime)
     125        ltime = int(self.queue.get_property("current-level-time")/MS_TO_NS)
    106126        #no overruns for the first 2 seconds:
    107         if time.time()-self.start_time<2.0 or ltime<QUEUE_TIME:
     127        elapsed = time.time()-self.start_time
     128        if elapsed<2.0 or ltime<(QUEUE_TIME/MS_TO_NS/2*75/100):
     129            debug("sound sink queue overrun ignored: level=%s, elapsed time=%.1f", ltime, elapsed)
    108130            return
     131        debug("sound sink queue overrun: level=%s", ltime)
    109132        self.overruns += 1
    110         self.emit("overrun", mtime)
     133        self.buffer.set_property("min-threshold-time", QUEUE_MIN_TIME)
     134        self.emit("overrun", ltime)
    111135
    112136    def cleanup(self):
    113137        SoundPipeline.cleanup(self)
    114138        self.sink_type = ""
    115         self.volume = None
    116139        self.src = None
    117140
    118     def set_queue_delay(self, ms):
    119         assert self.queue
    120         assert ms>0
    121         self.queue.set_property("max-size-time", ms*1000000)
    122         log("queue delay set to %s, current-level-time=%s", ms, int(self.queue.get_property("current-level-time")/1000/1000))
    123 
    124     def set_mute(self, mute):
    125         self.volume.set_property('mute', mute)
    126 
    127     def is_muted(self):
    128         return bool(self.volume.get_property("mute"))
    129 
    130     def get_volume(self):
    131         assert VOLUME and self.volume
    132         return  self.volume.get_property("volume")
    133 
    134     def set_volume(self, volume):
    135         assert VOLUME and self.volume
    136         assert volume>=0 and volume<=100
    137         self.volume.set_property('volume', float(volume)/10.0)
    138 
    139141    def eos(self):
    140142        debug("eos()")
    141143        if self.src:
     
    148150            clt = self.queue.get_property("current-level-time")
    149151            info["queue.used_pct"] = int(min(QUEUE_TIME, clt)*100.0/QUEUE_TIME)
    150152            info["queue.overruns"] = self.overruns
    151         if VOLUME and self.volume:
    152             info["mute"] = self.volume.get_property("mute")
    153             info["volume"] = int(100.0*self.volume.get_property("volume"))
    154153        return info
    155154
    156155    def add_data(self, data, metadata=None):
    157         debug("sound sink: adding %s bytes to %s", len(data), self.src)
    158         #debug("sound sink: adding %s bytes to %s, metadata: %s, level=%s", len(data), self.src, metadata, int(self.queue.get_property("current-level-time")/1000000))
    159         if self.src:
    160             buf = gst.Buffer(data)
    161             if metadata:
    162                 ts = metadata.get("timestamp")
    163                 if ts is not None:
    164                     buf.timestamp = ts
    165                 d = metadata.get("duration")
    166                 if d is not None:
    167                     buf.duration = d
    168             #buf.size = size
    169             #buf.timestamp = timestamp
    170             #buf.duration = duration
    171             #buf.offset = offset
    172             #buf.offset_end = offset_end
    173             #buf.set_caps(gst.caps_from_string(caps))
    174             r = self.src.emit("push-buffer", buf)
    175             if r!=gst.FLOW_OK:
    176                 log.error("push-buffer error: %s", r)
    177                 self.emit('error', "push-buffer error: %s" % r)
    178             else:
    179                 self.buffer_count += 1
    180                 self.byte_count += len(data)
    181                 debug("sound sink: new level=%s", int(self.queue.get_property("current-level-time")/1000000))
     156        #debug("sound sink: adding %s bytes to %s, metadata: %s, level=%s", len(data), self.src, metadata, int(self.queue.get_property("current-level-time")/MS_TO_NS))
     157        if not self.src:
     158            return
     159        buf = gst.Buffer(data)
     160        if metadata:
     161            ts = metadata.get("timestamp")
     162            if ts is not None:
     163                buf.timestamp = ts
     164        self.push_buffer(buf)
     165   
     166    def push_buffer(self, buf):
     167        #buf.size = size
     168        #buf.timestamp = timestamp
     169        #buf.duration = duration
     170        #buf.offset = offset
     171        #buf.offset_end = offset_end
     172        #buf.set_caps(gst.caps_from_string(caps))
     173        r = self.src.emit("push-buffer", buf)
     174        if r!=gst.FLOW_OK:
     175            log.error("push-buffer error: %s", r)
     176            self.emit('error', "push-buffer error: %s" % r)
     177            return False
     178        self.buffer_count += 1
     179        self.byte_count += len(buf.data)
     180        ltime = int(self.queue.get_property("current-level-time")/MS_TO_NS)
     181        debug("sound sink: pushed %s bytes, new buffer level: %sms", len(buf.data), ltime)
     182        return True
    182183
    183     def need_data(self, src_arg, needed):
    184         debug("need_data: %s bytes in %s", needed, src_arg)
    185184
    186     def on_enough_data(self, *args):
    187         debug("on_enough_data(%s)", args)
    188185
    189 
    190186def main():
    191187    import os.path
    192188    import gobject
     
    219215
    220216    import logging
    221217    logging.basicConfig(format="%(asctime)s %(message)s")
    222     logging.root.setLevel(logging.INFO)
     218    logging.root.setLevel(logging.DEBUG)
    223219    f = open(filename, "rb")
    224220    data = f.read()
    225221    f.close()