From 4434882ef4b89a80bcb88527445d987871b84b64 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Wed, 31 Jul 2013 11:23:09 +0000 Subject: [PATCH] #362: try to prevent early overruns * let the client specify the codec to use via "sound-control start" * client can start its own pipeline before asking the server to start sending git-svn-id: https://xpra.org/svn/Xpra/trunk@4028 3bb7dfac-3a0b-4e04-842a-767bc560f471 --- src/xpra/client/ui_client_base.py | 87 +++++++++++++++++++------------ src/xpra/server/source.py | 11 ++-- src/xpra/sound/gstreamer_util.py | 8 ++- src/xpra/sound/sound_pipeline.py | 9 ++-- 4 files changed, 70 insertions(+), 45 deletions(-) diff --git a/src/xpra/client/ui_client_base.py b/src/xpra/client/ui_client_base.py index c2b8f11bbb..11a2b4e67d 100644 --- a/src/xpra/client/ui_client_base.py +++ b/src/xpra/client/ui_client_base.py @@ -103,6 +103,7 @@ def __init__(self): self.microphone_codecs = get_codecs(False, False) self.microphone_allowed = len(self.microphone_codecs)>0 self.sink_restart_pending = False + self.on_sink_ready = None self.sound_sink = None self.server_sound_sequence = False self.min_sound_sequence = 0 @@ -796,7 +797,7 @@ def sound_source_bitrate_changed(*args): self.emit("microphone-changed") try: from xpra.sound.gstreamer_util import start_sending_sound - self.sound_source = start_sending_sound(self.server_sound_decoders, self.microphone_codecs, self.server_pulseaudio_server, self.server_pulseaudio_id) + self.sound_source = start_sending_sound(None, self.server_sound_decoders, self.microphone_codecs, self.server_pulseaudio_server, self.server_pulseaudio_id) if not self.sound_source: return False self.sound_source.connect("new-buffer", self.new_sound_buffer) @@ -833,9 +834,22 @@ def start_receiving_sound(self): elif not self.server_sound_send: log.error("cannot start receiving sound: support not enabled on the server") else: + #choose a codec: + from xpra.sound.gstreamer_util import CODEC_ORDER + matching_codecs = [x for x in self.server_sound_encoders if x in self.speaker_codecs] + ordered_codecs = [x for x in CODEC_ORDER if x in matching_codecs] + if len(ordered_codecs)==0: + log.error("no matching codecs between server (%s) and client (%s)", self.server_sound_encoders, self.speaker_codecs) + return + codec = ordered_codecs[0] self.speaker_enabled = True - self.send("sound-control", "start") self.emit("speaker-changed") + def sink_ready(*args): + soundlog("sink_ready(%s) codec=%s", args, codec) + self.send("sound-control", "start", codec) + return False + self.on_sink_ready = sink_ready + self.start_sound_sink(codec) def stop_receiving_sound(self): """ ask the server to stop sending sound, toggle flag so we ignore further packets and emit client signal """ @@ -869,45 +883,50 @@ def send_new_sound_sequence(self): soundlog("send_new_sound_sequence() sequence=%s", self.min_sound_sequence) self.send("sound-control", "new-sequence", self.min_sound_sequence) + + def sound_sink_state_changed(self, sound_sink, state): + soundlog("sound_sink_state_changed(%s, %s) on_sink_ready=%s", sound_sink, state, self.on_sink_ready) + if state=="ready" and self.on_sink_ready: + if not self.on_sink_ready(): + self.on_sink_ready = None + self.emit("speaker-changed") + def sound_sink_bitrate_changed(self, sound_sink, bitrate): + soundlog("sound_sink_bitrate_changed(%s, %s)", sound_sink, bitrate) + self.emit("speaker-changed") + def sound_sink_error(self, sound_sink, error): + log.warn("stopping speaker because of error: %s", error) + self.stop_receiving_sound() + + def sound_sink_overrun(self, *args): + log.warn("re-starting speaker because of overrun") + if self.sink_restart_pending: + return + codec = self.sound_sink.codec + self.sink_restart_pending = True + if self.server_sound_sequence: + self.min_sound_sequence += 1 + #Note: the next sound packet will take care of starting a new pipeline + self.stop_receiving_sound() + def restart(): + soundlog("restart() sound_sink=%s, codec=%s, server_sound_sequence=%s", self.sound_sink, codec, self.server_sound_sequence) + if self.server_sound_sequence: + self.send_new_sound_sequence() + self.sink_restart_pending = False + self.start_receiving_sound() + return False + self.timeout_add(200, restart) + def start_sound_sink(self, codec): soundlog("start_sound_sink(%s)", codec) assert self.sound_sink is None - def sound_sink_state_changed(*args): - soundlog("sound_sink_state_changed(%s)", args) - self.emit("speaker-changed") - def sound_sink_bitrate_changed(*args): - soundlog("sound_sink_bitrate_changed(%s)", args) - self.emit("speaker-changed") - def sound_sink_error(*args): - log.warn("stopping speaker because of error: %s", args) - self.stop_receiving_sound() - def sound_sink_overrun(*args): - log.warn("re-starting speaker because of overrun") - if self.sink_restart_pending: - return - self.sink_restart_pending = True - if self.server_sound_sequence: - self.min_sound_sequence += 1 - #Note: the next sound packet will take care of starting a new pipeline - self.stop_receiving_sound() - def restart(): - soundlog("restart() sound_sink=%s, codec=%s, server_sound_sequence=%s", self.sound_sink, codec, self.server_sound_sequence) - if self.server_sound_sequence: - self.send_new_sound_sequence() - #prepare the new sound sink already: - #self.start_sound_sink(codec) - self.start_receiving_sound() - self.sink_restart_pending = False - return False - self.timeout_add(200, restart) try: soundlog("starting %s sound sink", codec) from xpra.sound.sink import SoundSink self.sound_sink = SoundSink(codec=codec) - self.sound_sink.connect("state-changed", sound_sink_state_changed) - self.sound_sink.connect("bitrate-changed", sound_sink_bitrate_changed) - self.sound_sink.connect("error", sound_sink_error) - self.sound_sink.connect("overrun", sound_sink_overrun) + self.sound_sink.connect("state-changed", self.sound_sink_state_changed) + self.sound_sink.connect("bitrate-changed", self.sound_sink_bitrate_changed) + self.sound_sink.connect("error", self.sound_sink_error) + self.sound_sink.connect("overrun", self.sound_sink_overrun) self.sound_sink.start() soundlog("%s sound sink started", codec) return True diff --git a/src/xpra/server/source.py b/src/xpra/server/source.py index 2c1cc10bcc..39674244c8 100644 --- a/src/xpra/server/source.py +++ b/src/xpra/server/source.py @@ -581,8 +581,8 @@ def startup_complete(self): if self.notify_startup_complete: self.send("startup-complete") - def start_sending_sound(self): - soundlog("start_sending_sound()") + def start_sending_sound(self, codec): + soundlog("start_sending_sound(%s)", codec) if self.suspended: log.warn("not starting sound as we are suspended") return @@ -591,7 +591,7 @@ def start_sending_sound(self): assert self.sound_receive, "cannot send sound: support is not enabled on the client" try: from xpra.sound.gstreamer_util import start_sending_sound - self.sound_source = start_sending_sound(self.sound_decoders, self.microphone_codecs, self.pulseaudio_server, self.pulseaudio_id) + self.sound_source = start_sending_sound(codec, self.sound_decoders, self.microphone_codecs, self.pulseaudio_server, self.pulseaudio_id) soundlog("start_sending_sound() sound source=%s", self.sound_source) if self.sound_source: self.sound_source.connect("new-buffer", self.new_sound_buffer) @@ -636,7 +636,10 @@ def sound_control(self, action, *args): if action=="stop": self.stop_sending_sound() elif action=="start": - self.start_sending_sound() + codec = None + if len(args)>0: + codec = args[0] + self.start_sending_sound(codec) elif action=="new-sequence": self.sound_source_sequence = args[0] #elif action=="quality": diff --git a/src/xpra/sound/gstreamer_util.py b/src/xpra/sound/gstreamer_util.py index 9774afaecb..55ebc5b5b9 100755 --- a/src/xpra/sound/gstreamer_util.py +++ b/src/xpra/sound/gstreamer_util.py @@ -155,14 +155,18 @@ def add_gst_capabilities(capabilities, receive=True, send=True, capabilities["sound.send"] = send and len(send_codecs)>0 -def start_sending_sound(remote_decoders, local_decoders, remote_pulseaudio_server, remote_pulseaudio_id): +def start_sending_sound(codec, remote_decoders, local_decoders, remote_pulseaudio_server, remote_pulseaudio_id): try: matching_codecs = [x for x in remote_decoders if x in local_decoders] ordered_codecs = [x for x in CODEC_ORDER if x in matching_codecs] if len(ordered_codecs)==0: log.error("no matching codecs between remote (%s) and local (%s) - sound disabled", remote_decoders, local_decoders) return None - codec = ordered_codecs[0] + if codec is not None and codec not in matching_codecs: + log.warn("invalid codec specified: %s", codec) + codec = None + if codec is None: + codec = ordered_codecs[0] log.info("using sound codec %s", codec) from xpra.sound.src import SoundSource if SOUND_TEST_MODE: diff --git a/src/xpra/sound/sound_pipeline.py b/src/xpra/sound/sound_pipeline.py index bfc0c71e50..19057635b0 100755 --- a/src/xpra/sound/sound_pipeline.py +++ b/src/xpra/sound/sound_pipeline.py @@ -61,11 +61,10 @@ def setup_pipeline_and_bus(self, elements): def do_get_state(self, state): if not self.pipeline: return "stopped" - if state==gst.STATE_PLAYING: - return "active" - if state==gst.STATE_NULL: - return "stopped" - return "unknown" + return {gst.STATE_PLAYING : "active", + gst.STATE_PAUSED : "paused", + gst.STATE_NULL : "stopped", + gst.STATE_READY : "ready"}.get(state, "unknown") def get_state(self): return self.state