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-clean-split-gstreamer-v3.patch

File sound-clean-split-gstreamer-v3.patch, 50.3 KB (added by Antoine Martin, 6 years ago)

allows us to completely remove all gstreamer imports from the client and server, we only deal with sound properties and let the sound subprocess do what it claims to be able to handle through these properties

  • xpra/client/gtk_base/bug_report.py

     
    9595        from xpra.codecs.loader import codec_versions, load_codecs
    9696        load_codecs()
    9797        try:
    98             from xpra.sound.gstreamer_util import get_info as get_sound_info
     98            from xpra.sound.wrapper import query_sound
     99            def get_sound_info():
     100                return query_sound()
    99101        except:
    100102            get_sound_info = None
    101103        if self.opengl_info:
  • xpra/client/gtk_base/session_info.py

     
    169169        tb.new_row("Pango",     client_vinfo("pango"),      server_vinfo("pango"))
    170170        tb.new_row("Python", label(python_platform.python_version()), label(server_version_info("server.python.version", "python_version", "python.version")))
    171171
    172         cl_gst_v, cl_pygst_v = "", ""
    173172        try:
    174             from xpra.sound.gstreamer_util import gst_version as cl_gst_v, pygst_version as cl_pygst_v
     173            from xpra.sound.wrapper import query_sound
     174            props = query_sound()
    175175        except Exception as e:
    176             log("cannot load gstreamer: %s", e)
    177         tb.new_row("GStreamer", label(make_version_str(cl_gst_v)), label(server_version_info("sound.gst.version", "gst_version")))
    178         tb.new_row("pygst", label(make_version_str(cl_pygst_v)), label(server_version_info("sound.pygst.version", "pygst_version")))
     176            log("cannot load sound information: %s", e)
     177            props = {}
     178        gst_version = props.get("gst.version", "")
     179        pygst_version = props.get("pygst.version", "")
     180        tb.new_row("GStreamer", label(make_version_str(gst_version)), label(server_version_info("sound.gst.version", "gst_version")))
     181        tb.new_row("pygst", label(make_version_str(pygst_version)), label(server_version_info("sound.pygst.version", "pygst_version")))
    179182        tb.new_row("OpenGL", label(make_version_str(self.client.opengl_props.get("opengl", "n/a"))), label("n/a"))
    180183        tb.new_row("OpenGL Vendor", label(make_version_str(self.client.opengl_props.get("vendor", ""))), label("n/a"))
    181184        tb.new_row("PyOpenGL", label(make_version_str(self.client.opengl_props.get("pyopengl", "n/a"))), label("n/a"))
  • xpra/client/ui_client_base.py

     
    5050from xpra.child_reaper import reaper_cleanup
    5151from xpra.make_thread import make_thread
    5252from xpra.os_util import Queue, os_info, platform_name, get_machine_id, get_user_uuid, bytestostr
    53 from xpra.util import nonl, std, AtomicInteger, AdHocStruct, log_screen_sizes, typedict, updict, csv, CLIENT_EXIT
     53from xpra.util import nonl, std, AtomicInteger, AdHocStruct, log_screen_sizes, typedict, updict, csv, engs, CLIENT_EXIT
    5454from xpra.version_util import get_version_info_full, get_platform_info
    5555try:
    5656    from xpra.clipboard.clipboard_base import ALL_CLIPBOARDS
     
    134134        self.encoding = None
    135135
    136136        #sound:
    137         try:
    138             from xpra.sound.gstreamer_util import has_gst
    139         except:
    140             has_gst = False
    141137        self.sound_source_plugin = None
    142         self.speaker_allowed = has_gst
     138        self.speaker_allowed = False
    143139        self.speaker_enabled = False
    144140        self.speaker_codecs = []
    145         self.microphone_allowed = has_gst
     141        self.microphone_allowed = False
    146142        self.microphone_enabled = False
    147143        self.microphone_codecs = []
    148         if has_gst:
    149             try:
    150                 from xpra.sound.wrapper import get_sound_codecs
    151                 self.speaker_codecs = get_sound_codecs(True, False)
    152                 self.speaker_allowed = len(self.speaker_codecs)>0
    153                 self.microphone_codecs = get_sound_codecs(False, False)
    154                 self.microphone_allowed = len(self.microphone_codecs)>0
    155             except Exception as e:
    156                 soundlog("sound support unavailable: %s", e)
    157         soundlog("speaker_allowed=%s, speaker_codecs=%s", self.speaker_allowed, csv(self.speaker_codecs))
    158         soundlog("microphone_allowed=%s, microphone_codecs=%s", self.microphone_allowed, csv(self.microphone_codecs))
    159144        self.av_sync = False
    160145        #sound state:
    161146        self.on_sink_ready = None
     
    272257        self.supports_mmap = MMAP_SUPPORTED and opts.mmap
    273258        self.mmap_group = opts.mmap_group
    274259
     260        self.sound_properties = {}
     261        self.sound_source_plugin = opts.sound_source
    275262        try:
    276             from xpra.sound.gstreamer_util import has_gst
    277         except:
    278             has_gst = False
    279         self.sound_source_plugin = opts.sound_source
    280         self.speaker_allowed = sound_option(opts.speaker) in ("on", "off") and has_gst
    281         self.speaker_enabled = sound_option(opts.speaker)=="on" and has_gst
    282         self.microphone_allowed = sound_option(opts.microphone) in ("on", "off") and has_gst
    283         self.microphone_enabled = sound_option(opts.microphone)=="on" and has_gst
    284         if opts.speaker_codec:
    285             self.speaker_codecs = opts.speaker_codec
    286         if len(self.speaker_codecs)==0 and self.speaker_allowed:
    287             self.speaker_allowed = False
    288         if opts.microphone_codec:
    289             self.microphone_codecs = opts.microphone_codec
    290         if len(self.microphone_codecs)==0 and self.microphone_allowed:
    291             self.microphone_allowed = False
     263            from xpra.sound.wrapper import query_sound
     264            self.sound_properties = query_sound()
     265        except ImportError as e:
     266            soundlog("sound support is not available: %s", e)
     267        except Exception as e:
     268            soundlog.error("Error: failed to query sound subsystem:")
     269            soundlog.error(" %s", e)
     270        encoders = self.sound_properties.get("encoders", [])
     271        decoders = self.sound_properties.get("decoders", [])
     272        def option_or_all(name, options, all_values):
     273            if not options:
     274                return all_values       #not specified on command line: use default
     275            invalid_options = [x for x in options if x not in all_values]
     276            if len(invalid_options)==0:
     277                return options          #all good
     278            soundlog.warn("Warning: invalid value%s for %s: %s", engs(invalid_options), name, invalid_options)
     279            soundlog.warn(" valid option%s: %s", engs(all_values), csv(all_values))
     280            #only keep the valid options:
     281            return [x for x in options if x in all_values]
     282        self.speaker_codecs = option_or_all("speaker-codec", opts.speaker_codec, decoders)
     283        self.microphone_codecs = option_or_all("microphone-codec", opts.microphone_codec, encoders)
     284        self.speaker_allowed = len(self.speaker_codecs)>0 and sound_option(opts.speaker) in ("on", "off")
     285        self.microphone_allowed = len(self.microphone_codecs)>0 and sound_option(opts.microphone) in ("on", "off")
     286        self.speaker_enabled = self.speaker_allowed and sound_option(opts.speaker)=="on"
     287        self.microphone_enabled = self.microphone_allowed and sound_option(opts.microphone)=="on"
    292288        self.av_sync = opts.av_sync
     289        soundlog("speaker: codecs=%s, allowed=%s, enabled=%s", encoders, self.speaker_allowed, csv(self.speaker_codecs))
     290        soundlog("microphone: codecs=%s, allowed=%s, enabled=%s", decoders, self.microphone_allowed, csv(self.microphone_codecs))
     291        soundlog("av-sync=%s", self.av_sync)
    293292
    294293        self.readonly = opts.readonly
    295294        self.windows_enabled = opts.windows
     
    11351134        #hack: workaround namespace issue ("encodings" vs "encoding"..)
    11361135        capabilities["encodings.rgb_formats"] = rgb_formats
    11371136
    1138         sound_caps = {}
    1139         try:
    1140             import xpra.sound
    1141             log("loaded %s", xpra.sound)
     1137        if self.sound_properties:
     1138            sound_caps = self.sound_properties
     1139            sound_caps["send"] = self.microphone_allowed
     1140            sound_caps["receive"] = self.speaker_allowed
    11421141            try:
    1143                 from xpra.sound.gstreamer_util import has_gst, get_info as get_gst_info
    1144                 sound_caps.update(get_gst_info(receive=self.speaker_allowed, send=self.microphone_allowed,
    1145                                      receive_codecs=self.speaker_codecs, send_codecs=self.microphone_codecs))
    1146             except Exception as e:
    1147                 log.error("failed to setup sound: %s", e, exc_info=True)
    1148                 self.speaker_allowed = False
    1149                 self.microphone_allowed = False
    1150                 has_gst = False
    1151             if has_gst:
    1152                 try:
    1153                     from xpra.sound.pulseaudio_util import get_info as get_pa_info
    1154                     sound_caps.update(get_pa_info())
    1155                     sound_caps.update(get_gst_info(receive=self.speaker_allowed, send=self.microphone_allowed,
    1156                                          receive_codecs=self.speaker_codecs, send_codecs=self.microphone_codecs))
    1157                 except Exception:
    1158                     pass
     1142                from xpra.sound.pulseaudio_util import get_info as get_pa_info
     1143                sound_caps.update(get_pa_info())
     1144            except Exception:
     1145                pass
    11591146            updict(capabilities, "sound", sound_caps)
    11601147            soundlog("sound capabilities: %s", sound_caps)
    1161         except ImportError as e:
    1162             soundlog.warn("sound support not available: %s", e)
    11631148        #batch options:
    11641149        for bprop in ("always", "min_delay", "max_delay", "delay", "max_events", "max_pixels", "time_unit"):
    11651150            evalue = os.environ.get("XPRA_BATCH_%s" % bprop.upper())
     
    15921577        soundlog("start_sending_sound()")
    15931578        assert self.microphone_allowed, "microphone forwarding is disabled"
    15941579        assert self.server_sound_receive, "client support for receiving sound is disabled"
    1595         from xpra.sound.gstreamer_util import ALLOW_SOUND_LOOP
     1580        from xpra.sound.sound_util import ALLOW_SOUND_LOOP
    15961581        if self._remote_machine_id and self._remote_machine_id==get_machine_id() and not ALLOW_SOUND_LOOP:
    15971582            #looks like we're on the same machine, verify it's a different user:
    15981583            if self._remote_uuid==get_user_uuid():
     
    16671652            log.error("cannot start receiving sound: support not enabled on the server")
    16681653            return
    16691654        #choose a codec:
    1670         from xpra.sound.gstreamer_util import CODEC_ORDER
     1655        from xpra.sound.sound_util import CODEC_ORDER
    16711656        matching_codecs = [x for x in self.server_sound_encoders if x in self.speaker_codecs]
    16721657        ordered_codecs = [x for x in CODEC_ORDER if x in matching_codecs]
    16731658        if len(ordered_codecs)==0:
  • xpra/scripts/main.py

     
    674674                        h.append(" * %-16s: %s" % (k,v))
    675675                raise InitInfo("known logging filters: \n%s" % "\n".join(h))
    676676    if options.sound_source=="help":
    677         from xpra.sound.gstreamer_util import NAME_TO_INFO_PLUGIN
     677        from xpra.sound.sound_util import NAME_TO_INFO_PLUGIN
    678678        try:
    679             from xpra.sound.wrapper import query_sound_sources
    680             source_plugins = query_sound_sources()
     679            from xpra.sound.wrapper import query_sound
     680            source_plugins = query_sound().get("sources", [])
    681681        except Exception as e:
    682682            raise InitInfo(e)
    683683            source_plugins = []
     
    757757    print("")
    758758
    759759def show_sound_codec_help(is_server, speaker_codecs, microphone_codecs):
    760     from xpra.sound.gstreamer_util import has_gst
    761     from xpra.sound.wrapper import get_sound_codecs
    762     if not has_gst:
     760    from xpra.sound.wrapper import query_sound
     761    props = query_sound()
     762    if not props:
    763763        return "sound is not supported - gstreamer not present or not accessible"
    764764    info = []
    765     all_speaker_codecs = get_sound_codecs(True, is_server)
     765    all_speaker_codecs = props.get("decoders")
    766766    invalid_sc = [x for x in speaker_codecs if x not in all_speaker_codecs]
    767767    hs = "help" in speaker_codecs
    768768    if hs:
     
    774774    elif len(speaker_codecs)==0:
    775775        speaker_codecs += all_speaker_codecs
    776776
    777     all_microphone_codecs = get_sound_codecs(True, is_server)
     777    all_microphone_codecs = props.get("decoders")
    778778    invalid_mc = [x for x in microphone_codecs if x not in all_microphone_codecs]
    779779    hm = "help" in microphone_codecs
    780780    if hm:
  • xpra/server/server_base.py

     
    3030from xpra.simple_stats import to_std_unit
    3131from xpra.child_reaper import getChildReaper
    3232from xpra.os_util import thread, get_hex_uuid, livefds, load_binary_file
    33 from xpra.util import typedict, updict, log_screen_sizes, engs, repr_ellipsized, \
     33from xpra.util import typedict, updict, log_screen_sizes, engs, repr_ellipsized, csv, \
    3434    SERVER_EXIT, SERVER_ERROR, SERVER_SHUTDOWN, DETACH_REQUEST, NEW_CLIENT, DONE, IDLE_TIMEOUT
    3535from xpra.net.bytestreams import set_socket_timeout
    3636from xpra.platform import get_username
     37from xpra.platform.paths import get_icon_filename
    3738from xpra.child_reaper import reaper_cleanup
    3839from xpra.scripts.config import python_platform, parse_bool_or_int
    3940from xpra.scripts.main import sound_option
     
    125126        self.pulseaudio = False
    126127        self.pulseaudio_command = None
    127128        self.pulseaudio_proc = None
     129        self.sound_properties = {}
    128130
    129131        #encodings:
    130132        self.allowed_encodings = None
     
    226228        #sound:
    227229        self.pulseaudio = opts.pulseaudio
    228230        self.pulseaudio_command = opts.pulseaudio_command
    229         self.init_sound_options(opts.sound_source, opts.speaker, opts.speaker_codec, opts.microphone, opts.microphone_codec)
     231        self.init_sound_options(opts)
    230232
    231233        log("starting component init")
    232234        self.init_clipboard()
     
    390392                full_trace = self.is_child_alive(proc)
    391393                soundlog.warn("error trying to stop pulseaudio", exc_info=full_trace)
    392394
    393     def init_sound_options(self, sound_source_plugin, speaker, speaker_codec, microphone, microphone_codec):
     395    def init_sound_options(self, opts):
     396        #sound_source_plugin, speaker, speaker_codec, microphone, microphone_codec):
     397        #opts.sound_source, opts.speaker, opts.speaker_codec, opts.microphone, opts.microphone_codec
    394398        try:
    395             from xpra.sound.gstreamer_util import has_gst
    396             from xpra.sound.wrapper import get_sound_codecs
     399            from xpra.sound.wrapper import query_sound
     400            self.sound_properties = query_sound()
     401        except ImportError as e:
     402            log("cannot load sound support: %s", e)
    397403        except Exception as e:
    398             log("cannot load gstreamer: %s", e)
    399             has_gst = False
    400         log("init_sound_options%s has_gst=%s", (sound_source_plugin, speaker, speaker_codec, microphone, microphone_codec), has_gst)
    401         self.sound_source_plugin = sound_source_plugin
    402         self.supports_speaker = sound_option(speaker) in ("on", "off") and has_gst
    403         self.supports_microphone = sound_option(microphone) in ("on", "off") and has_gst
    404         self.speaker_codecs = speaker_codec
    405         if len(self.speaker_codecs)==0 and self.supports_speaker:
    406             self.speaker_codecs = get_sound_codecs(True, True)
    407             self.supports_speaker = len(self.speaker_codecs)>0
    408         self.microphone_codecs = microphone_codec
    409         if len(self.microphone_codecs)==0 and self.supports_microphone:
    410             self.microphone_codecs = get_sound_codecs(False, False)
    411             self.supports_microphone = len(self.microphone_codecs)>0
    412         try:
    413             from xpra.platform.paths import get_icon_filename
    414             from xpra.sound.pulseaudio_util import set_icon_path
    415             set_icon_path(get_icon_filename("xpra.png"))
    416         except Exception as e:
    417             log.warn("Warning: failed to set pulseaudio tagging icon:")
    418             log.warn(" %s", e)
     404            soundlog.error("Error: failed to query sound subsystem:")
     405            soundlog.error(" %s", e)
     406        self.sound_source_plugin = opts.sound_source
     407        def option_or_all(name, options, all_values):
     408            if not options:
     409                return all_values       #not specified on command line: use default
     410            invalid_options = [x for x in options if x not in all_values]
     411            if len(invalid_options)==0:
     412                return options          #all good
     413            soundlog.warn("Warning: invalid value%s for %s: %s", engs(invalid_options), name, invalid_options)
     414            soundlog.warn(" valid option%s: %s", engs(all_values), csv(all_values))
     415            #only keep the valid options:
     416            return [x for x in options if x in all_values]
     417        self.speaker_codecs = option_or_all("speaker-codec", opts.speaker_codec, self.sound_properties.get("encoders", []))
     418        self.microphone_codecs = option_or_all("microphone-codec", opts.microphone_codec, self.sound_properties.get("decoders", []))
     419        self.supports_speaker = len(self.speaker_codecs)>0 and sound_option(opts.speaker) in ("on", "off")
     420        self.supports_microphone = len(self.microphone_codecs)>0 and sound_option(opts.microphone) in ("on", "off")
     421        if bool(self.sound_properties):
     422            self.sound_properties["send"] = len(self.speaker_codecs)>0
     423            self.sound_properties["receive"] = len(self.microphone_codecs)>0
     424            try:
     425                from xpra.sound.pulseaudio_util import set_icon_path, get_info as get_pa_info
     426                self.sound_properties.update(get_pa_info())
     427                set_icon_path(get_icon_filename("xpra.png"))
     428            except Exception as e:
     429                log.warn("Warning: failed to set pulseaudio tagging icon:")
     430                log.warn(" %s", e)
     431        log("init_sound_options sound properties=%s", self.sound_properties)
    419432
    420433    def init_clipboard(self):
    421434        clipboardlog("init_clipboard() enabled=%s, filter file=%s", self.supports_clipboard, self.clipboard_filter_file)
     
    841854                          get_window_id,
    842855                          self.supports_mmap, self.av_sync,
    843856                          self.core_encodings, self.encodings, self.default_encoding, self.scaling_control,
     857                          self.sound_properties,
    844858                          self.sound_source_plugin,
    845859                          self.supports_speaker, self.supports_microphone,
    846860                          self.speaker_codecs, self.microphone_codecs,
  • xpra/server/source.py

     
    173173                 get_window_id,
    174174                 supports_mmap, av_sync,
    175175                 core_encodings, encodings, default_encoding, scaling_control,
     176                 sound_properties,
    176177                 sound_source_plugin,
    177178                 supports_speaker, supports_microphone,
    178179                 speaker_codecs, microphone_codecs,
     
    184185                 get_window_id,
    185186                 supports_mmap, av_sync,
    186187                 core_encodings, encodings, default_encoding, scaling_control,
     188                 sound_properties,
    187189                 sound_source_plugin,
    188190                 supports_speaker, supports_microphone,
    189191                 speaker_codecs, microphone_codecs,
     
    221223        self.mouse_echo = False
    222224        self.mouse_last_position = None
    223225        # sound:
     226        self.sound_properties = sound_properties
    224227        self.sound_source_plugin = sound_source_plugin
    225228        self.supports_speaker = supports_speaker
    226229        self.speaker_codecs = speaker_codecs
     
    800803            soundlog.warn("not starting sound as we are suspended")
    801804            return
    802805        try:
    803             from xpra.sound.gstreamer_util import ALLOW_SOUND_LOOP
    804             from xpra.sound.wrapper import start_sending_sound
     806            from xpra.sound.sound_util import ALLOW_SOUND_LOOP
    805807            if self.machine_id and self.machine_id==get_machine_id() and not ALLOW_SOUND_LOOP:
    806808                #looks like we're on the same machine, verify it's a different user:
    807809                if self.uuid==get_user_uuid():
     
    812814            assert self.sound_source is None, "a sound source already exists"
    813815            assert self.sound_receive, "cannot send sound: support is not enabled on the client"
    814816            assert codec in self.sound_decoders, "cannot use codec '%s' which is not in the decoders list: %s" % (codec, self.sound_decoders)
    815             ss = start_sending_sound(self.sound_source_plugin, codec, volume, [codec], self.pulseaudio_server, self.pulseaudio_id)
     817            from xpra.sound.wrapper import start_sending_sound
     818            plugins = self.sound_properties.get("plugins")
     819            ss = start_sending_sound(plugins, self.sound_source_plugin, codec, volume, [codec], self.pulseaudio_server, self.pulseaudio_id)
    816820            self.sound_source = ss
    817821            soundlog("start_sending_sound() sound source=%s", ss)
    818822            if ss:
     
    990994            cinfo = ""
    991995            if ss:
    992996                try:
    993                     from xpra.sound.gstreamer_util import ENCODER_LATENCY
     997                    from xpra.sound.sound_util import ENCODER_LATENCY
    994998                    encoder_latency = ENCODER_LATENCY.get(ss.codec, 0)
    995999                    cinfo = "%s " % ss.codec
    9961000                except Exception as e:
     
    11661170
    11671171    def hello(self, server_capabilities):
    11681172        capabilities = server_capabilities.copy()
    1169         if self.wants_sound:
    1170             sound_caps = {}
    1171             try:
    1172                 from xpra.sound.gstreamer_util import get_info as get_gst_info
    1173                 sound_caps.update(get_gst_info(receive=self.supports_microphone, send=self.supports_speaker,
    1174                                  receive_codecs=self.speaker_codecs, send_codecs=self.microphone_codecs))
    1175             except ImportError:
    1176                 pass
    1177             except Exception as e:
    1178                 log.error("failed to setup sound: %s", e)
    1179             try:
    1180                 from xpra.sound.pulseaudio_util import get_info as get_pa_info
    1181                 sound_caps.update(get_pa_info())
    1182             except ImportError:
    1183                 pass
    1184             except Exception as e:
    1185                 log.error("failed to setup sound: %s", e)
    1186             updict(capabilities, "sound", sound_caps)
    1187             log("sound capabilities: %s", sound_caps)
     1173        if self.wants_sound and self.sound_properties:
     1174            updict(capabilities, "sound", self.sound_properties)
    11881175        if self.wants_encodings or self.send_windows:
    11891176            assert self.encoding, "cannot send windows/encodings without an encoding!"
    11901177            encoding = self.encoding
  • xpra/sound/gstreamer_util.py

     
    77import sys
    88import os
    99
     10from xpra.sound.sound_util import CODEC_ORDER, VORBIS, OPUS, FLAC, MP3, WAV, WAVPACK, SPEEX
    1011from xpra.log import Logger
    1112log = Logger("sound")
    1213
     
    2728    return queue_time
    2829
    2930
    30 ALLOW_SOUND_LOOP = os.environ.get("XPRA_ALLOW_SOUND_LOOP", "0")=="1"
    3131GSTREAMER1 = os.environ.get("XPRA_GSTREAMER1", "0")=="1"
    32 MONITOR_DEVICE_NAME = os.environ.get("XPRA_MONITOR_DEVICE_NAME", "")
    3332def force_enabled(codec_name):
    3433    return os.environ.get("XPRA_SOUND_CODEC_ENABLE_%s" % codec_name.upper(), "0")=="1"
    3534
    36 NAME_TO_SRC_PLUGIN = {
    37     "auto"          : "autoaudiosrc",
    38     "alsa"          : "alsasrc",
    39     "oss"           : "osssrc",
    40     "oss4"          : "oss4src",
    41     "jack"          : "jackaudiosrc",
    42     "osx"           : "osxaudiosrc",
    43     "test"          : "audiotestsrc",
    44     "pulse"         : "pulsesrc",
    45     "direct"        : "directsoundsrc",
    46     }
    47 SRC_TO_NAME_PLUGIN = {}
    48 for k,v in NAME_TO_SRC_PLUGIN.items():
    49     SRC_TO_NAME_PLUGIN[v] = k
    50 PLUGIN_TO_DESCRIPTION = {
    51     "pulsesrc"      : "Pulseaudio",
    52     "jacksrc"       : "JACK Audio Connection Kit",
    53     }
    54 NAME_TO_INFO_PLUGIN = {
    55     "auto"          : "Wrapper audio source for automatically detected audio source",
    56     "alsa"          : "Read from a sound card via ALSA",
    57     "oss"           : "Capture from a sound card via OSS",
    58     "oss4"          : "Capture from a sound card via OSS version 4",
    59     "jack"          : "Captures audio from a JACK server",
    60     "osx"           : "Input from a sound card in OS X",
    61     "test"          : "Creates audio test signals of given frequency and volume",
    62     "pulse"         : "Captures audio from a PulseAudio server",
    63     "direct"        : "directsoundsrc",
    64     }
    6535
    66 
    67 VORBIS = "vorbis"
    68 AAC = "aac"
    69 FLAC = "flac"
    70 MP3 = "mp3"
    71 WAV = "wav"
    72 OPUS = "opus"
    73 SPEEX = "speex"
    74 WAVPACK = "wavpack"
    75 
    7636#format: encoder, container-formatter, decoder, container-parser
    7737#we keep multiple options here for the same encoding
    7838#and will populate the ones that are actually available into the "CODECS" dict
     
    9656OGG = "ogg"
    9757MUX_OPTIONS = [
    9858               (GDP,    "gdppay",   "gdpdepay"),
    99                (OGG,    "oggmux"    "oggdemux")
     59               (OGG,    "oggmux",   "oggdemux"),
    10060              ]
    10161
    10262#these encoders require an "audioconvert" element:
     
    12181                               },
    12282           }
    12383
    124 #based on the encoder options above:
    125 ENCODER_LATENCY = {
    126         MP3         : 250,
    127         FLAC        : 50,
    128         WAV         : 0,
    129         WAVPACK     : 600,
    130         OPUS        : 0,
    131         SPEEX       : 0,
    132        }
    13384
    134 CODEC_ORDER = [VORBIS, OPUS, FLAC, MP3, WAV, WAVPACK, SPEEX]    #AAC is untested
    135 
    136 
    13785gst = None
    13886has_gst = False
    13987
     
    357305    return s
    358306
    359307
    360 def get_source_plugins():
    361     sources = []
    362     from xpra.sound.pulseaudio_util import has_pa
    363     #we have to put pulsesrc first if pulseaudio is installed
    364     #because using autoaudiosource does not work properly for us:
    365     #it may still choose pulse, but without choosing the right device.
    366     if has_pa():
    367         sources.append("pulsesrc")
    368     sources.append("autoaudiosrc")
    369     if sys.platform.startswith("darwin"):
    370         sources.append("osxaudiosrc")
    371     elif sys.platform.startswith("win"):
    372         sources.append("directsoundsrc")
    373     if os.name=="posix":
    374         sources += ["alsasrc", "jackaudiosrc",
    375                     "osssrc", "oss4src",
    376                     "osxaudiosrc", "jackaudiosrc"]
    377     sources.append("audiotestsrc")
    378     return sources
    379 
    380 def get_available_source_plugins():
    381     return [x for x in get_source_plugins() if has_plugins(x)]
    382 
    383 def get_test_defaults(remote):
    384     return  {"wave" : 2, "freq" : 110, "volume" : 0.4}
    385 
    386 WARNED_MULTIPLE_DEVICES = False
    387 def get_pulse_defaults(remote):
    388     """
    389         choose the device to use
    390     """
    391     from xpra.sound.pulseaudio_util import has_pa, get_pa_device_options, get_default_sink
    392     from xpra.sound.pulseaudio_util import get_pulse_server, get_pulse_id, set_source_mute
    393     if not has_pa():
    394         log.warn("pulseaudio is not available!")
    395         return    None
    396     pa_server = get_pulse_server()
    397     log("start sound, remote pulseaudio server=%s, local pulseaudio server=%s", remote.pulseaudio_server, pa_server)
    398     #only worth comparing if we have a real server string
    399     #one that starts with {UUID}unix:/..
    400     if pa_server and pa_server.startswith("{") and \
    401         remote.pulseaudio_server and remote.pulseaudio_server==pa_server:
    402         log.error("identical Pulseaudio server, refusing to create a sound loop - sound disabled")
    403         return    None
    404     pa_id = get_pulse_id()
    405     log("start sound, client id=%s, server id=%s", remote.pulseaudio_id, pa_id)
    406     if remote.pulseaudio_id and remote.pulseaudio_id==pa_id:
    407         log.error("identical Pulseaudio ID, refusing to create a sound loop - sound disabled")
    408         return    None
    409     monitor_devices = get_pa_device_options(True, False)
    410     log("found pulseaudio monitor devices: %s", monitor_devices)
    411     if len(monitor_devices)==0:
    412         log.error("could not detect any Pulseaudio monitor devices - sound forwarding is disabled")
    413         return    None
    414     if len(monitor_devices)>1 and MONITOR_DEVICE_NAME:
    415         monitor_devices = dict((k,v) for k,v in monitor_devices.items() if k.find(MONITOR_DEVICE_NAME)>=0 or v.find(MONITOR_DEVICE_NAME)>0)
    416         if len(monitor_devices)==0:
    417             log.warn("Pulseaudio monitor device name filter '%s' did not match any devices", MONITOR_DEVICE_NAME)
    418             return None
    419         elif len(monitor_devices)>1:
    420             log.warn("Pulseaudio monitor device name filter '%s' matched %i devices", MONITOR_DEVICE_NAME, len(monitor_devices))
    421     #default to first one:
    422     monitor_device, monitor_device_name = monitor_devices.items()[0]
    423     if len(monitor_devices)>1:
    424         default_sink = get_default_sink()
    425         default_monitor = default_sink+".monitor"
    426         global WARNED_MULTIPLE_DEVICES
    427         if not WARNED_MULTIPLE_DEVICES:
    428             WARNED_MULTIPLE_DEVICES = True
    429             log.warn("found more than one audio monitor device:")
    430             for k,v in monitor_devices.items():
    431                 log.warn(" * %s", v)
    432                 log.warn("   %s", k)
    433             log.warn(" use the environment variable XPRA_MONITOR_DEVICE_NAME to select a specific one")
    434         if default_monitor in monitor_devices:
    435             monitor_device = default_monitor
    436             monitor_device_name = monitor_devices.get(default_monitor)
    437             if not WARNED_MULTIPLE_DEVICES:
    438                 log.warn("using monitor of default sink: %s", monitor_device_name)
    439         else:
    440             if not WARNED_MULTIPLE_DEVICES:
    441                 log.warn("using the first device")
    442     log.info("using pulseaudio device:")
    443     log.info(" '%s'", monitor_device_name)
    444     #make sure it is not muted:
    445     set_source_mute(monitor_device, mute=False)
    446     return {"device" : monitor_device}
    447 
    448 #a list of functions to call to get the plugin options
    449 #at runtime (so we can perform runtime checks on remote data,
    450 # to avoid sound loops for example)
    451 DEFAULT_SRC_PLUGIN_OPTIONS = {
    452     "test"                  : get_test_defaults,
    453     "pulse"                 : get_pulse_defaults,
    454     }
    455 
    456 
    457 def parse_element_options(options_str):
    458     #parse the options string and add the pairs:
    459     options = {}
    460     for s in options_str.split(","):
    461         if not s:
    462             continue
    463         try:
    464             k,v = s.split("=", 1)
    465             options[k] = v
    466         except Exception as e:
    467             log.warn("failed to parse plugin option '%s': %s", s, e)
    468     return options
    469 
    470 def get_sound_source_options(plugin, options_str, remote):
    471     """
    472         Given a plugin (short name), options string and remote info,
    473         return the options for the plugin given,
    474         using the dynamic defaults (which may use remote info)
    475         and applying the options string on top.
    476     """
    477     #ie: get_sound_source_options("audiotestsrc", "wave=4,freq=220", {remote_pulseaudio_server=XYZ}):
    478     #use the defaults as starting point:
    479     defaults_fn = DEFAULT_SRC_PLUGIN_OPTIONS.get(plugin)
    480     if defaults_fn:
    481         options = defaults_fn(remote)
    482         if options is None:
    483             #means failure
    484             return None
    485     else:
    486         options = {}
    487     options.update(parse_element_options(options_str))
    488     return options
    489 
    490 def parse_sound_source(sound_source_plugin, remote):
    491     #format: PLUGINNAME:options
    492     #ie: test:wave=2,freq=110,volume=0.4
    493     #ie: pulse:device=device.alsa_input.pci-0000_00_14.2.analog-stereo
    494     plugin = sound_source_plugin.split(":")[0]
    495     options_str = (sound_source_plugin+":").split(":",1)[1]
    496     simple_str = (plugin).lower().strip()
    497     if not simple_str:
    498         #choose the first one from
    499         options = get_available_source_plugins()
    500         if not options:
    501             log.error("no source plugins available")
    502             return None
    503         log("parse_sound_source: no plugin specified, using default: %s", options[0])
    504         simple_str = options[0]
    505     for s in ("src", "sound", "audio"):
    506         if simple_str.endswith(s):
    507             simple_str = simple_str[:-len(s)]
    508     gst_sound_source_plugin = NAME_TO_SRC_PLUGIN.get(simple_str)
    509     if not gst_sound_source_plugin:
    510         log.error("unknown source plugin: '%s' / '%s'", simple_str, sound_source_plugin)
    511         return  None, {}
    512     log("parse_sound_source(%s, %s) plugin=%s", sound_source_plugin, remote, gst_sound_source_plugin)
    513     options = get_sound_source_options(simple_str, options_str, remote)
    514     log("get_sound_source_options%s=%s", (simple_str, options_str, remote), options)
    515     if options is None:
    516         #means error
    517         return None, {}
    518     return gst_sound_source_plugin, options
    519 
    520 
    521 def get_info(receive=True, send=True, receive_codecs=[], send_codecs=[]):
    522     if not has_gst:
    523         return  {}
    524     return {"gst.version"   : gst_version,
    525             "pygst.version" : pygst_version,
    526             "decoders"      : receive_codecs,
    527             "encoders"      : send_codecs,
    528             "receive"       : receive and len(receive_codecs)>0,
    529             "send"          : send and len(send_codecs)>0,
    530             "plugins"       : get_all_plugin_names(),
    531             }
    532 
    533 
    534308def main():
    535309    from xpra.platform import init, clean
    536310    try:
  • xpra/sound/sound_util.py

     
     1#!/usr/bin/env python
     2# This file is part of Xpra.
     3# Copyright (C) 2010-2015 Antoine Martin <antoine@devloop.org.uk>
     4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     5# later version. See the file COPYING for details.
     6
     7
     8# Some gstreamer bits that we want to be able to import
     9# without importing all the rest of gstreamer_util
     10
     11import os, sys
     12from xpra.log import Logger
     13log = Logger("sound")
     14
     15ALLOW_SOUND_LOOP = os.environ.get("XPRA_ALLOW_SOUND_LOOP", "0")=="1"
     16MONITOR_DEVICE_NAME = os.environ.get("XPRA_MONITOR_DEVICE_NAME", "")
     17
     18
     19VORBIS = "vorbis"
     20AAC = "aac"
     21FLAC = "flac"
     22MP3 = "mp3"
     23WAV = "wav"
     24OPUS = "opus"
     25SPEEX = "speex"
     26WAVPACK = "wavpack"
     27
     28CODEC_ORDER = [VORBIS, OPUS, FLAC, MP3, WAV, WAVPACK, SPEEX]    #AAC is untested
     29
     30NAME_TO_INFO_PLUGIN = {
     31    "auto"          : "Automatic audio source selection",
     32    "alsa"          : "ALSA Linux Sound",
     33    "oss"           : "OSS sound cards",
     34    "oss4"          : "OSS version 4 sound cards",
     35    "jack"          : "JACK audio sound server",
     36    "osx"           : "Mac OS X sound cards",
     37    "test"          : "Test signal",
     38    "pulse"         : "PulseAudio",
     39    "direct"        : "Microsoft Windows Direct Sound",
     40    }
     41
     42#based on the encoder options defined in gstreamer util..
     43ENCODER_LATENCY = {
     44        MP3         : 250,
     45        FLAC        : 50,
     46        WAV         : 0,
     47        WAVPACK     : 600,
     48        OPUS        : 0,
     49        SPEEX       : 0,
     50       }
     51
     52NAME_TO_SRC_PLUGIN = {
     53    "auto"          : "autoaudiosrc",
     54    "alsa"          : "alsasrc",
     55    "oss"           : "osssrc",
     56    "oss4"          : "oss4src",
     57    "jack"          : "jackaudiosrc",
     58    "osx"           : "osxaudiosrc",
     59    "test"          : "audiotestsrc",
     60    "pulse"         : "pulsesrc",
     61    "direct"        : "directsoundsrc",
     62    }
     63SRC_TO_NAME_PLUGIN = {}
     64for k,v in NAME_TO_SRC_PLUGIN.items():
     65    SRC_TO_NAME_PLUGIN[v] = k
     66PLUGIN_TO_DESCRIPTION = {
     67    "pulsesrc"      : "Pulseaudio",
     68    "jacksrc"       : "JACK Audio Connection Kit",
     69    }
     70
     71
     72def get_test_defaults(remote):
     73    return  {"wave" : 2, "freq" : 110, "volume" : 0.4}
     74
     75WARNED_MULTIPLE_DEVICES = False
     76def get_pulse_defaults(remote):
     77    """
     78        choose the device to use
     79    """
     80    from xpra.sound.pulseaudio_util import has_pa, get_pa_device_options, get_default_sink
     81    from xpra.sound.pulseaudio_util import get_pulse_server, get_pulse_id, set_source_mute
     82    if not has_pa():
     83        log.warn("pulseaudio is not available!")
     84        return    None
     85    pa_server = get_pulse_server()
     86    log("start sound, remote pulseaudio server=%s, local pulseaudio server=%s", remote.pulseaudio_server, pa_server)
     87    #only worth comparing if we have a real server string
     88    #one that starts with {UUID}unix:/..
     89    if pa_server and pa_server.startswith("{") and \
     90        remote.pulseaudio_server and remote.pulseaudio_server==pa_server:
     91        log.error("identical Pulseaudio server, refusing to create a sound loop - sound disabled")
     92        return    None
     93    pa_id = get_pulse_id()
     94    log("start sound, client id=%s, server id=%s", remote.pulseaudio_id, pa_id)
     95    if remote.pulseaudio_id and remote.pulseaudio_id==pa_id:
     96        log.error("identical Pulseaudio ID, refusing to create a sound loop - sound disabled")
     97        return    None
     98    monitor_devices = get_pa_device_options(True, False)
     99    log("found pulseaudio monitor devices: %s", monitor_devices)
     100    if len(monitor_devices)==0:
     101        log.error("could not detect any Pulseaudio monitor devices - sound forwarding is disabled")
     102        return    None
     103    if len(monitor_devices)>1 and MONITOR_DEVICE_NAME:
     104        monitor_devices = dict((k,v) for k,v in monitor_devices.items() if k.find(MONITOR_DEVICE_NAME)>=0 or v.find(MONITOR_DEVICE_NAME)>0)
     105        if len(monitor_devices)==0:
     106            log.warn("Pulseaudio monitor device name filter '%s' did not match any devices", MONITOR_DEVICE_NAME)
     107            return None
     108        elif len(monitor_devices)>1:
     109            log.warn("Pulseaudio monitor device name filter '%s' matched %i devices", MONITOR_DEVICE_NAME, len(monitor_devices))
     110    #default to first one:
     111    monitor_device, monitor_device_name = monitor_devices.items()[0]
     112    if len(monitor_devices)>1:
     113        default_sink = get_default_sink()
     114        default_monitor = default_sink+".monitor"
     115        global WARNED_MULTIPLE_DEVICES
     116        if not WARNED_MULTIPLE_DEVICES:
     117            WARNED_MULTIPLE_DEVICES = True
     118            log.warn("found more than one audio monitor device:")
     119            for k,v in monitor_devices.items():
     120                log.warn(" * %s", v)
     121                log.warn("   %s", k)
     122            log.warn(" use the environment variable XPRA_MONITOR_DEVICE_NAME to select a specific one")
     123        if default_monitor in monitor_devices:
     124            monitor_device = default_monitor
     125            monitor_device_name = monitor_devices.get(default_monitor)
     126            if not WARNED_MULTIPLE_DEVICES:
     127                log.warn("using monitor of default sink: %s", monitor_device_name)
     128        else:
     129            if not WARNED_MULTIPLE_DEVICES:
     130                log.warn("using the first device")
     131    log.info("using pulseaudio device:")
     132    log.info(" '%s'", monitor_device_name)
     133    #make sure it is not muted:
     134    set_source_mute(monitor_device, mute=False)
     135    return {"device" : monitor_device}
     136
     137#a list of functions to call to get the plugin options
     138#at runtime (so we can perform runtime checks on remote data,
     139# to avoid sound loops for example)
     140DEFAULT_SRC_PLUGIN_OPTIONS = {
     141    "test"                  : get_test_defaults,
     142    "pulse"                 : get_pulse_defaults,
     143    }
     144
     145
     146
     147
     148def get_source_plugins():
     149    sources = []
     150    from xpra.sound.pulseaudio_util import has_pa
     151    #we have to put pulsesrc first if pulseaudio is installed
     152    #because using autoaudiosource does not work properly for us:
     153    #it may still choose pulse, but without choosing the right device.
     154    if has_pa():
     155        sources.append("pulsesrc")
     156    sources.append("autoaudiosrc")
     157    if sys.platform.startswith("darwin"):
     158        sources.append("osxaudiosrc")
     159    elif sys.platform.startswith("win"):
     160        sources.append("directsoundsrc")
     161    if os.name=="posix":
     162        sources += ["alsasrc", "jackaudiosrc",
     163                    "osssrc", "oss4src",
     164                    "osxaudiosrc", "jackaudiosrc"]
     165    sources.append("audiotestsrc")
     166    return sources
     167
     168
     169def parse_element_options(options_str):
     170    #parse the options string and add the pairs:
     171    options = {}
     172    for s in options_str.split(","):
     173        if not s:
     174            continue
     175        try:
     176            k,v = s.split("=", 1)
     177            options[k] = v
     178        except Exception as e:
     179            log.warn("failed to parse plugin option '%s': %s", s, e)
     180    return options
     181
     182
     183def get_sound_source_options(plugin, options_str, remote):
     184    """
     185        Given a plugin (short name), options string and remote info,
     186        return the options for the plugin given,
     187        using the dynamic defaults (which may use remote info)
     188        and applying the options string on top.
     189    """
     190    #ie: get_sound_source_options("audiotestsrc", "wave=4,freq=220", {remote_pulseaudio_server=XYZ}):
     191    #use the defaults as starting point:
     192    defaults_fn = DEFAULT_SRC_PLUGIN_OPTIONS.get(plugin)
     193    if defaults_fn:
     194        options = defaults_fn(remote)
     195        if options is None:
     196            #means failure
     197            return None
     198    else:
     199        options = {}
     200    options.update(parse_element_options(options_str))
     201    return options
     202
     203
     204def parse_sound_source(all_plugins, sound_source_plugin, remote):
     205    #format: PLUGINNAME:options
     206    #ie: test:wave=2,freq=110,volume=0.4
     207    #ie: pulse:device=device.alsa_input.pci-0000_00_14.2.analog-stereo
     208    plugin = sound_source_plugin.split(":")[0]
     209    options_str = (sound_source_plugin+":").split(":",1)[1]
     210    simple_str = (plugin).lower().strip()
     211    if not simple_str:
     212        #choose the first one from
     213        options = [x for x in get_source_plugins() if x in all_plugins]
     214        if not options:
     215            log.error("no source plugins available")
     216            return None
     217        log("parse_sound_source: no plugin specified, using default: %s", options[0])
     218        simple_str = options[0]
     219    for s in ("src", "sound", "audio"):
     220        if simple_str.endswith(s):
     221            simple_str = simple_str[:-len(s)]
     222    gst_sound_source_plugin = NAME_TO_SRC_PLUGIN.get(simple_str)
     223    if not gst_sound_source_plugin:
     224        log.error("unknown source plugin: '%s' / '%s'", simple_str, sound_source_plugin)
     225        return  None, {}
     226    log("parse_sound_source(%s, %s, %s) plugin=%s", all_plugins, sound_source_plugin, remote, gst_sound_source_plugin)
     227    options = get_sound_source_options(simple_str, options_str, remote)
     228    log("get_sound_source_options%s=%s", (simple_str, options_str, remote), options)
     229    if options is None:
     230        #means error
     231        return None, {}
     232    return gst_sound_source_plugin, options
  • xpra/sound/src.py

    Property changes on: xpra/sound/sound_util.py
    ___________________________________________________________________
    Added: svn:executable
    ## -0,0 +1 ##
    +*
    \ No newline at end of property
     
    1212from xpra.util import csv
    1313from xpra.sound.sound_pipeline import SoundPipeline, gobject
    1414from xpra.gtk_common.gobject_util import n_arg_signal
    15 from xpra.sound.gstreamer_util import plugin_str, get_encoder_formatter, get_source_plugins, normv, \
     15from xpra.sound.sound_util import get_source_plugins
     16from xpra.sound.gstreamer_util import plugin_str, get_encoder_formatter, normv, \
    1617                                MP3, CODECS, CODEC_ORDER, ENCODER_DEFAULT_OPTIONS, MUXER_DEFAULT_OPTIONS, ENCODER_NEEDS_AUDIOCONVERT, MS_TO_NS
    1718from xpra.scripts.config import InitExit
    1819from xpra.log import Logger
  • xpra/sound/wrapper.py

     
    55
    66import os
    77import time
     8import sys
    89
     10from xpra.sound.sound_util import parse_sound_source, get_source_plugins, parse_element_options
    911from xpra.net.subprocess_wrapper import subprocess_caller, subprocess_callee, glib, exec_kwargs, exec_env
    1012from xpra.platform.paths import get_sound_command
    1113from xpra.util import AdHocStruct
    12 from xpra.scripts.config import InitExit
     14from xpra.scripts.config import InitExit, InitException
    1315from xpra.log import Logger
    1416log = Logger("sound")
    1517
     
    6365            glib.timeout_add(FAKE_EXIT*1000, process_exit)
    6466        if FAKE_CRASH>0:
    6567            def force_exit():
    66                 import sys
    6768                sys.exit(1)
    6869            glib.timeout_add(FAKE_CRASH*1000, force_exit)
    6970        subprocess_callee.start(self)
     
    110111        subproc = sound_play
    111112        info = "play"
    112113    elif mode=="_sound_query":
    113         if len(args)!=1:
    114             raise Exception("invalid number of arguments for sound query: %s (one subcommand required)" % len(args))
    115         subcommand = args[0]
    116         from xpra.sound.gstreamer_util import get_available_source_plugins, can_decode, can_encode
    117         if subcommand=="encoders":
    118             v = can_encode()
    119         elif subcommand=="decoders":
    120             v = can_decode()
    121         elif subcommand=="sources":
    122             v = get_available_source_plugins()
    123         else:
    124             raise Exception("invalid subcommand: %s" % subcommand)
    125         print("%s=%s" % (subcommand, ",".join(v)))
     114        from xpra.sound.gstreamer_util import can_decode, can_encode, get_muxers, get_demuxers, \
     115                            gst_version, pygst_version, get_all_plugin_names
     116        plugins = get_all_plugin_names()
     117        sources = [x for x in get_source_plugins() if x in plugins]
     118        d = {"encoders"         : can_encode(),
     119             "decoders"         : can_decode(),
     120             "sources"          : sources,
     121             "muxers"           : get_muxers(),
     122             "demuxers"         : get_demuxers(),
     123             "gst.version"      : gst_version,
     124             "pygst.version"    : pygst_version,
     125             "plugins"          : plugins,
     126            }
     127        for k,v in d.items():
     128            print("%s=%s" % (k, ",".join(str(x) for x in v)))
    126129        return 0
    127130    else:
    128131        log.error("unknown mode: %s" % mode)
     
    132135    #the plugin to use (ie: 'pulsesrc' for src.py or 'autoaudiosink' for sink.py)
    133136    plugin = args[2]
    134137    #plugin options (ie: "device=monitor_device,something=value")
    135     from xpra.sound.gstreamer_util import parse_element_options
    136138    options = parse_element_options(args[3])
    137139    #codecs:
    138140    codecs = [x.strip() for x in args[4].split(",")]
     
    151153        return 0
    152154    except InitExit as e:
    153155        log.error("%s: %s", info, e)
     156        return e.status
     157    except InitException as e:
     158        log.error("%s: %s", info, e)
    154159        return 1
    155160    except Exception:
    156161        log.error("run_sound%s error", (mode, error_cb, options, args), exc_info=True)
     
    293298            return "sink_subprocess_wrapper(%s)" % self.process
    294299
    295300
    296 def start_sending_sound(sound_source_plugin, codec, volume, remote_decoders, remote_pulseaudio_server, remote_pulseaudio_id):
    297     log("start_sending_sound%s", (sound_source_plugin, codec, volume, remote_decoders, remote_pulseaudio_server, remote_pulseaudio_id))
    298     from xpra.sound.gstreamer_util import has_gst, parse_sound_source
    299     assert has_gst
     301def start_sending_sound(plugins, sound_source_plugin, codec, volume, remote_decoders, remote_pulseaudio_server, remote_pulseaudio_id):
     302    log("start_sending_sound%s", (plugins, sound_source_plugin, codec, volume, remote_decoders, remote_pulseaudio_server, remote_pulseaudio_id))
    300303    try:
    301304        #info about the remote end:
    302305        remote = AdHocStruct()
     
    303306        remote.pulseaudio_server = remote_pulseaudio_server
    304307        remote.pulseaudio_id = remote_pulseaudio_id
    305308        remote.remote_decoders = remote_decoders
    306         plugin, options = parse_sound_source(sound_source_plugin, remote)
     309        plugin, options = parse_sound_source(plugins, sound_source_plugin, remote)
    307310        if not plugin:
    308311            log.error("failed to setup '%s' sound stream source", (sound_source_plugin or "auto"))
    309312            return  None
     
    324327        log.error("failed to start sound sink", exc_info=True)
    325328        return None
    326329
    327 def query_sound(subcommand):
    328     import sys
     330def query_sound():
    329331    import subprocess
    330     command = get_sound_command()+["_sound_query", subcommand]
     332    command = get_sound_command()+["_sound_query"]
    331333    _add_debug_args(command)
    332334    kwargs = exec_kwargs()
    333335    env = exec_env()
    334     log("query_sound(%s) command=%s, env=%s, kwargs=%s", subcommand, command, env, kwargs)
     336    log("query_sound() command=%s, env=%s, kwargs=%s", command, env, kwargs)
    335337    proc = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=sys.stderr.fileno(), env=env, **kwargs)
    336338    out, err = proc.communicate(None)
    337     log("query_sound(%s) process returned %s", subcommand, proc.returncode)
    338     log("query_sound(%s) out=%s, err=%s", subcommand, out, err)
     339    log("query_sound() process returned %s", proc.returncode)
     340    log("query_sound() out=%s, err=%s", out, err)
    339341    if proc.returncode!=0:
    340         return []
    341     mline = "%s=" % subcommand      #ie: "decoders="
     342        return {}
     343    d = {}
    342344    for x in out.decode("utf8").splitlines():
    343         if x.startswith(mline):
    344             return x[len(mline):].split(",")
    345     return []
    346 
    347 def query_sound_sources():
    348     return query_sound("sources")
    349 
    350 def query_sound_encoders():
    351     return query_sound("encoders")
    352 
    353 def query_sound_decoders():
    354     return query_sound("decoders")
    355 
    356 def query_sound_muxers():
    357     return query_sound("muxers")
    358 
    359 def query_sound_demuxers():
    360     return query_sound("demuxers")
    361 
    362 
    363 def get_sound_codecs(is_speaker, is_server):
    364     from xpra.sound.gstreamer_util import has_gst
    365     if not has_gst:
    366         return []
    367     try:
    368         if (is_server and is_speaker) or (not is_server and not is_speaker):
    369             return query_sound_encoders()
    370         else:
    371             return query_sound_decoders()
    372     except Exception as e:
    373         log.warn("failed to get list of codecs: %s" % e)
    374         return []
     345        kv = x.split("=", 1)
     346        if len(kv)==2:
     347            #ie: kv = ["decoders", "mp3,vorbis"]
     348            d[kv[0]] = kv[1].split(",")     #d["decoders"] = ["mp3", "vorbis"]
     349    return d