Skip to content

Commit

Permalink
#362: try to prevent early overruns
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
totaam committed Jul 31, 2013
1 parent a2e86bb commit 4434882
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 45 deletions.
87 changes: 53 additions & 34 deletions src/xpra/client/ui_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 """
Expand Down Expand Up @@ -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
Expand Down
11 changes: 7 additions & 4 deletions src/xpra/server/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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":
Expand Down
8 changes: 6 additions & 2 deletions src/xpra/sound/gstreamer_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 4 additions & 5 deletions src/xpra/sound/sound_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 4434882

Please sign in to comment.